UWPApplication.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.Globalization; 9 using System.IO; 10 using System.IO.Abstractions; 11 using System.Linq; 12 using System.Reflection; 13 using System.Runtime.InteropServices; 14 using System.Text; 15 using System.Threading.Tasks; 16 using System.Windows; 17 using System.Windows.Input; 18 using System.Windows.Media; 19 using System.Windows.Media.Imaging; 20 using System.Xml; 21 22 using ManagedCommon; 23 using Microsoft.Plugin.Program.Logger; 24 using Wox.Infrastructure; 25 using Wox.Infrastructure.Image; 26 using Wox.Plugin; 27 using Wox.Plugin.Common; 28 using Wox.Plugin.Common.Win32; 29 using Wox.Plugin.Logger; 30 31 using PackageVersion = Microsoft.Plugin.Program.Programs.UWP.PackageVersion; 32 33 namespace Microsoft.Plugin.Program.Programs 34 { 35 [Serializable] 36 public class UWPApplication : IProgram 37 { 38 private static readonly IFileSystem FileSystem = new FileSystem(); 39 private static readonly IPath Path = FileSystem.Path; 40 private static readonly IFile File = FileSystem.File; 41 42 public string AppListEntry { get; set; } 43 44 public string UniqueIdentifier { get; set; } 45 46 public string DisplayName { get; set; } 47 48 public string Description { get; set; } 49 50 public string UserModelId { get; set; } 51 52 public string BackgroundColor { get; set; } 53 54 public string EntryPoint { get; set; } 55 56 public string Name => DisplayName; 57 58 public string Location => Package.Location; 59 60 // Localized path based on windows display language 61 public string LocationLocalized => Package.LocationLocalized; 62 63 public bool Enabled { get; set; } 64 65 public bool CanRunElevated { get; set; } 66 67 public string LogoPath { get; set; } 68 69 public LogoType LogoType { get; set; } 70 71 public UWP Package { get; set; } 72 73 private string logoUri; 74 75 private const string ContrastWhite = "contrast-white"; 76 77 private const string ContrastBlack = "contrast-black"; 78 79 // Function to calculate the score of a result 80 private int Score(string query) 81 { 82 var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName); 83 var descriptionMatch = StringMatcher.FuzzySearch(query, Description); 84 var score = new[] { displayNameMatch.Score, descriptionMatch.Score / 2 }.Max(); 85 return score; 86 } 87 88 // Function to set the subtitle based on the Type of application 89 private static string SetSubtitle() 90 { 91 return Properties.Resources.powertoys_run_plugin_program_packaged_application; 92 } 93 94 public Result Result(string query, string queryArguments, IPublicAPI api) 95 { 96 ArgumentNullException.ThrowIfNull(api); 97 98 var score = Score(query); 99 if (score <= 0) 100 { // no need to create result if score is 0 101 return null; 102 } 103 104 var result = new Result 105 { 106 SubTitle = SetSubtitle(), 107 Icon = Logo, 108 Score = score, 109 ContextData = this, 110 ProgramArguments = queryArguments, 111 Action = e => 112 { 113 Launch(api, queryArguments); 114 return true; 115 }, 116 }; 117 118 // To set the title to always be the display name of the packaged application 119 result.Title = DisplayName; 120 result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData; 121 122 // Using CurrentCulture since this is user facing 123 var toolTipTitle = result.Title; 124 var toolTipText = LocationLocalized; 125 result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText); 126 127 return result; 128 } 129 130 public List<ContextMenuResult> ContextMenus(string queryArguments, IPublicAPI api) 131 { 132 ArgumentNullException.ThrowIfNull(api); 133 134 var contextMenus = new List<ContextMenuResult>(); 135 136 if (CanRunElevated) 137 { 138 contextMenus.Add( 139 new ContextMenuResult 140 { 141 PluginName = Assembly.GetExecutingAssembly().GetName().Name, 142 Title = Properties.Resources.wox_plugin_program_run_as_administrator, 143 Glyph = "\xE7EF", 144 FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets", 145 AcceleratorKey = Key.Enter, 146 AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, 147 Action = _ => 148 { 149 string command = "shell:AppsFolder\\" + UniqueIdentifier; 150 command = Environment.ExpandEnvironmentVariables(command.Trim()); 151 152 var info = ShellCommand.SetProcessStartInfo(command, verb: "runas"); 153 info.UseShellExecute = true; 154 info.Arguments = queryArguments; 155 Process.Start(info); 156 return true; 157 }, 158 }); 159 160 // We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users. 161 } 162 163 contextMenus.Add( 164 new ContextMenuResult 165 { 166 PluginName = Assembly.GetExecutingAssembly().GetName().Name, 167 Title = Properties.Resources.wox_plugin_program_open_containing_folder, 168 Glyph = "\xE838", 169 FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets", 170 AcceleratorKey = Key.E, 171 AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, 172 Action = _ => 173 { 174 Helper.OpenInShell(Package.Location); 175 176 return true; 177 }, 178 }); 179 180 contextMenus.Add(new ContextMenuResult 181 { 182 PluginName = Assembly.GetExecutingAssembly().GetName().Name, 183 Title = Properties.Resources.wox_plugin_program_open_in_console, 184 Glyph = "\xE756", 185 FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets", 186 AcceleratorKey = Key.C, 187 AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, 188 Action = (context) => 189 { 190 try 191 { 192 Helper.OpenInConsole(Package.Location); 193 return true; 194 } 195 catch (Exception e) 196 { 197 Log.Exception($"Failed to open {Name} in console, {e.Message}", e, GetType()); 198 return false; 199 } 200 }, 201 }); 202 203 return contextMenus; 204 } 205 206 private async void Launch(IPublicAPI api, string queryArguments) 207 { 208 var appManager = new ApplicationActivationHelper.ApplicationActivationManager(); 209 const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None; 210 await Task.Run(() => 211 { 212 try 213 { 214 appManager.ActivateApplication(UserModelId, queryArguments, noFlags, out var unusedPid); 215 } 216 catch (Exception ex) 217 { 218 ProgramLogger.Exception($"Unable to launch UWP {DisplayName}", ex, MethodBase.GetCurrentMethod().DeclaringType, queryArguments); 219 var name = "Plugin: " + Properties.Resources.wox_plugin_program_plugin_name; 220 var message = $"{Properties.Resources.powertoys_run_plugin_program_uwp_failed}: {DisplayName}"; 221 api.ShowMsg(name, message, string.Empty); 222 } 223 }).ConfigureAwait(false); 224 } 225 226 public UWPApplication(IAppxManifestApplication manifestApp, UWP package) 227 { 228 ArgumentNullException.ThrowIfNull(manifestApp); 229 230 var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId); 231 UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId); 232 233 hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier); 234 UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier); 235 236 hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName); 237 DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName); 238 239 hr = manifestApp.GetStringValue("Description", out var tmpDescription); 240 Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription); 241 242 hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor); 243 BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor); 244 245 hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint); 246 EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint); 247 248 Package = package ?? throw new ArgumentNullException(nameof(package)); 249 250 DisplayName = ResourceFromPri(package.FullName, DisplayName); 251 Description = ResourceFromPri(package.FullName, Description); 252 logoUri = LogoUriFromManifest(manifestApp); 253 254 Enabled = true; 255 CanRunElevated = IfApplicationCanRunElevated(); 256 } 257 258 private bool IfApplicationCanRunElevated() 259 { 260 if (EntryPoint == "Windows.FullTrustApplication") 261 { 262 return true; 263 } 264 else 265 { 266 var manifest = Package.Location + "\\AppxManifest.xml"; 267 if (File.Exists(manifest)) 268 { 269 try 270 { 271 // Check the manifest to verify if the Trust Level for the application is "mediumIL" 272 var file = File.ReadAllText(manifest); 273 var xmlDoc = new XmlDocument(); 274 xmlDoc.LoadXml(file); 275 var xmlRoot = xmlDoc.DocumentElement; 276 var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable); 277 namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10"); 278 var trustLevelNode = xmlRoot.SelectSingleNode("//*[local-name()='Application' and @uap10:TrustLevel]", namespaceManager); // 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 279 280 if (trustLevelNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL") 281 { 282 return true; 283 } 284 } 285 catch (Exception e) 286 { 287 ProgramLogger.Exception($"Unable to parse manifest file for {DisplayName}", e, MethodBase.GetCurrentMethod().DeclaringType, manifest); 288 } 289 } 290 } 291 292 return false; 293 } 294 295 internal string ResourceFromPri(string packageFullName, string resourceReference) 296 { 297 const string prefix = "ms-resource:"; 298 299 // Using OrdinalIgnoreCase since this is used internally 300 if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) 301 { 302 // magic comes from @talynone 303 // https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153 304 string key = resourceReference.Substring(prefix.Length); 305 string parsed; 306 string parsedFallback = string.Empty; 307 308 // Using Ordinal/OrdinalIgnoreCase since these are used internally 309 if (key.StartsWith("//", StringComparison.Ordinal)) 310 { 311 parsed = prefix + key; 312 } 313 else if (key.StartsWith('/')) 314 { 315 parsed = prefix + "//" + key; 316 } 317 else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase)) 318 { 319 parsed = prefix + key; 320 } 321 else 322 { 323 parsed = prefix + "///resources/" + key; 324 325 // e.g. for Windows Terminal version >= 1.12 DisplayName and Description resources are not in the 'resources' subtree 326 parsedFallback = prefix + "///" + key; 327 } 328 329 var outBuffer = new StringBuilder(128); 330 string source = $"@{{{packageFullName}? {parsed}}}"; 331 var capacity = (uint)outBuffer.Capacity; 332 var hResult = NativeMethods.SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero); 333 if (hResult != HRESULT.S_OK) 334 { 335 if (!string.IsNullOrEmpty(parsedFallback)) 336 { 337 string sourceFallback = $"@{{{packageFullName}? {parsedFallback}}}"; 338 hResult = NativeMethods.SHLoadIndirectString(sourceFallback, outBuffer, capacity, IntPtr.Zero); 339 if (hResult == HRESULT.S_OK) 340 { 341 var loaded = outBuffer.ToString(); 342 if (!string.IsNullOrEmpty(loaded)) 343 { 344 return loaded; 345 } 346 else 347 { 348 ProgramLogger.Exception($"Can't load null or empty result pri {sourceFallback} in uwp location {Package.Location}", new ArgumentNullException(null), GetType(), Package.Location); 349 350 return string.Empty; 351 } 352 } 353 } 354 355 // https://github.com/Wox-launcher/Wox/issues/964 356 // known hresult 2147942522: 357 // 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'. 358 // for 359 // Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description 360 // Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription 361 var e = Marshal.GetExceptionForHR((int)hResult); 362 ProgramLogger.Exception($"Load pri failed {source} with HResult {hResult} and location {Package.Location}", e, GetType(), Package.Location); 363 364 return string.Empty; 365 } 366 else 367 { 368 var loaded = outBuffer.ToString(); 369 if (!string.IsNullOrEmpty(loaded)) 370 { 371 return loaded; 372 } 373 else 374 { 375 ProgramLogger.Exception($"Can't load null or empty result pri {source} in uwp location {Package.Location}", new ArgumentNullException(null), GetType(), Package.Location); 376 377 return string.Empty; 378 } 379 } 380 } 381 else 382 { 383 return resourceReference; 384 } 385 } 386 387 private static readonly Dictionary<PackageVersion, string> _logoKeyFromVersion = new Dictionary<PackageVersion, string> 388 { 389 { PackageVersion.Windows10, "Square44x44Logo" }, 390 { PackageVersion.Windows81, "Square30x30Logo" }, 391 { PackageVersion.Windows8, "SmallLogo" }, 392 }; 393 394 internal string LogoUriFromManifest(IAppxManifestApplication app) 395 { 396 if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key)) 397 { 398 var hr = app.GetStringValue(key, out var logoUriFromApp); 399 _ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp); 400 return logoUriFromApp; 401 } 402 else 403 { 404 return string.Empty; 405 } 406 } 407 408 public void UpdateLogoPath(Theme theme) 409 { 410 LogoPathFromUri(logoUri, theme); 411 } 412 413 // scale factors on win10: https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, 414 private static readonly Dictionary<PackageVersion, List<int>> _scaleFactors = new Dictionary<PackageVersion, List<int>> 415 { 416 { PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } }, 417 { PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } }, 418 { PackageVersion.Windows8, new List<int> { 100 } }, 419 }; 420 421 private bool SetScaleIcons(string path, string colorscheme, bool highContrast = false) 422 { 423 var extension = Path.GetExtension(path); 424 if (extension != null) 425 { 426 var end = path.Length - extension.Length; 427 var prefix = path.Substring(0, end); 428 var paths = new List<string> { }; 429 430 if (!highContrast) 431 { 432 paths.Add(path); 433 } 434 435 if (_scaleFactors.TryGetValue(Package.Version, out List<int> factors)) 436 { 437 foreach (var factor in factors) 438 { 439 if (highContrast) 440 { 441 paths.Add($"{prefix}.scale-{factor}_{colorscheme}{extension}"); 442 paths.Add($"{prefix}.{colorscheme}_scale-{factor}{extension}"); 443 } 444 else 445 { 446 paths.Add($"{prefix}.scale-{factor}{extension}"); 447 } 448 } 449 } 450 451 var selectedIconPath = paths.FirstOrDefault(File.Exists); 452 if (!string.IsNullOrEmpty(selectedIconPath)) 453 { 454 LogoPath = selectedIconPath; 455 if (highContrast) 456 { 457 LogoType = LogoType.HighContrast; 458 } 459 else 460 { 461 LogoType = LogoType.Colored; 462 } 463 464 return true; 465 } 466 } 467 468 return false; 469 } 470 471 private bool SetTargetSizeIcon(string path, string colorscheme, bool highContrast = false) 472 { 473 var extension = Path.GetExtension(path); 474 if (extension != null) 475 { 476 var end = path.Length - extension.Length; 477 var prefix = path.Substring(0, end); 478 var paths = new List<string> { }; 479 const int appIconSize = 36; 480 var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel(); 481 var pathFactorPairs = new Dictionary<string, int>(); 482 483 foreach (var factor in targetSizes) 484 { 485 if (highContrast) 486 { 487 string suffixThemePath = $"{prefix}.targetsize-{factor}_{colorscheme}{extension}"; 488 string prefixThemePath = $"{prefix}.{colorscheme}_targetsize-{factor}{extension}"; 489 paths.Add(suffixThemePath); 490 paths.Add(prefixThemePath); 491 pathFactorPairs.Add(suffixThemePath, factor); 492 pathFactorPairs.Add(prefixThemePath, factor); 493 } 494 else 495 { 496 string simplePath = $"{prefix}.targetsize-{factor}{extension}"; 497 string altformUnPlatedPath = $"{prefix}.targetsize-{factor}_altform-unplated{extension}"; 498 paths.Add(simplePath); 499 paths.Add(altformUnPlatedPath); 500 pathFactorPairs.Add(simplePath, factor); 501 pathFactorPairs.Add(altformUnPlatedPath, factor); 502 } 503 } 504 505 var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists); 506 if (!string.IsNullOrEmpty(selectedIconPath)) 507 { 508 LogoPath = selectedIconPath; 509 if (highContrast) 510 { 511 LogoType = LogoType.HighContrast; 512 } 513 else 514 { 515 LogoType = LogoType.Colored; 516 } 517 518 return true; 519 } 520 } 521 522 return false; 523 } 524 525 private bool SetColoredIcon(string path, string colorscheme) 526 { 527 var isSetColoredScaleIcon = SetScaleIcons(path, colorscheme); 528 if (isSetColoredScaleIcon) 529 { 530 return true; 531 } 532 533 var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorscheme); 534 if (isSetColoredTargetIcon) 535 { 536 return true; 537 } 538 539 var isSetHighContrastScaleIcon = SetScaleIcons(path, colorscheme, true); 540 if (isSetHighContrastScaleIcon) 541 { 542 return true; 543 } 544 545 var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorscheme, true); 546 if (isSetHighContrastTargetIcon) 547 { 548 return true; 549 } 550 551 return false; 552 } 553 554 private bool SetHighContrastIcon(string path, string colorscheme) 555 { 556 var isSetHighContrastScaleIcon = SetScaleIcons(path, colorscheme, true); 557 if (isSetHighContrastScaleIcon) 558 { 559 return true; 560 } 561 562 var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorscheme, true); 563 if (isSetHighContrastTargetIcon) 564 { 565 return true; 566 } 567 568 var isSetColoredScaleIcon = SetScaleIcons(path, colorscheme); 569 if (isSetColoredScaleIcon) 570 { 571 return true; 572 } 573 574 var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorscheme); 575 if (isSetColoredTargetIcon) 576 { 577 return true; 578 } 579 580 return false; 581 } 582 583 internal void LogoPathFromUri(string uri, Theme theme) 584 { 585 // all https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets 586 // windows 10 https://msdn.microsoft.com/library/windows/apps/dn934817.aspx 587 // windows 8.1 https://msdn.microsoft.com/library/windows/apps/hh965372.aspx#target_size 588 // windows 8 https://msdn.microsoft.com/library/windows/apps/br211475.aspx 589 string path; 590 bool isLogoUriSet; 591 592 // Using Ordinal since this is used internally with uri 593 if (uri.Contains('\\', StringComparison.Ordinal)) 594 { 595 path = Path.Combine(Package.Location, uri); 596 } 597 else 598 { 599 // for C:\Windows\MiracastView, etc. 600 path = Path.Combine(Package.Location, "Assets", uri); 601 } 602 603 switch (theme) 604 { 605 case Theme.HighContrastBlack: 606 case Theme.HighContrastOne: 607 case Theme.HighContrastTwo: 608 isLogoUriSet = SetHighContrastIcon(path, ContrastBlack); 609 break; 610 case Theme.HighContrastWhite: 611 isLogoUriSet = SetHighContrastIcon(path, ContrastWhite); 612 break; 613 case Theme.Light: 614 isLogoUriSet = SetColoredIcon(path, ContrastWhite); 615 break; 616 default: 617 isLogoUriSet = SetColoredIcon(path, ContrastBlack); 618 break; 619 } 620 621 if (!isLogoUriSet) 622 { 623 LogoPath = string.Empty; 624 LogoType = LogoType.Error; 625 ProgramLogger.Exception($"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException(), GetType(), Package.Location); 626 } 627 } 628 629 public ImageSource Logo() 630 { 631 if (LogoType == LogoType.Colored) 632 { 633 var logo = ImageFromPath(LogoPath); 634 var platedImage = PlatedImage(logo); 635 return platedImage; 636 } 637 else 638 { 639 return ImageFromPath(LogoPath); 640 } 641 } 642 643 private const int _dpiScale100 = 96; 644 645 private ImageSource PlatedImage(BitmapImage image) 646 { 647 if (!string.IsNullOrEmpty(BackgroundColor)) 648 { 649 string currentBackgroundColor; 650 if (BackgroundColor == "transparent") 651 { 652 // Using InvariantCulture since this is internal 653 currentBackgroundColor = SystemParameters.WindowGlassBrush.ToString(CultureInfo.InvariantCulture); 654 } 655 else 656 { 657 currentBackgroundColor = BackgroundColor; 658 } 659 660 var padding = 8; 661 var width = image.Width + (2 * padding); 662 var height = image.Height + (2 * padding); 663 var x = 0; 664 var y = 0; 665 666 var group = new DrawingGroup(); 667 var converted = ColorConverter.ConvertFromString(currentBackgroundColor); 668 if (converted != null) 669 { 670 var color = (Color)converted; 671 var brush = new SolidColorBrush(color); 672 var pen = new Pen(brush, 1); 673 var backgroundArea = new Rect(0, 0, width, height); 674 var rectangleGeometry = new RectangleGeometry(backgroundArea, 8, 8); 675 var rectDrawing = new GeometryDrawing(brush, pen, rectangleGeometry); 676 group.Children.Add(rectDrawing); 677 678 var imageArea = new Rect(x + padding, y + padding, image.Width, image.Height); 679 var imageDrawing = new ImageDrawing(image, imageArea); 680 group.Children.Add(imageDrawing); 681 682 // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush 683 var visual = new DrawingVisual(); 684 var context = visual.RenderOpen(); 685 context.DrawDrawing(group); 686 context.Close(); 687 688 var bitmap = new RenderTargetBitmap( 689 Convert.ToInt32(width), 690 Convert.ToInt32(height), 691 _dpiScale100, 692 _dpiScale100, 693 PixelFormats.Pbgra32); 694 695 bitmap.Render(visual); 696 697 return bitmap; 698 } 699 else 700 { 701 ProgramLogger.Exception($"Unable to convert background string {BackgroundColor} to color for {Package.Location}", new InvalidOperationException(), GetType(), Package.Location); 702 703 return new BitmapImage(new Uri(Constant.ErrorIcon)); 704 } 705 } 706 else 707 { 708 // todo use windows theme as background 709 return image; 710 } 711 } 712 713 private BitmapImage ImageFromPath(string path) 714 { 715 if (File.Exists(path)) 716 { 717 var memoryStream = new MemoryStream(); 718 using (var fileStream = File.OpenRead(path)) 719 { 720 fileStream.CopyTo(memoryStream); 721 memoryStream.Position = 0; 722 723 var image = new BitmapImage(); 724 image.BeginInit(); 725 image.StreamSource = memoryStream; 726 image.EndInit(); 727 return image; 728 } 729 } 730 else 731 { 732 ProgramLogger.Exception($"Unable to get logo for {UserModelId} from {path} and located in {Package.Location}", new FileNotFoundException(), GetType(), path); 733 return new BitmapImage(new Uri(ImageLoader.ErrorIconPath)); 734 } 735 } 736 737 public override string ToString() 738 { 739 return $"{DisplayName}: {Description}"; 740 } 741 } 742 743 public enum LogoType 744 { 745 Error, 746 Colored, 747 HighContrast, 748 } 749 }