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.Schema" Version="4.7.10" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.13.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.13.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.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--> <!--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--> <!--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" /> <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(); var app = new UWP.Application();
// Act // Act
var result = app.FormattedPriReferenceValue(packageName, rawPriReferenceValue); var result = UWP.Application.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
// Assert // Assert
Assert.IsTrue(result == expectedFormat, 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SQLite; using System.Data.SQLite;
@ -130,4 +130,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
} }
} }
} }
} }

View file

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

View file

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

View file

@ -9,11 +9,12 @@ namespace Flow.Launcher.Plugin.Program
/// <summary> /// <summary>
/// Interaction logic for AddProgramSource.xaml /// Interaction logic for AddProgramSource.xaml
/// </summary> /// </summary>
public partial class AddProgramSource public partial class AddProgramSource : Window
{ {
private PluginInitContext _context; private PluginInitContext _context;
private Settings.ProgramSource _editing; private ProgramSource _editing;
private Settings _settings; private Settings _settings;
private bool update;
public AddProgramSource(PluginInitContext context, Settings settings) public AddProgramSource(PluginInitContext context, Settings settings)
{ {
@ -21,14 +22,19 @@ namespace Flow.Launcher.Plugin.Program
_context = context; _context = context;
_settings = settings; _settings = settings;
Directory.Focus(); 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(); InitializeComponent();
_context = context;
_editing = source;
_settings = settings;
update = true;
Chkbox.IsChecked = _editing.Enabled;
Directory.Text = _editing.Location; Directory.Text = _editing.Location;
} }
@ -47,34 +53,54 @@ namespace Flow.Launcher.Plugin.Program
Close(); Close();
} }
private void ButtonAdd_OnClick(object sender, RoutedEventArgs e) private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
{ {
string s = Directory.Text; string path = Directory.Text;
if (!System.IO.Directory.Exists(s)) bool modified = false;
if (!System.IO.Directory.Exists(path))
{ {
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_invalid_path")); System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_invalid_path"));
return; 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 var source = new ProgramSource(path);
{ modified = true;
Location = Directory.Text,
UniqueIdentifier = Directory.Text
};
_settings.ProgramSources.Insert(0, source); _settings.ProgramSources.Insert(0, source);
ProgramSetting.ProgramSettingDisplayList.Add(source); ProgramSetting.ProgramSettingDisplayList.Add(source);
} }
else
{
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source"));
return;
}
} }
else 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(); 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_add">Add</system:String>
<system:String x:Key="flowlauncher_plugin_program_name">Name</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_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_disable">Disable</system:String>
<system:String x:Key="flowlauncher_plugin_program_location">Location</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_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_suffixes">File Type</system:String>
<system:String x:Key="flowlauncher_plugin_program_reindex">Reindex</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_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_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_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">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_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> <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_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_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_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_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> <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;
using NLog.Config;
using NLog.Targets;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security; using System.Security;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Plugin.Program.Logger namespace Flow.Launcher.Plugin.Program.Logger
{ {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,14 +11,10 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger;
using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Threading.Channels; using System.Threading.Channels;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin.Program.Views.Models;
using IniParser; using IniParser;
namespace Flow.Launcher.Plugin.Program.Programs namespace Flow.Launcher.Plugin.Program.Programs
@ -27,7 +23,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
public class Win32 : IProgram, IEquatable<Win32> public class Win32 : IProgram, IEquatable<Win32>
{ {
public string Name { get; set; } 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 IcoPath { get; set; }
public string FullPath { get; set; } public string FullPath { get; set; }
public string LnkResolvedPath { 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 ShortcutExtension = "lnk";
private const string UrlExtension = "url"; private const string UrlExtension = "url";
private const string ExeExtension = "exe"; private const string ExeExtension = "exe";
private string _uid = string.Empty;
private static readonly Win32 Default = new Win32() private static readonly Win32 Default = new Win32()
{ {
@ -225,10 +222,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|Win32Program|{path}" + ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
$"|Permission denied when trying to load the program from {path}", e); $"|Permission denied when trying to load the program from {path}", e);
return new Win32() return Default;
{
Valid = false, Enabled = false
};
} }
} }
@ -248,7 +242,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (extension == ExeExtension && File.Exists(target)) if (extension == ExeExtension && File.Exists(target))
{ {
program.LnkResolvedPath = program.FullPath; program.LnkResolvedPath = program.FullPath;
program.FullPath = Path.GetFullPath(target).ToLower(); program.FullPath = Path.GetFullPath(target).ToLowerInvariant();
program.ExecutableName = Path.GetFileName(target); program.ExecutableName = Path.GetFileName(target);
var description = _helper.description; var description = _helper.description;
@ -279,6 +273,14 @@ namespace Flow.Launcher.Plugin.Program.Programs
program.Valid = false; program.Valid = false;
return program; 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. #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) catch (Exception e)
{ {
@ -291,7 +293,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
#endif #endif
} }
private static Win32 UrlProgram(string path) private static Win32 UrlProgram(string path, string[] protocols)
{ {
var program = Win32Program(path); var program = Win32Program(path);
program.Valid = false; program.Valid = false;
@ -306,9 +308,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
return program; 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.LnkResolvedPath = url;
program.Valid = true; program.Valid = true;
@ -345,30 +347,28 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
$"|Permission denied when trying to load the program from {path}", e); $"|Permission denied when trying to load the program from {path}", e);
return new Win32() return Default;
{
Valid = false, Enabled = false
};
} }
} }
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)) if (!Directory.Exists(directory))
return Enumerable.Empty<string>(); return Enumerable.Empty<string>();
return Directory.EnumerateFiles(directory, "*", new EnumerationOptions return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{ {
IgnoreInaccessible = true, RecurseSubdirectories = true IgnoreInaccessible = true,
RecurseSubdirectories = recursive
}).Where(x => suffixes.Contains(Extension(x))); }).Where(x => suffixes.Contains(Extension(x)));
} }
private static string Extension(string path) private static string Extension(string path)
{ {
var extension = Path.GetExtension(path)?.ToLower(); var extension = Path.GetExtension(path)?.ToLowerInvariant();
if (!string.IsNullOrEmpty(extension)) if (!string.IsNullOrEmpty(extension))
{ {
return extension.Substring(1); return extension.Substring(1); // remove dot
} }
else 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) var paths = ExceptDisabledSource(sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes)), x => x) .AsParallel()
.Distinct(); .SelectMany(s => ProgramPaths(s.Location, suffixes)))
.Distinct();
var programs = paths.Select(x => Extension(x) switch
{
ExeExtension => ExeProgram(x),
ShortcutExtension => LnkProgram(x),
UrlExtension => UrlProgram(x),
_ => Win32Program(x)
});
var programs = paths.Select(x => GetProgramFromPath(x, protocols));
return programs; 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 directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms); var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
var paths1 = ProgramPaths(directory1, suffixes); var paths1 = ProgramPaths(directory1, suffixes);
@ -404,17 +397,35 @@ namespace Flow.Launcher.Plugin.Program.Programs
var toFilter = paths1.Concat(paths2); 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()) var programs = ExceptDisabledSource(toFilter.Distinct())
.Select(x => Extension(x) switch .Select(x => Extension(x) switch
{ {
ShortcutExtension => LnkProgram(x), ShortcutExtension => LnkProgram(x),
UrlExtension => UrlProgram(x), UrlExtension => UrlProgram(x, protocols),
ExeExtension => ExeProgram(x),
_ => Win32Program(x) _ => Win32Program(x)
}); });
return programs; 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 // https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"; 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.Concat(GetPathFromRegistry(rootUser));
} }
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p))); toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
var filtered = ExceptDisabledSource(toFilter); var programs = ExceptDisabledSource(toFilter)
.Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
return filtered.Select(GetProgramFromPath).ToList(); // ToList due to disposing issue return programs;
} }
private static IEnumerable<string> GetPathFromRegistry(RegistryKey root) 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)) if (string.IsNullOrEmpty(path))
return Default; return Default;
@ -489,14 +499,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (!File.Exists(path)) if (!File.Exists(path))
return Default; return Default;
var entry = Win32Program(path); return Extension(path) switch
{
return entry; 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, 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) 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 => .SelectMany(g =>
{ {
var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList(); var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList();
@ -547,8 +562,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
try try
{ {
var programs = Enumerable.Empty<Win32>(); 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); programs = programs.Concat(unregistered);
@ -556,16 +573,22 @@ namespace Flow.Launcher.Plugin.Program.Programs
if (settings.EnableRegistrySource) if (settings.EnableRegistrySource)
{ {
var appPaths = AppPathsPrograms(settings.GetSuffixes()); var appPaths = AppPathsPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(appPaths); autoIndexPrograms = autoIndexPrograms.Concat(appPaths);
} }
if (settings.EnableStartMenuSource) if (settings.EnableStartMenuSource)
{ {
var startMenu = StartMenuPrograms(settings.GetSuffixes()); var startMenu = StartMenuPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(startMenu); autoIndexPrograms = autoIndexPrograms.Concat(startMenu);
} }
if (settings.EnablePATHSource)
{
var path = PATHPrograms(settings.GetSuffixes(), protocols);
autoIndexPrograms = autoIndexPrograms.Concat(path);
}
autoIndexPrograms = ProgramsHasher(autoIndexPrograms); autoIndexPrograms = ProgramsHasher(autoIndexPrograms);
return programs.Concat(autoIndexPrograms).Where(x => x.Valid).Distinct().ToArray(); return programs.Concat(autoIndexPrograms).Where(x => x.Valid).Distinct().ToArray();
@ -600,6 +623,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
return UniqueIdentifier == other.UniqueIdentifier; 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() private static IEnumerable<string> GetStartMenuPaths()
{ {
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs); var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
@ -640,7 +675,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
await Task.Run(Main.IndexWin32Programs); await Task.Run(Main.IndexWin32Programs);
} }
} }
public static void WatchDirectory(string directory) public static void WatchDirectory(string directory)
{ {
if (!Directory.Exists(directory)) if (!Directory.Exists(directory))
@ -653,7 +688,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default); watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.EnableRaisingEvents = true; watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true; watcher.IncludeSubdirectories = true;
Watchers.Add(watcher); Watchers.Add(watcher);
} }

View file

@ -1,19 +1,26 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Windows.Foundation.Metadata; using Flow.Launcher.Plugin.Program.Views.Models;
namespace Flow.Launcher.Plugin.Program namespace Flow.Launcher.Plugin.Program
{ {
public class Settings public class Settings
{ {
public DateTime LastIndexTime { get; set; } 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[] ProgramSuffixes { get; set; } = null;
public string[] CustomSuffixes { get; set; } = Array.Empty<string>(); // Custom suffixes only public string[] CustomSuffixes { get; set; } = Array.Empty<string>(); // Custom suffixes only
public string[] CustomProtocols { get; set; } = Array.Empty<string>(); 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 EnableDescription { get; set; } = false;
public bool HideAppsPath { get; set; } = true; public bool HideAppsPath { get; set; } = true;
public bool EnableRegistrySource { get; set; } = true; public bool EnableRegistrySource { get; set; } = true;
public bool EnablePATHSource { get; set; } = true;
public string CustomizedExplorer { get; set; } = Explorer; public string CustomizedExplorer { get; set; } = Explorer;
public string CustomizedArgs { get; set; } = ExplorerArgs; public string CustomizedArgs { get; set; } = ExplorerArgs;
@ -119,25 +128,5 @@ namespace Flow.Launcher.Plugin.Program
internal const string Explorer = "explorer"; internal const string Explorer = "explorer";
internal const string ExplorerArgs = "%s"; 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.Linq;
using System.Text;
using System.Threading.Tasks;
using Flow.Launcher.Plugin.Program.Views.Models; using Flow.Launcher.Plugin.Program.Views.Models;
namespace Flow.Launcher.Plugin.Program.Views.Commands namespace Flow.Launcher.Plugin.Program.Views.Commands
{ {
internal static class ProgramSettingDisplay 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 // Even though these are disabled, we still want to display them so users can enable later on
Main._settings return Main._settings
.DisabledProgramSources .DisabledProgramSources
.Where(t1 => !Main._settings .Union(Main._settings.ProgramSources)
.ProgramSources // program sourcces added above already, so exlcude .ToList();
.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;
} }
internal static void LoadAllApplications(this List<ProgramSource> list) internal static void DisplayAllPrograms()
{ {
Main._win32s var win32 = Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.ToList() .Select(x => new ProgramSource(x));
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
.Add(
new ProgramSource
{
Name = t1.Name,
Location = t1.ParentDirectory,
UniqueIdentifier = t1.UniqueIdentifier,
Enabled = t1.Enabled
}
));
Main._uwps var uwp = Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.ToList() .Select(x => new ProgramSource(x));
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
.Add( ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
new ProgramSource ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
{
Name = t1.DisplayName,
Location = t1.Package.Location,
UniqueIdentifier = t1.UniqueIdentifier,
Enabled = t1.Enabled
}
));
} }
internal static void SetProgramSourcesStatus(this List<ProgramSource> list, List<ProgramSource> selectedProgramSourcesToDisable, bool status) internal static void SetProgramSourcesStatus(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
{ {
ProgramSetting.ProgramSettingDisplayList foreach(var program in ProgramSetting.ProgramSettingDisplayList)
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status)) {
.ToList() if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
.ForEach(t1 => t1.Enabled = status); {
program.Enabled = status;
}
}
Main._win32s foreach(var program in Main._win32s)
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status)) {
.ToList() if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
.ForEach(t1 => t1.Enabled = status); {
program.Enabled = status;
}
}
Main._uwps foreach (var program in Main._uwps)
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status)) {
.ToList() if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
.ForEach(t1 => t1.Enabled = status); {
program.Enabled = status;
}
}
} }
internal static void StoreDisabledInSettings(this List<ProgramSource> list) internal static void StoreDisabledInSettings()
{ {
Main._settings.ProgramSources // Disabled, not in DisabledProgramSources or ProgramSources
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && !x.Enabled)) var tmp = ProgramSetting.ProgramSettingDisplayList
.ToList()
.ForEach(t1 => t1.Enabled = false);
ProgramSetting.ProgramSettingDisplayList
.Where(t1 => !t1.Enabled .Where(t1 => !t1.Enabled
&& !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) && !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)
.ToList() && !Main._settings.ProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
.ForEach(x => Main._settings.DisabledProgramSources
.Add( Main._settings.DisabledProgramSources.AddRange(tmp);
new Settings.DisabledProgramSource
{
Name = x.Name,
Location = x.Location,
UniqueIdentifier = x.UniqueIdentifier,
Enabled = false
}
));
} }
internal static void RemoveDisabledFromSettings(this List<ProgramSource> list) internal static void RemoveDisabledFromSettings()
{ {
Main._settings.ProgramSources Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
.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));
} }
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems) internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
{ {
if (selectedItems.Where(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0 // Not in cache
&& selectedItems.Where(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0) 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; return true;
// ProgramSources holds list of user added directories, // ProgramSources holds list of user added directories,
// so when we enable/disable we need to reindex to show/not show the programs // so when we enable/disable we need to reindex to show/not show the programs
// that are found in those directories. // 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 true;
return false; 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 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"> mc:Ignorable="d">
<Grid Margin="0"> <Grid Margin="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="170" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="60" /> <RowDefinition Height="60" />
</Grid.RowDefinitions> </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 <StackPanel
Grid.Row="0" Grid.Row="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Orientation="Vertical"> Orientation="Vertical">
<StackPanel Width="Auto" Orientation="Vertical"> <Separator
<StackPanel Width="Auto" Orientation="Horizontal"> Height="1"
<CheckBox BorderBrush="{DynamicResource Color03B}"
Name="StartMenuEnabled" BorderThickness="1" />
Width="220" <DockPanel
Margin="70,8,10,8" Margin="70,10,0,8"
Content="{DynamicResource flowlauncher_plugin_program_index_start}" HorizontalAlignment="Stretch"
IsChecked="{Binding EnableStartMenuSource}" LastChildFill="True">
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" /> <TextBlock
<CheckBox MinWidth="120"
Name="RegistryEnabled" Margin="0,5,10,0"
Margin="70,8,10,8" Text="{DynamicResource flowlauncher_plugin_program_index_option}" />
Content="{DynamicResource flowlauncher_plugin_program_index_registry}" <WrapPanel
IsChecked="{Binding EnableRegistrySource}" Width="Auto"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" /> Margin="0,0,14,0"
</StackPanel> HorizontalAlignment="Right"
DockPanel.Dock="Right">
<Separator
Height="1"
BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" />
<StackPanel Width="Auto" Orientation="Horizontal">
<CheckBox <CheckBox
Name="HideLnkEnabled" Name="HideLnkEnabled"
Width="220" Margin="12,0,12,0"
Margin="70,8,10,8"
Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}" Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}"
IsChecked="{Binding HideAppsPath}" IsChecked="{Binding HideAppsPath}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" />
<CheckBox <CheckBox
Name="DescriptionEnabled" Name="DescriptionEnabled"
Margin="70,8,10,8" Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_enable_description}" Content="{DynamicResource flowlauncher_plugin_program_enable_description}"
IsChecked="{Binding EnableDescription}" IsChecked="{Binding EnableDescription}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" />
</StackPanel> </WrapPanel>
<Separator </DockPanel>
Height="1" <Separator
BorderBrush="{DynamicResource Color03B}" Height="1"
BorderThickness="1" /> BorderBrush="{DynamicResource Color03B}"
</StackPanel> BorderThickness="1" />
<StackPanel <StackPanel
Width="Auto" Width="Auto"
Margin="10,6,0,0" Margin="60,6,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
@ -107,8 +137,8 @@
</StackPanel> </StackPanel>
<ListView <ListView
x:Name="programSourceView" x:Name="programSourceView"
Grid.Row="1" Grid.Row="2"
Margin="20,0,20,0" Margin="70,0,20,0"
AllowDrop="True" AllowDrop="True"
BorderBrush="DarkGray" BorderBrush="DarkGray"
BorderThickness="1" BorderThickness="1"
@ -116,6 +146,8 @@
Drop="programSourceView_Drop" Drop="programSourceView_Drop"
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler" GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler"
PreviewMouseRightButtonUp="ProgramSourceView_PreviewMouseRightButtonUp" PreviewMouseRightButtonUp="ProgramSourceView_PreviewMouseRightButtonUp"
MouseDoubleClick="programSourceView_MouseDoubleClick"
SelectionChanged="programSourceView_SelectionChanged"
SelectionMode="Extended"> SelectionMode="Extended">
<ListView.View> <ListView.View>
<GridView> <GridView>
@ -126,7 +158,7 @@
</DataTemplate> </DataTemplate>
</GridViewColumn.CellTemplate> </GridViewColumn.CellTemplate>
</GridViewColumn> </GridViewColumn>
<GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enable}"> <GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enabled}">
<GridViewColumn.CellTemplate> <GridViewColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<TextBlock <TextBlock
@ -147,7 +179,7 @@
</ListView.View> </ListView.View>
</ListView> </ListView>
<DockPanel <DockPanel
Grid.Row="2" Grid.Row="3"
Grid.RowSpan="1" Grid.RowSpan="1"
Margin="0,0,20,10"> Margin="0,0,20,10">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
@ -173,66 +205,3 @@
</DockPanel> </DockPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
@ -36,6 +35,7 @@ namespace Flow.Launcher.Plugin.Program.Views
_settings.EnableDescription = value; _settings.EnableDescription = value;
} }
} }
public bool HideAppsPath public bool HideAppsPath
{ {
get => _settings.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 public string CustomizedExplorerPath
{ {
get => _settings.CustomizedExplorer; get => _settings.CustomizedExplorer;
@ -88,7 +98,7 @@ namespace Flow.Launcher.Plugin.Program.Views
private void Setting_Loaded(object sender, RoutedEventArgs e) private void Setting_Loaded(object sender, RoutedEventArgs e)
{ {
ProgramSettingDisplayList = _settings.ProgramSources.LoadProgramSources(); ProgramSettingDisplayList = ProgramSettingDisplay.LoadProgramSources();
programSourceView.ItemsSource = ProgramSettingDisplayList; programSourceView.ItemsSource = ProgramSettingDisplayList;
ViewRefresh(); ViewRefresh();
@ -147,20 +157,35 @@ namespace Flow.Launcher.Plugin.Program.Views
private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e) private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
{ {
var selectedProgramSource = programSourceView.SelectedItem as Settings.ProgramSource; var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
if (selectedProgramSource != null) EditProgramSource(selectedProgramSource);
{ }
var add = new AddProgramSource(selectedProgramSource, _settings);
if (add.ShowDialog() ?? false) private void EditProgramSource(ProgramSource selectedProgramSource)
{ {
ReIndexing(); if (selectedProgramSource == null)
}
}
else
{ {
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"); string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
MessageBox.Show(msg); 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) private void btnReindex_Click(object sender, RoutedEventArgs e)
@ -199,19 +224,16 @@ namespace Flow.Launcher.Plugin.Program.Views
{ {
foreach (string directory in directories) 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 var source = new ProgramSource(directory);
{
Location = directory,
UniqueIdentifier = directory
};
directoriesToAdd.Add(source); directoriesToAdd.Add(source);
} }
} }
if (directoriesToAdd.Count() > 0) if (directoriesToAdd.Count > 0)
{ {
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x)); directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.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) private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
{ {
ProgramSettingDisplayList.LoadAllApplications(); ProgramSettingDisplay.DisplayAllPrograms();
ViewRefresh(); ViewRefresh();
} }
@ -235,18 +257,14 @@ namespace Flow.Launcher.Plugin.Program.Views
.SelectedItems.Cast<ProgramSource>() .SelectedItems.Cast<ProgramSource>()
.ToList(); .ToList();
if (selectedItems.Count() == 0) if (selectedItems.Count == 0)
{ {
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"); string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
MessageBox.Show(msg); MessageBox.Show(msg);
return; return;
} }
if (selectedItems if (IsAllItemsUserAdded(selectedItems))
.Where(t1 => !_settings
.ProgramSources
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
.Count() == 0)
{ {
var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source")); 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); DeleteProgramSources(selectedItems);
} }
else if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems)) else if (HasMoreOrEqualEnabledItems(selectedItems))
{ {
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, false); ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false);
ProgramSettingDisplayList.StoreDisabledInSettings(); ProgramSettingDisplay.StoreDisabledInSettings();
} }
else else
{ {
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, true); ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true);
ProgramSettingDisplayList.RemoveDisabledFromSettings(); ProgramSettingDisplay.RemoveDisabledFromSettings();
} }
if (selectedItems.IsReindexRequired()) if (selectedItems.IsReindexRequired())
@ -329,35 +347,41 @@ namespace Flow.Launcher.Plugin.Program.Views
dataView.Refresh(); 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 var selectedItems = programSourceView
.SelectedItems.Cast<ProgramSource>() .SelectedItems.Cast<ProgramSource>()
.ToList(); .ToList();
if (selectedItems if (IsAllItemsUserAdded(selectedItems))
.Where(t1 => !_settings
.ProgramSources
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
.Count() == 0)
{ {
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete"); btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
return;
} }
else if (HasMoreOrEqualEnabledItems(selectedItems))
if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
{ {
btnProgramSourceStatus.Content = "Disable"; btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
} }
else 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));
}
} }
} }