2022-08-09 00:35:38 +00:00
|
|
|
|
using System;
|
2014-01-04 12:26:13 +00:00
|
|
|
|
using System.Collections.Generic;
|
2015-10-31 18:06:57 +00:00
|
|
|
|
using System.Diagnostics;
|
2025-02-24 07:15:26 +00:00
|
|
|
|
using System.IO;
|
2014-01-04 12:26:13 +00:00
|
|
|
|
using System.Linq;
|
2021-01-02 09:58:30 +00:00
|
|
|
|
using System.Threading;
|
2016-11-30 01:07:48 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2016-01-06 21:34:42 +00:00
|
|
|
|
using System.Windows.Controls;
|
2020-04-21 09:12:17 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Program.Programs;
|
|
|
|
|
|
using Flow.Launcher.Plugin.Program.Views;
|
2022-10-20 08:36:58 +00:00
|
|
|
|
using Flow.Launcher.Plugin.Program.Views.Models;
|
2025-04-02 12:17:32 +00:00
|
|
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
2021-05-27 11:28:17 +00:00
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
2024-05-26 10:01:29 +00:00
|
|
|
|
using Path = System.IO.Path;
|
2014-01-04 12:26:13 +00:00
|
|
|
|
|
2020-04-21 09:12:17 +00:00
|
|
|
|
namespace Flow.Launcher.Plugin.Program
|
2014-01-04 12:26:13 +00:00
|
|
|
|
{
|
2025-04-01 06:21:29 +00:00
|
|
|
|
public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable
|
2014-01-04 12:26:13 +00:00
|
|
|
|
{
|
2025-04-09 04:14:07 +00:00
|
|
|
|
private static readonly string ClassName = nameof(Main);
|
|
|
|
|
|
|
2025-04-01 06:21:29 +00:00
|
|
|
|
private const string Win32CacheName = "Win32";
|
|
|
|
|
|
private const string UwpCacheName = "UWP";
|
2016-04-22 22:03:32 +00:00
|
|
|
|
|
2025-04-01 06:21:29 +00:00
|
|
|
|
internal static List<Win32> _win32s { get; private set; }
|
|
|
|
|
|
internal static List<UWPApp> _uwps { get; private set; }
|
|
|
|
|
|
internal static Settings _settings { get; private set; }
|
2019-10-17 20:53:00 +00:00
|
|
|
|
|
2025-04-08 08:29:03 +00:00
|
|
|
|
internal static SemaphoreSlim _win32sLock = new(1, 1);
|
|
|
|
|
|
internal static SemaphoreSlim _uwpsLock = new(1, 1);
|
|
|
|
|
|
|
2021-11-06 06:10:29 +00:00
|
|
|
|
internal static PluginInitContext Context { get; private set; }
|
2016-04-21 00:53:21 +00:00
|
|
|
|
|
2025-10-26 12:51:08 +00:00
|
|
|
|
private static readonly Lock _lastIndexTimeLock = new();
|
|
|
|
|
|
|
2025-09-15 07:40:49 +00:00
|
|
|
|
private static readonly List<Result> emptyResults = [];
|
2021-05-24 02:21:39 +00:00
|
|
|
|
|
2025-10-26 12:59:29 +00:00
|
|
|
|
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
|
|
|
|
|
|
private static MemoryCache cache = new(cacheOptions);
|
|
|
|
|
|
|
2024-05-26 10:01:29 +00:00
|
|
|
|
private static readonly string[] commonUninstallerNames =
|
|
|
|
|
|
{
|
|
|
|
|
|
"uninst.exe",
|
|
|
|
|
|
"unins000.exe",
|
|
|
|
|
|
"uninst000.exe",
|
|
|
|
|
|
"uninstall.exe"
|
|
|
|
|
|
};
|
2025-01-24 04:11:48 +00:00
|
|
|
|
private static readonly string[] commonUninstallerPrefixs =
|
|
|
|
|
|
{
|
2025-01-24 10:35:11 +00:00
|
|
|
|
"uninstall",//en
|
|
|
|
|
|
"卸载",//zh-cn
|
|
|
|
|
|
"卸載",//zh-tw
|
|
|
|
|
|
"видалити",//uk-UA
|
|
|
|
|
|
"удалить",//ru
|
|
|
|
|
|
"désinstaller",//fr
|
|
|
|
|
|
"アンインストール",//ja
|
|
|
|
|
|
"deïnstalleren",//nl
|
|
|
|
|
|
"odinstaluj",//pl
|
|
|
|
|
|
"afinstallere",//da
|
|
|
|
|
|
"deinstallieren",//de
|
|
|
|
|
|
"삭제",//ko
|
|
|
|
|
|
"деинсталирај",//sr
|
|
|
|
|
|
"desinstalar",//pt-pt
|
|
|
|
|
|
"desinstalar",//pt-br
|
|
|
|
|
|
"desinstalar",//es
|
|
|
|
|
|
"desinstalar",//es-419
|
|
|
|
|
|
"disinstallare",//it
|
|
|
|
|
|
"avinstallere",//nb-NO
|
|
|
|
|
|
"odinštalovať",//sk
|
|
|
|
|
|
"kaldır",//tr
|
|
|
|
|
|
"odinstalovat",//cs
|
|
|
|
|
|
"إلغاء التثبيت",//ar
|
|
|
|
|
|
"gỡ bỏ",//vi-vn
|
|
|
|
|
|
"הסרה"//he
|
2025-01-24 04:11:48 +00:00
|
|
|
|
};
|
|
|
|
|
|
private const string ExeUninstallerSuffix = ".exe";
|
|
|
|
|
|
private const string InkUninstallerSuffix = ".lnk";
|
2024-05-26 10:01:29 +00:00
|
|
|
|
|
2025-02-21 01:32:58 +00:00
|
|
|
|
private static readonly string WindowsAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps");
|
2025-02-20 06:59:16 +00:00
|
|
|
|
|
2021-01-02 09:58:30 +00:00
|
|
|
|
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
2014-01-04 12:26:13 +00:00
|
|
|
|
{
|
2025-10-26 12:59:29 +00:00
|
|
|
|
var result = await cache.GetOrCreateAsync(query.Search, async entry =>
|
2022-02-06 23:34:50 +00:00
|
|
|
|
{
|
2025-10-26 12:59:29 +00:00
|
|
|
|
var resultList = await Task.Run(async () =>
|
2025-10-26 12:40:37 +00:00
|
|
|
|
{
|
2025-11-06 12:48:41 +00:00
|
|
|
|
// Preparing win32 programs
|
2025-11-06 07:28:13 +00:00
|
|
|
|
List<Win32> win32s;
|
2025-11-07 07:30:07 +00:00
|
|
|
|
bool win32LockAcquired = false;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-11-06 12:46:29 +00:00
|
|
|
|
await _win32sLock.WaitAsync(token);
|
2025-11-07 07:30:07 +00:00
|
|
|
|
win32LockAcquired = true;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
win32s = [.. _win32s];
|
|
|
|
|
|
}
|
2025-11-06 12:46:29 +00:00
|
|
|
|
catch (OperationCanceledException)
|
2025-11-06 07:28:13 +00:00
|
|
|
|
{
|
2025-11-06 12:46:29 +00:00
|
|
|
|
return emptyResults;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
}
|
2025-11-07 07:30:07 +00:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// Only release the lock if it was acquired
|
|
|
|
|
|
if (win32LockAcquired) _win32sLock.Release();
|
|
|
|
|
|
}
|
2025-11-06 07:28:13 +00:00
|
|
|
|
|
2025-11-06 12:48:41 +00:00
|
|
|
|
// Preparing UWP programs
|
2025-11-06 07:28:13 +00:00
|
|
|
|
List<UWPApp> uwps;
|
2025-11-07 07:30:07 +00:00
|
|
|
|
bool uwpsLockAcquired = false;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-11-06 12:46:29 +00:00
|
|
|
|
await _uwpsLock.WaitAsync(token);
|
2025-11-07 07:30:07 +00:00
|
|
|
|
uwpsLockAcquired = true;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
uwps = [.. _uwps];
|
|
|
|
|
|
}
|
2025-11-06 12:46:29 +00:00
|
|
|
|
catch (OperationCanceledException)
|
2025-11-06 07:28:13 +00:00
|
|
|
|
{
|
2025-11-06 12:46:29 +00:00
|
|
|
|
return emptyResults;
|
2025-11-06 07:28:13 +00:00
|
|
|
|
}
|
2025-11-07 07:30:07 +00:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// Only release the lock if it was acquired
|
|
|
|
|
|
if (uwpsLockAcquired) _uwpsLock.Release();
|
|
|
|
|
|
}
|
2025-11-06 07:28:13 +00:00
|
|
|
|
|
2025-11-06 12:48:41 +00:00
|
|
|
|
// Start querying programs
|
2025-10-26 12:59:29 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// Collect all UWP Windows app directories
|
|
|
|
|
|
var uwpsDirectories = _settings.HideDuplicatedWindowsApp ? _uwps
|
|
|
|
|
|
.Where(uwp => !string.IsNullOrEmpty(uwp.Location)) // Exclude invalid paths
|
|
|
|
|
|
.Where(uwp => uwp.Location.StartsWith(WindowsAppPath, StringComparison.OrdinalIgnoreCase)) // Keep system apps
|
|
|
|
|
|
.Select(uwp => uwp.Location.TrimEnd('\\')) // Remove trailing slash
|
|
|
|
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
.ToArray() : null;
|
|
|
|
|
|
|
2025-11-06 07:28:13 +00:00
|
|
|
|
return win32s.Cast<IProgram>()
|
|
|
|
|
|
.Concat(uwps)
|
2025-10-26 12:59:29 +00:00
|
|
|
|
.AsParallel()
|
|
|
|
|
|
.WithCancellation(token)
|
|
|
|
|
|
.Where(HideUninstallersFilter)
|
|
|
|
|
|
.Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories))
|
|
|
|
|
|
.Where(p => p.Enabled)
|
|
|
|
|
|
.Select(p => p.Result(query.Search, Context.API))
|
|
|
|
|
|
.Where(r => string.IsNullOrEmpty(query.Search) || r?.Score > 0)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
return emptyResults;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, token);
|
|
|
|
|
|
|
|
|
|
|
|
resultList = resultList.Count != 0 ? resultList : emptyResults;
|
2022-02-06 23:34:50 +00:00
|
|
|
|
|
2025-10-26 12:59:29 +00:00
|
|
|
|
entry.SetSize(resultList.Count);
|
|
|
|
|
|
entry.SetSlidingExpiration(TimeSpan.FromHours(8));
|
2025-11-06 07:29:38 +00:00
|
|
|
|
|
2025-10-26 12:59:29 +00:00
|
|
|
|
return resultList;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2014-01-04 12:26:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-26 10:01:29 +00:00
|
|
|
|
private bool HideUninstallersFilter(IProgram program)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_settings.HideUninstallers) return true;
|
|
|
|
|
|
if (program is not Win32 win32) return true;
|
2025-01-24 04:11:48 +00:00
|
|
|
|
|
|
|
|
|
|
// First check the executable path
|
2024-05-26 10:01:29 +00:00
|
|
|
|
var fileName = Path.GetFileName(win32.ExecutablePath);
|
2025-01-24 04:11:48 +00:00
|
|
|
|
// For cases when the uninstaller is named like "uninst.exe"
|
|
|
|
|
|
if (commonUninstallerNames.Contains(fileName, StringComparer.OrdinalIgnoreCase)) return false;
|
|
|
|
|
|
// For cases when the uninstaller is named like "Uninstall Program Name.exe"
|
|
|
|
|
|
foreach (var prefix in commonUninstallerPrefixs)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
|
|
|
|
|
|
fileName.EndsWith(ExeUninstallerSuffix, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-25 22:34:07 +00:00
|
|
|
|
// Second check the lnk path
|
2025-01-24 04:11:48 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(win32.LnkResolvedPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
var inkFileName = Path.GetFileName(win32.FullPath);
|
|
|
|
|
|
// For cases when the uninstaller is named like "Uninstall Program Name.ink"
|
|
|
|
|
|
foreach (var prefix in commonUninstallerPrefixs)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (inkFileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
|
|
|
|
|
|
inkFileName.EndsWith(InkUninstallerSuffix, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2024-05-26 10:01:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-02-21 01:29:43 +00:00
|
|
|
|
private static bool HideDuplicatedWindowsAppFilter(IProgram program, string[] uwpsDirectories)
|
2025-02-20 06:59:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (uwpsDirectories == null || uwpsDirectories.Length == 0) return true;
|
|
|
|
|
|
if (program is UWPApp) return true;
|
|
|
|
|
|
|
|
|
|
|
|
var location = program.Location.TrimEnd('\\'); // Ensure trailing slash
|
|
|
|
|
|
if (string.IsNullOrEmpty(location))
|
|
|
|
|
|
return true; // Keep if location is invalid
|
|
|
|
|
|
|
|
|
|
|
|
if (!location.StartsWith(WindowsAppPath, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
return true; // Keep if not a Windows app
|
|
|
|
|
|
|
|
|
|
|
|
// Check if the any Win32 executable directory contains UWP Windows app location matches
|
|
|
|
|
|
return !uwpsDirectories.Any(uwpDirectory =>
|
|
|
|
|
|
location.StartsWith(uwpDirectory, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-02 09:58:30 +00:00
|
|
|
|
public async Task InitAsync(PluginInitContext context)
|
2014-01-04 12:26:13 +00:00
|
|
|
|
{
|
2021-11-06 18:58:28 +00:00
|
|
|
|
Context = context;
|
2021-01-02 09:58:30 +00:00
|
|
|
|
|
2021-05-13 11:29:21 +00:00
|
|
|
|
_settings = context.API.LoadSettingJsonStorage<Settings>();
|
2021-01-02 09:58:30 +00:00
|
|
|
|
|
2025-04-08 11:21:03 +00:00
|
|
|
|
var _win32sCount = 0;
|
|
|
|
|
|
var _uwpsCount = 0;
|
2025-04-08 13:46:02 +00:00
|
|
|
|
await Context.API.StopwatchLogInfoAsync(ClassName, "Preload programs cost", async () =>
|
2021-03-08 08:16:54 +00:00
|
|
|
|
{
|
2025-04-08 11:44:30 +00:00
|
|
|
|
var pluginCacheDirectory = Context.CurrentPluginMetadata.PluginCacheDirectoryPath;
|
|
|
|
|
|
FilesFolders.ValidateDirectory(pluginCacheDirectory);
|
2025-02-24 07:15:26 +00:00
|
|
|
|
|
2025-02-24 08:11:49 +00:00
|
|
|
|
static void MoveFile(string sourcePath, string destinationPath)
|
2025-02-24 07:15:26 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (!File.Exists(sourcePath))
|
|
|
|
|
|
{
|
2025-02-24 08:11:49 +00:00
|
|
|
|
return;
|
2025-02-24 07:15:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (File.Exists(destinationPath))
|
|
|
|
|
|
{
|
2025-02-24 08:11:49 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(sourcePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Ignore, we will handle next time we start the plugin
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
2025-02-24 07:15:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var destinationDirectory = Path.GetDirectoryName(destinationPath);
|
|
|
|
|
|
if (!Directory.Exists(destinationDirectory) && (!string.IsNullOrEmpty(destinationDirectory)))
|
|
|
|
|
|
{
|
2025-02-24 08:11:49 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(destinationDirectory);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Ignore, we will handle next time we start the plugin
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Move(sourcePath, destinationPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Ignore, we will handle next time we start the plugin
|
2025-02-24 07:15:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 04:45:31 +00:00
|
|
|
|
// If plugin cache directory is this: D:\\Data\\Cache\\Plugins\\Flow.Launcher.Plugin.Program
|
|
|
|
|
|
// then the parent directory is: D:\\Data\\Cache
|
|
|
|
|
|
// So we can use the parent of the parent directory to get the cache directory path
|
|
|
|
|
|
var directoryInfo = new DirectoryInfo(pluginCacheDirectory);
|
2025-06-23 05:13:45 +00:00
|
|
|
|
var cacheDirectory = directoryInfo.Parent?.Parent?.FullName;
|
|
|
|
|
|
// Move old cache files to the new cache directory if cache directory exists
|
|
|
|
|
|
if (!string.IsNullOrEmpty(cacheDirectory))
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldWin32CacheFile = Path.Combine(cacheDirectory, $"{Win32CacheName}.cache");
|
|
|
|
|
|
var newWin32CacheFile = Path.Combine(pluginCacheDirectory, $"{Win32CacheName}.cache");
|
|
|
|
|
|
MoveFile(oldWin32CacheFile, newWin32CacheFile);
|
|
|
|
|
|
var oldUWPCacheFile = Path.Combine(cacheDirectory, $"{UwpCacheName}.cache");
|
|
|
|
|
|
var newUWPCacheFile = Path.Combine(pluginCacheDirectory, $"{UwpCacheName}.cache");
|
|
|
|
|
|
MoveFile(oldUWPCacheFile, newUWPCacheFile);
|
|
|
|
|
|
}
|
2025-02-24 07:15:26 +00:00
|
|
|
|
|
2025-04-08 08:29:03 +00:00
|
|
|
|
await _win32sLock.WaitAsync();
|
2025-09-15 07:40:49 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
|
|
|
|
|
|
_win32sCount = _win32s.Count;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_win32sLock.Release();
|
|
|
|
|
|
}
|
2025-04-08 08:29:03 +00:00
|
|
|
|
|
|
|
|
|
|
await _uwpsLock.WaitAsync();
|
2025-09-15 07:40:49 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
|
|
|
|
|
|
_uwpsCount = _uwps.Count;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_uwpsLock.Release();
|
|
|
|
|
|
}
|
2021-01-02 09:58:30 +00:00
|
|
|
|
});
|
2025-04-09 05:08:28 +00:00
|
|
|
|
Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>");
|
|
|
|
|
|
Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>");
|
2025-04-08 08:29:03 +00:00
|
|
|
|
|
2025-04-08 11:21:03 +00:00
|
|
|
|
var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0;
|
2021-01-02 09:58:30 +00:00
|
|
|
|
|
2025-10-26 12:51:08 +00:00
|
|
|
|
bool needReindex;
|
|
|
|
|
|
lock (_lastIndexTimeLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
needReindex = _settings.LastIndexTime.AddHours(30) < DateTime.Now;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (cacheEmpty || needReindex)
|
2021-01-02 09:58:30 +00:00
|
|
|
|
{
|
2023-04-24 13:22:18 +00:00
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await IndexProgramsAsync().ConfigureAwait(false);
|
|
|
|
|
|
WatchProgramUpdate();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2021-01-02 09:58:30 +00:00
|
|
|
|
{
|
2023-04-24 13:22:18 +00:00
|
|
|
|
WatchProgramUpdate();
|
|
|
|
|
|
}
|
2021-05-24 02:21:39 +00:00
|
|
|
|
|
2023-04-24 13:22:18 +00:00
|
|
|
|
static void WatchProgramUpdate()
|
|
|
|
|
|
{
|
|
|
|
|
|
Win32.WatchProgramUpdate(_settings);
|
2025-04-09 04:44:26 +00:00
|
|
|
|
_ = UWPPackage.WatchPackageChangeAsync();
|
2023-04-24 13:22:18 +00:00
|
|
|
|
}
|
2014-08-10 14:22:54 +00:00
|
|
|
|
}
|
2014-03-20 19:53:18 +00:00
|
|
|
|
|
2025-12-25 05:01:47 +00:00
|
|
|
|
public static async Task IndexWin32ProgramsAsync(bool resetCache)
|
2014-08-10 14:22:54 +00:00
|
|
|
|
{
|
2025-04-08 08:29:03 +00:00
|
|
|
|
await _win32sLock.WaitAsync();
|
2025-04-08 11:27:53 +00:00
|
|
|
|
try
|
2025-04-01 06:21:29 +00:00
|
|
|
|
{
|
2025-04-08 11:27:53 +00:00
|
|
|
|
var win32S = Win32.All(_settings);
|
|
|
|
|
|
_win32s.Clear();
|
|
|
|
|
|
foreach (var win32 in win32S)
|
|
|
|
|
|
{
|
|
|
|
|
|
_win32s.Add(win32);
|
|
|
|
|
|
}
|
2025-12-25 05:01:47 +00:00
|
|
|
|
if (resetCache)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetCache();
|
|
|
|
|
|
}
|
2025-04-08 11:27:53 +00:00
|
|
|
|
await Context.API.SaveCacheBinaryStorageAsync<List<Win32>>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
2025-10-26 12:51:08 +00:00
|
|
|
|
lock (_lastIndexTimeLock)
|
|
|
|
|
|
{
|
2025-10-26 12:56:08 +00:00
|
|
|
|
_settings.LastIndexTime = DateTime.Now;
|
|
|
|
|
|
}
|
2025-10-26 12:51:08 +00:00
|
|
|
|
}
|
2025-04-08 11:27:53 +00:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2025-04-09 04:14:07 +00:00
|
|
|
|
Context.API.LogException(ClassName, "Failed to index Win32 programs", e);
|
2025-04-08 11:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_win32sLock.Release();
|
2025-04-01 06:21:29 +00:00
|
|
|
|
}
|
2019-09-04 22:05:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 05:01:47 +00:00
|
|
|
|
public static async Task IndexUwpProgramsAsync(bool resetCache)
|
2019-09-04 22:05:17 +00:00
|
|
|
|
{
|
2025-04-08 08:29:03 +00:00
|
|
|
|
await _uwpsLock.WaitAsync();
|
2025-04-08 11:27:53 +00:00
|
|
|
|
try
|
2025-04-01 06:21:29 +00:00
|
|
|
|
{
|
2025-04-08 11:27:53 +00:00
|
|
|
|
var uwps = UWPPackage.All(_settings);
|
|
|
|
|
|
_uwps.Clear();
|
|
|
|
|
|
foreach (var uwp in uwps)
|
|
|
|
|
|
{
|
|
|
|
|
|
_uwps.Add(uwp);
|
|
|
|
|
|
}
|
2025-12-25 05:01:47 +00:00
|
|
|
|
if (resetCache)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetCache();
|
|
|
|
|
|
}
|
2025-04-08 11:27:53 +00:00
|
|
|
|
await Context.API.SaveCacheBinaryStorageAsync<List<UWPApp>>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath);
|
2025-10-26 12:51:08 +00:00
|
|
|
|
lock (_lastIndexTimeLock)
|
|
|
|
|
|
{
|
2025-10-26 12:56:08 +00:00
|
|
|
|
_settings.LastIndexTime = DateTime.Now;
|
|
|
|
|
|
}
|
2025-10-26 12:51:08 +00:00
|
|
|
|
}
|
2025-04-08 11:27:53 +00:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2025-04-09 04:14:07 +00:00
|
|
|
|
Context.API.LogException(ClassName, "Failed to index Uwp programs", e);
|
2025-04-08 11:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_uwpsLock.Release();
|
2025-04-01 06:21:29 +00:00
|
|
|
|
}
|
2014-08-10 14:22:54 +00:00
|
|
|
|
}
|
2014-03-22 05:50:20 +00:00
|
|
|
|
|
2022-08-09 00:35:38 +00:00
|
|
|
|
public static async Task IndexProgramsAsync()
|
2019-09-04 22:05:17 +00:00
|
|
|
|
{
|
2025-04-01 06:21:29 +00:00
|
|
|
|
var win32Task = Task.Run(async () =>
|
2022-10-29 18:37:22 +00:00
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
await Context.API.StopwatchLogInfoAsync(ClassName, "Win32Program index cost", () => IndexWin32ProgramsAsync(resetCache: true));
|
2022-10-29 18:37:22 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-04-01 06:21:29 +00:00
|
|
|
|
var uwpTask = Task.Run(async () =>
|
2022-10-29 18:37:22 +00:00
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
await Context.API.StopwatchLogInfoAsync(ClassName, "UWPProgram index cost", () => IndexUwpProgramsAsync(resetCache: true));
|
2022-10-29 18:37:22 +00:00
|
|
|
|
});
|
2025-04-01 06:21:29 +00:00
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(win32Task, uwpTask).ConfigureAwait(false);
|
2021-11-30 00:31:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-26 12:59:29 +00:00
|
|
|
|
internal static void ResetCache()
|
|
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
var newCache = new MemoryCache(cacheOptions);
|
|
|
|
|
|
|
|
|
|
|
|
// Atomically swap and get the previous cache instance, avoids double-dispose/lost-assignment race
|
|
|
|
|
|
// where each caller receives a distinct prior instance to dispose.
|
|
|
|
|
|
var oldCache = Interlocked.Exchange(ref cache, newCache);
|
|
|
|
|
|
|
|
|
|
|
|
// Dispose the previous instance (if any)- each caller gets a unique prior instance from above
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
oldCache?.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
Context.API.LogException(ClassName, "Failed to dispose old program cache", e);
|
|
|
|
|
|
}
|
2025-10-26 12:59:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-06 21:34:42 +00:00
|
|
|
|
public Control CreateSettingPanel()
|
2014-03-29 07:52:57 +00:00
|
|
|
|
{
|
2025-04-01 06:21:29 +00:00
|
|
|
|
return new ProgramSetting(Context, _settings);
|
2014-03-29 07:52:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-07 12:17:49 +00:00
|
|
|
|
public string GetTranslatedPluginTitle()
|
|
|
|
|
|
{
|
2021-11-06 18:58:28 +00:00
|
|
|
|
return Context.API.GetTranslation("flowlauncher_plugin_program_plugin_name");
|
2015-02-07 12:17:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string GetTranslatedPluginDescription()
|
|
|
|
|
|
{
|
2021-11-06 18:58:28 +00:00
|
|
|
|
return Context.API.GetTranslation("flowlauncher_plugin_program_plugin_description");
|
2015-02-07 12:17:49 +00:00
|
|
|
|
}
|
2015-02-07 15:49:46 +00:00
|
|
|
|
|
|
|
|
|
|
public List<Result> LoadContextMenus(Result selectedResult)
|
|
|
|
|
|
{
|
2019-10-17 11:47:00 +00:00
|
|
|
|
var menuOptions = new List<Result>();
|
2016-08-22 01:21:22 +00:00
|
|
|
|
var program = selectedResult.ContextData as IProgram;
|
|
|
|
|
|
if (program != null)
|
2015-02-07 15:49:46 +00:00
|
|
|
|
{
|
2021-11-06 18:58:28 +00:00
|
|
|
|
menuOptions = program.ContextMenus(Context.API);
|
2016-08-18 00:16:40 +00:00
|
|
|
|
}
|
2019-10-17 11:47:00 +00:00
|
|
|
|
|
|
|
|
|
|
menuOptions.Add(
|
2021-01-02 09:58:30 +00:00
|
|
|
|
new Result
|
|
|
|
|
|
{
|
2021-11-06 18:58:28 +00:00
|
|
|
|
Title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_program"),
|
2021-01-02 09:58:30 +00:00
|
|
|
|
Action = c =>
|
|
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var disabled = await DisableProgramAsync(program);
|
|
|
|
|
|
if (disabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetCache();
|
|
|
|
|
|
Context.API.ShowMsg(
|
|
|
|
|
|
Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
|
|
|
|
|
|
Context.API.GetTranslation(
|
|
|
|
|
|
"flowlauncher_plugin_program_disable_dlgtitle_success_message"));
|
|
|
|
|
|
}
|
|
|
|
|
|
Context.API.ReQuery();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
Context.API.LogException(ClassName, "Failed to disable program", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2021-01-02 09:58:30 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
},
|
2021-10-07 10:58:54 +00:00
|
|
|
|
IcoPath = "Images/disable.png",
|
|
|
|
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xece4"),
|
2021-01-02 09:58:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
);
|
2019-10-17 11:47:00 +00:00
|
|
|
|
|
|
|
|
|
|
return menuOptions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 05:01:47 +00:00
|
|
|
|
private static async Task<bool> DisableProgramAsync(IProgram programToDelete)
|
2019-10-17 11:47:00 +00:00
|
|
|
|
{
|
2019-10-23 22:23:57 +00:00
|
|
|
|
if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
2025-12-25 05:01:47 +00:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2019-10-17 11:47:00 +00:00
|
|
|
|
|
2025-04-08 08:29:03 +00:00
|
|
|
|
await _uwpsLock.WaitAsync();
|
2025-09-15 07:40:49 +00:00
|
|
|
|
try
|
2022-10-18 13:30:55 +00:00
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
var program = _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
|
|
|
|
|
if (program != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
program.Enabled = false;
|
|
|
|
|
|
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
|
|
|
|
|
// Reindex UWP programs
|
|
|
|
|
|
_ = Task.Run(() => IndexUwpProgramsAsync(resetCache: false));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-15 07:40:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2025-04-08 08:29:03 +00:00
|
|
|
|
_uwpsLock.Release();
|
2025-09-15 07:40:49 +00:00
|
|
|
|
}
|
2025-04-08 08:29:03 +00:00
|
|
|
|
|
|
|
|
|
|
await _win32sLock.WaitAsync();
|
2025-09-15 07:40:49 +00:00
|
|
|
|
try
|
2022-10-18 13:30:55 +00:00
|
|
|
|
{
|
2025-12-25 05:01:47 +00:00
|
|
|
|
var program = _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
|
|
|
|
|
if (program != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
program.Enabled = false;
|
|
|
|
|
|
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
|
|
|
|
|
// Reindex Win32 programs
|
|
|
|
|
|
_ = Task.Run(() => IndexWin32ProgramsAsync(resetCache: false));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2022-10-18 13:30:55 +00:00
|
|
|
|
}
|
2025-09-15 07:40:49 +00:00
|
|
|
|
finally
|
2025-04-08 08:59:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
_win32sLock.Release();
|
|
|
|
|
|
}
|
2025-09-15 07:40:49 +00:00
|
|
|
|
|
2025-12-25 05:01:47 +00:00
|
|
|
|
return false;
|
2015-02-07 15:49:46 +00:00
|
|
|
|
}
|
2016-03-27 01:49:05 +00:00
|
|
|
|
|
2019-12-16 10:01:45 +00:00
|
|
|
|
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
2016-03-27 01:49:05 +00:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2019-12-10 11:01:12 +00:00
|
|
|
|
runProcess(info);
|
2016-03-27 01:49:05 +00:00
|
|
|
|
}
|
2016-08-22 01:21:22 +00:00
|
|
|
|
catch (Exception)
|
2016-03-27 01:49:05 +00:00
|
|
|
|
{
|
2022-11-29 10:13:55 +00:00
|
|
|
|
var title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
|
2023-11-12 00:00:03 +00:00
|
|
|
|
var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"),
|
|
|
|
|
|
info.FileName);
|
2025-07-20 04:57:56 +00:00
|
|
|
|
Context.API.ShowMsgError(title, message);
|
2016-03-27 01:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-10-06 02:15:06 +00:00
|
|
|
|
|
2021-01-02 09:58:30 +00:00
|
|
|
|
public async Task ReloadDataAsync()
|
2019-10-06 02:15:06 +00:00
|
|
|
|
{
|
2022-08-09 00:35:38 +00:00
|
|
|
|
await IndexProgramsAsync();
|
2019-10-06 02:15:06 +00:00
|
|
|
|
}
|
2022-10-21 10:39:03 +00:00
|
|
|
|
|
2022-01-08 21:50:27 +00:00
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
Win32.Dispose();
|
|
|
|
|
|
}
|
2014-01-04 12:26:13 +00:00
|
|
|
|
}
|
2022-08-09 00:35:38 +00:00
|
|
|
|
}
|