2020-08-18 01:00:56 +08:00
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
2020-08-13 02:46:11 +08:00
using System ;
2020-08-12 00:08:44 +08:00
using System.Collections.Generic ;
using System.Diagnostics ;
2020-08-18 01:00:56 +08:00
using System.Globalization ;
2020-08-12 00:08:44 +08:00
using System.IO ;
2020-11-03 01:33:43 +08:00
using System.IO.Abstractions ;
2020-08-12 00:08:44 +08:00
using System.Linq ;
2020-08-18 01:00:56 +08:00
using System.Reflection ;
2020-08-12 00:08:44 +08:00
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading.Tasks ;
2020-08-22 04:39:08 +08:00
using System.Windows ;
2020-08-13 02:46:11 +08:00
using System.Windows.Input ;
2020-08-12 00:08:44 +08:00
using System.Windows.Media ;
using System.Windows.Media.Imaging ;
2020-10-24 06:05:07 +08:00
using ManagedCommon ;
2020-08-13 02:46:11 +08:00
using Microsoft.Plugin.Program.Logger ;
2020-08-18 01:00:56 +08:00
using Microsoft.Plugin.Program.Win32 ;
2020-08-13 02:46:11 +08:00
using Wox.Infrastructure ;
2020-08-18 01:00:56 +08:00
using Wox.Infrastructure.Image ;
2020-08-13 02:46:11 +08:00
using Wox.Plugin ;
2020-10-24 04:06:22 +08:00
using Wox.Plugin.Logger ;
2020-08-13 02:46:11 +08:00
using Wox.Plugin.SharedCommands ;
2020-08-18 01:00:56 +08:00
using static Microsoft . Plugin . Program . Programs . UWP ;
2020-08-12 00:08:44 +08:00
namespace Microsoft.Plugin.Program.Programs
2020-08-18 01:00:56 +08:00
{
[Serializable]
public class UWPApplication : IProgram
{
2020-11-03 01:33:43 +08:00
private static readonly IFileSystem FileSystem = new FileSystem ( ) ;
private static readonly IPath Path = FileSystem . Path ;
private static readonly IFile File = FileSystem . File ;
2020-08-18 01:00:56 +08:00
public string AppListEntry { get ; set ; }
public string UniqueIdentifier { get ; set ; }
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 Name = > DisplayName ;
public string Location = > Package . Location ;
public bool Enabled { get ; set ; }
public bool CanRunElevated { get ; set ; }
public string LogoPath { get ; set ; }
2020-08-22 04:39:08 +08:00
public LogoType LogoType { get ; set ; }
2020-08-18 01:00:56 +08:00
public UWP Package { get ; set ; }
private string logoUri ;
2020-08-22 04:39:08 +08:00
private const string ContrastWhite = "contrast-white" ;
private const string ContrastBlack = "contrast-black" ;
2020-08-18 01:00:56 +08:00
// Function to calculate the score of a result
private int Score ( string query )
{
var displayNameMatch = StringMatcher . FuzzySearch ( query , DisplayName ) ;
var descriptionMatch = StringMatcher . FuzzySearch ( query , Description ) ;
var score = new [ ] { displayNameMatch . Score , descriptionMatch . Score / 2 } . Max ( ) ;
return score ;
}
// Function to set the subtitle based on the Type of application
2020-09-03 06:24:59 +08:00
private static string SetSubtitle ( )
2020-08-18 01:00:56 +08:00
{
2020-09-03 06:24:59 +08:00
return Properties . Resources . powertoys_run_plugin_program_packaged_application ;
2020-08-18 01:00:56 +08:00
}
2020-09-18 06:39:28 +08:00
public Result Result ( string query , string queryArguments , IPublicAPI api )
2020-08-18 01:00:56 +08:00
{
if ( api = = null )
{
throw new ArgumentNullException ( nameof ( api ) ) ;
}
var score = Score ( query ) ;
if ( score < = 0 )
{ // no need to create result if score is 0
return null ;
}
var result = new Result
{
2020-09-03 06:24:59 +08:00
SubTitle = SetSubtitle ( ) ,
2020-08-18 01:00:56 +08:00
Icon = Logo ,
Score = score ,
ContextData = this ,
2020-10-22 02:49:55 +08:00
ProgramArguments = queryArguments ,
2020-08-18 01:00:56 +08:00
Action = e = >
{
2020-09-18 06:39:28 +08:00
Launch ( api , queryArguments ) ;
2020-08-18 01:00:56 +08:00
return true ;
} ,
} ;
// To set the title to always be the displayname of the packaged application
result . Title = DisplayName ;
2020-10-27 06:14:33 +08:00
result . SetTitleHighlightData ( StringMatcher . FuzzySearch ( query , Name ) . MatchData ) ;
2020-08-18 01:00:56 +08:00
2020-10-31 07:43:09 +08:00
// Using CurrentCulture since this is user facing
2020-09-03 06:24:59 +08:00
var toolTipTitle = string . Format ( CultureInfo . CurrentCulture , "{0}: {1}" , Properties . Resources . powertoys_run_plugin_program_file_name , result . Title ) ;
var toolTipText = string . Format ( CultureInfo . CurrentCulture , "{0}: {1}" , Properties . Resources . powertoys_run_plugin_program_file_path , Package . Location ) ;
2020-08-18 01:00:56 +08:00
result . ToolTipData = new ToolTipData ( toolTipTitle , toolTipText ) ;
return result ;
}
2020-10-31 02:41:23 +08:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive.")]
2020-10-22 02:49:55 +08:00
public List < ContextMenuResult > ContextMenus ( string queryArguments , IPublicAPI api )
2020-08-18 01:00:56 +08:00
{
if ( api = = null )
{
throw new ArgumentNullException ( nameof ( api ) ) ;
}
var contextMenus = new List < ContextMenuResult > ( ) ;
if ( CanRunElevated )
{
contextMenus . Add (
new ContextMenuResult
{
PluginName = Assembly . GetExecutingAssembly ( ) . GetName ( ) . Name ,
2020-09-03 06:24:59 +08:00
Title = Properties . Resources . wox_plugin_program_run_as_administrator ,
2020-08-18 01:00:56 +08:00
Glyph = "\xE7EF" ,
FontFamily = "Segoe MDL2 Assets" ,
AcceleratorKey = Key . Enter ,
AcceleratorModifiers = ModifierKeys . Control | ModifierKeys . Shift ,
Action = _ = >
{
string command = "shell:AppsFolder\\" + UniqueIdentifier ;
command = Environment . ExpandEnvironmentVariables ( command . Trim ( ) ) ;
var info = ShellCommand . SetProcessStartInfo ( command , verb : "runas" ) ;
info . UseShellExecute = true ;
2020-10-22 02:49:55 +08:00
info . Arguments = queryArguments ;
2020-08-18 01:00:56 +08:00
Process . Start ( info ) ;
return true ;
} ,
} ) ;
}
contextMenus . Add (
new ContextMenuResult
{
PluginName = Assembly . GetExecutingAssembly ( ) . GetName ( ) . Name ,
2020-09-03 06:24:59 +08:00
Title = Properties . Resources . wox_plugin_program_open_containing_folder ,
2020-08-18 01:00:56 +08:00
Glyph = "\xE838" ,
FontFamily = "Segoe MDL2 Assets" ,
AcceleratorKey = Key . E ,
AcceleratorModifiers = ModifierKeys . Control | ModifierKeys . Shift ,
Action = _ = >
{
Main . StartProcess ( Process . Start , new ProcessStartInfo ( "explorer" , Package . Location ) ) ;
return true ;
} ,
} ) ;
contextMenus . Add ( new ContextMenuResult
{
PluginName = Assembly . GetExecutingAssembly ( ) . GetName ( ) . Name ,
2020-09-03 06:24:59 +08:00
Title = Properties . Resources . wox_plugin_program_open_in_console ,
2020-08-18 01:00:56 +08:00
Glyph = "\xE756" ,
FontFamily = "Segoe MDL2 Assets" ,
AcceleratorKey = Key . C ,
AcceleratorModifiers = ModifierKeys . Control | ModifierKeys . Shift ,
Action = ( context ) = >
{
try
{
Helper . OpenInConsole ( Package . Location ) ;
return true ;
}
catch ( Exception e )
{
2020-09-24 07:32:06 +08:00
Log . Exception ( $"Failed to open {Name} in console, {e.Message}" , e , GetType ( ) ) ;
2020-08-18 01:00:56 +08:00
return false ;
}
} ,
} ) ;
return contextMenus ;
}
2020-10-31 02:41:23 +08:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive, and showing the user an error message")]
2020-09-18 06:39:28 +08:00
private async void Launch ( IPublicAPI api , string queryArguments )
2020-08-18 01:00:56 +08:00
{
var appManager = new ApplicationActivationHelper . ApplicationActivationManager ( ) ;
const ApplicationActivationHelper . ActivateOptions noFlags = ApplicationActivationHelper . ActivateOptions . None ;
await Task . Run ( ( ) = >
{
try
{
2020-09-18 06:39:28 +08:00
appManager . ActivateApplication ( UserModelId , queryArguments , noFlags , out var unusedPid ) ;
2020-08-18 01:00:56 +08:00
}
catch ( Exception )
{
2020-09-09 02:51:24 +08:00
var name = "Plugin: " + Properties . Resources . wox_plugin_program_plugin_name ;
var message = $"{Properties.Resources.powertoys_run_plugin_program_uwp_failed}: {DisplayName}" ;
2020-08-18 01:00:56 +08:00
api . ShowMsg ( name , message , string . Empty ) ;
}
} ) . ConfigureAwait ( false ) ;
}
public UWPApplication ( IAppxManifestApplication manifestApp , UWP package )
{
if ( manifestApp = = null )
{
throw new ArgumentNullException ( nameof ( manifestApp ) ) ;
}
var hr = manifestApp . GetAppUserModelId ( out var tmpUserModelId ) ;
UserModelId = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpUserModelId ) ;
hr = manifestApp . GetAppUserModelId ( out var tmpUniqueIdentifier ) ;
UniqueIdentifier = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpUniqueIdentifier ) ;
hr = manifestApp . GetStringValue ( "DisplayName" , out var tmpDisplayName ) ;
DisplayName = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpDisplayName ) ;
hr = manifestApp . GetStringValue ( "Description" , out var tmpDescription ) ;
Description = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpDescription ) ;
hr = manifestApp . GetStringValue ( "BackgroundColor" , out var tmpBackgroundColor ) ;
BackgroundColor = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpBackgroundColor ) ;
hr = manifestApp . GetStringValue ( "EntryPoint" , out var tmpEntryPoint ) ;
EntryPoint = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , tmpEntryPoint ) ;
Package = package ? ? throw new ArgumentNullException ( nameof ( package ) ) ;
DisplayName = ResourceFromPri ( package . FullName , DisplayName ) ;
Description = ResourceFromPri ( package . FullName , Description ) ;
logoUri = LogoUriFromManifest ( manifestApp ) ;
Enabled = true ;
CanRunElevated = IfApplicationcanRunElevated ( ) ;
}
private bool IfApplicationcanRunElevated ( )
{
if ( EntryPoint = = "Windows.FullTrustApplication" )
{
return true ;
}
else
{
var manifest = Package . Location + "\\AppxManifest.xml" ;
if ( File . Exists ( manifest ) )
{
var file = File . ReadAllText ( manifest ) ;
2020-10-31 07:43:09 +08:00
// Using OrdinalIgnoreCase since this is used internally
2020-08-18 01:00:56 +08:00
if ( file . Contains ( "TrustLevel=\"mediumIL\"" , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
}
}
return false ;
}
internal string ResourceFromPri ( string packageFullName , string resourceReference )
{
const string prefix = "ms-resource:" ;
2020-10-31 07:43:09 +08:00
// Using OrdinalIgnoreCase since this is used internally
2020-08-18 01:00:56 +08:00
if ( ! string . IsNullOrWhiteSpace ( resourceReference ) & & resourceReference . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
{
// magic comes from @talynone
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
string key = resourceReference . Substring ( prefix . Length ) ;
string parsed ;
2020-10-31 07:43:09 +08:00
// Using Ordinal/OrdinalIgnorcase since these are used internally
2020-08-18 01:00:56 +08:00
if ( key . StartsWith ( "//" , StringComparison . Ordinal ) )
{
parsed = prefix + key ;
}
else if ( key . StartsWith ( "/" , StringComparison . Ordinal ) )
{
parsed = prefix + "//" + key ;
}
else if ( key . Contains ( "resources" , StringComparison . OrdinalIgnoreCase ) )
{
parsed = prefix + key ;
}
else
{
parsed = prefix + "///resources/" + key ;
}
var outBuffer = new StringBuilder ( 128 ) ;
string source = $"@{{{packageFullName}? {parsed}}}" ;
var capacity = ( uint ) outBuffer . Capacity ;
var hResult = NativeMethods . SHLoadIndirectString ( source , outBuffer , capacity , IntPtr . Zero ) ;
if ( hResult = = Hresult . Ok )
{
var loaded = outBuffer . ToString ( ) ;
if ( ! string . IsNullOrEmpty ( loaded ) )
{
return loaded ;
}
else
{
2020-09-24 07:32:06 +08:00
ProgramLogger . Exception ( $"Can't load null or empty result pri {source} in uwp location {Package.Location}" , new NullReferenceException ( ) , GetType ( ) , Package . Location ) ;
2020-08-18 01:00:56 +08:00
return string . Empty ;
}
}
else
{
// https://github.com/Wox-launcher/Wox/issues/964
// known hresult 2147942522:
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
// for
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
var e = Marshal . GetExceptionForHR ( ( int ) hResult ) ;
2020-09-24 07:32:06 +08:00
ProgramLogger . Exception ( $"Load pri failed {source} with HResult {hResult} and location {Package.Location}" , e , GetType ( ) , Package . Location ) ;
2020-08-18 01:00:56 +08:00
return string . Empty ;
}
}
else
{
return resourceReference ;
}
}
internal string LogoUriFromManifest ( IAppxManifestApplication app )
2020-08-12 00:08:44 +08:00
{
var logoKeyFromVersion = new Dictionary < PackageVersion , string >
{
{ PackageVersion . Windows10 , "Square44x44Logo" } ,
{ PackageVersion . Windows81 , "Square30x30Logo" } ,
{ PackageVersion . Windows8 , "SmallLogo" } ,
2020-08-18 01:00:56 +08:00
} ;
if ( logoKeyFromVersion . ContainsKey ( Package . Version ) )
{
var key = logoKeyFromVersion [ Package . Version ] ;
var hr = app . GetStringValue ( key , out var logoUri ) ;
_ = AppxPackageHelper . CheckHRAndReturnOrThrow ( hr , logoUri ) ;
return logoUri ;
}
else
{
return string . Empty ;
}
}
public void UpdatePath ( Theme theme )
{
2020-08-22 04:39:08 +08:00
LogoPathFromUri ( this . logoUri , theme ) ;
2020-08-18 01:00:56 +08:00
}
2020-08-22 04:39:08 +08:00
private bool SetScaleIcons ( string path , string colorscheme , bool highContrast = false )
2020-08-18 01:00:56 +08:00
{
var extension = Path . GetExtension ( path ) ;
if ( extension ! = null )
{
var end = path . Length - extension . Length ;
var prefix = path . Substring ( 0 , end ) ;
2020-08-22 04:39:08 +08:00
var paths = new List < string > { } ;
2020-08-12 00:08:44 +08:00
var scaleFactors = new Dictionary < PackageVersion , List < int > >
{
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
{ PackageVersion . Windows10 , new List < int > { 100 , 125 , 150 , 200 , 400 } } ,
{ PackageVersion . Windows81 , new List < int > { 100 , 120 , 140 , 160 , 180 } } ,
2020-08-13 02:46:11 +08:00
{ PackageVersion . Windows8 , new List < int > { 100 } } ,
2020-08-18 01:00:56 +08:00
} ;
2020-08-22 04:39:08 +08:00
if ( ! highContrast )
{
paths . Add ( path ) ;
}
2020-08-18 01:00:56 +08:00
if ( scaleFactors . ContainsKey ( Package . Version ) )
{
foreach ( var factor in scaleFactors [ Package . Version ] )
{
2020-08-22 04:39:08 +08:00
if ( highContrast )
{
paths . Add ( $"{prefix}.scale-{factor}_{colorscheme}{extension}" ) ;
paths . Add ( $"{prefix}.{colorscheme}_scale-{factor}{extension}" ) ;
}
else
{
paths . Add ( $"{prefix}.scale-{factor}{extension}" ) ;
}
2020-08-18 01:00:56 +08:00
}
}
2020-08-22 04:39:08 +08:00
var selectedIconPath = paths . FirstOrDefault ( File . Exists ) ;
if ( ! string . IsNullOrEmpty ( selectedIconPath ) )
2020-08-18 01:00:56 +08:00
{
2020-08-22 04:39:08 +08:00
LogoPath = selectedIconPath ;
if ( highContrast )
{
LogoType = LogoType . HighContrast ;
}
else
{
LogoType = LogoType . Colored ;
}
return true ;
2020-08-18 01:00:56 +08:00
}
2020-08-22 04:39:08 +08:00
}
2020-08-18 01:00:56 +08:00
2020-08-22 04:39:08 +08:00
return false ;
}
2020-08-18 01:00:56 +08:00
2020-08-22 04:39:08 +08:00
private bool SetTargetSizeIcon ( string path , string colorscheme , bool highContrast = false )
{
var extension = Path . GetExtension ( path ) ;
if ( extension ! = null )
{
var end = path . Length - extension . Length ;
var prefix = path . Substring ( 0 , end ) ;
var paths = new List < string > { } ;
int appIconSize = 36 ;
var targetSizes = new List < int > { 16 , 24 , 30 , 36 , 44 , 60 , 72 , 96 , 128 , 180 , 256 } . AsParallel ( ) ;
var pathFactorPairs = new Dictionary < string , int > ( ) ;
foreach ( var factor in targetSizes )
{
if ( highContrast )
{
string suffixThemePath = $"{prefix}.targetsize-{factor}_{colorscheme}{extension}" ;
string prefixThemePath = $"{prefix}.{colorscheme}_targetsize-{factor}{extension}" ;
2020-08-18 01:00:56 +08:00
paths . Add ( suffixThemePath ) ;
paths . Add ( prefixThemePath ) ;
pathFactorPairs . Add ( suffixThemePath , factor ) ;
pathFactorPairs . Add ( prefixThemePath , factor ) ;
}
2020-08-22 04:39:08 +08:00
else
{
string simplePath = $"{prefix}.targetsize-{factor}{extension}" ;
string altformUnPlatedPath = $"{prefix}.targetsize-{factor}_altform-unplated{extension}" ;
paths . Add ( simplePath ) ;
paths . Add ( altformUnPlatedPath ) ;
pathFactorPairs . Add ( simplePath , factor ) ;
pathFactorPairs . Add ( altformUnPlatedPath , factor ) ;
}
}
2020-08-18 01:00:56 +08:00
2020-08-22 04:39:08 +08:00
var selectedIconPath = paths . OrderBy ( x = > Math . Abs ( pathFactorPairs . GetValueOrDefault ( x ) - appIconSize ) ) . FirstOrDefault ( File . Exists ) ;
if ( ! string . IsNullOrEmpty ( selectedIconPath ) )
{
LogoPath = selectedIconPath ;
if ( highContrast )
2020-08-18 01:00:56 +08:00
{
2020-08-22 04:39:08 +08:00
LogoType = LogoType . HighContrast ;
2020-08-18 01:00:56 +08:00
}
else
{
2020-08-22 04:39:08 +08:00
LogoType = LogoType . Colored ;
2020-08-18 01:00:56 +08:00
}
2020-08-22 04:39:08 +08:00
return true ;
2020-08-18 01:00:56 +08:00
}
}
2020-08-22 04:39:08 +08:00
return false ;
}
private bool SetColoredIcon ( string path , string colorscheme )
{
var isSetColoredScaleIcon = SetScaleIcons ( path , colorscheme ) ;
if ( isSetColoredScaleIcon )
{
return true ;
}
var isSetColoredTargetIcon = SetTargetSizeIcon ( path , colorscheme ) ;
if ( isSetColoredTargetIcon )
{
return true ;
}
var isSetHighContrastScaleIcon = SetScaleIcons ( path , colorscheme , true ) ;
if ( isSetHighContrastScaleIcon )
{
return true ;
}
var isSetHighContrastTargetIcon = SetTargetSizeIcon ( path , colorscheme , true ) ;
if ( isSetHighContrastTargetIcon )
{
return true ;
}
return false ;
}
private bool SetHighContrastIcon ( string path , string colorscheme )
{
var isSetHighContrastScaleIcon = SetScaleIcons ( path , colorscheme , true ) ;
if ( isSetHighContrastScaleIcon )
{
return true ;
}
var isSetHighContrastTargetIcon = SetTargetSizeIcon ( path , colorscheme , true ) ;
if ( isSetHighContrastTargetIcon )
{
return true ;
}
var isSetColoredScaleIcon = SetScaleIcons ( path , colorscheme ) ;
if ( isSetColoredScaleIcon )
{
return true ;
}
var isSetColoredTargetIcon = SetTargetSizeIcon ( path , colorscheme ) ;
if ( isSetColoredTargetIcon )
{
return true ;
}
return false ;
}
internal void LogoPathFromUri ( string uri , Theme theme )
{
// 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 ;
bool isLogoUriSet ;
2020-10-31 07:43:09 +08:00
// Using Ordinal since this is used internally with uri
2020-08-22 04:39:08 +08:00
if ( uri . Contains ( "\\" , StringComparison . Ordinal ) )
{
path = Path . Combine ( Package . Location , uri ) ;
}
2020-08-18 01:00:56 +08:00
else
{
2020-08-22 04:39:08 +08:00
// for C:\Windows\MiracastView etc
path = Path . Combine ( Package . Location , "Assets" , uri ) ;
}
if ( theme = = Theme . HighContrastBlack | | theme = = Theme . HighContrastOne | | theme = = Theme . HighContrastTwo )
{
isLogoUriSet = SetHighContrastIcon ( path , ContrastBlack ) ;
}
else if ( theme = = Theme . HighContrastWhite )
{
isLogoUriSet = SetHighContrastIcon ( path , ContrastWhite ) ;
}
else if ( theme = = Theme . Light )
{
isLogoUriSet = SetColoredIcon ( path , ContrastWhite ) ;
}
else
{
isLogoUriSet = SetColoredIcon ( path , ContrastBlack ) ;
}
if ( ! isLogoUriSet )
{
LogoPath = string . Empty ;
LogoType = LogoType . Error ;
2020-09-24 07:32:06 +08:00
ProgramLogger . Exception ( $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}" , new FileNotFoundException ( ) , GetType ( ) , Package . Location ) ;
2020-08-18 01:00:56 +08:00
}
}
public ImageSource Logo ( )
{
2020-08-22 04:39:08 +08:00
if ( LogoType = = LogoType . Colored )
{
var logo = ImageFromPath ( LogoPath ) ;
var platedImage = PlatedImage ( logo ) ;
return platedImage ;
}
else
{
return ImageFromPath ( LogoPath ) ;
}
}
2020-09-24 07:32:06 +08:00
private const int _dpiScale100 = 96 ;
2020-08-22 04:39:08 +08:00
private ImageSource PlatedImage ( BitmapImage image )
{
if ( ! string . IsNullOrEmpty ( BackgroundColor ) )
{
string currentBackgroundColor ;
if ( BackgroundColor = = "transparent" )
{
2020-10-31 07:43:09 +08:00
// Using InvariantCulture since this is internal
2020-08-22 04:39:08 +08:00
currentBackgroundColor = SystemParameters . WindowGlassBrush . ToString ( CultureInfo . InvariantCulture ) ;
}
else
{
currentBackgroundColor = BackgroundColor ;
}
var padding = 8 ;
var width = image . Width + ( 2 * padding ) ;
var height = image . Height + ( 2 * padding ) ;
var x = 0 ;
var y = 0 ;
var group = new DrawingGroup ( ) ;
var converted = ColorConverter . ConvertFromString ( currentBackgroundColor ) ;
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 , height ) ;
var rectangleGeometry = new RectangleGeometry ( backgroundArea ) ;
var rectDrawing = new GeometryDrawing ( brush , pen , rectangleGeometry ) ;
group . Children . Add ( rectDrawing ) ;
var imageArea = new Rect ( x + padding , y + padding , 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 ( ) ;
2020-09-24 07:32:06 +08:00
2020-08-22 04:39:08 +08:00
var bitmap = new RenderTargetBitmap (
Convert . ToInt32 ( width ) ,
Convert . ToInt32 ( height ) ,
2020-09-24 07:32:06 +08:00
_dpiScale100 ,
_dpiScale100 ,
2020-08-22 04:39:08 +08:00
PixelFormats . Pbgra32 ) ;
2020-09-24 07:32:06 +08:00
2020-08-22 04:39:08 +08:00
bitmap . Render ( visual ) ;
2020-09-24 07:32:06 +08:00
2020-08-22 04:39:08 +08:00
return bitmap ;
}
else
{
2020-09-24 07:32:06 +08:00
ProgramLogger . Exception ( $"Unable to convert background string {BackgroundColor} to color for {Package.Location}" , new InvalidOperationException ( ) , GetType ( ) , Package . Location ) ;
2020-08-22 04:39:08 +08:00
return new BitmapImage ( new Uri ( Constant . ErrorIcon ) ) ;
}
}
else
{
// todo use windows theme as background
return image ;
}
2020-08-18 01:00:56 +08:00
}
private BitmapImage ImageFromPath ( string path )
{
if ( File . Exists ( path ) )
{
MemoryStream memoryStream = new MemoryStream ( ) ;
byte [ ] fileBytes = File . ReadAllBytes ( path ) ;
memoryStream . Write ( fileBytes , 0 , fileBytes . Length ) ;
memoryStream . Position = 0 ;
var image = new BitmapImage ( ) ;
image . BeginInit ( ) ;
image . StreamSource = memoryStream ;
image . EndInit ( ) ;
return image ;
}
else
{
2020-09-24 07:32:06 +08:00
ProgramLogger . Exception ( $"Unable to get logo for {UserModelId} from {path} and located in {Package.Location}" , new FileNotFoundException ( ) , GetType ( ) , path ) ;
2020-08-18 01:00:56 +08:00
return new BitmapImage ( new Uri ( ImageLoader . ErrorIconPath ) ) ;
}
}
public override string ToString ( )
{
return $"{DisplayName}: {Description}" ;
}
2020-08-12 00:08:44 +08:00
}
2020-08-22 04:39:08 +08:00
public enum LogoType
{
Error ,
Colored ,
HighContrast ,
}
2020-08-18 01:00:56 +08:00
}