mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge Dev
This commit is contained in:
commit
bf9b464be8
23 changed files with 482 additions and 632 deletions
|
|
@ -55,7 +55,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Droplex" Version="1.4.1" />
|
||||
<PackageReference Include="FSharp.Core" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ namespace Flow.Launcher.Plugin
|
|||
public string ProgressBarColor { get; set; } = "#26a0da";
|
||||
|
||||
/// <summary>
|
||||
/// Suggests a result's file extension should use full width of the default preview panel.
|
||||
/// Suggests the preview image of result should use full width of the default preview panel by result's file extension.
|
||||
/// </summary>
|
||||
/// <param name="extension">File extension. Dot included.</param>
|
||||
public static bool ShouldUseBigThumbnail(string extension)
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Flow.Launcher.Test.Plugins
|
||||
{
|
||||
[TestFixture]
|
||||
public class ProgramTest
|
||||
{
|
||||
[TestCase("Microsoft.WindowsCamera", "ms-resource:LensSDK/Resources/AppTitle", "ms-resource://Microsoft.WindowsCamera/LensSDK/Resources/AppTitle")]
|
||||
[TestCase("microsoft.windowscommunicationsapps", "ms-resource://microsoft.windowscommunicationsapps/hxoutlookintl/AppManifest_MailDesktop_DisplayName",
|
||||
"ms-resource://microsoft.windowscommunicationsapps/hxoutlookintl/AppManifest_MailDesktop_DisplayName")]
|
||||
[TestCase("windows.immersivecontrolpanel", "ms-resource:DisplayName", "ms-resource://windows.immersivecontrolpanel/Resources/DisplayName")]
|
||||
[TestCase("Microsoft.MSPaint", "ms-resource:AppName", "ms-resource://Microsoft.MSPaint/Resources/AppName")]
|
||||
public void WhenGivenPriReferenceValueShouldReturnCorrectFormat(string packageName, string rawPriReferenceValue, string expectedFormat)
|
||||
{
|
||||
// Arrange
|
||||
var app = new UWP.Application();
|
||||
|
||||
// Act
|
||||
var result = UWP.Application.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result == expectedFormat,
|
||||
$"Expected Pri reference format: {expectedFormat}{Environment.NewLine} " +
|
||||
$"Actual: {result}{Environment.NewLine}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ Global
|
|||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
|
|
|
|||
|
|
@ -118,6 +118,14 @@
|
|||
<system:String x:Key="browserMoreThemes">Theme Gallery</system:String>
|
||||
<system:String x:Key="howToCreateTheme">How to create a theme</system:String>
|
||||
<system:String x:Key="hiThere">Hi There</system:String>
|
||||
<system:String x:Key="SampleTitleExplorer">Explorer</system:String>
|
||||
<system:String x:Key="SampleSubTitleExplorer">Search for files, folders and file contents</system:String>
|
||||
<system:String x:Key="SampleTitleWebSearch">WebSearch</system:String>
|
||||
<system:String x:Key="SampleSubTitleWebSearch">Search the web with different search engine support</system:String>
|
||||
<system:String x:Key="SampleTitleProgram">Program</system:String>
|
||||
<system:String x:Key="SampleSubTitleProgram">Launch programs as admin or a different user</system:String>
|
||||
<system:String x:Key="SampleTitleProcessKiller">ProcessKiller</system:String>
|
||||
<system:String x:Key="SampleSubTitleProcessKiller">Terminate unwanted processes</system:String>
|
||||
<system:String x:Key="queryBoxFont">Query Box Font</system:String>
|
||||
<system:String x:Key="resultItemFont">Result Item Font</system:String>
|
||||
<system:String x:Key="windowMode">Window Mode</system:String>
|
||||
|
|
|
|||
|
|
@ -443,10 +443,9 @@
|
|||
Margin="2,2,2,0"
|
||||
Panel.ZIndex="1"
|
||||
Background="Transparent"
|
||||
IsItemsHost="true"
|
||||
IsItemsHost="true"
|
||||
LastChildFill="False" />
|
||||
<Border
|
||||
Grid.Column="1">
|
||||
<Border Grid.Column="1">
|
||||
<ContentPresenter Grid.Column="1" ContentSource="SelectedContent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
|
|
@ -1011,6 +1010,7 @@
|
|||
TextAlignment="Left" />
|
||||
<DockPanel DockPanel.Dock="Right">
|
||||
<TextBox
|
||||
Loaded="Plugin_GotFocus"
|
||||
Name="pluginFilterTxb"
|
||||
Width="150"
|
||||
Height="34"
|
||||
|
|
@ -1445,6 +1445,7 @@
|
|||
DockPanel.Dock="Right"
|
||||
FontSize="14"
|
||||
KeyDown="PluginStoreFilterTxb_OnKeyDown"
|
||||
Loaded="PluginStore_GotFocus"
|
||||
LostFocus="RefreshPluginStoreEventHandler"
|
||||
Text=""
|
||||
TextAlignment="Left"
|
||||
|
|
@ -1795,7 +1796,12 @@
|
|||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Border Width="{Binding WindowWidthSize}" Style="{DynamicResource WindowBorderStyle}">
|
||||
<Border
|
||||
Width="{Binding WindowWidthSize}"
|
||||
Margin="0"
|
||||
SnapsToDevicePixels="True"
|
||||
Style="{DynamicResource WindowBorderStyle}"
|
||||
UseLayoutRounding="True">
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
<Border.Clip>
|
||||
<MultiBinding Converter="{StaticResource BorderClipConverter}">
|
||||
|
|
@ -1813,6 +1819,7 @@
|
|||
<Border Grid.Row="0">
|
||||
<TextBox
|
||||
x:Name="QueryTextBox"
|
||||
IsHitTestVisible="False"
|
||||
IsReadOnly="True"
|
||||
Style="{DynamicResource QueryBoxStyle}"
|
||||
Text="{DynamicResource hiThere}" />
|
||||
|
|
@ -1850,7 +1857,10 @@
|
|||
</Border>
|
||||
|
||||
<ContentControl Grid.Row="2">
|
||||
<flowlauncher:ResultListBox DataContext="{Binding PreviewResults, Mode=OneTime}" Visibility="Visible" />
|
||||
<flowlauncher:ResultListBox
|
||||
DataContext="{Binding PreviewResults, Mode=OneTime}"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="Visible" />
|
||||
</ContentControl>
|
||||
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -562,5 +562,15 @@ namespace Flow.Launcher
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
private void PluginStore_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Keyboard.Focus(pluginStoreFilterTxb);
|
||||
}
|
||||
|
||||
private void Plugin_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Keyboard.Focus(pluginFilterTxb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,14 +195,32 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private async Task LoadImageAsync()
|
||||
{
|
||||
// We need to modify the property not field here to trigger the OnPropertyChanged event
|
||||
Image = await LoadImageInternalAsync(Result.IcoPath, Result.Icon, false).ConfigureAwait(false);
|
||||
var imagePath = Result.IcoPath;
|
||||
var iconDelegate = Result.Icon;
|
||||
if (ImageLoader.CacheContainImage(imagePath, false))
|
||||
{
|
||||
image = await LoadImageInternalAsync(imagePath, iconDelegate, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to modify the property not field here to trigger the OnPropertyChanged event
|
||||
Image = await LoadImageInternalAsync(imagePath, iconDelegate, false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPreviewImageAsync()
|
||||
{
|
||||
var imagePath = Result.PreviewImage ?? Result.IcoPath;
|
||||
PreviewImage = await LoadImageInternalAsync(Result.IcoPath, Result.Icon, true).ConfigureAwait(false);
|
||||
var iconDelegate = Result.Icon;
|
||||
if (ImageLoader.CacheContainImage(imagePath, true))
|
||||
{
|
||||
previewImage = await LoadImageInternalAsync(imagePath, iconDelegate, true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to modify the property not field here to trigger the OnPropertyChanged event
|
||||
PreviewImage = await LoadImageInternalAsync(imagePath, iconDelegate, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Result Result { get; }
|
||||
|
|
|
|||
|
|
@ -609,26 +609,26 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
new Result
|
||||
{
|
||||
Title = "Explorer",
|
||||
SubTitle = "Search for files, folders and file contents",
|
||||
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
|
||||
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png")
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "WebSearch",
|
||||
SubTitle = "Search the web with different search engine support",
|
||||
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
|
||||
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "Program",
|
||||
SubTitle = "Launch programs as admin or a different user",
|
||||
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
|
||||
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "ProcessKiller",
|
||||
SubTitle = "Terminate unwanted processes",
|
||||
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
|
||||
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,21 +19,28 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
|
|||
|
||||
internal static List<Bookmark> LoadAllBookmarks(Settings setting)
|
||||
{
|
||||
|
||||
var chromeBookmarks = new ChromeBookmarkLoader();
|
||||
var mozBookmarks = new FirefoxBookmarkLoader();
|
||||
var edgeBookmarks = new EdgeBookmarkLoader();
|
||||
|
||||
var allBookmarks = new List<Bookmark>();
|
||||
|
||||
// Add Firefox bookmarks
|
||||
allBookmarks.AddRange(mozBookmarks.GetBookmarks());
|
||||
if (setting.LoadChromeBookmark)
|
||||
{
|
||||
// Add Chrome bookmarks
|
||||
var chromeBookmarks = new ChromeBookmarkLoader();
|
||||
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
|
||||
}
|
||||
|
||||
// Add Chrome bookmarks
|
||||
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
|
||||
if (setting.LoadFirefoxBookmark)
|
||||
{
|
||||
// Add Firefox bookmarks
|
||||
var mozBookmarks = new FirefoxBookmarkLoader();
|
||||
allBookmarks.AddRange(mozBookmarks.GetBookmarks());
|
||||
}
|
||||
|
||||
// Add Edge (Chromium) bookmarks
|
||||
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
|
||||
if (setting.LoadEdgeBookmark)
|
||||
{
|
||||
// Add Edge (Chromium) bookmarks
|
||||
var edgeBookmarks = new EdgeBookmarkLoader();
|
||||
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
|
||||
}
|
||||
|
||||
foreach (var browser in setting.CustomChromiumBrowsers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@
|
|||
<system:String x:Key="flowlauncher_plugin_browserbookmark_browserBookmarkDataDirectory">Data Directory Path</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_addBrowserBookmark">Add</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_removeBrowserBookmark">Delete</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_others">Others</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -10,6 +10,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Models
|
|||
|
||||
public string BrowserPath { get; set; }
|
||||
|
||||
public bool LoadChromeBookmark { get; set; } = true;
|
||||
public bool LoadFirefoxBookmark { get; set; } = true;
|
||||
public bool LoadEdgeBookmark { get; set; } = true;
|
||||
|
||||
public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +1,66 @@
|
|||
<UserControl
|
||||
x:Class="Flow.Launcher.Plugin.BrowserBookmark.Views.SettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="500"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="10">
|
||||
x:Class="Flow.Launcher.Plugin.BrowserBookmark.Views.SettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="500"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="60,0,10,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Margin="0,0,0,0" Orientation="Vertical">
|
||||
<TextBlock Margin="10" Text="{DynamicResource flowlauncher_plugin_browserbookmark_loadBrowserFrom}" />
|
||||
<ListView
|
||||
Name="CustomBrowsers"
|
||||
Height="auto"
|
||||
Margin="10"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="1"
|
||||
ItemsSource="{Binding Settings.CustomChromiumBrowsers}"
|
||||
MouseDoubleClick="MouseDoubleClickOnSelectedCustomBrowser"
|
||||
SelectedItem="{Binding SelectedCustomBrowser}"
|
||||
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn DisplayMemberBinding="{Binding Name, Mode=OneWay}" Header="{DynamicResource flowlauncher_plugin_browserbookmark_browserName}" />
|
||||
<GridViewColumn DisplayMemberBinding="{Binding DataDirectoryPath, Mode=OneWay}" Header="{DynamicResource flowlauncher_plugin_browserbookmark_browserBookmarkDataDirectory}" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<StackPanel Margin="0,10,0,10" Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="10" Text="{DynamicResource flowlauncher_plugin_browserbookmark_loadBrowserFrom}"/>
|
||||
<CheckBox Margin="0,0,15,0"
|
||||
Content="Chrome"
|
||||
IsChecked="{Binding Settings.LoadChromeBookmark}"/>
|
||||
<CheckBox Margin="0,0,15,0"
|
||||
Content="Edge"
|
||||
IsChecked="{Binding Settings.LoadEdgeBookmark}"/>
|
||||
<CheckBox Margin="0,0,15,0"
|
||||
Content="Firefox"
|
||||
IsChecked="{Binding Settings.LoadFirefoxBookmark}"/>
|
||||
<Button
|
||||
MinWidth="120"
|
||||
Margin="10"
|
||||
Click="NewCustomBrowser"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_addBrowserBookmark}" />
|
||||
<Button
|
||||
MinWidth="120"
|
||||
Margin="10"
|
||||
Click="DeleteCustomBrowser"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_removeBrowserBookmark}" />
|
||||
Margin="0,0,15,0"
|
||||
Click="Others_Click"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_others}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Name="CustomBrowsersList" Visibility="Collapsed">
|
||||
<ListView
|
||||
Name="CustomBrowsers"
|
||||
Height="auto"
|
||||
Margin="10"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="1"
|
||||
ItemsSource="{Binding Settings.CustomChromiumBrowsers}"
|
||||
MouseDoubleClick="MouseDoubleClickOnSelectedCustomBrowser"
|
||||
SelectedItem="{Binding SelectedCustomBrowser}"
|
||||
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn DisplayMemberBinding="{Binding Name, Mode=OneWay}"
|
||||
Header="{DynamicResource flowlauncher_plugin_browserbookmark_browserName}"/>
|
||||
<GridViewColumn DisplayMemberBinding="{Binding DataDirectoryPath, Mode=OneWay}"
|
||||
Header="{DynamicResource flowlauncher_plugin_browserbookmark_browserBookmarkDataDirectory}"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button
|
||||
MinWidth="130"
|
||||
Margin="10"
|
||||
Click="NewCustomBrowser"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_addBrowserBookmark}"/>
|
||||
<Button
|
||||
MinWidth="120"
|
||||
Margin="10"
|
||||
Click="DeleteCustomBrowser"
|
||||
Content="{DynamicResource flowlauncher_plugin_browserbookmark_removeBrowserBookmark}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System.Windows;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using System.Windows.Input;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Flow.Launcher.Plugin.BrowserBookmark.Views
|
||||
{
|
||||
|
|
@ -59,5 +60,15 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Views
|
|||
var window = new CustomBrowserSettingWindow(SelectedCustomBrowser);
|
||||
window.ShowDialog();
|
||||
}
|
||||
private void Others_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (CustomBrowsersList.Visibility == Visibility.Collapsed)
|
||||
{
|
||||
CustomBrowsersList.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
CustomBrowsersList.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<!--Dialogues-->
|
||||
<!-- Dialogues -->
|
||||
<system:String x:Key="plugin_pluginsmanager_downloading_plugin">Downloading plugin</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_download_error">Error: Unable to download the plugin</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_installing_plugin">Installing Plugin</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_success_restart">Plugin successfully installed. Restarting Flow, please wait...</system:String>
|
||||
|
|
@ -27,14 +29,14 @@
|
|||
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)</system:String>
|
||||
|
||||
<!--Controls-->
|
||||
|
||||
<!--Plugin Infos-->
|
||||
<!-- Controls -->
|
||||
|
||||
<!-- Plugin Infos -->
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_unknown_author">Unknown Author</system:String>
|
||||
|
||||
<!--Context menu items-->
|
||||
<!-- Context menu items -->
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_title">Open website</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle">Visit the plugin's website</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title">See source code</system:String>
|
||||
|
|
@ -44,6 +46,6 @@
|
|||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title">Go to Flow's plugins repository</system:String>
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle">Visit the PluginsManifest repository to see community-made plugin submissions</system:String>
|
||||
|
||||
<!--Settings menu items-->
|
||||
<!-- Settings menu items -->
|
||||
<system:String x:Key="plugin_pluginsmanager_plugin_settings_unknown_source">Install from unknown source warning</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -176,7 +176,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return;
|
||||
}
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"));
|
||||
|
||||
Context.API.RestartApp();
|
||||
|
|
|
|||
|
|
@ -81,7 +81,9 @@
|
|||
|
||||
<!-- Dialogs -->
|
||||
<system:String x:Key="flowlauncher_plugin_program_disable_dlgtitle_success">Success</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_disable_dlgtitle_error">Error</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_disable_dlgtitle_success_message">Successfully disabled this program from displaying in your query</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_run_as_administrator_not_supported_message">This app is not intended to be run as administrator</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_run_failed">Unable to run {0}</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
|
|
@ -225,9 +226,9 @@ namespace Flow.Launcher.Plugin.Program
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Unable to start: {info.FileName}";
|
||||
Context.API.ShowMsg(name, message, string.Empty);
|
||||
var title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
|
||||
var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"), info.FileName);
|
||||
Context.API.ShowMsg(title, string.Format(message, info.FileName), string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
public class ApplicationActivationHelper
|
||||
{
|
||||
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
|
||||
public enum ActivateOptions
|
||||
{
|
||||
None = 0x00000000,
|
||||
DesignMode = 0x00000001,
|
||||
NoErrorUI = 0x00000002,
|
||||
NoSplashScreen = 0x00000004,
|
||||
}
|
||||
|
||||
/// ApplicationActivationManager
|
||||
[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IApplicationActivationManager
|
||||
{
|
||||
IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
|
||||
IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
|
||||
IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
|
||||
}
|
||||
|
||||
// Application Activation Manager Class
|
||||
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
|
||||
public class ApplicationActivationManager : IApplicationActivationManager
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
|
||||
public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
public class AppxPackageHelper
|
||||
{
|
||||
// This function returns a list of attributes of applications
|
||||
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
||||
{
|
||||
IAppxFactory appxFactory = (IAppxFactory)new AppxFactory();
|
||||
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
|
||||
var reader = appxFactory.CreateManifestReader(stream);
|
||||
var manifestApps = reader.GetApplications();
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
string appListEntry;
|
||||
var manifestApp = manifestApps.GetCurrent();
|
||||
manifestApp.GetStringValue("AppListEntry", out appListEntry);
|
||||
if (appListEntry != "none")
|
||||
{
|
||||
apps.Add(manifestApp);
|
||||
}
|
||||
manifestApps.MoveNext();
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application
|
||||
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781"), ComImport]
|
||||
public class AppxFactory
|
||||
{
|
||||
}
|
||||
|
||||
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxFactory
|
||||
{
|
||||
void _VtblGap0_2(); // skip 2 methods
|
||||
IAppxManifestReader CreateManifestReader(IStream inputStream);
|
||||
}
|
||||
|
||||
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestReader
|
||||
{
|
||||
void _VtblGap0_1(); // skip 1 method
|
||||
IAppxManifestProperties GetProperties();
|
||||
void _VtblGap1_5(); // skip 5 methods
|
||||
IAppxManifestApplicationsEnumerator GetApplications();
|
||||
}
|
||||
|
||||
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplicationsEnumerator
|
||||
{
|
||||
IAppxManifestApplication GetCurrent();
|
||||
bool GetHasCurrent();
|
||||
bool MoveNext();
|
||||
}
|
||||
|
||||
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplication
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
|
||||
[PreserveSig]
|
||||
int GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
|
||||
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestProperties
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetBoolValue([MarshalAs(UnmanagedType.LPWStr)]string name, out bool value);
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,22 +3,17 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Xml.Linq;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Management.Deployment;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.Program.Logger;
|
||||
using Rect = System.Windows.Rect;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Threading.Channels;
|
||||
using System.Xml;
|
||||
using Windows.ApplicationModel.Core;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
@ -30,9 +25,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
public string FamilyName { get; }
|
||||
public string Location { get; set; }
|
||||
|
||||
public Application[] Apps { get; set; }
|
||||
public Application[] Apps { get; set; } = Array.Empty<Application>();
|
||||
|
||||
public PackageVersion Version { get; set; }
|
||||
|
||||
public UWP(Package package)
|
||||
{
|
||||
|
|
@ -40,71 +34,133 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
Name = package.Id.Name;
|
||||
FullName = package.Id.FullName;
|
||||
FamilyName = package.Id.FamilyName;
|
||||
InitializeAppInfo();
|
||||
}
|
||||
|
||||
private void InitializeAppInfo()
|
||||
public void InitAppsInPackage(Package package)
|
||||
{
|
||||
var path = Path.Combine(Location, "AppxManifest.xml");
|
||||
|
||||
var namespaces = XmlNamespaces(path);
|
||||
InitPackageVersion(namespaces);
|
||||
|
||||
const uint noAttribute = 0x80;
|
||||
const Stgm nonExclusiveRead = Stgm.Read | Stgm.ShareDenyNone;
|
||||
var hResult = SHCreateStreamOnFileEx(path, nonExclusiveRead, noAttribute, false, null, out IStream stream);
|
||||
|
||||
if (hResult == Hresult.Ok)
|
||||
var applist = new List<Application>();
|
||||
// WinRT
|
||||
var appListEntries = package.GetAppListEntries();
|
||||
foreach (var app in appListEntries)
|
||||
{
|
||||
List<AppxPackageHelper.IAppxManifestApplication> _apps = AppxPackageHelper.GetAppsFromManifest(stream);
|
||||
try
|
||||
{
|
||||
var tmp = new Application(app, this);
|
||||
applist.Add(tmp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
|
||||
"|Unexpected exception occurs when trying to construct a Application from package"
|
||||
+ $"{FullName} from location {Location}", e);
|
||||
}
|
||||
}
|
||||
Apps = applist.ToArray();
|
||||
|
||||
Apps = _apps.Select(x => new Application(x, this))
|
||||
.Where(a => !string.IsNullOrEmpty(a.UserModelId)
|
||||
&& !string.IsNullOrEmpty(a.DisplayName))
|
||||
.ToArray();
|
||||
try
|
||||
{
|
||||
var xmlDoc = GetManifestXml();
|
||||
if (xmlDoc == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var xmlRoot = xmlDoc.DocumentElement;
|
||||
var packageVersion = GetPackageVersionFromManifest(xmlRoot);
|
||||
if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) ||
|
||||
!bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
|
||||
namespaceManager.AddNamespace("d", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name
|
||||
namespaceManager.AddNamespace("rescap", "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities");
|
||||
namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
|
||||
|
||||
var allowElevationNode = xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager);
|
||||
bool packageCanElevate = allowElevationNode != null;
|
||||
|
||||
var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager);
|
||||
foreach (var app in Apps)
|
||||
{
|
||||
// According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
|
||||
// and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
|
||||
var id = app.UserModelId.Split('!')[1];
|
||||
var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager);
|
||||
if (appNode != null)
|
||||
{
|
||||
app.CanRunElevated = packageCanElevate || Application.IfAppCanRunElevated(appNode);
|
||||
|
||||
// local name to fit all versions
|
||||
var visualElement = appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager);
|
||||
var logoUri = visualElement?.Attributes[logoName]?.Value;
|
||||
app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64));
|
||||
var previewUri = visualElement?.Attributes[bigLogoName]?.Value;
|
||||
app.PreviewImagePath = app.LogoPathFromUri(previewUri, (128, 128));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
|
||||
"|Unexpected exception occurs when trying to construct a Application from package"
|
||||
+ $"{FullName} from location {Location}", e);
|
||||
}
|
||||
}
|
||||
|
||||
private XmlDocument GetManifestXml()
|
||||
{
|
||||
var manifest = Path.Combine(Location, "AppxManifest.xml");
|
||||
try
|
||||
{
|
||||
var file = File.ReadAllText(manifest);
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(file);
|
||||
return xmlDoc;
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "An unexpected error occured and unable to parse AppxManifest.xml", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot)
|
||||
{
|
||||
if (xmlRoot != null)
|
||||
{
|
||||
|
||||
var namespaces = xmlRoot.Attributes;
|
||||
foreach (XmlAttribute ns in namespaces)
|
||||
{
|
||||
if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion))
|
||||
{
|
||||
return packageVersion;
|
||||
}
|
||||
}
|
||||
|
||||
ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
|
||||
"|Trying to get the package version of the UWP program, but an unknown UWP appmanifest version in package "
|
||||
+ $"{FullName} from location {Location}", new FormatException());
|
||||
return PackageVersion.Unknown;
|
||||
}
|
||||
else
|
||||
{
|
||||
var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
|
||||
"|Error caused while trying to get the details of the UWP program", e);
|
||||
|
||||
Apps = Array.Empty<Application>();
|
||||
}
|
||||
|
||||
if (stream != null && Marshal.ReleaseComObject(stream) > 0)
|
||||
{
|
||||
Log.Error("Flow.Launcher.Plugin.Program.Programs.UWP", "AppxManifest.xml was leaked");
|
||||
ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
|
||||
"|Can't parse AppManifest.xml of package "
|
||||
+ $"{FullName} from location {Location}", new ArgumentNullException(nameof(xmlRoot)));
|
||||
return PackageVersion.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
|
||||
private static string[] XmlNamespaces(string path)
|
||||
private static readonly Dictionary<string, PackageVersion> versionFromNamespace = new()
|
||||
{
|
||||
XDocument z = XDocument.Load(path);
|
||||
if (z.Root != null)
|
||||
{
|
||||
var namespaces = z.Root.Attributes().Where(a => a.IsNamespaceDeclaration).GroupBy(
|
||||
a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
|
||||
a => XNamespace.Get(a.Value)
|
||||
).Select(
|
||||
g => g.First().ToString()
|
||||
).ToArray();
|
||||
return namespaces;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
|
||||
$"|Error occured while trying to get the XML from {path}", new ArgumentNullException());
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitPackageVersion(string[] namespaces)
|
||||
{
|
||||
var versionFromNamespace = new Dictionary<string, PackageVersion>
|
||||
{
|
||||
{
|
||||
"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10
|
||||
},
|
||||
|
|
@ -114,23 +170,33 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
foreach (var n in versionFromNamespace.Keys)
|
||||
private static readonly Dictionary<PackageVersion, string> smallLogoNameFromVersion = new()
|
||||
{
|
||||
{
|
||||
if (namespaces.Contains(n))
|
||||
{
|
||||
Version = versionFromNamespace[n];
|
||||
return;
|
||||
}
|
||||
}
|
||||
PackageVersion.Windows10, "Square44x44Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows81, "Square30x30Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows8, "SmallLogo"
|
||||
},
|
||||
};
|
||||
|
||||
ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" +
|
||||
"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version "
|
||||
+ $"{FullName} from location {Location} is returned.", new FormatException());
|
||||
|
||||
Version = PackageVersion.Unknown;
|
||||
}
|
||||
private static readonly Dictionary<PackageVersion, string> bigLogoNameFromVersion = new()
|
||||
{
|
||||
{
|
||||
PackageVersion.Windows10, "Square150x150Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows81, "Square150x150Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows8, "Logo"
|
||||
},
|
||||
};
|
||||
|
||||
public static Application[] All()
|
||||
{
|
||||
|
|
@ -144,6 +210,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
try
|
||||
{
|
||||
u = new UWP(p);
|
||||
u.InitAppsInPackage(p);
|
||||
}
|
||||
#if !DEBUG
|
||||
catch (Exception e)
|
||||
|
|
@ -194,22 +261,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
var ps = m.FindPackagesForUser(id);
|
||||
ps = ps.Where(p =>
|
||||
{
|
||||
bool valid;
|
||||
try
|
||||
{
|
||||
var f = p.IsFramework;
|
||||
var d = p.IsDevelopmentMode;
|
||||
var path = p.InstalledLocation.Path;
|
||||
valid = !f && !d && !string.IsNullOrEmpty(path);
|
||||
return !f && !d && !string.IsNullOrEmpty(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and "
|
||||
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{id}", "An unexpected error occured and "
|
||||
+ $"unable to verify if package is valid", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
});
|
||||
return ps;
|
||||
}
|
||||
|
|
@ -248,7 +312,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
PackageChangeChannel.Reader.TryRead(out _);
|
||||
await Task.Run(Main.IndexUwpPrograms);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,23 +343,27 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
private string _uid = string.Empty;
|
||||
public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); }
|
||||
public string DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string UserModelId { get; set; }
|
||||
public string BackgroundColor { get; set; }
|
||||
|
||||
public string EntryPoint { get; set; }
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string UserModelId { get; set; } = string.Empty;
|
||||
//public string BackgroundColor { get; set; } = string.Empty; // preserve for future use
|
||||
public string Name => DisplayName;
|
||||
public string Location => Package.Location;
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public bool CanRunElevated { get; set; }
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool CanRunElevated { get; set; } = false;
|
||||
public string LogoPath { get; set; } = string.Empty;
|
||||
public string PreviewImagePath { get; set; } = string.Empty;
|
||||
|
||||
public string LogoUri { get; set; }
|
||||
public string LogoPath { get; set; }
|
||||
public UWP Package { get; set; }
|
||||
|
||||
public Application() { }
|
||||
public Application(AppListEntry appListEntry, UWP package)
|
||||
{
|
||||
UserModelId = appListEntry.AppUserModelId;
|
||||
UniqueIdentifier = appListEntry.AppUserModelId;
|
||||
DisplayName = appListEntry.DisplayInfo.DisplayName;
|
||||
Description = appListEntry.DisplayInfo.Description;
|
||||
Location = package.Location;
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
|
|
@ -335,8 +403,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
var result = new Result
|
||||
{
|
||||
Title = title,
|
||||
SubTitle = Main._settings.HideAppsPath ? string.Empty : Package.Location,
|
||||
Icon = Logo,
|
||||
SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
|
||||
IcoPath = LogoPath,
|
||||
Score = matchResult.Score,
|
||||
TitleHighlightData = matchResult.MatchData,
|
||||
ContextData = this,
|
||||
|
|
@ -349,20 +417,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
!e.SpecialKeyState.WinPressed
|
||||
);
|
||||
|
||||
if (elevated && CanRunElevated)
|
||||
bool shouldRunElevated = elevated && CanRunElevated;
|
||||
_ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
|
||||
if (elevated && !shouldRunElevated)
|
||||
{
|
||||
LaunchElevated();
|
||||
}
|
||||
else
|
||||
{
|
||||
Launch(api);
|
||||
|
||||
if (elevated)
|
||||
{
|
||||
var title = "Plugin: Program";
|
||||
var message = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator_not_supported_message");
|
||||
api.ShowMsg(title, message, string.Empty);
|
||||
}
|
||||
var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
|
||||
var message = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator_not_supported_message");
|
||||
api.ShowMsg(title, message, string.Empty);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -382,7 +443,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
|
||||
Action = _ =>
|
||||
{
|
||||
Main.Context.API.OpenDirectory(Package.Location);
|
||||
Main.Context.API.OpenDirectory(Location);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
|
@ -397,7 +458,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
|
||||
Action = _ =>
|
||||
{
|
||||
LaunchElevated();
|
||||
Task.Run(() => Launch(true)).ConfigureAwait(false);
|
||||
return true;
|
||||
},
|
||||
IcoPath = "Images/cmd.png"
|
||||
|
|
@ -407,192 +468,61 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
return contextMenus;
|
||||
}
|
||||
|
||||
private async void Launch(IPublicAPI api)
|
||||
private void Launch(bool elevated = false)
|
||||
{
|
||||
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
|
||||
const string noArgs = "";
|
||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = appManager.ActivateApplication(UserModelId, noArgs, noFlags, out _);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Can't start UWP: {DisplayName}";
|
||||
api.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void LaunchElevated()
|
||||
{
|
||||
string command = "shell:AppsFolder\\" + UniqueIdentifier;
|
||||
string command = "shell:AppsFolder\\" + UserModelId;
|
||||
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
||||
|
||||
var info = new ProcessStartInfo(command)
|
||||
{
|
||||
UseShellExecute = true, Verb = "runas",
|
||||
UseShellExecute = true,
|
||||
Verb = elevated ? "runas" : ""
|
||||
};
|
||||
|
||||
Main.StartProcess(Process.Start, info);
|
||||
}
|
||||
|
||||
public Application(AppxPackageHelper.IAppxManifestApplication manifestApp, UWP package)
|
||||
internal static bool IfAppCanRunElevated(XmlNode appNode)
|
||||
{
|
||||
// This is done because we cannot use the keyword 'out' along with a property
|
||||
// According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
|
||||
// and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
|
||||
|
||||
manifestApp.GetAppUserModelId(out string tmpUserModelId);
|
||||
manifestApp.GetAppUserModelId(out string tmpUniqueIdentifier);
|
||||
manifestApp.GetStringValue("DisplayName", out string tmpDisplayName);
|
||||
manifestApp.GetStringValue("Description", out string tmpDescription);
|
||||
manifestApp.GetStringValue("BackgroundColor", out string tmpBackgroundColor);
|
||||
manifestApp.GetStringValue("EntryPoint", out string tmpEntryPoint);
|
||||
|
||||
UserModelId = tmpUserModelId;
|
||||
UniqueIdentifier = tmpUniqueIdentifier;
|
||||
DisplayName = tmpDisplayName;
|
||||
Description = tmpDescription;
|
||||
BackgroundColor = tmpBackgroundColor;
|
||||
EntryPoint = tmpEntryPoint;
|
||||
|
||||
Package = package;
|
||||
|
||||
DisplayName = ResourceFromPri(package.FullName, package.Name, DisplayName);
|
||||
Description = ResourceFromPri(package.FullName, package.Name, Description);
|
||||
LogoUri = LogoUriFromManifest(manifestApp);
|
||||
LogoPath = LogoPathFromUri(LogoUri);
|
||||
|
||||
Enabled = true;
|
||||
CanRunElevated = CanApplicationRunElevated();
|
||||
return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" ||
|
||||
appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL";
|
||||
}
|
||||
|
||||
private bool CanApplicationRunElevated()
|
||||
{
|
||||
if (EntryPoint == "Windows.FullTrustApplication")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var manifest = Package.Location + "\\AppxManifest.xml";
|
||||
if (File.Exists(manifest))
|
||||
{
|
||||
var file = File.ReadAllText(manifest);
|
||||
|
||||
if (file.Contains("TrustLevel=\"mediumIL\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string ResourceFromPri(string packageFullName, string packageName, string rawReferenceValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawReferenceValue) || !rawReferenceValue.StartsWith("ms-resource:"))
|
||||
return rawReferenceValue;
|
||||
|
||||
var formattedPriReference = FormattedPriReferenceValue(packageName, rawReferenceValue);
|
||||
|
||||
var outBuffer = new StringBuilder(128);
|
||||
string source = $"@{{{packageFullName}? {formattedPriReference}}}";
|
||||
var capacity = (uint)outBuffer.Capacity;
|
||||
var hResult = SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var loaded = outBuffer.ToString();
|
||||
if (!string.IsNullOrEmpty(loaded))
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
|
||||
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawPriReferenceValue) || !rawPriReferenceValue.StartsWith(prefix))
|
||||
return rawPriReferenceValue;
|
||||
|
||||
string key = rawPriReferenceValue.Substring(prefix.Length);
|
||||
if (key.StartsWith("//"))
|
||||
return $"{prefix}{key}";
|
||||
|
||||
if (!key.StartsWith("/"))
|
||||
{
|
||||
key = $"/{key}";
|
||||
}
|
||||
|
||||
if (!key.ToLower().Contains("resources"))
|
||||
{
|
||||
key = $"/Resources{key}";
|
||||
}
|
||||
|
||||
return $"{prefix}//{packageName}{key}";
|
||||
}
|
||||
|
||||
internal string LogoUriFromManifest(AppxPackageHelper.IAppxManifestApplication app)
|
||||
{
|
||||
var logoKeyFromVersion = new Dictionary<PackageVersion, string>
|
||||
{
|
||||
{
|
||||
PackageVersion.Windows10, "Square44x44Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows81, "Square30x30Logo"
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows8, "SmallLogo"
|
||||
},
|
||||
};
|
||||
if (logoKeyFromVersion.ContainsKey(Package.Version))
|
||||
{
|
||||
var key = logoKeyFromVersion[Package.Version];
|
||||
_ = app.GetStringValue(key, out string logoUri);
|
||||
return logoUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LogoPathFromUri(string uri)
|
||||
internal string LogoPathFromUri(string uri, (int, int) desiredSize)
|
||||
{
|
||||
// all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
|
||||
// windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
|
||||
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
|
||||
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
|
||||
|
||||
string path = Path.Combine(Package.Location, uri);
|
||||
|
||||
var logoPath = TryToFindLogo(uri, path);
|
||||
if (String.IsNullOrEmpty(logoPath))
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
// TODO: Don't know why, just keep it at the moment
|
||||
// Maybe on older version of Windows 10?
|
||||
// for C:\Windows\MiracastView etc
|
||||
return TryToFindLogo(uri, Path.Combine(Package.Location, "Assets", uri));
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
|
||||
$"|{UserModelId} 's logo uri is null or empty: {Location}", new ArgumentException("uri"));
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string path = Path.Combine(Location, uri);
|
||||
|
||||
var pxCount = desiredSize.Item1 * desiredSize.Item2;
|
||||
var logoPath = TryToFindLogo(uri, path, pxCount);
|
||||
if (logoPath == string.Empty)
|
||||
{
|
||||
var tmp = Path.Combine(Location, "Assets", uri);
|
||||
if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// TODO: Don't know why, just keep it at the moment
|
||||
// Maybe on older version of Windows 10?
|
||||
// for C:\Windows\MiracastView etc
|
||||
return TryToFindLogo(uri, tmp, pxCount);
|
||||
}
|
||||
}
|
||||
return logoPath;
|
||||
|
||||
string TryToFindLogo(string uri, string path)
|
||||
string TryToFindLogo(string uri, string path, int px)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
|
|
@ -604,42 +534,38 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
|
||||
var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
|
||||
if (String.IsNullOrEmpty(logoNamePrefix) || String.IsNullOrEmpty(logoDir) || !Directory.Exists(logoDir))
|
||||
if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir))
|
||||
{
|
||||
// Known issue: Edge always triggers it since logo is not at uri
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Package.Location}", new FileNotFoundException());
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var files = Directory.EnumerateFiles(logoDir);
|
||||
var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}");
|
||||
|
||||
// Currently we don't care which one to choose
|
||||
// Just ignore all qualifiers
|
||||
// select like logo.[xxx_yyy].png
|
||||
// https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
|
||||
var logos = files.Where(file =>
|
||||
Path.GetFileName(file)?.StartsWith(logoNamePrefix, StringComparison.OrdinalIgnoreCase) ?? false
|
||||
&& extension.Equals(Path.GetExtension(file), StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
|
||||
var selected = logos.FirstOrDefault();
|
||||
var closest = selected;
|
||||
int min = int.MaxValue;
|
||||
foreach(var logo in logos)
|
||||
foreach (var logo in logos)
|
||||
{
|
||||
|
||||
var imageStream = File.OpenRead(logo);
|
||||
var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
|
||||
var height = decoder.Frames[0].PixelHeight;
|
||||
var width = decoder.Frames[0].PixelWidth;
|
||||
int pixelCountDiff = Math.Abs(height * width - 1936); // 44*44=1936
|
||||
if(pixelCountDiff < min)
|
||||
int pixelCountDiff = Math.Abs(height * width - px);
|
||||
if (pixelCountDiff < min)
|
||||
{
|
||||
// try to find the closest to 44x44 logo
|
||||
// try to find the closest to desired size
|
||||
closest = logo;
|
||||
if (pixelCountDiff == 0)
|
||||
break; // found 44x44
|
||||
break; // found
|
||||
min = pixelCountDiff;
|
||||
}
|
||||
}
|
||||
|
|
@ -651,111 +577,112 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Package.Location}", new FileNotFoundException());
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
|
||||
$"|Unable to find extension from {uri} for {UserModelId} " +
|
||||
$"in package location {Package.Location}", new FileNotFoundException());
|
||||
$"in package location {Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ImageSource Logo()
|
||||
{
|
||||
var logo = ImageFromPath(LogoPath);
|
||||
var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
|
||||
#region logo legacy
|
||||
// preserve for potential future use
|
||||
|
||||
// todo magic! temp fix for cross thread object
|
||||
plated.Freeze();
|
||||
return plated;
|
||||
}
|
||||
//public ImageSource Logo()
|
||||
//{
|
||||
// var logo = ImageFromPath(LogoPath);
|
||||
// var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
|
||||
|
||||
// // todo magic! temp fix for cross thread object
|
||||
// plated.Freeze();
|
||||
// return plated;
|
||||
//}
|
||||
//private BitmapImage ImageFromPath(string path)
|
||||
//{
|
||||
// if (File.Exists(path))
|
||||
// {
|
||||
// var image = new BitmapImage();
|
||||
// image.BeginInit();
|
||||
// image.UriSource = new Uri(path);
|
||||
// image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
// image.EndInit();
|
||||
// image.Freeze();
|
||||
// return image;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" +
|
||||
// $"|Unable to get logo for {UserModelId} from {path} and" +
|
||||
// $" located in {Location}", new FileNotFoundException());
|
||||
// return new BitmapImage(new Uri(Constant.MissingImgIcon));
|
||||
// }
|
||||
//}
|
||||
|
||||
private BitmapImage ImageFromPath(string path)
|
||||
{
|
||||
// TODO: Consider using infrastructure.image.imageloader?
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.UriSource = new Uri(path);
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.EndInit();
|
||||
image.Freeze();
|
||||
return image;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" +
|
||||
$"|Unable to get logo for {UserModelId} from {path} and" +
|
||||
$" located in {Package.Location}", new FileNotFoundException());
|
||||
return new BitmapImage(new Uri(Constant.MissingImgIcon));
|
||||
}
|
||||
}
|
||||
//private ImageSource PlatedImage(BitmapImage image)
|
||||
//{
|
||||
// if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
|
||||
// {
|
||||
// var width = image.Width;
|
||||
// var height = image.Height;
|
||||
// var x = 0;
|
||||
// var y = 0;
|
||||
|
||||
private ImageSource PlatedImage(BitmapImage image)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
|
||||
{
|
||||
var width = image.Width;
|
||||
var height = image.Height;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
// var group = new DrawingGroup();
|
||||
|
||||
var group = new DrawingGroup();
|
||||
// var converted = ColorConverter.ConvertFromString(BackgroundColor);
|
||||
// if (converted != null)
|
||||
// {
|
||||
// var color = (Color)converted;
|
||||
// var brush = new SolidColorBrush(color);
|
||||
// var pen = new Pen(brush, 1);
|
||||
// var backgroundArea = new Rect(0, 0, width, width);
|
||||
// var rectabgle = new RectangleGeometry(backgroundArea);
|
||||
// var rectDrawing = new GeometryDrawing(brush, pen, rectabgle);
|
||||
// group.Children.Add(rectDrawing);
|
||||
|
||||
var converted = ColorConverter.ConvertFromString(BackgroundColor);
|
||||
if (converted != null)
|
||||
{
|
||||
var color = (Color)converted;
|
||||
var brush = new SolidColorBrush(color);
|
||||
var pen = new Pen(brush, 1);
|
||||
var backgroundArea = new Rect(0, 0, width, width);
|
||||
var rectabgle = new RectangleGeometry(backgroundArea);
|
||||
var rectDrawing = new GeometryDrawing(brush, pen, rectabgle);
|
||||
group.Children.Add(rectDrawing);
|
||||
// var imageArea = new Rect(x, y, image.Width, image.Height);
|
||||
// var imageDrawing = new ImageDrawing(image, imageArea);
|
||||
// group.Children.Add(imageDrawing);
|
||||
|
||||
var imageArea = new Rect(x, y, image.Width, image.Height);
|
||||
var imageDrawing = new ImageDrawing(image, imageArea);
|
||||
group.Children.Add(imageDrawing);
|
||||
// // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
|
||||
// var visual = new DrawingVisual();
|
||||
// var context = visual.RenderOpen();
|
||||
// context.DrawDrawing(group);
|
||||
// context.Close();
|
||||
// const int dpiScale100 = 96;
|
||||
// var bitmap = new RenderTargetBitmap(
|
||||
// Convert.ToInt32(width), Convert.ToInt32(height),
|
||||
// dpiScale100, dpiScale100,
|
||||
// PixelFormats.Pbgra32
|
||||
// );
|
||||
// bitmap.Render(visual);
|
||||
// return bitmap;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" +
|
||||
// $"|Unable to convert background string {BackgroundColor} " +
|
||||
// $"to color for {Location}", new InvalidOperationException());
|
||||
|
||||
// http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
|
||||
var visual = new DrawingVisual();
|
||||
var context = visual.RenderOpen();
|
||||
context.DrawDrawing(group);
|
||||
context.Close();
|
||||
const int dpiScale100 = 96;
|
||||
var bitmap = new RenderTargetBitmap(
|
||||
Convert.ToInt32(width), Convert.ToInt32(height),
|
||||
dpiScale100, dpiScale100,
|
||||
PixelFormats.Pbgra32
|
||||
);
|
||||
bitmap.Render(visual);
|
||||
return bitmap;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" +
|
||||
$"|Unable to convert background string {BackgroundColor} " +
|
||||
$"to color for {Package.Location}", new InvalidOperationException());
|
||||
|
||||
return new BitmapImage(new Uri(Constant.MissingImgIcon));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo use windows theme as background
|
||||
return image;
|
||||
}
|
||||
}
|
||||
// return new BitmapImage(new Uri(Constant.MissingImgIcon));
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // todo use windows theme as background
|
||||
// return image;
|
||||
// }
|
||||
//}
|
||||
|
||||
#endregion
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
|
|
@ -786,27 +713,5 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
Windows8,
|
||||
Unknown
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum Stgm : uint
|
||||
{
|
||||
Read = 0x0,
|
||||
ShareExclusive = 0x10,
|
||||
ShareDenyNone = 0x40
|
||||
}
|
||||
|
||||
private enum Hresult : uint
|
||||
{
|
||||
Ok = 0x0000,
|
||||
}
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create,
|
||||
IStream reserved, out IStream stream);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf,
|
||||
IntPtr ppvReserved);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,10 +143,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
FileName = FullPath,
|
||||
WorkingDirectory = ParentDirectory,
|
||||
UseShellExecute = true,
|
||||
Verb = runAsAdmin ? "runas" : null
|
||||
Verb = runAsAdmin ? "runas" : ""
|
||||
};
|
||||
|
||||
Task.Run(() => Main.StartProcess(Process.Start, info));
|
||||
_ = Task.Run(() => Main.StartProcess(Process.Start, info));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,6 +270,10 @@ And you can download <a href="https://github.com/Flow-Launcher/Flow.Launcher/dis
|
|||
<a href="https://appwrite.io">
|
||||
<img src='https://appwrite.io/images-ee/press/logo-1.svg' width="25%"/>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/itsonlyfrans">
|
||||
<img src='https://avatars.githubusercontent.com/u/46535667?v=4' width="10%"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="left">
|
||||
|
|
|
|||
Loading…
Reference in a new issue