Merge Dev

This commit is contained in:
DB p 2022-11-15 00:23:27 +09:00
commit c7eb4e213c
22 changed files with 586 additions and 552 deletions

View file

@ -58,7 +58,7 @@
<PackageReference Include="NLog.Schema" Version="4.7.10" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.13.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
<!--ToolGood.Words.Pinyin v3.0.2.6 results in high memory usage when search with pinyin is enabled-->
<!--Bumping to it or higher needs to test and ensure this is no longer a problem-->
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />

View file

@ -19,7 +19,7 @@ namespace Flow.Launcher.Test.Plugins
var app = new UWP.Application();
// Act
var result = app.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
var result = UWP.Application.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
// Assert
Assert.IsTrue(result == expectedFormat,

View file

@ -1,4 +1,4 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System;
using System.Collections.Generic;
using System.Data.SQLite;
@ -130,4 +130,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
}
}
}
}
}

View file

@ -3,7 +3,8 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<UseWPF>true</UseWPF>
<ProjectGuid>{9B130CC5-14FB-41FF-B310-0A95B6894C37}</ProjectGuid>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Flow.Launcher.Plugin.BrowserBookmark</RootNamespace>
@ -23,7 +24,7 @@
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
@ -39,15 +40,6 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="x64\SQLite.Interop.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="x86\SQLite.Interop.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
@ -64,8 +56,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Data.SQLite" Version="1.0.114.4" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.114.3" />
<PackageReference Include="System.Data.SQLite" Version="1.0.116" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.116" />
<PackageReference Include="UnidecodeSharp" Version="1.0.0" />
</ItemGroup>

View file

@ -10,8 +10,7 @@
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
WindowStartupLocation="CenterScreen">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
@ -20,7 +19,6 @@
<RowDefinition />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
@ -54,42 +52,78 @@
</Button>
</Grid>
</StackPanel>
<StackPanel Margin="26,12,26,0">
<StackPanel Margin="0,0,0,12">
<StackPanel Margin="26,0,26,0">
<StackPanel Grid.Row="0" Margin="0,0,0,12">
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource flowlauncher_plugin_program_directory}"
Text="{DynamicResource flowlauncher_plugin_program_edit_program_source_title}"
TextAlignment="Left" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<Grid>
<StackPanel>
<TextBlock
FontSize="14"
Text="{DynamicResource flowlauncher_plugin_program_edit_program_source_tips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
</StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<Grid Width="470">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox
Name="Directory"
<TextBlock
Grid.Row="0"
Grid.Column="0"
MinWidth="248"
Margin="0,7"
VerticalAlignment="Center" />
<Button
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource flowlauncher_plugin_program_directory}" />
<DockPanel
Grid.Row="0"
Grid.Column="1"
LastChildFill="True">
<Button
Width="70"
HorizontalAlignment="Right"
Click="BrowseButton_Click"
DockPanel.Dock="Right"
Content="{DynamicResource flowlauncher_plugin_program_browse}" />
<TextBox
Name="Directory"
Margin="10"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</DockPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource flowlauncher_plugin_program_enabled}" />
<CheckBox x:Name="Chkbox"
Grid.Row="1"
Grid.Column="1"
MinWidth="80"
Margin="10,10,0,10"
Padding="14,6,14,6"
HorizontalAlignment="Right"
Click="BrowseButton_Click"
Content="{DynamicResource flowlauncher_plugin_program_browse}" />
Margin="10,0"
VerticalAlignment="Center" />
</Grid>
</StackPanel>
</StackPanel>
</StackPanel>
<Border
Grid.Row="1"
Margin="0,14,0,0"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0,1,0,0">
@ -97,19 +131,17 @@
<Button
x:Name="btnCancel"
MinWidth="140"
Margin="0,0,5,0"
Margin="10,0,5,0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnAdd"
MinWidth="140"
Margin="5,0,0,0"
HorizontalAlignment="Right"
Click="ButtonAdd_OnClick"
Margin="5,0,10,0"
Click="BtnAdd_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_update}"
Style="{DynamicResource AccentButtonStyle}" />
</StackPanel>
</Border>
</Grid>
</Window>
</Window>

View file

@ -9,11 +9,12 @@ namespace Flow.Launcher.Plugin.Program
/// <summary>
/// Interaction logic for AddProgramSource.xaml
/// </summary>
public partial class AddProgramSource
public partial class AddProgramSource : Window
{
private PluginInitContext _context;
private Settings.ProgramSource _editing;
private ProgramSource _editing;
private Settings _settings;
private bool update;
public AddProgramSource(PluginInitContext context, Settings settings)
{
@ -21,14 +22,19 @@ namespace Flow.Launcher.Plugin.Program
_context = context;
_settings = settings;
Directory.Focus();
Chkbox.IsChecked = true;
update = false;
btnAdd.Content = _context.API.GetTranslation("flowlauncher_plugin_program_add");
}
public AddProgramSource(Settings.ProgramSource edit, Settings settings)
public AddProgramSource(PluginInitContext context, Settings settings, ProgramSource source)
{
_editing = edit;
_settings = settings;
InitializeComponent();
_context = context;
_editing = source;
_settings = settings;
update = true;
Chkbox.IsChecked = _editing.Enabled;
Directory.Text = _editing.Location;
}
@ -47,34 +53,54 @@ namespace Flow.Launcher.Plugin.Program
Close();
}
private void ButtonAdd_OnClick(object sender, RoutedEventArgs e)
private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
{
string s = Directory.Text;
if (!System.IO.Directory.Exists(s))
string path = Directory.Text;
bool modified = false;
if (!System.IO.Directory.Exists(path))
{
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_invalid_path"));
return;
}
if (_editing == null)
if (!update)
{
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == Directory.Text))
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(path, System.StringComparison.OrdinalIgnoreCase)))
{
var source = new ProgramSource
{
Location = Directory.Text,
UniqueIdentifier = Directory.Text
};
var source = new ProgramSource(path);
modified = true;
_settings.ProgramSources.Insert(0, source);
ProgramSetting.ProgramSettingDisplayList.Add(source);
}
else
{
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source"));
return;
}
}
else
{
_editing.Location = Directory.Text;
// Separate checks to avoid changing UniqueIdentifier of UWP
if (!_editing.Location.Equals(path, System.StringComparison.OrdinalIgnoreCase))
{
if (ProgramSetting.ProgramSettingDisplayList
.Any(x => x.UniqueIdentifier.Equals(path, System.StringComparison.OrdinalIgnoreCase)))
{
// Check if the new location is used
// No need to check win32 or uwp, just override them
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source"));
return;
}
modified = true;
_editing.Location = path; // Changes UniqueIdentifier internally
}
if (_editing.Enabled != Chkbox.IsChecked)
{
modified = true;
_editing.Enabled = Chkbox.IsChecked ?? true;
}
}
DialogResult = true;
DialogResult = modified;
Close();
}
}

View file

@ -1,58 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.Program.Programs;
namespace Flow.Launcher.Plugin.Program
{
//internal static class FileChangeWatcher
//{
// private static readonly List<string> WatchedPath = new List<string>();
// // todo remove previous watcher events
// public static void AddAll(List<UnregisteredPrograms> sources, string[] suffixes)
// {
// foreach (var s in sources)
// {
// if (Directory.Exists(s.Location))
// {
// AddWatch(s.Location, suffixes);
// }
// }
// }
// public static void AddWatch(string path, string[] programSuffixes, bool includingSubDirectory = true)
// {
// if (WatchedPath.Contains(path)) return;
// if (!Directory.Exists(path))
// {
// Log.Warn($"|FileChangeWatcher|{path} doesn't exist");
// return;
// }
// WatchedPath.Add(path);
// foreach (string fileType in programSuffixes)
// {
// FileSystemWatcher watcher = new FileSystemWatcher
// {
// Path = path,
// IncludeSubdirectories = includingSubDirectory,
// Filter = $"*.{fileType}",
// EnableRaisingEvents = true
// };
// watcher.Changed += FileChanged;
// watcher.Created += FileChanged;
// watcher.Deleted += FileChanged;
// watcher.Renamed += FileChanged;
// }
// }
// private static void FileChanged(object source, FileSystemEventArgs e)
// {
// Task.Run(() =>
// {
// Main.IndexPrograms();
// });
// }
//}
}

View file

@ -10,16 +10,21 @@
<system:String x:Key="flowlauncher_plugin_program_add">Add</system:String>
<system:String x:Key="flowlauncher_plugin_program_name">Name</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable">Enable</system:String>
<system:String x:Key="flowlauncher_plugin_program_enabled">Enabled</system:String>
<system:String x:Key="flowlauncher_plugin_program_disable">Disable</system:String>
<system:String x:Key="flowlauncher_plugin_program_location">Location</system:String>
<system:String x:Key="flowlauncher_plugin_program_all_programs">All Programs</system:String>
<system:String x:Key="flowlauncher_plugin_program_suffixes">File Type</system:String>
<system:String x:Key="flowlauncher_plugin_program_reindex">Reindex</system:String>
<system:String x:Key="flowlauncher_plugin_program_indexing">Indexing</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_start">Index Start Menu</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_source">Index Sources</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_option">Options</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_start">Start Menu</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_start_tooltip">When enabled, Flow will load programs from the start menu</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_registry">Index Registry</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_registry">Registry</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_registry_tooltip">When enabled, Flow will load programs from the registry</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_PATH">PATH</system:String>
<system:String x:Key="flowlauncher_plugin_program_index_PATH_tooltip">When enabled, Flow will load programs from the PATH environment variable</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_hidelnkpath">Hide app path</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_hidelnkpath_tooltip">For executable files such as UWP or lnk, hide the file path from being visible</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_description">Search in Program Description</system:String>
@ -34,8 +39,12 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location alreaday exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_update">OK</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_tips">Edit directory and status of this program source.</system:String>
<system:String x:Key="flowlauncher_plugin_program_update">Update</system:String>
<system:String x:Key="flowlauncher_plugin_program_only_index_tip">Program Plugin will only index files with selected suffixes and .url files with selected protocols.</system:String>
<system:String x:Key="flowlauncher_plugin_program_update_file_suffixes">Successfully updated file suffixes</system:String>
<system:String x:Key="flowlauncher_plugin_program_suffixes_cannot_empty">File suffixes can't be empty</system:String>

View file

@ -1,13 +1,8 @@
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Plugin.Program.Logger
{

View file

@ -1,7 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@ -11,9 +9,8 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.Program.Programs;
using Flow.Launcher.Plugin.Program.Views;
using Flow.Launcher.Plugin.Program.Views.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program
@ -82,13 +79,9 @@ namespace Flow.Launcher.Plugin.Program
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
{
_win32Storage = new BinaryStorage<Win32[]>("Win32");
_win32s = _win32Storage.TryLoad(new Win32[]
{
});
_win32s = _win32Storage.TryLoad(Array.Empty<Win32>());
_uwpStorage = new BinaryStorage<UWP.Application[]>("UWP");
_uwps = _uwpStorage.TryLoad(new UWP.Application[]
{
});
_uwps = _uwpStorage.TryLoad(Array.Empty<UWP.Application>());
});
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
@ -102,7 +95,7 @@ namespace Flow.Launcher.Plugin.Program
var b = Task.Run(() =>
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPPRogram index cost", IndexUwpPrograms);
});
if (cacheEmpty)
@ -123,18 +116,23 @@ namespace Flow.Launcher.Plugin.Program
{
var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major;
var applications = support ? UWP.All() : new UWP.Application[]
{
};
var applications = support ? UWP.All() : Array.Empty<UWP.Application>();
_uwps = applications;
ResetCache();
}
public static async Task IndexProgramsAsync()
{
var t1 = Task.Run(IndexWin32Programs);
var t2 = Task.Run(IndexUwpPrograms);
await Task.WhenAll(t1, t2).ConfigureAwait(false);
var a = Task.Run(() =>
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
});
var b = Task.Run(() =>
{
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpPrograms);
});
await Task.WhenAll(a, b).ConfigureAwait(false);
_settings.LastIndexTime = DateTime.Today;
}
@ -190,29 +188,33 @@ namespace Flow.Launcher.Plugin.Program
return menuOptions;
}
private void DisableProgram(IProgram programToDelete)
private static void DisableProgram(IProgram programToDelete)
{
if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
return;
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.Enabled = false;
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
_win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
.Enabled = false;
_settings.DisabledProgramSources
.Add(
new Settings.DisabledProgramSource
{
Name = programToDelete.Name,
Location = programToDelete.Location,
UniqueIdentifier = programToDelete.UniqueIdentifier,
Enabled = false
}
);
{
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
_ = Task.Run(() =>
{
IndexUwpPrograms();
_settings.LastIndexTime = DateTime.Today;
});
}
else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
{
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
_ = Task.Run(() =>
{
IndexWin32Programs();
_settings.LastIndexTime = DateTime.Today;
});
}
}
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
@ -233,6 +235,7 @@ namespace Flow.Launcher.Plugin.Program
{
await IndexProgramsAsync();
}
public void Dispose()
{
Win32.Dispose();

View file

@ -1,8 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.Program.Programs
{

View file

@ -1,20 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Windows.Storage;
namespace Flow.Launcher.Plugin.Program.Programs
{
public class AppxPackageHelper
{
// This function returns a list of attributes of applications
public List<IAppxManifestApplication> getAppsFromManifest(IStream stream)
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
{
IAppxFactory appxFactory = (IAppxFactory)new AppxFactory();
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
var appxFactory = new AppxFactory();
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
var reader = appxFactory.CreateManifestReader(stream);
var manifestApps = reader.GetApplications();
while (manifestApps.GetHasCurrent())
{

View file

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using Accessibility;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Policy;
namespace Flow.Launcher.Plugin.Program.Programs
{

View file

@ -18,7 +18,6 @@ using Flow.Launcher.Plugin.Program.Logger;
using Rect = System.Windows.Rect;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger;
using System.Runtime.Versioning;
using System.Threading.Channels;
namespace Flow.Launcher.Plugin.Program.Programs
@ -42,39 +41,27 @@ namespace Flow.Launcher.Plugin.Program.Programs
FullName = package.Id.FullName;
FamilyName = package.Id.FamilyName;
InitializeAppInfo();
Apps = Apps.Where(a =>
{
var valid =
!string.IsNullOrEmpty(a.UserModelId) &&
!string.IsNullOrEmpty(a.DisplayName);
return valid;
}).ToArray();
}
private void InitializeAppInfo()
{
AppxPackageHelper _helper = new AppxPackageHelper();
var path = Path.Combine(Location, "AppxManifest.xml");
var namespaces = XmlNamespaces(path);
InitPackageVersion(namespaces);
const uint noAttribute = 0x80;
const Stgm exclusiveRead = Stgm.Read | Stgm.ShareExclusive;
var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out IStream stream);
const Stgm nonExclusiveRead = Stgm.Read | Stgm.ShareDenyNone;
var hResult = SHCreateStreamOnFileEx(path, nonExclusiveRead, noAttribute, false, null, out IStream stream);
if (hResult == Hresult.Ok)
{
var apps = new List<Application>();
List<AppxPackageHelper.IAppxManifestApplication> _apps = AppxPackageHelper.GetAppsFromManifest(stream);
List<AppxPackageHelper.IAppxManifestApplication> _apps = _helper.getAppsFromManifest(stream);
foreach (var _app in _apps)
{
var app = new Application(_app, this);
apps.Add(app);
}
Apps = apps.Where(a => a.AppListEntry != "none").ToArray();
Apps = _apps.Select(x => new Application(x, this))
.Where(a => !string.IsNullOrEmpty(a.UserModelId)
&& !string.IsNullOrEmpty(a.DisplayName))
.ToArray();
}
else
{
@ -82,17 +69,17 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
"|Error caused while trying to get the details of the UWP program", e);
Apps = new List<Application>().ToArray();
Apps = Array.Empty<Application>();
}
if (Marshal.ReleaseComObject(stream) > 0)
if (stream != null && Marshal.ReleaseComObject(stream) > 0)
{
Log.Error("Flow.Launcher.Plugin.Program.Programs.UWP", "AppxManifest.xml was leaked");
}
}
/// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
private string[] XmlNamespaces(string path)
private static string[] XmlNamespaces(string path)
{
XDocument z = XDocument.Load(path);
if (z.Root != null)
@ -110,9 +97,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
$"|Error occured while trying to get the XML from {path}", new ArgumentNullException());
return new string[]
{
};
return Array.Empty<string>();
}
}
@ -178,16 +163,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
var updatedListWithoutDisabledApps = applications
.Where(t1 => !Main._settings.DisabledProgramSources
.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => x);
.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
return updatedListWithoutDisabledApps.ToArray();
}
else
{
return new Application[]
{
};
return Array.Empty<Application>();
}
}
@ -233,9 +215,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
else
{
return new Package[]
{
};
return Array.Empty<Package>();
}
}
@ -297,7 +277,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
[Serializable]
public class Application : IProgram
{
public string AppListEntry { get; set; }
public string UniqueIdentifier { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
@ -317,7 +296,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
public Application() { }
public Result Result(string query, IPublicAPI api)
{
string title;
@ -544,7 +522,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
public string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
public static string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
{
const string prefix = "ms-resource:";
@ -701,9 +679,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
private BitmapImage ImageFromPath(string path)
{
// TODO: Consider using infrastructure.image.imageloader?
if (File.Exists(path))
{
var image = new BitmapImage(new Uri(path));
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(path);
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
return image;
}
else
@ -775,6 +759,23 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
return $"{DisplayName}: {Description}";
}
public override bool Equals(object obj)
{
if (obj is Application other)
{
return UniqueIdentifier == other.UniqueIdentifier;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return UniqueIdentifier.GetHashCode();
}
}
public enum PackageVersion
@ -790,6 +791,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
Read = 0x0,
ShareExclusive = 0x10,
ShareDenyNone = 0x40
}
private enum Hresult : uint

View file

@ -11,14 +11,10 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger;
using System.Collections;
using System.Diagnostics;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Threading.Channels;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Plugin.Program.Views.Models;
using IniParser;
namespace Flow.Launcher.Plugin.Program.Programs
@ -27,7 +23,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
public class Win32 : IProgram, IEquatable<Win32>
{
public string Name { get; set; }
public string UniqueIdentifier { get; set; }
public string UniqueIdentifier { get => _uid; set => _uid = value.ToLowerInvariant(); } // For path comparison
public string IcoPath { get; set; }
public string FullPath { get; set; }
public string LnkResolvedPath { get; set; }
@ -41,6 +37,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
private const string ShortcutExtension = "lnk";
private const string UrlExtension = "url";
private const string ExeExtension = "exe";
private string _uid = string.Empty;
private static readonly Win32 Default = new Win32()
{
@ -225,10 +222,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
$"|Permission denied when trying to load the program from {path}", e);
return new Win32()
{
Valid = false, Enabled = false
};
return Default;
}
}
@ -248,7 +242,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (extension == ExeExtension && File.Exists(target))
{
program.LnkResolvedPath = program.FullPath;
program.FullPath = Path.GetFullPath(target).ToLower();
program.FullPath = Path.GetFullPath(target).ToLowerInvariant();
program.ExecutableName = Path.GetFileName(target);
var description = _helper.description;
@ -279,6 +273,14 @@ namespace Flow.Launcher.Plugin.Program.Programs
program.Valid = false;
return program;
}
catch (FileNotFoundException e)
{
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
"|An unexpected error occurred in the calling method LnkProgram", e);
program.Valid = false;
return program;
}
#if !DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
catch (Exception e)
{
@ -291,7 +293,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
#endif
}
private static Win32 UrlProgram(string path)
private static Win32 UrlProgram(string path, string[] protocols)
{
var program = Win32Program(path);
program.Valid = false;
@ -306,9 +308,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
return program;
}
foreach(var protocol in Main._settings.GetProtocols())
foreach (var protocol in protocols)
{
if(url.StartsWith(protocol))
if (url.StartsWith(protocol))
{
program.LnkResolvedPath = url;
program.Valid = true;
@ -345,30 +347,28 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
$"|Permission denied when trying to load the program from {path}", e);
return new Win32()
{
Valid = false, Enabled = false
};
return Default;
}
}
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes)
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes, bool recursive = true)
{
if (!Directory.Exists(directory))
return Enumerable.Empty<string>();
return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{
IgnoreInaccessible = true, RecurseSubdirectories = true
IgnoreInaccessible = true,
RecurseSubdirectories = recursive
}).Where(x => suffixes.Contains(Extension(x)));
}
private static string Extension(string path)
{
var extension = Path.GetExtension(path)?.ToLower();
var extension = Path.GetExtension(path)?.ToLowerInvariant();
if (!string.IsNullOrEmpty(extension))
{
return extension.Substring(1);
return extension.Substring(1); // remove dot
}
else
{
@ -376,27 +376,20 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
private static IEnumerable<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
private static IEnumerable<Win32> UnregisteredPrograms(List<ProgramSource> sources, string[] suffixes, string[] protocols)
{
// Disabled custom sources are not in DisabledProgramSources
var paths = ExceptDisabledSource(sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes)), x => x)
.Distinct();
var programs = paths.Select(x => Extension(x) switch
{
ExeExtension => ExeProgram(x),
ShortcutExtension => LnkProgram(x),
UrlExtension => UrlProgram(x),
_ => Win32Program(x)
});
.AsParallel()
.SelectMany(s => ProgramPaths(s.Location, suffixes)))
.Distinct();
var programs = paths.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes)
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes, string[] protocols)
{
var disabledProgramsList = Main._settings.DisabledProgramSources;
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
var paths1 = ProgramPaths(directory1, suffixes);
@ -404,17 +397,35 @@ namespace Flow.Launcher.Plugin.Program.Programs
var toFilter = paths1.Concat(paths2);
var programs = ExceptDisabledSource(toFilter.Distinct())
.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
private static IEnumerable<Win32> PATHPrograms(string[] suffixes, string[] protocols)
{
var pathEnv = Environment.GetEnvironmentVariable("Path");
if (String.IsNullOrEmpty(pathEnv))
{
return Array.Empty<Win32>();
}
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
var toFilter = paths.AsParallel().SelectMany(p => ProgramPaths(p, suffixes, recursive: false));
var programs = ExceptDisabledSource(toFilter.Distinct())
.Select(x => Extension(x) switch
{
ShortcutExtension => LnkProgram(x),
UrlExtension => UrlProgram(x),
UrlExtension => UrlProgram(x, protocols),
ExeExtension => ExeProgram(x),
_ => Win32Program(x)
});
return programs;
}
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes)
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes, string[] protocols)
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
@ -434,12 +445,11 @@ namespace Flow.Launcher.Plugin.Program.Programs
toFilter = toFilter.Concat(GetPathFromRegistry(rootUser));
}
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
var filtered = ExceptDisabledSource(toFilter);
return filtered.Select(GetProgramFromPath).ToList(); // ToList due to disposing issue
var programs = ExceptDisabledSource(toFilter)
.Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
return programs;
}
private static IEnumerable<string> GetPathFromRegistry(RegistryKey root)
@ -479,7 +489,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
private static Win32 GetProgramFromPath(string path)
private static Win32 GetProgramFromPath(string path, string[] protocols)
{
if (string.IsNullOrEmpty(path))
return Default;
@ -489,14 +499,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (!File.Exists(path))
return Default;
var entry = Win32Program(path);
return entry;
return Extension(path) switch
{
ShortcutExtension => LnkProgram(path),
ExeExtension => ExeProgram(path),
UrlExtension => UrlProgram(path, protocols),
_ => Win32Program(path)
}; ;
}
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> sources)
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> paths)
{
return ExceptDisabledSource(sources, x => x);
return ExceptDisabledSource(paths, x => x.ToLowerInvariant());
}
public static IEnumerable<TSource> ExceptDisabledSource<TSource>(IEnumerable<TSource> sources,
@ -531,7 +545,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
private static IEnumerable<Win32> ProgramsHasher(IEnumerable<Win32> programs)
{
return programs.GroupBy(p => p.FullPath.ToLower())
return programs.GroupBy(p => p.FullPath.ToLowerInvariant())
.AsParallel()
.SelectMany(g =>
{
var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList();
@ -547,8 +562,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
try
{
var programs = Enumerable.Empty<Win32>();
var suffixes = settings.GetSuffixes();
var protocols = settings.GetProtocols();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.GetSuffixes());
var unregistered = UnregisteredPrograms(settings.ProgramSources, suffixes, protocols);
programs = programs.Concat(unregistered);
@ -556,16 +573,22 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (settings.EnableRegistrySource)
{
var appPaths = AppPathsPrograms(settings.GetSuffixes());
var appPaths = AppPathsPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(appPaths);
}
if (settings.EnableStartMenuSource)
{
var startMenu = StartMenuPrograms(settings.GetSuffixes());
var startMenu = StartMenuPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(startMenu);
}
if (settings.EnablePATHSource)
{
var path = PATHPrograms(settings.GetSuffixes(), protocols);
autoIndexPrograms = autoIndexPrograms.Concat(path);
}
autoIndexPrograms = ProgramsHasher(autoIndexPrograms);
return programs.Concat(autoIndexPrograms).Where(x => x.Valid).Distinct().ToArray();
@ -600,6 +623,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
return UniqueIdentifier == other.UniqueIdentifier;
}
public override bool Equals(object obj)
{
if (obj is Win32 other)
{
return UniqueIdentifier == other.UniqueIdentifier;
}
else
{
return false;
}
}
private static IEnumerable<string> GetStartMenuPaths()
{
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
@ -640,7 +675,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
await Task.Run(Main.IndexWin32Programs);
}
}
public static void WatchDirectory(string directory)
{
if (!Directory.Exists(directory))
@ -653,7 +688,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true;
Watchers.Add(watcher);
}

View file

@ -1,19 +1,26 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using Windows.Foundation.Metadata;
using Flow.Launcher.Plugin.Program.Views.Models;
namespace Flow.Launcher.Plugin.Program
{
public class Settings
{
public DateTime LastIndexTime { get; set; }
public List<ProgramSource> ProgramSources { get; set; } = new List<ProgramSource>();
public List<DisabledProgramSource> DisabledProgramSources { get; set; } = new List<DisabledProgramSource>();
[Obsolete, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
/// <summary>
/// User-added program sources' directories
/// </summary>
public List<ProgramSource> ProgramSources { get; set; } = new List<ProgramSource>();
/// <summary>
/// Disabled single programs, not including User-added directories
/// </summary>
public List<ProgramSource> DisabledProgramSources { get; set; } = new List<ProgramSource>();
[Obsolete("Should use GetSuffixes() instead."), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string[] ProgramSuffixes { get; set; } = null;
public string[] CustomSuffixes { get; set; } = Array.Empty<string>(); // Custom suffixes only
public string[] CustomProtocols { get; set; } = Array.Empty<string>();
@ -111,6 +118,8 @@ namespace Flow.Launcher.Plugin.Program
public bool EnableDescription { get; set; } = false;
public bool HideAppsPath { get; set; } = true;
public bool EnableRegistrySource { get; set; } = true;
public bool EnablePATHSource { get; set; } = true;
public string CustomizedExplorer { get; set; } = Explorer;
public string CustomizedArgs { get; set; } = ExplorerArgs;
@ -119,25 +128,5 @@ namespace Flow.Launcher.Plugin.Program
internal const string Explorer = "explorer";
internal const string ExplorerArgs = "%s";
/// <summary>
/// Contains user added folder location contents as well as all user disabled applications
/// </summary>
/// <remarks>
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
/// </remarks>
public class ProgramSource
{
private string name;
public string Location { get; set; }
public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
public bool Enabled { get; set; } = true;
public string UniqueIdentifier { get; set; }
}
public class DisabledProgramSource : ProgramSource { }
}
}

View file

@ -1,146 +1,88 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flow.Launcher.Plugin.Program.Views.Models;
namespace Flow.Launcher.Plugin.Program.Views.Commands
{
internal static class ProgramSettingDisplay
{
internal static List<ProgramSource> LoadProgramSources(this List<Settings.ProgramSource> programSources)
internal static List<ProgramSource> LoadProgramSources()
{
var list = new List<ProgramSource>();
programSources.ForEach(x => list
.Add(
new ProgramSource
{
Enabled = x.Enabled,
Location = x.Location,
Name = x.Name,
UniqueIdentifier = x.UniqueIdentifier
}
));
// Even though these are disabled, we still want to display them so users can enable later on
Main._settings
.DisabledProgramSources
.Where(t1 => !Main._settings
.ProgramSources // program sourcces added above already, so exlcude
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
.Select(x => x)
.ToList()
.ForEach(x => list
.Add(
new ProgramSource
{
Enabled = x.Enabled,
Location = x.Location,
Name = x.Name,
UniqueIdentifier = x.UniqueIdentifier
}
));
return list;
return Main._settings
.DisabledProgramSources
.Union(Main._settings.ProgramSources)
.ToList();
}
internal static void LoadAllApplications(this List<ProgramSource> list)
internal static void DisplayAllPrograms()
{
Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.ToList()
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
.Add(
new ProgramSource
{
Name = t1.Name,
Location = t1.ParentDirectory,
UniqueIdentifier = t1.UniqueIdentifier,
Enabled = t1.Enabled
}
));
var win32 = Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.ToList()
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
.Add(
new ProgramSource
{
Name = t1.DisplayName,
Location = t1.Package.Location,
UniqueIdentifier = t1.UniqueIdentifier,
Enabled = t1.Enabled
}
));
var uwp = Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
}
internal static void SetProgramSourcesStatus(this List<ProgramSource> list, List<ProgramSource> selectedProgramSourcesToDisable, bool status)
internal static void SetProgramSourcesStatus(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
{
ProgramSetting.ProgramSettingDisplayList
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
.ToList()
.ForEach(t1 => t1.Enabled = status);
foreach(var program in ProgramSetting.ProgramSettingDisplayList)
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
Main._win32s
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
.ToList()
.ForEach(t1 => t1.Enabled = status);
foreach(var program in Main._win32s)
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
Main._uwps
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
.ToList()
.ForEach(t1 => t1.Enabled = status);
foreach (var program in Main._uwps)
{
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
{
program.Enabled = status;
}
}
}
internal static void StoreDisabledInSettings(this List<ProgramSource> list)
internal static void StoreDisabledInSettings()
{
Main._settings.ProgramSources
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && !x.Enabled))
.ToList()
.ForEach(t1 => t1.Enabled = false);
ProgramSetting.ProgramSettingDisplayList
// Disabled, not in DisabledProgramSources or ProgramSources
var tmp = ProgramSetting.ProgramSettingDisplayList
.Where(t1 => !t1.Enabled
&& !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.ToList()
.ForEach(x => Main._settings.DisabledProgramSources
.Add(
new Settings.DisabledProgramSource
{
Name = x.Name,
Location = x.Location,
UniqueIdentifier = x.UniqueIdentifier,
Enabled = false
}
));
&& !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)
&& !Main._settings.ProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
Main._settings.DisabledProgramSources.AddRange(tmp);
}
internal static void RemoveDisabledFromSettings(this List<ProgramSource> list)
internal static void RemoveDisabledFromSettings()
{
Main._settings.ProgramSources
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
.ToList()
.ForEach(t1 => t1.Enabled = true);
Main._settings.DisabledProgramSources
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
.ToList()
.ForEach(x => Main._settings.DisabledProgramSources.Remove(x));
Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
}
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
{
if (selectedItems.Where(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0
&& selectedItems.Where(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
// Not in cache
if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
&& selectedItems.Any(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
return true;
// ProgramSources holds list of user added directories,
// so when we enable/disable we need to reindex to show/not show the programs
// that are found in those directories.
if (selectedItems.Where(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
if (selectedItems.Any(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
return true;
return false;

View file

@ -1,5 +1,86 @@

using System;
using System.IO;
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin.Program.Programs;
namespace Flow.Launcher.Plugin.Program.Views.Models
{
public class ProgramSource : Settings.ProgramSource { }
/// <summary>
/// Contains user added folder location contents as well as all user disabled applications
/// </summary>
/// <remarks>
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
/// </remarks>
public class ProgramSource
{
private string name;
private string loc;
public string Location
{
get => loc;
set
{
loc = value;
UniqueIdentifier = value.ToLowerInvariant();
}
}
public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
public bool Enabled { get; set; } = true;
public string UniqueIdentifier { get; private set; }
[JsonConstructor]
public ProgramSource(string name, string location, bool enabled, string uniqueIdentifier)
{
loc = location;
this.name = name;
Enabled = enabled;
if (location.Equals(uniqueIdentifier, StringComparison.OrdinalIgnoreCase))
{
UniqueIdentifier = location.ToLowerInvariant(); // To make sure old config can be reset to case-insensitive
}
else
{
UniqueIdentifier = uniqueIdentifier; // For uwp apps
}
}
/// <summary>
/// Add source by location
/// </summary>
/// <param name="location">location of program source</param>
/// <param name="enabled">enabled</param>
public ProgramSource(string location, bool enabled = true)
{
loc = location;
Enabled = enabled;
UniqueIdentifier = location.ToLowerInvariant(); // For path comparison
}
public ProgramSource(IProgram source)
{
loc = source.Location;
Name = source.Name;
Enabled = source.Enabled;
UniqueIdentifier = source.UniqueIdentifier;
}
public override bool Equals(object obj)
{
return obj is ProgramSource other && other.UniqueIdentifier == this.UniqueIdentifier;
}
public bool Equals(IProgram program)
{
return program != null && program.UniqueIdentifier == this.UniqueIdentifier;
}
public override int GetHashCode()
{
return HashCode.Combine(UniqueIdentifier);
}
}
}

View file

@ -10,58 +10,88 @@
mc:Ignorable="d">
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="170" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<DockPanel
Margin="70,10,0,8"
HorizontalAlignment="Stretch"
LastChildFill="True">
<TextBlock
MinWidth="120"
Margin="0,5,10,0"
Text="{DynamicResource flowlauncher_plugin_program_index_source}" />
<WrapPanel
Width="Auto"
Margin="0,0,14,0"
HorizontalAlignment="Right"
DockPanel.Dock="Right">
<CheckBox
Name="StartMenuEnabled"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_index_start}"
IsChecked="{Binding EnableStartMenuSource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" />
<CheckBox
Name="RegistryEnabled"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_index_registry}"
IsChecked="{Binding EnableRegistrySource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" />
<CheckBox
Name="PATHEnabled"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_index_PATH}"
IsChecked="{Binding EnablePATHSource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_PATH_tooltip}" />
</WrapPanel>
</DockPanel>
<StackPanel
Grid.Row="0"
Grid.Row="1"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel Width="Auto" Orientation="Vertical">
<StackPanel Width="Auto" Orientation="Horizontal">
<CheckBox
Name="StartMenuEnabled"
Width="220"
Margin="70,8,10,8"
Content="{DynamicResource flowlauncher_plugin_program_index_start}"
IsChecked="{Binding EnableStartMenuSource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" />
<CheckBox
Name="RegistryEnabled"
Margin="70,8,10,8"
Content="{DynamicResource flowlauncher_plugin_program_index_registry}"
IsChecked="{Binding EnableRegistrySource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" />
</StackPanel>
<Separator
Height="1"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" />
<StackPanel Width="Auto" Orientation="Horizontal">
<Separator
Height="1"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" />
<DockPanel
Margin="70,10,0,8"
HorizontalAlignment="Stretch"
LastChildFill="True">
<TextBlock
MinWidth="120"
Margin="0,5,10,0"
Text="{DynamicResource flowlauncher_plugin_program_index_option}" />
<WrapPanel
Width="Auto"
Margin="0,0,14,0"
HorizontalAlignment="Right"
DockPanel.Dock="Right">
<CheckBox
Name="HideLnkEnabled"
Width="220"
Margin="70,8,10,8"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}"
IsChecked="{Binding HideAppsPath}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" />
<CheckBox
Name="DescriptionEnabled"
Margin="70,8,10,8"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_enable_description}"
IsChecked="{Binding EnableDescription}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" />
</StackPanel>
<Separator
Height="1"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" />
</StackPanel>
</WrapPanel>
</DockPanel>
<Separator
Height="1"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" />
<StackPanel
Width="Auto"
Margin="10,6,0,0"
Margin="60,6,0,0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
@ -107,8 +137,8 @@
</StackPanel>
<ListView
x:Name="programSourceView"
Grid.Row="1"
Margin="20,0,20,0"
Grid.Row="2"
Margin="70,0,20,0"
AllowDrop="True"
BorderBrush="DarkGray"
BorderThickness="1"
@ -116,6 +146,8 @@
Drop="programSourceView_Drop"
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler"
PreviewMouseRightButtonUp="ProgramSourceView_PreviewMouseRightButtonUp"
MouseDoubleClick="programSourceView_MouseDoubleClick"
SelectionChanged="programSourceView_SelectionChanged"
SelectionMode="Extended">
<ListView.View>
<GridView>
@ -126,7 +158,7 @@
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enable}">
<GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enabled}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
@ -147,7 +179,7 @@
</ListView.View>
</ListView>
<DockPanel
Grid.Row="2"
Grid.Row="3"
Grid.RowSpan="1"
Margin="0,0,20,10">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
@ -173,66 +205,3 @@
</DockPanel>
</Grid>
</UserControl>

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@ -36,6 +35,7 @@ namespace Flow.Launcher.Plugin.Program.Views
_settings.EnableDescription = value;
}
}
public bool HideAppsPath
{
get => _settings.HideAppsPath;
@ -66,6 +66,16 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
public bool EnablePATHSource
{
get => _settings.EnablePATHSource;
set
{
_settings.EnablePATHSource = value;
ReIndexing();
}
}
public string CustomizedExplorerPath
{
get => _settings.CustomizedExplorer;
@ -88,7 +98,7 @@ namespace Flow.Launcher.Plugin.Program.Views
private void Setting_Loaded(object sender, RoutedEventArgs e)
{
ProgramSettingDisplayList = _settings.ProgramSources.LoadProgramSources();
ProgramSettingDisplayList = ProgramSettingDisplay.LoadProgramSources();
programSourceView.ItemsSource = ProgramSettingDisplayList;
ViewRefresh();
@ -147,20 +157,35 @@ namespace Flow.Launcher.Plugin.Program.Views
private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
{
var selectedProgramSource = programSourceView.SelectedItem as Settings.ProgramSource;
if (selectedProgramSource != null)
{
var add = new AddProgramSource(selectedProgramSource, _settings);
if (add.ShowDialog() ?? false)
{
ReIndexing();
}
}
else
var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
EditProgramSource(selectedProgramSource);
}
private void EditProgramSource(ProgramSource selectedProgramSource)
{
if (selectedProgramSource == null)
{
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
MessageBox.Show(msg);
}
else
{
var add = new AddProgramSource(context, _settings, selectedProgramSource);
if (add.ShowDialog() ?? false)
{
if (selectedProgramSource.Enabled)
{
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource }, true); // sync status in win32, uwp and disabled
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
else
{
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource }, false);
ProgramSettingDisplay.StoreDisabledInSettings();
}
ReIndexing();
}
}
}
private void btnReindex_Click(object sender, RoutedEventArgs e)
@ -199,19 +224,16 @@ namespace Flow.Launcher.Plugin.Program.Views
{
foreach (string directory in directories)
{
if (Directory.Exists(directory) && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == directory))
if (Directory.Exists(directory)
&& !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
{
var source = new ProgramSource
{
Location = directory,
UniqueIdentifier = directory
};
var source = new ProgramSource(directory);
directoriesToAdd.Add(source);
}
}
if (directoriesToAdd.Count() > 0)
if (directoriesToAdd.Count > 0)
{
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
@ -224,7 +246,7 @@ namespace Flow.Launcher.Plugin.Program.Views
private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
{
ProgramSettingDisplayList.LoadAllApplications();
ProgramSettingDisplay.DisplayAllPrograms();
ViewRefresh();
}
@ -235,18 +257,14 @@ namespace Flow.Launcher.Plugin.Program.Views
.SelectedItems.Cast<ProgramSource>()
.ToList();
if (selectedItems.Count() == 0)
if (selectedItems.Count == 0)
{
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
MessageBox.Show(msg);
return;
}
if (selectedItems
.Where(t1 => !_settings
.ProgramSources
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
.Count() == 0)
if (IsAllItemsUserAdded(selectedItems))
{
var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
@ -257,17 +275,17 @@ namespace Flow.Launcher.Plugin.Program.Views
DeleteProgramSources(selectedItems);
}
else if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
else if (HasMoreOrEqualEnabledItems(selectedItems))
{
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, false);
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false);
ProgramSettingDisplayList.StoreDisabledInSettings();
ProgramSettingDisplay.StoreDisabledInSettings();
}
else
{
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, true);
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true);
ProgramSettingDisplayList.RemoveDisabledFromSettings();
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
if (selectedItems.IsReindexRequired())
@ -329,35 +347,41 @@ namespace Flow.Launcher.Plugin.Program.Views
dataView.Refresh();
}
private bool IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(List<ProgramSource> selectedItems)
private static bool HasMoreOrEqualEnabledItems(List<ProgramSource> items)
{
return selectedItems.Where(x => x.Enabled).Count() >= selectedItems.Where(x => !x.Enabled).Count();
var enableCount = items.Where(x => x.Enabled).Count();
return enableCount >= items.Count - enableCount;
}
private void Row_OnClick(object sender, RoutedEventArgs e)
private void programSourceView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItems = programSourceView
.SelectedItems.Cast<ProgramSource>()
.ToList();
.SelectedItems.Cast<ProgramSource>()
.ToList();
if (selectedItems
.Where(t1 => !_settings
.ProgramSources
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
.Count() == 0)
if (IsAllItemsUserAdded(selectedItems))
{
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
return;
}
if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
else if (HasMoreOrEqualEnabledItems(selectedItems))
{
btnProgramSourceStatus.Content = "Disable";
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
}
else
{
btnProgramSourceStatus.Content = "Enable";
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_enable");
}
}
private void programSourceView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
EditProgramSource(selectedProgramSource);
}
private bool IsAllItemsUserAdded(List<ProgramSource> items)
{
return items.All(x => _settings.ProgramSources.Any(y => y.UniqueIdentifier == x.UniqueIdentifier));
}
}
}
}