// // Copyright (c) PlaceholderCompany. All rights reserved. // // // Copyright (c) 2022-2023 Peter Kirmeier namespace SystemTrayMenu.UserInterface { using System; using System.Collections.Specialized; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media.Imaging; using System.Windows.Threading; using Microsoft.Win32; using SystemTrayMenu.Utilities; /// /// Logic of About window. /// public partial class AboutBox : Window { private static readonly Regex RegexUrl = new(@"(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?"); private string? entryAssemblyName; private string? callingAssemblyName; private string? executingAssemblyName; private NameValueCollection entryAssemblyAttribCollection = new(); public AboutBox() { InitializeComponent(); Assembly myassembly = Assembly.GetExecutingAssembly(); string myname = myassembly.GetName().Name ?? string.Empty; using (Stream? imgstream = myassembly.GetManifestResourceStream(myname + ".Resources.SystemTrayMenu.png")) { if (imgstream != null) { BitmapImage imageSource = new (); imageSource.BeginInit(); imageSource.StreamSource = imgstream; imageSource.EndInit(); ImagePictureBox.Source = imageSource; Icon = imageSource; } } Loaded += AboutBox_Load; TabPanelDetails.Visibility = Visibility.Collapsed; buttonSystemInfo.Visibility = Visibility.Collapsed; } // // single line of text to show in the application title section of the about box dialog // // // defaults to "%title%" // %title% = Assembly: AssemblyTitle // public string AppTitle { get => (string)AppTitleLabel.Content; set => AppTitleLabel.Content = value; } // // single line of text to show in the description section of the about box dialog // // // defaults to "%description%" // %description% = Assembly: AssemblyDescription // public string AppDescription { get => (string)AppDescriptionLabel.Content; set { if (string.IsNullOrEmpty(value)) { AppDescriptionLabel.Visibility = Visibility.Collapsed; } else { AppDescriptionLabel.Visibility = Visibility.Visible; AppDescriptionLabel.Content = value; } } } // // single line of text to show in the version section of the about dialog // // // defaults to "Version %version%" // %version% = Assembly: AssemblyVersion // public string AppVersion { get => (string)AppVersionLabel.Content; set { if (string.IsNullOrEmpty(value)) { AppVersionLabel.Visibility = Visibility.Collapsed; } else { AppVersionLabel.Visibility = Visibility.Visible; AppVersionLabel.Content = value; } } } // // single line of text to show in the copyright section of the about dialog // // // defaults to "Copyright © %year%, %company%" // %company% = Assembly: AssemblyCompany // %year% = current 4-digit year // public string AppCopyright { get => (string)AppCopyrightLabel.Content; set { if (string.IsNullOrEmpty(value)) { AppCopyrightLabel.Visibility = Visibility.Collapsed; } else { AppCopyrightLabel.Visibility = Visibility.Visible; AppCopyrightLabel.Content = value; } } } // // multiple lines of miscellaneous text to show in rich text box // // // defaults to "%product% is %copyright%, %trademark%" // %product% = Assembly: AssemblyProduct // %copyright% = Assembly: AssemblyCopyright // %trademark% = Assembly: AssemblyTrademark // public string AppMoreInfo { get => new TextRange(MoreRichTextBox.Document.ContentStart, MoreRichTextBox.Document.ContentEnd).Text; set { if (string.IsNullOrEmpty(value)) { MoreRichTextBox.Visibility = Visibility.Collapsed; } else { MoreRichTextBox.Visibility = Visibility.Visible; MoreRichTextBox.Document.Blocks.Clear(); Paragraph para = new (); // Parse string to detect hyperlinks and add handlers to them // See: https://mycsharp.de/forum/threads/97560/erledigt-dynamische-hyperlinks-in-wpf-flowdocument?page=1 int lastPos = 0; foreach (Match match in RegexUrl.Matches(value).Cast()) { if (match.Index != lastPos) { para.Inlines.Add(value[lastPos..match.Index]); } var link = new Hyperlink(new Run(match.Value)) { NavigateUri = new Uri(match.Value), }; link.Click += MoreRichTextBox_LinkClicked; para.Inlines.Add(link); lastPos = match.Index + match.Length; } if (lastPos < value.Length) { para.Inlines.Add(value[lastPos..]); } MoreRichTextBox.Document.Blocks.Add(para); } } } // // returns the entry assembly for the current application domain // // // This is usually read-only, but in some weird cases (Smart Client apps) // you won't have an entry assembly, so you may want to set this manually. // private Assembly? AppEntryAssembly { get; set; } // // exception-safe retrieval of LastWriteTime for this assembly. // // File.GetLastWriteTime, or DateTime.MaxValue if exception was encountered. private static DateTime AssemblyLastWriteTime(Assembly a) { DateTime assemblyLastWriteTime = DateTime.MaxValue; // Location property not available for dynamic assemblies if (!a.IsDynamic) { if (!string.IsNullOrEmpty(a.Location)) { assemblyLastWriteTime = File.GetLastWriteTime(a.Location); } } return assemblyLastWriteTime; } // // returns DateTime this Assembly was last built. Will attempt to calculate from build number, if possible. // If not, the actual LastWriteTime on the assembly file will be returned. // // Assembly to get build date for // Don't attempt to use the build number to calculate the date // DateTime this assembly was last built private static DateTime AssemblyBuildDate(Assembly a, bool forceFileDate) { Version? assemblyVersion = a.GetName().Version; DateTime dt; if (forceFileDate || assemblyVersion == null) { dt = AssemblyLastWriteTime(a); } else { dt = DateTime.Parse("01/01/2000", CultureInfo.InvariantCulture).AddDays(assemblyVersion.Build).AddSeconds(assemblyVersion.Revision * 2); #pragma warning disable CS0618 if (TimeZone.IsDaylightSavingTime(dt, TimeZone.CurrentTimeZone.GetDaylightChanges(dt.Year))) #pragma warning restore CS0618 { dt = dt.AddHours(1); } if (dt > DateTime.Now || assemblyVersion.Build < 730 || assemblyVersion.Revision == 0) { dt = AssemblyLastWriteTime(a); } } return dt; } // // returns string name / string value pair of all attribs // for specified assembly // // // note that Assembly* values are pulled from AssemblyInfo file in project folder // // Trademark = AssemblyTrademark string // Debuggable = true // GUID = 7FDF68D5-8C6F-44C9-B391-117B5AFB5467 // CLSCompliant = true // Product = AssemblyProduct string // Copyright = AssemblyCopyright string // Company = AssemblyCompany string // Description = AssemblyDescription string // Title = AssemblyTitle string // private static NameValueCollection AssemblyAttribs(Assembly a) { string typeName; string name; string value; NameValueCollection nvc = new(); Regex r = new(@"(\.Assembly|\.)(?[^.]*)Attribute$", RegexOptions.IgnoreCase); foreach (object attrib in a.GetCustomAttributes(false)) { typeName = attrib.GetType().ToString(); name = r.Match(typeName).Groups["ColumnText"].ToString(); value = string.Empty; switch (typeName) { case "System.CLSCompliantAttribute": value = ((CLSCompliantAttribute)attrib).IsCompliant.ToString(CultureInfo.InvariantCulture); break; case "System.Diagnostics.DebuggableAttribute": value = ((System.Diagnostics.DebuggableAttribute)attrib).IsJITTrackingEnabled.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyCompanyAttribute": value = ((AssemblyCompanyAttribute)attrib).Company.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyConfigurationAttribute": value = ((AssemblyConfigurationAttribute)attrib).Configuration.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyCopyrightAttribute": value = ((AssemblyCopyrightAttribute)attrib).Copyright.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyDefaultAliasAttribute": value = ((AssemblyDefaultAliasAttribute)attrib).DefaultAlias.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyDelaySignAttribute": value = ((AssemblyDelaySignAttribute)attrib).DelaySign.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyDescriptionAttribute": value = ((AssemblyDescriptionAttribute)attrib).Description.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyInformationalVersionAttribute": value = ((AssemblyInformationalVersionAttribute)attrib).InformationalVersion.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyKeyFileAttribute": value = ((AssemblyKeyFileAttribute)attrib).KeyFile.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyProductAttribute": value = ((AssemblyProductAttribute)attrib).Product.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyTrademarkAttribute": value = ((AssemblyTrademarkAttribute)attrib).Trademark.ToString(CultureInfo.InvariantCulture); break; case "System.Reflection.AssemblyTitleAttribute": value = ((AssemblyTitleAttribute)attrib).Title.ToString(CultureInfo.InvariantCulture); break; case "System.Resources.NeutralResourcesLanguageAttribute": value = ((System.Resources.NeutralResourcesLanguageAttribute)attrib).CultureName.ToString(CultureInfo.InvariantCulture); break; case "System.Resources.SatelliteContractVersionAttribute": value = ((System.Resources.SatelliteContractVersionAttribute)attrib).Version.ToString(CultureInfo.InvariantCulture); break; case "System.Runtime.InteropServices.ComCompatibleVersionAttribute": { System.Runtime.InteropServices.ComCompatibleVersionAttribute x; x = (System.Runtime.InteropServices.ComCompatibleVersionAttribute)attrib; value = x.MajorVersion + "." + x.MinorVersion + "." + x.RevisionNumber + "." + x.BuildNumber; break; } case "System.Runtime.InteropServices.ComVisibleAttribute": value = ((System.Runtime.InteropServices.ComVisibleAttribute)attrib).Value.ToString(CultureInfo.InvariantCulture); break; case "System.Runtime.InteropServices.GuidAttribute": value = ((System.Runtime.InteropServices.GuidAttribute)attrib).Value.ToString(CultureInfo.InvariantCulture); break; case "System.Runtime.InteropServices.TypeLibVersionAttribute": { System.Runtime.InteropServices.TypeLibVersionAttribute x; x = (System.Runtime.InteropServices.TypeLibVersionAttribute)attrib; value = x.MajorVersion + "." + x.MinorVersion; break; } case "System.Security.AllowPartiallyTrustedCallersAttribute": value = "(Present)"; break; default: // debug.writeline("** unknown assembly attribute '" + TypeName + "'") value = typeName; break; } if (nvc[name] == null) { nvc.Add(name, value); } } // add some extra values that are not in the AssemblyInfo, but nice to have // codebase try { if (!a.IsDynamic) { nvc.Add("CodeBase", a.Location.Replace("file:///", string.Empty, StringComparison.InvariantCulture)); } } catch (NotSupportedException) { nvc.Add("CodeBase", "(not supported)"); } // build date DateTime dt = AssemblyBuildDate(a, false); if (dt == DateTime.MaxValue) { nvc.Add("BuildDate", "(unknown)"); } else { nvc.Add("BuildDate", dt.ToString("yyyy-MM-dd hh:mm tt", CultureInfo.InvariantCulture)); } // location try { if (!a.IsDynamic) { nvc.Add("Location", a.Location); } } catch (NotSupportedException) { nvc.Add("Location", "(not supported)"); } string version = "(unknown)"; AssemblyName assemblyName = a.GetName(); if (assemblyName.Version != null && (assemblyName.Version.Major != 0 || assemblyName.Version.Minor != 0)) { if (!a.IsDynamic) { version = a.GetName().Version?.ToString() ?? version; } } nvc.Add("Version", version); if (!a.IsDynamic) { nvc.Add("FullName", a.FullName); } return nvc; } // // reads an HKLM Windows Registry key value // private static string RegistryHklmValue(string keyName, string subKeyRef) { string strSysInfoPath = string.Empty; try { RegistryKey? rk = Registry.LocalMachine.OpenSubKey(keyName); if (rk != null) { strSysInfoPath = (string)rk.GetValue(subKeyRef, string.Empty)!; } } catch (Exception ex) { Log.Warn($"KeyName:'{keyName}' SubKeyRef:'{subKeyRef}'", ex); } return strSysInfoPath; } // // populate a listview with the specified key and value strings // private static void Populate(ListView lvw, string key, string? value) { if (!string.IsNullOrEmpty(value)) { lvw.Items.Add(new AssemblyDetailsListViewItem { Key = key, Value = value, }); } } // // populate details for a single assembly // private static void PopulateAssemblyDetails(Assembly? a, ListView lvw) { lvw.Items.Clear(); if (a != null) { Populate(lvw, "Image Runtime Version", a.ImageRuntimeVersion); NameValueCollection nvc = AssemblyAttribs(a); foreach (string strKey in nvc) { Populate(lvw, strKey, nvc[strKey]); } } } // // matches assembly by Assembly.GetName.ColumnText; returns nothing if no match // private static Assembly? MatchAssemblyByName(string assemblyName) { foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { if (a.GetName().Name == assemblyName) { return a; } } return null; } private void TabPanelDetails_SelectedIndexChanged(object sender, SelectionChangedEventArgs e) { if (TabPanelDetails.SelectedItem == TabPageAssemblyDetails) { AssemblyNamesComboBox.Focus(); } } // // launch the MSInfo "system information" application (works on XP, 2003, and Vista) // private void ShowSysInfo() { string strSysInfoPath = RegistryHklmValue(@"SOFTWARE\Microsoft\Shared Tools Location", "MSINFO"); if (string.IsNullOrEmpty(strSysInfoPath)) { strSysInfoPath = RegistryHklmValue(@"SOFTWARE\Microsoft\Shared Tools\MSINFO", "PATH"); } if (string.IsNullOrEmpty(strSysInfoPath)) { MessageBox.Show( "System Information is unavailable at this time." + Environment.NewLine + Environment.NewLine + "(couldn't find path for Microsoft System Information Tool in the registry.)", Title, MessageBoxButton.OK, MessageBoxImage.Warning); return; } Log.ProcessStart(strSysInfoPath); } // // populates the Application Information listview // private void PopulateAppInfo() { AppDomain d = AppDomain.CurrentDomain; Populate(AppInfoListView, "Application ColumnText", Assembly.GetEntryAssembly()?.GetName().Name); Populate(AppInfoListView, "Application Base", d.SetupInformation.ApplicationBase); Populate(AppInfoListView, "Friendly ColumnText", d.FriendlyName); Populate(AppInfoListView, " ", " "); Populate(AppInfoListView, "Entry Assembly", entryAssemblyName); Populate(AppInfoListView, "Executing Assembly", executingAssemblyName); Populate(AppInfoListView, "Calling Assembly", callingAssemblyName); } // // populate Assembly Information listview with ALL assemblies // private void PopulateAssemblies() { foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) { PopulateAssemblySummary(a); } AssemblyNamesComboBox.SelectedIndex = AssemblyNamesComboBox.Items.IndexOf(entryAssemblyName); } // // populate Assembly Information listview with summary view for a specific assembly // private void PopulateAssemblySummary(Assembly a) { NameValueCollection nvc = AssemblyAttribs(a); string strAssemblyName = a.GetName().Name ?? "?"; string strAssemblyNameFull = strAssemblyName; if (strAssemblyName == callingAssemblyName) { strAssemblyNameFull += " (calling)"; } else if (strAssemblyName == executingAssemblyName) { strAssemblyNameFull += " (executing)"; } else if (strAssemblyName == entryAssemblyName) { strAssemblyNameFull += " (entry)"; } AssemblyInfoListView.Items.Add(new AssemblyInfoListViewItem { Name = strAssemblyNameFull, Version = nvc["version"] ?? string.Empty, Built = nvc["builddate"] ?? string.Empty, CodeBase = nvc["codebase"] ?? string.Empty, Tag = strAssemblyName, }); AssemblyNamesComboBox.Items.Add(strAssemblyName); } // // retrieves a cached value from the entry assembly attribute lookup collection // private string? EntryAssemblyAttrib(string strName) { if (entryAssemblyAttribCollection[strName] == null) { return ""; } else { return entryAssemblyAttribCollection[strName]?.ToString(CultureInfo.InvariantCulture); } } // // Populate all the form labels with tokenized text // private void PopulateLabels() { // get entry assembly attribs entryAssemblyAttribCollection = AssemblyAttribs(AppEntryAssembly!); // replace all labels and window title Title = ReplaceTokens(Title); AppTitle = ReplaceTokens(AppTitle); if (AppDescriptionLabel.Visibility == Visibility.Visible) { AppDescription = ReplaceTokens(AppDescription); } if (AppCopyrightLabel.Visibility == Visibility.Visible) { AppCopyright = ReplaceTokens(AppCopyright); } if (AppVersionLabel.Visibility == Visibility.Visible) { AppVersion = ReplaceTokens(AppVersion); } if (AppDateLabel.Visibility == Visibility.Visible) { AppDateLabel.Content = ReplaceTokens((string)AppDateLabel.Content); } if (MoreRichTextBox.Visibility == Visibility.Visible) { AppMoreInfo = ReplaceTokens(AppMoreInfo); } } // // perform assemblyinfo to string replacements on labels // private string ReplaceTokens(string s) { return s.Replace("%title%", EntryAssemblyAttrib("title"), StringComparison.InvariantCulture) .Replace("%copyright%", EntryAssemblyAttrib("copyright"), StringComparison.InvariantCulture) .Replace("%description%", EntryAssemblyAttrib("description"), StringComparison.InvariantCulture) .Replace("%company%", EntryAssemblyAttrib("company"), StringComparison.InvariantCulture) .Replace("%product%", EntryAssemblyAttrib("product"), StringComparison.InvariantCulture) .Replace("%trademark%", EntryAssemblyAttrib("trademark"), StringComparison.InvariantCulture) .Replace("%year%", DateTime.Now.Year.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture) .Replace("%version%", EntryAssemblyAttrib("version"), StringComparison.InvariantCulture) .Replace("%builddate%", EntryAssemblyAttrib("builddate"), StringComparison.InvariantCulture); } // // things to do when form is loaded // private void AboutBox_Load(object sender, RoutedEventArgs e) { // if the user didn't provide an assembly, try to guess which one is the entry assembly AppEntryAssembly ??= Assembly.GetEntryAssembly()!; AppEntryAssembly ??= Assembly.GetExecutingAssembly(); executingAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; callingAssemblyName = Assembly.GetCallingAssembly().GetName().Name; // for web hosted apps, GetEntryAssembly = nothing entryAssemblyName = Assembly.GetEntryAssembly()?.GetName().Name; TabPanelDetails.Visibility = Visibility.Collapsed; if (MoreRichTextBox.Visibility != Visibility.Visible) { Height -= MoreRichTextBox.Height; } Dispatcher.Invoke( DispatcherPriority.Loaded, new Action(delegate { Cursor = Cursors.Wait; PopulateLabels(); Cursor = null; })); } // // expand about dialog to show additional advanced details // private void DetailsButton_Click(object sender, RoutedEventArgs e) { Cursor = Cursors.Wait; MoreRichTextBox.Visibility = Visibility.Collapsed; TabPanelDetails.Visibility = Visibility.Visible; buttonSystemInfo.Visibility = Visibility.Visible; buttonDetails.Visibility = Visibility.Collapsed; UpdateLayout(); // Force AutoSize to update the height before switching to manual mode SizeToContent = SizeToContent.Manual; ResizeMode = ResizeMode.CanResizeWithGrip; TabPanelDetails.Height = double.NaN; if (Width < 580) { Width = 580; } PopulateAssemblies(); PopulateAppInfo(); Cursor = null; } // // for detailed system info, launch the external Microsoft system info app // private void SysInfoButton_Click(object sender, RoutedEventArgs e) { ShowSysInfo(); } /// /// Closes the window. /// private void OkButton_Click(object sender, RoutedEventArgs e) { Close(); } // // if an assembly is double-clicked, go to the detail page for that assembly // private void AssemblyInfoListView_DoubleClick(object sender, MouseButtonEventArgs e) { if (AssemblyInfoListView.SelectedItems.Count > 0) { string? strAssemblyName = Convert.ToString(((AssemblyInfoListViewItem?)AssemblyInfoListView.SelectedItems[0])?.Tag, CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(strAssemblyName)) { AssemblyNamesComboBox.SelectedIndex = AssemblyNamesComboBox.Items.IndexOf(strAssemblyName); TabPanelDetails.SelectedItem = TabPageAssemblyDetails; } } } // // if a new assembly is selected from the combo box, show details for that assembly // private void AssemblyNamesComboBox_SelectedIndexChanged(object sender, SelectionChangedEventArgs e) { string? strAssemblyName = Convert.ToString(AssemblyNamesComboBox.SelectedItem, CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(strAssemblyName)) { PopulateAssemblyDetails(MatchAssemblyByName(strAssemblyName), AssemblyDetailsListView); } } // // sort the assembly list by column // private void AssemblyInfoListView_ColumnClick(object sender, RoutedEventArgs e) { AssemblyInfoListView.Items.SortDescriptions.Clear(); AssemblyInfoListView.Items.SortDescriptions.Add(new SortDescription( ((GridViewColumnHeader)e.OriginalSource).Column.Header.ToString(), ListSortDirection.Ascending)); AssemblyInfoListView.Items.Refresh(); } // // launch any http:// or mailto: links clicked in the body of the rich text box // private void MoreRichTextBox_LinkClicked(object sender, RoutedEventArgs e) { Log.ProcessStart(((Hyperlink)sender).NavigateUri.ToString()); } /// /// Type for ListView items. /// private class AssemblyDetailsListViewItem { public string Key { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; } /// /// Type for ListView items. /// private class AssemblyInfoListViewItem { public string Name { get; set; } = string.Empty; public string Version { get; set; } = string.Empty; public string Built { get; set; } = string.Empty; public string CodeBase { get; set; } = string.Empty; public string Tag { get; set; } = string.Empty; } } }