diff --git a/WindowsTools/Extensions/DataType/Enums/MenuType.cs b/WindowsTools/Extensions/DataType/Enums/MenuType.cs index c953c95..36b80ee 100644 --- a/WindowsTools/Extensions/DataType/Enums/MenuType.cs +++ b/WindowsTools/Extensions/DataType/Enums/MenuType.cs @@ -5,19 +5,14 @@ /// public enum MenuType { - /// - /// 根菜单 - /// - RootMenu = 0, - /// /// 一级菜单 /// - FirstLevelMenu = 1, + FirstLevelMenu = 0, /// /// 二级菜单 /// - SecondLevelMenu = 2 + SecondLevelMenu = 1 } } diff --git a/WindowsTools/Extensions/DataType/Enums/OperationKind.cs b/WindowsTools/Extensions/DataType/Enums/OperationKind.cs index b2ce983..5ee9496 100644 --- a/WindowsTools/Extensions/DataType/Enums/OperationKind.cs +++ b/WindowsTools/Extensions/DataType/Enums/OperationKind.cs @@ -23,6 +23,7 @@ public enum OperationKind MenuLightThemeIconPathEmpty = 18, MenuDarkThemeIconPathEmpty = 19, MenuProgramPathEmpty = 20, - MenuMatchRuleEmpty = 21 + MenuMatchRuleEmpty = 21, + ShellMenuNeedToRefreshData = 22 } } diff --git a/WindowsTools/Extensions/DataType/Methods/BinaryReaderExtension.cs b/WindowsTools/Extensions/DataType/Methods/BinaryReaderExtension.cs index 75bae73..dc7fe23 100644 --- a/WindowsTools/Extensions/DataType/Methods/BinaryReaderExtension.cs +++ b/WindowsTools/Extensions/DataType/Methods/BinaryReaderExtension.cs @@ -43,7 +43,7 @@ public static string ReadNullTerminatedString(this BinaryReader reader, Encoding using BinaryReader binaryReader = new(reader.BaseStream, encoding, true); StringBuilder result = new(); char c; - while ((c = binaryReader.ReadChar()) != '\0') + while ((c = binaryReader.ReadChar()) is not '\0') { result.Append(c); } diff --git a/WindowsTools/Extensions/PriExtract/HierarchicalSchemaSection.cs b/WindowsTools/Extensions/PriExtract/HierarchicalSchemaSection.cs index fc53a48..523e69b 100644 --- a/WindowsTools/Extensions/PriExtract/HierarchicalSchemaSection.cs +++ b/WindowsTools/Extensions/PriExtract/HierarchicalSchemaSection.cs @@ -120,8 +120,8 @@ public HierarchicalSchemaSection(string sectionIdentifier, BinaryReader binaryRe byte flags = binaryReader.ReadByte(); uint nameOffset = binaryReader.ReadUInt16() | (uint)((flags & 0xF) << 16); ushort index = binaryReader.ReadUInt16(); - bool isScope = (flags & 0x10) != 0; - bool nameInAscii = (flags & 0x20) != 0; + bool isScope = (flags & 0x10) is not 0; + bool nameInAscii = (flags & 0x20) is not 0; scopeAndItemInfosList.Add(new ScopeAndItemInfo() { Parent = parent, @@ -170,7 +170,7 @@ public HierarchicalSchemaSection(string sectionIdentifier, BinaryReader binaryRe binaryReader.BaseStream.Seek(pos, SeekOrigin.Begin); - string name = scopeAndItemInfosList[i].FullPathLength != 0 + string name = scopeAndItemInfosList[i].FullPathLength is not 0 ? binaryReader.ReadNullTerminatedString(scopeAndItemInfosList[i].NameInAscii ? Encoding.ASCII : Encoding.Unicode) : string.Empty; @@ -212,7 +212,7 @@ public HierarchicalSchemaSection(string sectionIdentifier, BinaryReader binaryRe ushort parent = scopeAndItemInfosList[scopeAndItemInfosList[i].Parent].Index; - if (parent != 0xFFFF) + if (parent is not 0xFFFF) { if (scopeAndItemInfosList[i].IsScope) { diff --git a/WindowsTools/Extensions/PriExtract/ReferencedFileSection.cs b/WindowsTools/Extensions/PriExtract/ReferencedFileSection.cs index bd05090..47e9971 100644 --- a/WindowsTools/Extensions/PriExtract/ReferencedFileSection.cs +++ b/WindowsTools/Extensions/PriExtract/ReferencedFileSection.cs @@ -110,7 +110,7 @@ public ReferencedFileSection(string sectionIdentifier, BinaryReader binaryReader for (int i = 0; i < numFolders; i++) { - if (folderInfosList[i].ParentFolder != 0xFFFF) + if (folderInfosList[i].ParentFolder is not 0xFFFF) { referencedFolders[i].Parent = referencedFolders[folderInfosList[i].ParentFolder]; } @@ -126,7 +126,7 @@ public ReferencedFileSection(string sectionIdentifier, BinaryReader binaryReader ReferencedFileOrFolder parentFolder; - if (fileInfos[i].ParentFolder != 0xFFFF) + if (fileInfos[i].ParentFolder is not 0xFFFF) parentFolder = referencedFolders[fileInfos[i].ParentFolder]; else parentFolder = null; diff --git a/WindowsTools/Extensions/PriExtract/ResourceMapSection.cs b/WindowsTools/Extensions/PriExtract/ResourceMapSection.cs index 28df930..761c9e5 100644 --- a/WindowsTools/Extensions/PriExtract/ResourceMapSection.cs +++ b/WindowsTools/Extensions/PriExtract/ResourceMapSection.cs @@ -86,7 +86,7 @@ public ResourceMapSection(string sectionIdentifier, BinaryReader binaryReader, b byte[] environmentReferencesDataArray = binaryReader.ReadBytes(environmentReferencesLength); byte[] schemaReferenceDataArray = binaryReader.ReadBytes(hierarchicalSchemaReferenceLength); - if (schemaReferenceDataArray.Length != 0) + if (schemaReferenceDataArray.Length is not 0) using (BinaryReader r = new(new MemoryStream(schemaReferenceDataArray, false))) { ushort majorVersion = r.ReadUInt16(); @@ -222,7 +222,7 @@ public ResourceMapSection(string sectionIdentifier, BinaryReader binaryReader, b { byte type = binaryReader.ReadByte(); - if (type == 0x01) + if (type is 0x01) { ResourceValueType resourceValueType = resourceValueTypeTableList[binaryReader.ReadByte()]; ushort sourceFileIndex = binaryReader.ReadUInt16(); @@ -239,7 +239,7 @@ public ResourceMapSection(string sectionIdentifier, BinaryReader binaryReader, b DataOffset = 0 }); } - else if (type == 0x00) + else if (type is 0x00) { ResourceValueType resourceValueType = resourceValueTypeTableList[binaryReader.ReadByte()]; ushort length = binaryReader.ReadUInt16(); @@ -300,7 +300,7 @@ public ResourceMapSection(string sectionIdentifier, BinaryReader binaryReader, b if (candidateInfo.Type is 0x01) { - int? sourceFile = candidateInfo.SourceFileIndex == 0 ? null : candidateInfo.SourceFileIndex - 1; + int? sourceFile = candidateInfo.SourceFileIndex is 0 ? null : candidateInfo.SourceFileIndex - 1; candidatesList.Add(new Candidate() { diff --git a/WindowsTools/Extensions/PriExtract/ReverseMapSection.cs b/WindowsTools/Extensions/PriExtract/ReverseMapSection.cs index 5a15806..09a3399 100644 --- a/WindowsTools/Extensions/PriExtract/ReverseMapSection.cs +++ b/WindowsTools/Extensions/PriExtract/ReverseMapSection.cs @@ -105,19 +105,19 @@ public ReverseMapSection(string sectionIdentifier, BinaryReader binaryReader) for (int i = 0; i < numScopes + numItems; i++) { - bool nameInAscii = (scopeAndItemInfoList[i].Item3 & 0x20000000) != 0; + bool nameInAscii = (scopeAndItemInfoList[i].Item3 & 0x20000000) is not 0; long pos = (nameInAscii ? asciiDataOffset : unicodeDataOffset) + (scopeAndItemInfoList[i].Item4 * (nameInAscii ? 1 : 2)); binaryReader.BaseStream.Seek(pos, SeekOrigin.Begin); string name = string.Empty; - if (scopeAndItemInfoList[i].Item2 != 0) + if (scopeAndItemInfoList[i].Item2 is not 0) { name = binaryReader.ReadNullTerminatedString(nameInAscii ? Encoding.ASCII : Encoding.Unicode); } ushort index = scopeAndItemInfoList[i].Item5; - bool isScope = (scopeAndItemInfoList[i].Item3 & 0x10000000) != 0; + bool isScope = (scopeAndItemInfoList[i].Item3 & 0x10000000) is not 0; if (isScope) { @@ -152,11 +152,11 @@ public ReverseMapSection(string sectionIdentifier, BinaryReader binaryReader) for (int i = 0; i < numScopes + numItems; i++) { ushort index = scopeAndItemInfoList[i].Item5; - bool isScope = (scopeAndItemInfoList[i].Item3 & 0x10000000) != 0; + bool isScope = (scopeAndItemInfoList[i].Item3 & 0x10000000) is not 0; ushort parent = scopeAndItemInfoList[i].Item1; parent = scopeAndItemInfoList[parent].Item5; - if (parent != 0xFFFF) + if (parent is not 0xFFFF) { if (isScope) { @@ -180,7 +180,7 @@ public ReverseMapSection(string sectionIdentifier, BinaryReader binaryReader) { Tuple saiInfo = scopeAndItemInfoList[scopeExInfo[i].Item3 + j]; - bool isScope = (saiInfo.Item3 & 0x10000000) != 0; + bool isScope = (saiInfo.Item3 & 0x10000000) is not 0; childrenArray[j] = isScope ? scopesArray[saiInfo.Item5] : itemsArray[saiInfo.Item5]; } diff --git a/WindowsTools/Extensions/ShellMenu/ShellMenuItem.cs b/WindowsTools/Extensions/ShellMenu/ShellMenuItem.cs index f845484..26f7788 100644 --- a/WindowsTools/Extensions/ShellMenu/ShellMenuItem.cs +++ b/WindowsTools/Extensions/ShellMenu/ShellMenuItem.cs @@ -63,6 +63,11 @@ public sealed class ShellMenuItem /// public string MenuParameter { get; set; } + /// + /// 是否总是需要提权运行 + /// + public bool IsAlwaysRunAsAdministrator { get; set; } + /// /// 是否启用文件夹背景菜单项 /// diff --git a/WindowsTools/Helpers/Controls/TeachingTipHelper.cs b/WindowsTools/Helpers/Controls/TeachingTipHelper.cs index 87cef8d..e8a0adc 100644 --- a/WindowsTools/Helpers/Controls/TeachingTipHelper.cs +++ b/WindowsTools/Helpers/Controls/TeachingTipHelper.cs @@ -29,7 +29,7 @@ public static async Task ShowAsync(TeachingTip teachingTip, int duration = 2000) { foreach (UIElement item in ((MainWindow.Current.Content as MainPage).Content as Grid).Children) { - if ((item as FrameworkElement).Name == teachingTip.Name) + if ((item as FrameworkElement).Name.Equals(teachingTip.Name)) { ((MainWindow.Current.Content as MainPage).Content as Grid).Children.Remove(item); break; diff --git a/WindowsTools/Helpers/Root/FeatureAccessHelper.cs b/WindowsTools/Helpers/Root/FeatureAccessHelper.cs index bdd52b8..3ca700d 100644 --- a/WindowsTools/Helpers/Root/FeatureAccessHelper.cs +++ b/WindowsTools/Helpers/Root/FeatureAccessHelper.cs @@ -41,7 +41,7 @@ static FeatureAccessHelper() { int length = 0; - if (Kernel32Library.GetCurrentPackageFamilyName(ref length, null) != Kernel32Library.APPMODEL_ERROR_NO_PACKAGE) + if (Kernel32Library.GetCurrentPackageFamilyName(ref length, null) is not (int)Kernel32Library.APPMODEL_ERROR_NO_PACKAGE) { StringBuilder packageFamilyNameBuilder = new(length + 1); Kernel32Library.GetCurrentPackageFamilyName(ref length, packageFamilyNameBuilder); diff --git a/WindowsTools/Helpers/Root/RegistryHelper.cs b/WindowsTools/Helpers/Root/RegistryHelper.cs index c7b4df1..7a984c1 100644 --- a/WindowsTools/Helpers/Root/RegistryHelper.cs +++ b/WindowsTools/Helpers/Root/RegistryHelper.cs @@ -2,8 +2,10 @@ using System; using System.Diagnostics.Tracing; using System.IO; +using System.Threading; using WindowsTools.Extensions.Registry; using WindowsTools.Services.Root; +using WindowsTools.WindowsAPI.PInvoke.Advapi32; namespace WindowsTools.Helpers.Root { @@ -187,5 +189,34 @@ public static RegistryEnumKeyItem EnumSubKey(string rootKey) return registryEnumKeyItem; } + + /// + /// 添加注册表监控 + /// + public static void MonitorRegistryValueChange(string rootKey) + { + ManualResetEvent manualResetEvent = null; + RegisteredWaitHandle registeredWaitHandle = null; + + try + { + if (Registry.CurrentUser.OpenSubKey(rootKey, false) is RegistryKey registryKey) + { + manualResetEvent = new(false); + int ret = Advapi32Library.RegNotifyChangeKeyValue(registryKey.Handle.DangerousGetHandle(), true, REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_FILTER.REG_NOTIFY_THREAD_AGNOSTIC, manualResetEvent.SafeWaitHandle.DangerousGetHandle(), true); + registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(manualResetEvent, (state, timeout) => + { + registeredWaitHandle?.Unregister(manualResetEvent); + manualResetEvent.Close(); + registryKey.Close(); + NotifyKeyValueChanged?.Invoke(null, EventArgs.Empty); + }, null, Timeout.Infinite, true); + } + } + catch (Exception e) + { + LogService.WriteLog(EventLevel.Error, string.Format("Monitor Registry rootKey change {0} failed", rootKey), e); + } + } } } diff --git a/WindowsTools/Helpers/Root/RuntimeHelper.cs b/WindowsTools/Helpers/Root/RuntimeHelper.cs index fc9b429..8ab490e 100644 --- a/WindowsTools/Helpers/Root/RuntimeHelper.cs +++ b/WindowsTools/Helpers/Root/RuntimeHelper.cs @@ -24,7 +24,7 @@ static RuntimeHelper() private static void IsInMsixContainer() { int length = 0; - IsMSIX = Kernel32Library.GetCurrentPackageFamilyName(ref length, null) != Kernel32Library.APPMODEL_ERROR_NO_PACKAGE; + IsMSIX = Kernel32Library.GetCurrentPackageFamilyName(ref length, null) is not (int)Kernel32Library.APPMODEL_ERROR_NO_PACKAGE; } /// diff --git a/WindowsTools/Models/ShellMenuItemModel.cs b/WindowsTools/Models/ShellMenuItemModel.cs index 330441c..320aa3e 100644 --- a/WindowsTools/Models/ShellMenuItemModel.cs +++ b/WindowsTools/Models/ShellMenuItemModel.cs @@ -184,6 +184,11 @@ public int MenuIndex /// public string MenuParameter { get; set; } + /// + /// 是否总是需要提权运行 + /// + public bool IsAlwaysRunAsAdministrator { get; set; } + /// /// 是否启用文件夹背景菜单项 /// diff --git a/WindowsTools/Properties/AssemblyInfo.cs b/WindowsTools/Properties/AssemblyInfo.cs index ab53428..00cfae3 100644 --- a/WindowsTools/Properties/AssemblyInfo.cs +++ b/WindowsTools/Properties/AssemblyInfo.cs @@ -4,8 +4,8 @@ [assembly: AssemblyCompany("高怡飞")] [assembly: AssemblyCopyright("Copyright ©2024 高怡飞, All Rights Reserved.")] [assembly: AssemblyDescription("Windows 工具箱")] -[assembly: AssemblyFileVersion("2.8.825.0")] -[assembly: AssemblyInformationalVersion("2.8.825.0")] +[assembly: AssemblyFileVersion("2.9.904.0")] +[assembly: AssemblyInformationalVersion("2.9.904.0")] [assembly: AssemblyProduct("Windows 工具箱")] [assembly: AssemblyTitle("Windows 工具箱")] -[assembly: AssemblyVersion("2.8.825.0")] +[assembly: AssemblyVersion("2.9.904.0")] diff --git a/WindowsTools/Services/Shell/ShellMenuService.cs b/WindowsTools/Services/Shell/ShellMenuService.cs index 4e542d9..6e3999e 100644 --- a/WindowsTools/Services/Shell/ShellMenuService.cs +++ b/WindowsTools/Services/Shell/ShellMenuService.cs @@ -54,9 +54,7 @@ public static ShellMenuItem GetShellMenuItem() // 获取根菜单项下的所有子项(包括递归后的项) RegistryEnumKeyItem shellMenuRegistryKeyItem = RegistryHelper.EnumSubKey(shellMenuKey); - return shellMenuRegistryKeyItem.SubRegistryKeyList.Count is 1 - ? EnumShellMenuItem(shellMenuRegistryKeyItem.SubRegistryKeyList[0]) - : null; + return shellMenuRegistryKeyItem.SubRegistryKeyList.Count is 1 ? EnumShellMenuItem(shellMenuRegistryKeyItem.SubRegistryKeyList[0]) : null; } /// @@ -74,6 +72,7 @@ public static void SaveShellMenuItem(string menuKey, ShellMenuItem shellMenuItem RegistryHelper.SaveRegistryKey(menuKey, "DarkThemeIconPath", shellMenuItem.DarkThemeIconPath); RegistryHelper.SaveRegistryKey(menuKey, "MenuProgramPath", shellMenuItem.MenuProgramPath); RegistryHelper.SaveRegistryKey(menuKey, "MenuParameter", shellMenuItem.MenuParameter); + RegistryHelper.SaveRegistryKey(menuKey, "IsAlwaysRunAsAdministrator", shellMenuItem.IsAlwaysRunAsAdministrator); RegistryHelper.SaveRegistryKey(menuKey, "FolderBackground", shellMenuItem.FolderBackground); RegistryHelper.SaveRegistryKey(menuKey, "FolderDesktop", shellMenuItem.FolderDesktop); RegistryHelper.SaveRegistryKey(menuKey, "FolderDirectory", shellMenuItem.FolderDirectory); @@ -118,6 +117,7 @@ private static ShellMenuItem EnumShellMenuItem(RegistryEnumKeyItem registryEnumK DarkThemeIconPath = currentMenuItem.DarkThemeIconPath, MenuProgramPath = currentMenuItem.MenuProgramPath, MenuParameter = currentMenuItem.MenuParameter, + IsAlwaysRunAsAdministrator = currentMenuItem.IsAlwaysRunAsAdministrator, FolderBackground = currentMenuItem.FolderBackground, FolderDesktop = currentMenuItem.FolderDesktop, FolderDirectory = currentMenuItem.FolderDirectory, @@ -266,6 +266,7 @@ private static ShellMenuItem GetShellItemInfo(string menuKey) string darkThemeIconPath = RegistryHelper.ReadRegistryKey(menuKey, "DarkThemeIconPath"); string menuProgramPath = RegistryHelper.ReadRegistryKey(menuKey, "MenuProgramPath"); string menuParameter = RegistryHelper.ReadRegistryKey(menuKey, "MenuParameter"); + bool? isAlwaysRunAsAdministrator = RegistryHelper.ReadRegistryKey(menuKey, "IsAlwaysRunAsAdministrator"); bool? folderBackground = RegistryHelper.ReadRegistryKey(menuKey, "FolderBackground"); bool? folderDesktop = RegistryHelper.ReadRegistryKey(menuKey, "FolderDesktop"); bool? folderDirectory = RegistryHelper.ReadRegistryKey(menuKey, "FolderDirectory"); @@ -287,6 +288,7 @@ private static ShellMenuItem GetShellItemInfo(string menuKey) shellMenuItem.DarkThemeIconPath = darkThemeIconPath; shellMenuItem.MenuProgramPath = menuProgramPath; shellMenuItem.MenuParameter = menuParameter; + shellMenuItem.IsAlwaysRunAsAdministrator = isAlwaysRunAsAdministrator.HasValue && isAlwaysRunAsAdministrator.Value; shellMenuItem.FolderBackground = folderBackground.HasValue && folderBackground.Value; shellMenuItem.FolderDesktop = folderDesktop.HasValue && folderDesktop.Value; shellMenuItem.FolderDirectory = folderDirectory.HasValue && folderDirectory.Value; diff --git a/WindowsTools/Strings/Notification.Designer.cs b/WindowsTools/Strings/Notification.Designer.cs index 91cf665..c6c0a63 100644 --- a/WindowsTools/Strings/Notification.Designer.cs +++ b/WindowsTools/Strings/Notification.Designer.cs @@ -357,6 +357,15 @@ public static string ReadClipboardImageFailed { } } + /// + /// 查找类似 The menu storage content has changed and needs to be refreshed 的本地化字符串。 + /// + public static string ShellMenuNeedToRefreshData { + get { + return ResourceManager.GetString("ShellMenuNeedToRefreshData", resourceCulture); + } + } + /// /// 查找类似 App pinning to Start screen failed, please pin it manually 的本地化字符串。 /// diff --git a/WindowsTools/Strings/Notification.en-us.resx b/WindowsTools/Strings/Notification.en-us.resx index 1ee21fb..587ac1b 100644 --- a/WindowsTools/Strings/Notification.en-us.resx +++ b/WindowsTools/Strings/Notification.en-us.resx @@ -246,4 +246,7 @@ The menu dark theme icon is empty, please select icon file + + The menu storage content has changed and needs to be refreshed + \ No newline at end of file diff --git a/WindowsTools/Strings/Notification.resx b/WindowsTools/Strings/Notification.resx index 1ee21fb..587ac1b 100644 --- a/WindowsTools/Strings/Notification.resx +++ b/WindowsTools/Strings/Notification.resx @@ -246,4 +246,7 @@ The menu dark theme icon is empty, please select icon file + + The menu storage content has changed and needs to be refreshed + \ No newline at end of file diff --git a/WindowsTools/Strings/Notification.zh-hans.resx b/WindowsTools/Strings/Notification.zh-hans.resx index 4e4b92f..969d359 100644 --- a/WindowsTools/Strings/Notification.zh-hans.resx +++ b/WindowsTools/Strings/Notification.zh-hans.resx @@ -246,4 +246,7 @@ 菜单深色主题图标为空,请选择图标文件 + + 菜单存储内容已改变,需要刷新才能进行操作 + \ No newline at end of file diff --git a/WindowsTools/Strings/ShellMenu.Designer.cs b/WindowsTools/Strings/ShellMenu.Designer.cs index 0e4d9fb..2b86268 100644 --- a/WindowsTools/Strings/ShellMenu.Designer.cs +++ b/WindowsTools/Strings/ShellMenu.Designer.cs @@ -204,6 +204,24 @@ public static string IconFilterCondition { } } + /// + /// 查找类似 No 的本地化字符串。 + /// + public static string IsAlwaysRunAsAdministratorOffContent { + get { + return ResourceManager.GetString("IsAlwaysRunAsAdministratorOffContent", resourceCulture); + } + } + + /// + /// 查找类似 Yes 的本地化字符串。 + /// + public static string IsAlwaysRunAsAdministratorOnContent { + get { + return ResourceManager.GetString("IsAlwaysRunAsAdministratorOnContent", resourceCulture); + } + } + /// /// 查找类似 Light theme icon 的本地化字符串。 /// @@ -457,7 +475,7 @@ public static string PrecautionsContent2 { } /// - /// 查找类似 3. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. 的本地化字符串。 + /// 查找类似 3.Due to system limitations, the maximum number of secondary menus is 16 items 的本地化字符串。 /// public static string PrecautionsContent3 { get { @@ -465,6 +483,15 @@ public static string PrecautionsContent3 { } } + /// + /// 查找类似 4. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. 的本地化字符串。 + /// + public static string PrecautionsContent4 { + get { + return ResourceManager.GetString("PrecautionsContent4", resourceCulture); + } + } + /// /// 查找类似 Executable file (*.exe)|*.exe 的本地化字符串。 /// @@ -492,6 +519,15 @@ public static string RemoveMenuItem { } } + /// + /// 查找类似 Whether to always run as administrator 的本地化字符串。 + /// + public static string RunAsAdministrator { + get { + return ResourceManager.GetString("RunAsAdministrator", resourceCulture); + } + } + /// /// 查找类似 Save 的本地化字符串。 /// diff --git a/WindowsTools/Strings/ShellMenu.en-us.resx b/WindowsTools/Strings/ShellMenu.en-us.resx index 51a1db0..b7c0de4 100644 --- a/WindowsTools/Strings/ShellMenu.en-us.resx +++ b/WindowsTools/Strings/ShellMenu.en-us.resx @@ -240,8 +240,8 @@ 2.After the menu content is updated, there may be a delay. Please open it after a period of time - - 3. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. + + 4. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. Executable file (*.exe)|*.exe @@ -321,4 +321,16 @@ Please wait while loading menu items + + Whether to always run as administrator + + + No + + + Yes + + + 3.Due to system limitations, the maximum number of secondary menus is 16 items + \ No newline at end of file diff --git a/WindowsTools/Strings/ShellMenu.resx b/WindowsTools/Strings/ShellMenu.resx index a100ca4..e69546e 100644 --- a/WindowsTools/Strings/ShellMenu.resx +++ b/WindowsTools/Strings/ShellMenu.resx @@ -240,8 +240,8 @@ 2.After the menu content is updated, there may be a delay. Please open it after a period of time - - 3. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. + + 4. It is recommended to avoid too many secondary menus, because this may lead to a long menu loading time, which will cause a deadlock. Executable file (*.exe)|*.exe @@ -321,4 +321,16 @@ Please wait while loading menu items + + Whether to always run as administrator + + + No + + + Yes + + + 3.Due to system limitations, the maximum number of secondary menus is 16 items + \ No newline at end of file diff --git a/WindowsTools/Strings/ShellMenu.zh-hans.resx b/WindowsTools/Strings/ShellMenu.zh-hans.resx index b57520d..d5ee0fe 100644 --- a/WindowsTools/Strings/ShellMenu.zh-hans.resx +++ b/WindowsTools/Strings/ShellMenu.zh-hans.resx @@ -240,8 +240,8 @@ 2.菜单内容更新后,可能会存在延迟,请过一段时间再打开 - - 3.建议避免过多的二级菜单,因为这可能会导致菜单加载时间过长,从而引发卡顿现象。 + + 4.建议避免过多的二级菜单,因为这可能会导致菜单加载时间过长,从而引发卡顿现象。 可执行文件(*.exe)|*.exe @@ -321,4 +321,16 @@ 正在加载菜单项中,请稍候 + + 是否总是提权运行 + + + + + + + + + 3.由于系统限制,二级菜单最多项为 16 项 + \ No newline at end of file diff --git a/WindowsTools/UI/Backdrop/ColorConversion.cs b/WindowsTools/UI/Backdrop/ColorConversion.cs index 320ca36..1677d64 100644 --- a/WindowsTools/UI/Backdrop/ColorConversion.cs +++ b/WindowsTools/UI/Backdrop/ColorConversion.cs @@ -161,7 +161,7 @@ public static (double h, double s, double v) RgbToHsv((double r, double g, doubl // saturation is also equal to zero - you can think of saturation as basically // a measure of hue intensity, such that no hue at all corresponds to a // nonexistent intensity. - if (chroma == 0) + if (chroma is 0) { hue = 0.0; saturation = 0.0; @@ -263,7 +263,7 @@ public static (double r, double g, double b) HsvToRgb((double h, double s, doubl // If the chroma is zero, then we have a greyscale color. In that case, the maximum and the minimum RGB channels // have the same value (and, indeed, all of the RGB channels are the same), so we can just immediately return // the minimum value as the value of all the channels. - if (chroma == 0) + if (chroma is 0) { return (min, min, min); } diff --git a/WindowsTools/UI/TeachingTips/OperationResultTip.xaml.cs b/WindowsTools/UI/TeachingTips/OperationResultTip.xaml.cs index 50f58d7..faf3669 100644 --- a/WindowsTools/UI/TeachingTips/OperationResultTip.xaml.cs +++ b/WindowsTools/UI/TeachingTips/OperationResultTip.xaml.cs @@ -110,6 +110,12 @@ public OperationResultTip(OperationKind operationKind) OperationResultFailed.Visibility = Visibility.Visible; OperationResultFailed.Text = Notification.NoOperation; } + else if (operationKind is OperationKind.ShellMenuNeedToRefreshData) + { + OperationResultSuccess.Visibility = Visibility.Collapsed; + OperationResultFailed.Visibility = Visibility.Visible; + OperationResultFailed.Text = Notification.ShellMenuNeedToRefreshData; + } } public OperationResultTip(OperationKind operationKind, bool operationResult) diff --git a/WindowsTools/Views/Pages/FileNamePage.xaml.cs b/WindowsTools/Views/Pages/FileNamePage.xaml.cs index 13ff2b2..1a31cc4 100644 --- a/WindowsTools/Views/Pages/FileNamePage.xaml.cs +++ b/WindowsTools/Views/Pages/FileNamePage.xaml.cs @@ -464,7 +464,7 @@ private void OnCloseClicked(object sender, RoutedEventArgs args) /// private void OnForwardNavigateClicked(object sender, RoutedEventArgs args) { - CurrentIndex = CurrentIndex == 0 ? 3 : CurrentIndex - 1; + CurrentIndex = CurrentIndex is 0 ? 3 : CurrentIndex - 1; for (int index = 0; index < NameChangeList.Count; index++) { @@ -478,7 +478,7 @@ private void OnForwardNavigateClicked(object sender, RoutedEventArgs args) /// private void OnNextNavigateClicked(object sender, RoutedEventArgs args) { - CurrentIndex = CurrentIndex == 3 ? 0 : CurrentIndex + 1; + CurrentIndex = CurrentIndex is 3 ? 0 : CurrentIndex + 1; for (int index = 0; index < NameChangeList.Count; index++) { @@ -727,7 +727,7 @@ private string GetChangeRule(int index) /// private bool CheckOperationState() { - return RenameRule != string.Empty || StartNumber != string.Empty || IsChecked != false || LookUpString != string.Empty || ReplaceString != string.Empty; + return !string.IsNullOrEmpty(RenameRule) || !string.IsNullOrEmpty(StartNumber) || IsChecked || !string.IsNullOrEmpty(LookUpString) || !string.IsNullOrEmpty(ReplaceString); } /// diff --git a/WindowsTools/Views/Pages/PriExtractPage.xaml.cs b/WindowsTools/Views/Pages/PriExtractPage.xaml.cs index 6c55d28..59d68ea 100644 --- a/WindowsTools/Views/Pages/PriExtractPage.xaml.cs +++ b/WindowsTools/Views/Pages/PriExtractPage.xaml.cs @@ -968,7 +968,7 @@ public void ParseResourceFile(string filePath) key = resourceMapScopeAndItem.Name; } - if (key == string.Empty) + if (string.IsNullOrEmpty(key)) { continue; } diff --git a/WindowsTools/Views/Pages/ShellMenuPage.xaml b/WindowsTools/Views/Pages/ShellMenuPage.xaml index 3e40007..446c77b 100644 --- a/WindowsTools/Views/Pages/ShellMenuPage.xaml +++ b/WindowsTools/Views/Pages/ShellMenuPage.xaml @@ -12,8 +12,9 @@ xmlns:service="using:WindowsTools.Services.Controls.Settings" xmlns:string="using:WindowsTools.Strings" xmlns:sys="using:System" - ActualThemeChanged="{x:Bind OnActualThemeChanged}" + Loaded="{x:Bind OnLoaded}" NavigationCacheMode="Enabled" + Unloaded="{x:Bind OnUnLoaded}" mc:Ignorable="d"> @@ -311,12 +312,20 @@ TextWrapping="Wrap" /> + + @@ -741,6 +750,43 @@ TextChanged="{x:Bind OnMenuParameterTextChanged}" /> + + + + + + + + + + + + + + + + public sealed partial class ShellMenuPage : Page, INotifyPropertyChanged { + public static readonly string shellMenuConfigurationKey = @"Software\WindowsTools\ShellMenuConfigurationTest"; private readonly SynchronizationContext synchronizationContext = SynchronizationContext.Current; private readonly InMemoryRandomAccessStream emptyStream = new(); - private Guid editMenuGuid; - private string editMenuKey; - private int editMenuIndex; private string selectedDefaultIconPath = string.Empty; private string selectedLightThemeIconPath = string.Empty; private string selectedDarkThemeIconPath = string.Empty; - + private Guid editMenuGuid; + private string editMenuKey; + private int editMenuIndex; + private bool needToRefreshData; + public bool isChanger; private ShellMenuItemModel selectedItem; private bool _isLoading = false; @@ -335,6 +338,22 @@ public string MenuParameterText } } + private bool _isAlwaysRunAsAdministrator; + + public bool IsAlwaysRunAsAdministrator + { + get { return _isAlwaysRunAsAdministrator; } + + set + { + if (!Equals(_isAlwaysRunAsAdministrator, value)) + { + _isAlwaysRunAsAdministrator = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAlwaysRunAsAdministrator))); + } + } + } + private bool _folderBackgroundMatch; public bool FolderBackgroundMatch @@ -486,6 +505,7 @@ public ShellMenuPage() InitializeComponent(); IsLoading = true; SelectedFileMatchRule = FileMatchRuleList[4]; + RegistryHelper.NotifyKeyValueChanged += OnNotifyKeyValueChanged; Task.Run(async () => { @@ -501,7 +521,7 @@ public ShellMenuPage() if (rootShellMenuItem is not null) { - ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.RootMenu)); + ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.FirstLevelMenu)); if (ShellMenuItemCollection.Count is 0) { @@ -526,6 +546,8 @@ public ShellMenuPage() IsLoading = false; }, null); + + RegistryHelper.MonitorRegistryValueChange(@"Software\WindowsTools\ShellMenuTest"); }); } @@ -551,6 +573,26 @@ protected override void OnNavigatedTo(NavigationEventArgs args) #region 第二部分:根菜单页面——挂载的事件 + /// + /// 在已构造 FrameworkElement 并将其添加到对象树中并准备好交互时发生的事件 + /// + private void OnLoaded(object sender, RoutedEventArgs args) + { + foreach (ShellMenuItemModel shellMenuItem in ShellMenuItemCollection) + { + EnumModifyShellMenuItemTheme(shellMenuItem); + } + ActualThemeChanged += OnActualThemeChanged; + } + + /// + /// 当此对象不再连接到主对象树时发生的事件 + /// + private void OnUnLoaded(object sender, RoutedEventArgs args) + { + ActualThemeChanged -= OnActualThemeChanged; + } + /// /// 当前应用主题发生变化时对应的事件 /// @@ -591,6 +633,13 @@ private void OnMenuSettingsClicked(object sender, RoutedEventArgs args) /// private async void OnSaveClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + // 有部分内容是必填项,没填的内容进行提示 if (string.IsNullOrEmpty(MenuTitleText)) { @@ -660,6 +709,7 @@ await Task.Run(() => DarkThemeIconPath = DarkThemeIconPath, MenuProgramPath = MenuProgramPathText, MenuParameter = MenuParameterText, + IsAlwaysRunAsAdministrator = IsAlwaysRunAsAdministrator, FolderBackground = FolderBackgroundMatch, FolderDesktop = FolderDesktopMatch, FolderDirectory = FolderDirectoryMatch, @@ -668,6 +718,7 @@ await Task.Run(() => MenuFileMatchFormatText = MenuFileMatchFormatText }; + isChanger = true; ShellMenuService.SaveShellMenuItem(editMenuKey, shellMenuItem); // 复制选中的图标文件到指定目录 @@ -739,7 +790,7 @@ await Task.Run(() => if (rootShellMenuItem is not null) { - ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.RootMenu)); + ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.FirstLevelMenu)); if (ShellMenuItemCollection.Count is 0) { @@ -772,9 +823,16 @@ await Task.Run(() => /// /// 添加菜单 /// - private void OnAddMenuItemClicked(object sender, RoutedEventArgs args) + private async void OnAddMenuItemClicked(object sender, RoutedEventArgs args) { - Task.Run(() => + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + + await Task.Run(() => { Guid menuGuid = Guid.NewGuid(); string menuKey = string.Empty; @@ -786,7 +844,7 @@ private void OnAddMenuItemClicked(object sender, RoutedEventArgs args) menuKey = Path.Combine(@"Software\WindowsTools\ShellMenuTest", menuGuid.ToString()); menuIndex = 0; } - else if (selectedItem.MenuType is MenuType.RootMenu || selectedItem.MenuType is MenuType.FirstLevelMenu) + else if (selectedItem.MenuType is MenuType.FirstLevelMenu) { menuKey = Path.Combine(selectedItem.MenuKey, menuGuid.ToString()); menuIndex = selectedItem.SubMenuItemCollection.Count; @@ -814,6 +872,7 @@ private void OnAddMenuItemClicked(object sender, RoutedEventArgs args) MenuProgramPathText = string.Empty; MenuParameterText = string.Empty; FolderBackgroundMatch = false; + IsAlwaysRunAsAdministrator = false; FolderDesktopMatch = false; FolderDirectoryMatch = false; FolderDriveMatch = false; @@ -831,11 +890,18 @@ private void OnAddMenuItemClicked(object sender, RoutedEventArgs args) /// 移除菜单项 /// - private void OnRemoveMenuItemClicked(object sender, RoutedEventArgs args) + private async void OnRemoveMenuItemClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + if (selectedItem is not null) { - Task.Run(() => + await Task.Run(() => { // 移除指定的菜单项 ShellMenuService.RemoveShellMenuItem(selectedItem.MenuKey); @@ -871,13 +937,20 @@ private void OnRemoveMenuItemClicked(object sender, RoutedEventArgs args) /// /// 清空菜单项 /// - private void OnClearMenuClicked(object sender, RoutedEventArgs args) + private async void OnClearMenuClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + if (ShellMenuItemCollection.Count > 0) { string rootMenuKey = ShellMenuItemCollection[0].MenuKey; - Task.Run(() => + await Task.Run(() => { // 清空所有菜单项信息 ShellMenuService.RemoveShellMenuItem(rootMenuKey); @@ -918,7 +991,7 @@ private void OnRefreshClicked(object sender, RoutedEventArgs args) if (rootShellMenuItem is not null) { - ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.RootMenu)); + ShellMenuItemCollection.Add(EnumShellMenuItem(rootShellMenuItem, MenuType.FirstLevelMenu)); if (ShellMenuItemCollection.Count is 0) { @@ -942,6 +1015,7 @@ private void OnRefreshClicked(object sender, RoutedEventArgs args) } IsLoading = false; + needToRefreshData = false; }, null); }); } @@ -949,8 +1023,15 @@ private void OnRefreshClicked(object sender, RoutedEventArgs args) /// /// 编辑菜单 /// - private void OnEditMenuClicked(object sender, RoutedEventArgs args) + private async void OnEditMenuClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + // 获取选中项的菜单信息 if (selectedItem is not null && BreadCollection.Count is 1) { @@ -969,6 +1050,7 @@ private void OnEditMenuClicked(object sender, RoutedEventArgs args) DarkThemeIconPath = selectedItem.DarkThemeIconPath; MenuProgramPathText = selectedItem.MenuProgramPathText; MenuParameterText = selectedItem.MenuParameter; + IsAlwaysRunAsAdministrator = selectedItem.IsAlwaysRunAsAdministrator; FolderBackgroundMatch = selectedItem.FolderBackground; FolderDesktopMatch = selectedItem.FolderDesktop; FolderDirectoryMatch = selectedItem.FolderDirectory; @@ -983,6 +1065,8 @@ private void OnEditMenuClicked(object sender, RoutedEventArgs args) } } + NeedInputMatchFormat = !SelectedFileMatchRule.Equals(FileMatchRuleList[0]) && !SelectedFileMatchRule.Equals(FileMatchRuleList[4]); + DefaultIconImage.SetSource(emptyStream); LightThemeIconImage.SetSource(emptyStream); DarkThemeIconImage.SetSource(emptyStream); @@ -1045,16 +1129,30 @@ private void OnEditMenuClicked(object sender, RoutedEventArgs args) /// /// 向上移动菜单项 /// - private void OnMoveUpClicked(object sender, RoutedEventArgs args) + private async void OnMoveUpClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + EnumMoveUpShellMenuItem(selectedItem, ShellMenuItemCollection); } /// /// 向下移动菜单项 /// - private void OnMoveDownClicked(object sender, RoutedEventArgs args) + private async void OnMoveDownClicked(object sender, RoutedEventArgs args) { + // 菜单数据已发生更改,通知用户手动刷新 + if (needToRefreshData) + { + await TeachingTipHelper.ShowAsync(new OperationResultTip(OperationKind.ShellMenuNeedToRefreshData)); + return; + } + EnumMoveDownShellMenuItem(selectedItem, ShellMenuItemCollection); } @@ -1237,6 +1335,17 @@ private void OnMenuParameterTextChanged(object sender, TextChangedEventArgs args MenuParameterText = (sender as global::Windows.UI.Xaml.Controls.TextBox).Text; } + /// + /// 是否总是需要提权运行修改时触发的事件 + /// + private void OnIsAlwaysRunAsAdministratorToggled(object sender, RoutedEventArgs args) + { + if (sender is ToggleSwitch toggleSwitch) + { + IsAlwaysRunAsAdministrator = toggleSwitch.IsOn; + } + } + /// /// 修改菜单文件匹配规则 /// @@ -1280,7 +1389,27 @@ private void OnMenuFileMatchFormatTextChanged(object sender, TextChangedEventArg #endregion 第二部分:根菜单页面——挂载的事件 - #region 第三部分:递归遍历 + #region 第三部分:自定义事件 + + /// + /// 注册表内容发生变更时触发的事件 + /// + private void OnNotifyKeyValueChanged(object sender, EventArgs args) + { + if (!isChanger) + { + needToRefreshData = true; + } + + isChanger = false; + + // 注册的变化通知在使用一次后就消失了,需要重新注册 + RegistryHelper.MonitorRegistryValueChange(@"Software\WindowsTools\ShellMenuTest"); + } + + #endregion 第三部分:自定义事件 + + #region 第四部分:递归遍历 /// /// 枚举并递归菜单项信息 @@ -1291,7 +1420,7 @@ private ShellMenuItemModel EnumShellMenuItem(ShellMenuItem menuItem, MenuType me ShellMenuItemModel shellMenuItem = new() { MenuKey = menuItem.MenuKey, - IsSelected = menuType is MenuType.RootMenu, + IsSelected = menuType is MenuType.FirstLevelMenu, MenuType = menuType, MenuTitleText = menuItem.MenuTitleText, MenuGuid = menuItem.MenuGuid, @@ -1301,6 +1430,7 @@ private ShellMenuItemModel EnumShellMenuItem(ShellMenuItem menuItem, MenuType me MenuProgramPathText = menuItem.MenuProgramPath, MenuParameter = menuItem.MenuParameter, FolderBackground = menuItem.FolderBackground, + IsAlwaysRunAsAdministrator = menuItem.IsAlwaysRunAsAdministrator, FolderDesktop = menuItem.FolderDesktop, FolderDirectory = menuItem.FolderDirectory, FolderDrive = menuItem.FolderDrive, @@ -1312,6 +1442,7 @@ private ShellMenuItemModel EnumShellMenuItem(ShellMenuItem menuItem, MenuType me ShouldUseProgramIcon = menuItem.ShouldUseProgramIcon, }; + shellMenuItem.MenuIcon.SetSource(emptyStream); if (shellMenuItem.ShouldUseIcon) { // 使用应用程序图标 @@ -1334,64 +1465,64 @@ private ShellMenuItemModel EnumShellMenuItem(ShellMenuItem menuItem, MenuType me LogService.WriteLog(EventLevel.Error, string.Format("Get program icon {0} failed", shellMenuItem.MenuProgramPathText), e); } } - } - else - { - // 使用主题菜单图标 - if (shellMenuItem.ShouldUseThemeIcon) + else { - // 浅色主题图标 - if (ActualTheme is ElementTheme.Light && File.Exists(shellMenuItem.LightThemeIconPath)) + // 使用主题菜单图标 + if (shellMenuItem.ShouldUseThemeIcon) { - try + // 浅色主题图标 + if (ActualTheme is ElementTheme.Light && File.Exists(shellMenuItem.LightThemeIconPath)) { - Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.LightThemeIconPath); - MemoryStream memoryStream = new(); - icon.ToBitmap().Save(memoryStream, ImageFormat.Png); - memoryStream.Seek(0, SeekOrigin.Begin); - shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); - memoryStream.Dispose(); - } - catch (Exception e) - { - LogService.WriteLog(EventLevel.Error, string.Format("Get light theme icon {0} failed", shellMenuItem.LightThemeIconPath), e); - } - } - // 深色主题图标 - else if (ActualTheme is ElementTheme.Dark && File.Exists(shellMenuItem.DarkThemeIconPath)) - { - try - { - Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.DarkThemeIconPath); - MemoryStream memoryStream = new(); - icon.ToBitmap().Save(memoryStream, ImageFormat.Png); - memoryStream.Seek(0, SeekOrigin.Begin); - shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); - memoryStream.Dispose(); + try + { + Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.LightThemeIconPath); + MemoryStream memoryStream = new(); + icon.ToBitmap().Save(memoryStream, ImageFormat.Png); + memoryStream.Seek(0, SeekOrigin.Begin); + shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); + memoryStream.Dispose(); + } + catch (Exception e) + { + LogService.WriteLog(EventLevel.Error, string.Format("Get light theme icon {0} failed", shellMenuItem.LightThemeIconPath), e); + } } - catch (Exception e) + // 深色主题图标 + else if (ActualTheme is ElementTheme.Dark && File.Exists(shellMenuItem.DarkThemeIconPath)) { - LogService.WriteLog(EventLevel.Error, string.Format("Get dark theme icon {0} failed", shellMenuItem.DarkThemeIconPath), e); + try + { + Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.DarkThemeIconPath); + MemoryStream memoryStream = new(); + icon.ToBitmap().Save(memoryStream, ImageFormat.Png); + memoryStream.Seek(0, SeekOrigin.Begin); + shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); + memoryStream.Dispose(); + } + catch (Exception e) + { + LogService.WriteLog(EventLevel.Error, string.Format("Get dark theme icon {0} failed", shellMenuItem.DarkThemeIconPath), e); + } } } - } - else - { - // 默认图标 - if (File.Exists(shellMenuItem.DefaultIconPath)) + else { - try + // 默认图标 + if (File.Exists(shellMenuItem.DefaultIconPath)) { - Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.DefaultIconPath); - MemoryStream memoryStream = new(); - icon.ToBitmap().Save(memoryStream, ImageFormat.Png); - memoryStream.Seek(0, SeekOrigin.Begin); - shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); - memoryStream.Dispose(); - } - catch (Exception e) - { - LogService.WriteLog(EventLevel.Error, string.Format("Get default icon {0} failed", shellMenuItem.DefaultIconPath), e); + try + { + Icon icon = Icon.ExtractAssociatedIcon(shellMenuItem.DefaultIconPath); + MemoryStream memoryStream = new(); + icon.ToBitmap().Save(memoryStream, ImageFormat.Png); + memoryStream.Seek(0, SeekOrigin.Begin); + shellMenuItem.MenuIcon.SetSource(memoryStream.AsRandomAccessStream()); + memoryStream.Dispose(); + } + catch (Exception e) + { + LogService.WriteLog(EventLevel.Error, string.Format("Get default icon {0} failed", shellMenuItem.DefaultIconPath), e); + } } } } @@ -1400,11 +1531,7 @@ private ShellMenuItemModel EnumShellMenuItem(ShellMenuItem menuItem, MenuType me // 递归遍历子项 foreach (ShellMenuItem subMenuItem in menuItem.SubShellMenuItem) { - if (menuType is MenuType.RootMenu) - { - shellMenuItem.SubMenuItemCollection.Add(EnumShellMenuItem(subMenuItem, MenuType.FirstLevelMenu)); - } - else if (menuType is MenuType.FirstLevelMenu) + if (menuType is MenuType.FirstLevelMenu) { shellMenuItem.SubMenuItemCollection.Add(EnumShellMenuItem(subMenuItem, MenuType.SecondLevelMenu)); } @@ -1460,6 +1587,7 @@ private ShellMenuItemModel EnumRemoveItem(ShellMenuItemModel selectedItem, Shell DarkThemeIconPath = shellMenuItem.DarkThemeIconPath, MenuProgramPath = shellMenuItem.MenuProgramPathText, MenuParameter = shellMenuItem.MenuParameter, + IsAlwaysRunAsAdministrator = shellMenuItem.IsAlwaysRunAsAdministrator, FolderBackground = shellMenuItem.FolderBackground, FolderDesktop = shellMenuItem.FolderDesktop, FolderDirectory = shellMenuItem.FolderDirectory, @@ -1468,6 +1596,8 @@ private ShellMenuItemModel EnumRemoveItem(ShellMenuItemModel selectedItem, Shell MenuFileMatchFormatText = shellMenuItem.MenuFileMatchFormatText, }; + isChanger = true; + // 保存菜单新顺序 ShellMenuService.SaveShellMenuItem(shellMenuItem.MenuKey, selectedMenuItem); } @@ -1509,7 +1639,7 @@ private void EnumModifySelectedItem(ShellMenuItemModel selectedItem, ObservableC { selectedItem.IsSelected = true; IsEditMenuEnabled = true; - IsAddMenuEnabled = selectedItem.MenuType is MenuType.RootMenu || selectedItem.MenuType is MenuType.FirstLevelMenu; + IsAddMenuEnabled = selectedItem.MenuType is MenuType.FirstLevelMenu; if (shellMenuItemCollection.Count is 1) { @@ -1636,6 +1766,7 @@ private void EnumMoveUpShellMenuItem(ShellMenuItemModel selectedItem, Observable DarkThemeIconPath = shellMenuItemCollection[index - 1].DarkThemeIconPath, MenuProgramPath = shellMenuItemCollection[index - 1].MenuProgramPathText, MenuParameter = shellMenuItemCollection[index - 1].MenuParameter, + IsAlwaysRunAsAdministrator = shellMenuItemCollection[index - 1].IsAlwaysRunAsAdministrator, FolderBackground = shellMenuItemCollection[index - 1].FolderBackground, FolderDesktop = shellMenuItemCollection[index - 1].FolderDesktop, FolderDirectory = shellMenuItemCollection[index - 1].FolderDirectory, @@ -1658,6 +1789,7 @@ private void EnumMoveUpShellMenuItem(ShellMenuItemModel selectedItem, Observable DarkThemeIconPath = shellMenuItemCollection[index].DarkThemeIconPath, MenuProgramPath = shellMenuItemCollection[index].MenuProgramPathText, MenuParameter = shellMenuItemCollection[index].MenuParameter, + IsAlwaysRunAsAdministrator = shellMenuItemCollection[index].IsAlwaysRunAsAdministrator, FolderBackground = shellMenuItemCollection[index].FolderBackground, FolderDesktop = shellMenuItemCollection[index].FolderDesktop, FolderDirectory = shellMenuItemCollection[index].FolderDirectory, @@ -1666,6 +1798,7 @@ private void EnumMoveUpShellMenuItem(ShellMenuItemModel selectedItem, Observable MenuFileMatchFormatText = shellMenuItemCollection[index].MenuFileMatchFormatText }; + isChanger = true; ShellMenuService.SaveShellMenuItem(shellMenuItemCollection[index - 1].MenuKey, swappedMenuItem); ShellMenuService.SaveShellMenuItem(shellMenuItemCollection[index].MenuKey, selectedMenuItem); autoResetEvent.Set(); @@ -1726,6 +1859,7 @@ private void EnumMoveDownShellMenuItem(ShellMenuItemModel selectedItem, Observab DarkThemeIconPath = shellMenuItemCollection[index + 1].DarkThemeIconPath, MenuProgramPath = shellMenuItemCollection[index + 1].MenuProgramPathText, MenuParameter = shellMenuItemCollection[index + 1].MenuParameter, + IsAlwaysRunAsAdministrator = shellMenuItemCollection[index + 1].IsAlwaysRunAsAdministrator, FolderBackground = shellMenuItemCollection[index + 1].FolderBackground, FolderDesktop = shellMenuItemCollection[index + 1].FolderDesktop, FolderDirectory = shellMenuItemCollection[index + 1].FolderDirectory, @@ -1748,6 +1882,7 @@ private void EnumMoveDownShellMenuItem(ShellMenuItemModel selectedItem, Observab DarkThemeIconPath = shellMenuItemCollection[index].DarkThemeIconPath, MenuProgramPath = shellMenuItemCollection[index].MenuProgramPathText, MenuParameter = shellMenuItemCollection[index].MenuParameter, + IsAlwaysRunAsAdministrator = shellMenuItemCollection[index].IsAlwaysRunAsAdministrator, FolderBackground = shellMenuItemCollection[index].FolderBackground, FolderDesktop = shellMenuItemCollection[index].FolderDesktop, FolderDirectory = shellMenuItemCollection[index].FolderDirectory, @@ -1756,6 +1891,7 @@ private void EnumMoveDownShellMenuItem(ShellMenuItemModel selectedItem, Observab MenuFileMatchFormatText = shellMenuItemCollection[index].MenuFileMatchFormatText, }; + isChanger = true; ShellMenuService.SaveShellMenuItem(shellMenuItemCollection[index + 1].MenuKey, swappedMenuItem); ShellMenuService.SaveShellMenuItem(shellMenuItemCollection[index].MenuKey, selectedMenuItem); autoResetEvent.Set(); @@ -1785,6 +1921,6 @@ private void EnumMoveDownShellMenuItem(ShellMenuItemModel selectedItem, Observab } } - #endregion 第三部分:递归遍历 + #endregion 第四部分:递归遍历 } } diff --git a/WindowsTools/WindowsAPI/PInvoke/Advapi32/Advapi32Library.cs b/WindowsTools/WindowsAPI/PInvoke/Advapi32/Advapi32Library.cs index 7834767..73a184a 100644 --- a/WindowsTools/WindowsAPI/PInvoke/Advapi32/Advapi32Library.cs +++ b/WindowsTools/WindowsAPI/PInvoke/Advapi32/Advapi32Library.cs @@ -20,6 +20,6 @@ public static class Advapi32Library /// 如果此参数为 TRUE,则函数将立即返回并通过向指定事件发出信号来报告更改。 如果此参数为 FALSE,则函数在发生更改之前不会返回 。 /// 如果函数成功,则返回值为 ERROR_SUCCESS。如果函数失败,则返回值为 Winerror.h 中定义的非零错误代码。 [DllImport(Advapi32, CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "RegNotifyChangeKeyValue", SetLastError = false), PreserveSig] - public static extern int RegNotifyChangeKeyValue(UIntPtr hKey, [MarshalAs(UnmanagedType.Bool)] bool watchSubtree, REG_NOTIFY_FILTER notifyFilter, IntPtr hEvent, [MarshalAs(UnmanagedType.Bool)] bool asynchronous); + public static extern int RegNotifyChangeKeyValue(IntPtr hKey, [MarshalAs(UnmanagedType.Bool)] bool watchSubtree, REG_NOTIFY_FILTER notifyFilter, IntPtr hEvent, [MarshalAs(UnmanagedType.Bool)] bool asynchronous); } } diff --git a/WindowsToolsPackage/Package.appxmanifest b/WindowsToolsPackage/Package.appxmanifest index 7693f32..f68751b 100644 --- a/WindowsToolsPackage/Package.appxmanifest +++ b/WindowsToolsPackage/Package.appxmanifest @@ -13,7 +13,7 @@ + Version="2.9.904.0" /> ms-resource:PackageDisplayName diff --git a/WindowsToolsShellExtension/Commands/ExplorerCommand.cs b/WindowsToolsShellExtension/Commands/ExplorerCommand.cs index 8b9cbd9..204f5f7 100644 --- a/WindowsToolsShellExtension/Commands/ExplorerCommand.cs +++ b/WindowsToolsShellExtension/Commands/ExplorerCommand.cs @@ -9,6 +9,8 @@ using WindowsToolsShellExtension.Services.Controls.Settings; using WindowsToolsShellExtension.Services.Shell; using WindowsToolsShellExtension.WindowsAPI.ComTypes; +using WindowsToolsShellExtension.WindowsAPI.PInvoke.Shell32; +using WindowsToolsShellExtension.WindowsAPI.PInvoke.Shlwapi; namespace WindowsToolsShellExtension.Commands { @@ -23,10 +25,10 @@ public partial class ExplorerCommand(ShellMenuItem shellMenuItem) : IExplorerCom private readonly StrategyBasedComWrappers strategyBasedComWrappers = new(); private IntPtr site = IntPtr.Zero; - [GeneratedRegex(@"{files:split'([\s\S]*)'")] + [GeneratedRegex(@"{files-split:'([\s\S]*?)'}")] private static partial Regex MultiFileRegex { get; } - [GeneratedRegex(@"{folders:split'([\s\S]*)'")] + [GeneratedRegex(@"{folders-split:'([\s\S]*?)'}")] private static partial Regex MultiFolderRegex { get; } /// @@ -185,13 +187,14 @@ public int GetState(IShellItemArray psiItemArray, bool fOkToBeSlow, out EXPCMDST string standardDesktopPath = Path.GetFullPath(Environment.GetFolderPath(Environment.SpecialFolder.Desktop).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); string clickedPath = Path.GetFullPath(folderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); - pCmdState = standardDesktopPath.Equals(clickedPath, StringComparison.OrdinalIgnoreCase) ? shellMenuItem.FolderDesktop ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN : shellMenuItem.FolderBackground ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + pCmdState = standardDesktopPath.Equals(clickedPath, StringComparison.OrdinalIgnoreCase) ? EnumerateMatchFolderDesktop(shellMenuItem) : EnumerateMatchFolderDirectory(shellMenuItem); } else { pCmdState = EXPCMDSTATE.ECS_HIDDEN; } } + // 其他:文件或目录 else { if (psiItemArray.GetCount(out uint count) is 0 && count >= 1 && psiItemArray.GetItemAt(0, out IShellItem shellItem) is 0) @@ -199,81 +202,18 @@ public int GetState(IShellItemArray psiItemArray, bool fOkToBeSlow, out EXPCMDST shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string filePath); FileInfo fileInfo = new(filePath); - // 选中的是目录 + // 选中的第一个是目录 if ((fileInfo.Attributes & FileAttributes.Directory) is not 0) { string rootPath = Path.GetPathRoot(filePath); // 相同,为驱动器目录 - pCmdState = filePath.Equals(rootPath, StringComparison.OrdinalIgnoreCase) ? shellMenuItem.FolderDrive ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN : shellMenuItem.FolderDirectory ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + pCmdState = filePath.Equals(rootPath, StringComparison.OrdinalIgnoreCase) ? EnumerateMatchFolderDrive(shellMenuItem) : EnumerateMatchFolderDirectory(shellMenuItem); } // 选中的第一个是文件 else { - // 匹配文件格式 - if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[0])) - { - pCmdState = EXPCMDSTATE.ECS_HIDDEN; - } - else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[1])) - { - string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); - - bool isMatched = false; - foreach (string fileMatchFormat in fileMatchFormatArray) - { - if (filePath.Equals(fileMatchFormat.Trim(), StringComparison.OrdinalIgnoreCase)) - { - isMatched = true; - break; - } - } - - pCmdState = isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; - } - else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[2])) - { - string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); - bool isMatched = false; - - foreach (string fileMatchFormat in fileMatchFormatArray) - { - Regex fileMatchRegex = new(fileMatchFormat.Trim()); - - if (fileMatchRegex.IsMatch(filePath)) - { - isMatched = true; - break; - } - } - - pCmdState = isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; - } - else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[3])) - { - string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); - bool isMatched = false; - string extensionName = Path.GetExtension(filePath); - - foreach (string fileMatchFormat in fileMatchFormatArray) - { - if (extensionName.Equals(fileMatchFormat.Trim(), StringComparison.OrdinalIgnoreCase)) - { - isMatched = true; - break; - } - } - - pCmdState = isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; - } - else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[4])) - { - pCmdState = EXPCMDSTATE.ECS_ENABLED; - } - else - { - pCmdState = EXPCMDSTATE.ECS_HIDDEN; - } + pCmdState = EnumerateMatchFile(shellMenuItem, filePath); } } else @@ -298,12 +238,20 @@ public int Invoke(IShellItemArray psiItemArray, IntPtr pbc) // 没有子菜单,该菜单可以直接调用命令 if (shellMenuItem is not null && shellMenuItem.SubShellMenuItem.Count is 0) { - // Directory\Background - if (psiItemArray is null && site != IntPtr.Zero) + int fileMatchedCount = 0; + int folderMatchedCount = 0; + string selectedFilePath = string.Empty; + string selectedFolderPath = string.Empty; + List fileList = []; + List folderList = []; + List multiFileMatchedStringList = []; + List multiFolderMatchedStringList = []; + List replaceFileStringList = []; + List replaceFolderStringList = []; + + // psiItemArray 为空,可能为背景。查询点击背景时对应的文件夹路径 + if (psiItemArray is null) { - string folderPath = string.Empty; - - // 查询点击背景时对应的文件夹路径 Marshal.QueryInterface(site, typeof(WindowsAPI.ComTypes.IServiceProvider).GUID, out IntPtr serviceProviderPtr); WindowsAPI.ComTypes.IServiceProvider serviceProvider = (WindowsAPI.ComTypes.IServiceProvider)strategyBasedComWrappers.GetOrCreateObjectForComInstance(serviceProviderPtr, CreateObjectFlags.None); @@ -316,121 +264,114 @@ public int Invoke(IShellItemArray psiItemArray, IntPtr pbc) folderView.GetFolder(ref iShellItemGuid, out IntPtr iShellItemPtr); IShellItem shellItem = (IShellItem)strategyBasedComWrappers.GetOrCreateObjectForComInstance(iShellItemPtr, CreateObjectFlags.None); - - shellItem?.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out folderPath); - } - - // 读取参数 - string parameter = string.IsNullOrEmpty(shellMenuItem.MenuParameter) ? string.Empty : shellMenuItem.MenuParameter; - - if (!string.IsNullOrEmpty(folderPath)) - { - parameter = parameter.Replace("{folder}", folderPath); - ProcessHelper.StartProcess(shellMenuItem.MenuProgramPath, parameter, out _); + shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string filePath); + selectedFolderPath = filePath; + folderList.Add(filePath); } } - else + // psiItemArray不为空,正常获取所有选中的文件或目录 + else if (psiItemArray.GetCount(out uint count) is 0) { - if (psiItemArray.GetCount(out uint count) is 0) + for (uint index = 0; index < count; index++) { - // 选中单个文件 - if (count is 1) + if (psiItemArray.GetItemAt(index, out IShellItem shellItem) is 0) { - if (psiItemArray.GetItemAt(0, out IShellItem shellItem) is 0) - { - shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string filePath); - FileInfo fileInfo = new(filePath); - - // 读取参数 - string parameter = string.IsNullOrEmpty(shellMenuItem.MenuParameter) ? string.Empty : shellMenuItem.MenuParameter; + shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string filePath); + FileInfo fileInfo = new(filePath); - // 选中的是目录 - parameter = (fileInfo.Attributes & FileAttributes.Directory) is not 0 ? parameter.Replace("{folder}", filePath) : parameter.Replace("{file}", filePath); - - ProcessHelper.StartProcess(shellMenuItem.MenuProgramPath, parameter, out _); + // 当前项是目录 + if ((fileInfo.Attributes & FileAttributes.Directory) is not 0) + { + folderList.Add(filePath); + if (index is 0) + { + selectedFolderPath = filePath; + } } - } - else - { - List fileList = []; - List folderList = []; - - for (uint index = 0; index < count; index++) + // 当前项是文件 + else { - if (psiItemArray.GetItemAt(index, out IShellItem shellItem) is 0) + fileList.Add(filePath); + if (index is 0) { - shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string filePath); - FileInfo fileInfo = new(filePath); - - // 当前项是目录 - if ((fileInfo.Attributes & FileAttributes.Directory) is not 0) - { - folderList.Add(filePath); - } - // 当前项是文件 - else - { - fileList.Add(filePath); - } + selectedFilePath = filePath; } } + } + } + } + // 读取参数 + string parameter = string.IsNullOrEmpty(shellMenuItem.MenuParameter) ? string.Empty : shellMenuItem.MenuParameter; - // 读取参数 - string parameter = string.IsNullOrEmpty(shellMenuItem.MenuParameter) ? string.Empty : shellMenuItem.MenuParameter; + // 替换模板参数 + parameter = parameter.Replace("{file}", selectedFilePath); + parameter = parameter.Replace("{folder}", selectedFolderPath); - // 匹配多文件规则和匹配多目录规则 - MatchCollection multiFileMatchCollection = MultiFileRegex.Matches(parameter); - MatchCollection multiFolderMatchCollection = MultiFolderRegex.Matches(parameter); + MatchCollection multiFileMatchCollection = MultiFileRegex.Matches(parameter); + MatchCollection multiFolderMatchCollection = MultiFolderRegex.Matches(parameter); - int fileMatchedCount = 0; - int folderMatchedCount = 0; - List multiFileMatchedStringList = []; - List multiFolderMatchedStringList = []; - List replaceFileStringList = []; - List replaceFolderStringList = []; + // 匹配文件 + foreach (Match multiFileMatchItem in multiFileMatchCollection) + { + GroupCollection multiFileGroupCollection = multiFileMatchItem.Groups; - // 匹配文件 - foreach (Match multiFileMatchItem in multiFileMatchCollection) - { - GroupCollection multiFileGroupCollection = multiFileMatchItem.Groups; + if (multiFileGroupCollection.Count is 2) + { + multiFileMatchedStringList.Add(multiFileGroupCollection[0].Value); + replaceFileStringList.Add(multiFileGroupCollection[1].Value); + fileMatchedCount++; + } + } - if (multiFileGroupCollection.Count is 3) - { - multiFileMatchedStringList.Add(multiFileGroupCollection[1].Value); - replaceFileStringList.Add(multiFileGroupCollection[2].Value); - fileMatchedCount++; - } - } + // 匹配目录 + foreach (Match multiFolderMatchItem in multiFolderMatchCollection) + { + GroupCollection multiFolderGroupCollection = multiFolderMatchItem.Groups; - // 匹配目录 - foreach (Match multiFolderMatchItem in multiFolderMatchCollection) - { - GroupCollection multiFolderGroupCollection = multiFolderMatchItem.Groups; + if (multiFolderGroupCollection.Count is 2) + { + multiFolderMatchedStringList.Add(multiFolderGroupCollection[0].Value); + replaceFolderStringList.Add(multiFolderGroupCollection[1].Value); + folderMatchedCount++; + } + } - if (multiFolderGroupCollection.Count is 3) - { - multiFolderMatchedStringList.Add(multiFolderGroupCollection[1].Value); - replaceFolderStringList.Add(multiFolderGroupCollection[2].Value); - folderMatchedCount++; - } - } + // 替换匹配到的所有文件 + for (int index = 0; index < fileMatchedCount; index++) + { + string fileString = string.Join(replaceFileStringList[index], fileList); + parameter = parameter.Replace(multiFileMatchedStringList[index], fileString); + } - // 替换匹配到的所有文件 - for (int index = 0; index < fileMatchedCount; index++) - { - string fileString = string.Join(replaceFileStringList[index], fileList); - parameter = parameter.Replace(multiFileMatchedStringList[index], fileString); - } + // 替换匹配到的所有目录 + for (int index = 0; index < folderMatchedCount; index++) + { + string folderString = string.Join(replaceFolderStringList[index], folderList); + parameter = parameter.Replace(multiFolderMatchedStringList[index], folderString); + } - // 替换匹配到的所有目录 - for (int index = 0; index < folderMatchedCount; index++) - { - string folderString = string.Join(replaceFolderStringList[index], folderList); - parameter = parameter.Replace(multiFolderMatchedStringList[index], folderString); - } + ShlwapiLibrary.IUnknown_GetWindow(site, out IntPtr hwnd); + if (!string.IsNullOrEmpty(selectedFilePath)) + { + if (shellMenuItem.IsAlwaysRunAsAdministrator) + { + Shell32Library.ShellExecute(hwnd, "runas", shellMenuItem.MenuProgramPath, parameter, Path.GetDirectoryName(selectedFilePath), 1); + } + else + { + Shell32Library.ShellExecute(hwnd, "open", shellMenuItem.MenuProgramPath, parameter, Path.GetDirectoryName(selectedFilePath), 1); + } + } - ProcessHelper.StartProcess(shellMenuItem.MenuProgramPath, parameter, out _); - } + if (!string.IsNullOrEmpty(selectedFolderPath)) + { + if (shellMenuItem.IsAlwaysRunAsAdministrator) + { + Shell32Library.ShellExecute(hwnd, "runas", shellMenuItem.MenuProgramPath, parameter, selectedFolderPath, 1); + } + else + { + Shell32Library.ShellExecute(hwnd, "open", shellMenuItem.MenuProgramPath, parameter, selectedFolderPath, 1); } } } @@ -445,7 +386,6 @@ public int GetFlags(out EXPCMDFLAGS pFlags) if (shellMenuItem is not null) { pFlags = shellMenuItem.SubShellMenuItem.Count is 0 ? EXPCMDFLAGS.ECF_DEFAULT : EXPCMDFLAGS.ECF_HASSUBCOMMANDS; - return 0; } else @@ -508,5 +448,170 @@ public int GetSite(ref Guid riid, out IntPtr ppvSite) return unchecked((int)0x80004005); } } + + /// + /// 枚举子菜单项选项,检查是否有符合桌面目录的选项 + /// + private EXPCMDSTATE EnumerateMatchFolderDesktop(ShellMenuItem shellMenuItem) + { + if (shellMenuItem.SubShellMenuItem.Count is 0) + { + return shellMenuItem.FolderDesktop ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else + { + bool isMatched = false; + foreach (ShellMenuItem subShellMenuItem in shellMenuItem.SubShellMenuItem) + { + if (EnumerateMatchFolderDesktop(subShellMenuItem) is EXPCMDSTATE.ECS_ENABLED) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + } + + /// + /// 枚举子菜单项选项,检查是否有符合普通目录的选项 + /// + private EXPCMDSTATE EnumerateMatchFolderDirectory(ShellMenuItem shellMenuItem) + { + if (shellMenuItem.SubShellMenuItem.Count is 0) + { + return shellMenuItem.FolderDirectory ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else + { + bool isMatched = false; + foreach (ShellMenuItem subShellMenuItem in shellMenuItem.SubShellMenuItem) + { + if (EnumerateMatchFolderDirectory(subShellMenuItem) is EXPCMDSTATE.ECS_ENABLED) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + } + + /// + /// 枚举子菜单项选项,检查是否有符合驱动器的选项 + /// + private EXPCMDSTATE EnumerateMatchFolderDrive(ShellMenuItem shellMenuItem) + { + if (shellMenuItem.SubShellMenuItem.Count is 0) + { + return shellMenuItem.FolderDrive ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else + { + bool isMatched = false; + foreach (ShellMenuItem subShellMenuItem in shellMenuItem.SubShellMenuItem) + { + if (EnumerateMatchFolderDrive(subShellMenuItem) is EXPCMDSTATE.ECS_ENABLED) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + } + + /// + /// 枚举子菜单项选项,检查是否有符合文件的选项 + /// + private EXPCMDSTATE EnumerateMatchFile(ShellMenuItem shellMenuItem, string filePath) + { + if (shellMenuItem.SubShellMenuItem.Count is 0) + { + // 匹配文件格式 + if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[0])) + { + return EXPCMDSTATE.ECS_HIDDEN; + } + else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[1])) + { + string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); + + bool isMatched = false; + string fileName = Path.GetFileName(filePath); + foreach (string fileMatchFormat in fileMatchFormatArray) + { + if (fileName.Equals(fileMatchFormat.Trim(), StringComparison.OrdinalIgnoreCase)) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[2])) + { + string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); + bool isMatched = false; + + foreach (string fileMatchFormat in fileMatchFormatArray) + { + Regex fileMatchRegex = new(fileMatchFormat.Trim()); + string fileName = Path.GetFileName(filePath); + + if (fileMatchRegex.IsMatch(fileName)) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[3])) + { + string[] fileMatchFormatArray = shellMenuItem.MenuFileMatchFormatText.Split('|'); + bool isMatched = false; + string extensionName = Path.GetExtension(filePath); + + foreach (string fileMatchFormat in fileMatchFormatArray) + { + if (extensionName.Equals(fileMatchFormat.Trim(), StringComparison.OrdinalIgnoreCase)) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + else if (shellMenuItem.MenuFileMatchRule.Equals(ShellMenuService.FileMatchRuleList[4])) + { + return EXPCMDSTATE.ECS_ENABLED; + } + else + { + return EXPCMDSTATE.ECS_HIDDEN; + } + } + else + { + bool isMatched = false; + foreach (ShellMenuItem subShellMenuItem in shellMenuItem.SubShellMenuItem) + { + if (EnumerateMatchFile(subShellMenuItem, filePath) is EXPCMDSTATE.ECS_ENABLED) + { + isMatched = true; + break; + } + } + + return isMatched ? EXPCMDSTATE.ECS_ENABLED : EXPCMDSTATE.ECS_HIDDEN; + } + } } } diff --git a/WindowsToolsShellExtension/Extensions/ShellMenu/ShellMenuItem.cs b/WindowsToolsShellExtension/Extensions/ShellMenu/ShellMenuItem.cs index 0ecfa6b..f9e6f12 100644 --- a/WindowsToolsShellExtension/Extensions/ShellMenu/ShellMenuItem.cs +++ b/WindowsToolsShellExtension/Extensions/ShellMenu/ShellMenuItem.cs @@ -63,6 +63,11 @@ public sealed class ShellMenuItem /// public string MenuParameter { get; set; } + /// + /// 是否总是需要提权运行 + /// + public bool IsAlwaysRunAsAdministrator { get; set; } + /// /// 是否启用文件夹背景菜单项 /// diff --git a/WindowsToolsShellExtension/Helpers/Root/ProcessHelper.cs b/WindowsToolsShellExtension/Helpers/Root/ProcessHelper.cs deleted file mode 100644 index 409c9e6..0000000 --- a/WindowsToolsShellExtension/Helpers/Root/ProcessHelper.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32; -using WindowsToolsShellExtension.WindowsAPI.PInvoke.User32; - -namespace WindowsToolsShellExtension.Helpers.Root -{ - /// - /// 进程辅助类 - /// - public static class ProcessHelper - { - /// - /// 创建进程 - /// - public static unsafe void StartProcess(string processName, string arguments, out int processid) - { - Kernel32Library.GetStartupInfo(out STARTUPINFO startupInfo); - startupInfo.lpReserved = IntPtr.Zero; - startupInfo.lpDesktop = IntPtr.Zero; - startupInfo.lpTitle = IntPtr.Zero; - startupInfo.dwX = 0; - startupInfo.dwY = 0; - startupInfo.dwXSize = 0; - startupInfo.dwYSize = 0; - startupInfo.dwXCountChars = 500; - startupInfo.dwYCountChars = 500; - startupInfo.dwFlags = STARTF.STARTF_USESHOWWINDOW; - startupInfo.wShowWindow = WindowShowStyle.SW_SHOWNORMAL; - startupInfo.cbReserved2 = 0; - startupInfo.lpReserved2 = IntPtr.Zero; - startupInfo.cb = sizeof(STARTUPINFO); - - bool createResult = Kernel32Library.CreateProcess(null, string.Format("{0} {1}", processName, arguments), IntPtr.Zero, IntPtr.Zero, false, CREATE_PROCESS_FLAGS.None, IntPtr.Zero, null, ref startupInfo, out PROCESS_INFORMATION processInformation); - - if (createResult) - { - if (processInformation.hProcess != IntPtr.Zero) - { - Kernel32Library.CloseHandle(processInformation.hProcess); - } - - if (processInformation.hThread != IntPtr.Zero) - { - Kernel32Library.CloseHandle(processInformation.hThread); - } - - processid = processInformation.dwProcessId; - } - else - { - processid = 0; - } - } - } -} diff --git a/WindowsToolsShellExtension/Helpers/Root/RegistryHelper.cs b/WindowsToolsShellExtension/Helpers/Root/RegistryHelper.cs index 489e29a..ab51ebd 100644 --- a/WindowsToolsShellExtension/Helpers/Root/RegistryHelper.cs +++ b/WindowsToolsShellExtension/Helpers/Root/RegistryHelper.cs @@ -38,7 +38,8 @@ public static T ReadRegistryKey(string rootKey, string key) // 字符串类型 if (kind is RegistryValueKind.REG_SZ) { - value = (T)(object)Encoding.Unicode.GetString(data, 0, length); + string regValue = Encoding.Unicode.GetString(data, 0, length); + value = (T)(object)regValue[..^1]; } // 字符串类型 else if (kind is RegistryValueKind.REG_EXPAND_SZ) @@ -50,16 +51,19 @@ public static T ReadRegistryKey(string rootKey, string key) { value = (T)(object)data; } - // 32 位数字或布尔值 - else if (kind is RegistryValueKind.REG_DWORD || kind is RegistryValueKind.REG_DWORD_LITTLE_ENDIAN || kind is RegistryValueKind.REG_DWORD_BIG_ENDIAN) + // 32 位数字 + else if (kind is RegistryValueKind.REG_DWORD) { - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(data); - } + int regValue = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); - // 布尔值以字符串整数类型值存储 - value = typeof(T) == typeof(bool) || typeof(T) == typeof(bool?) ? (T)(object)BitConverter.ToBoolean(data, 0) : (T)(object)BitConverter.ToUInt32(data, 0); + value = typeof(T) == typeof(bool) || typeof(T) == typeof(bool?) ? (T)(object)Convert.ToBoolean(regValue) : (T)(object)regValue; + } + // 采用 big-endian 格式的 32 位数字 + else if (kind is RegistryValueKind.REG_DWORD_BIG_ENDIAN) + { + int regValue = data[3] | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); + + value = typeof(T) == typeof(bool) || typeof(T) == typeof(bool?) ? (T)(object)Convert.ToBoolean(regValue) : (T)(object)value; } // 字符串序列 else if (kind is RegistryValueKind.REG_MULTI_SZ) @@ -77,13 +81,12 @@ public static T ReadRegistryKey(string rootKey, string key) value = (T)(object)stringList.ToArray(); } // 64 位数字 - else if (kind is RegistryValueKind.REG_QWORD || kind is RegistryValueKind.REG_QWORD_LITTLE_ENDIAN) + else if (kind is RegistryValueKind.REG_QWORD) { - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(data); - } - value = (T)(object)BitConverter.ToUInt64(data, 0); + uint numLow = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); + uint numHigh = (uint)(data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24)); + long keyValue = (long)(((ulong)numHigh << 32) | numLow); + value = (T)(object)keyValue; } } } @@ -120,7 +123,7 @@ public static RegistryEnumKeyItem EnumSubKey(string rootKey) int result; int nameLength = name.Length; - while ((result = Advapi32Library.RegEnumKeyEx(hKey, subKeyList.Count, ref MemoryMarshal.GetReference(name), ref nameLength, null, null, null, null)) != 259) + while ((result = Advapi32Library.RegEnumKeyEx(hKey, subKeyList.Count, ref MemoryMarshal.GetReference(name), ref nameLength, null, null, null, null)) is not 259) { if (result is 0) { diff --git a/WindowsToolsShellExtension/Properties/AssemblyInfo.cs b/WindowsToolsShellExtension/Properties/AssemblyInfo.cs index cb271dc..c592c98 100644 --- a/WindowsToolsShellExtension/Properties/AssemblyInfo.cs +++ b/WindowsToolsShellExtension/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ [assembly: AssemblyCompany("高怡飞")] [assembly: AssemblyCopyright("Copyright ©2024 高怡飞, All Rights Reserved.")] [assembly: AssemblyDescription("Windows 工具箱 右键菜单扩展")] -[assembly: AssemblyFileVersion("2.8.825.0")] -[assembly: AssemblyInformationalVersion("2.8.825.0")] +[assembly: AssemblyFileVersion("2.9.904.0")] +[assembly: AssemblyInformationalVersion("2.9.904.0")] [assembly: AssemblyProduct("Windows 工具箱 右键菜单扩展")] [assembly: AssemblyTitle("Windows 工具箱 右键菜单扩展")] -[assembly: AssemblyVersion("2.8.825.0")] +[assembly: AssemblyVersion("2.9.904.0")] // 应用程序默认区域性的资源控制器设置 [assembly: NeutralResourcesLanguage("en-us")] diff --git a/WindowsToolsShellExtension/Services/Shell/ShellMenuService.cs b/WindowsToolsShellExtension/Services/Shell/ShellMenuService.cs index baedfd3..574445a 100644 --- a/WindowsToolsShellExtension/Services/Shell/ShellMenuService.cs +++ b/WindowsToolsShellExtension/Services/Shell/ShellMenuService.cs @@ -58,9 +58,7 @@ public static ShellMenuItem GetShellMenuItem() // 获取根菜单项下的所有子项(包括递归后的项) RegistryEnumKeyItem shellMenuRegistryKeyItem = RegistryHelper.EnumSubKey(shellMenuKey); - return shellMenuRegistryKeyItem.SubRegistryKeyList.Count is 1 - ? EnumShellMenuItem(shellMenuRegistryKeyItem.SubRegistryKeyList[0]) - : null; + return shellMenuRegistryKeyItem.SubRegistryKeyList.Count is 1 ? EnumShellMenuItem(shellMenuRegistryKeyItem.SubRegistryKeyList[0]) : null; } /// @@ -83,6 +81,7 @@ private static ShellMenuItem EnumShellMenuItem(RegistryEnumKeyItem registryEnumK DarkThemeIconPath = currentMenuItem.DarkThemeIconPath, MenuProgramPath = currentMenuItem.MenuProgramPath, MenuParameter = currentMenuItem.MenuParameter, + IsAlwaysRunAsAdministrator = currentMenuItem.IsAlwaysRunAsAdministrator, FolderBackground = currentMenuItem.FolderBackground, FolderDesktop = currentMenuItem.FolderDesktop, FolderDirectory = currentMenuItem.FolderDirectory, @@ -124,6 +123,7 @@ private static ShellMenuItem GetShellItemInfo(string menuKey) string darkThemeIconPath = RegistryHelper.ReadRegistryKey(menuKey, "DarkThemeIconPath"); string menuProgramPathText = RegistryHelper.ReadRegistryKey(menuKey, "MenuProgramPath"); string menuParameter = RegistryHelper.ReadRegistryKey(menuKey, "MenuParameter"); + bool? isAlwaysRunAsAdministrator = RegistryHelper.ReadRegistryKey(menuKey, "IsAlwaysRunAsAdministrator"); bool? folderBackground = RegistryHelper.ReadRegistryKey(menuKey, "FolderBackground"); bool? folderDesktop = RegistryHelper.ReadRegistryKey(menuKey, "FolderDesktop"); bool? folderDirectory = RegistryHelper.ReadRegistryKey(menuKey, "FolderDirectory"); @@ -145,6 +145,7 @@ private static ShellMenuItem GetShellItemInfo(string menuKey) shellMenuItem.DarkThemeIconPath = darkThemeIconPath; shellMenuItem.MenuProgramPath = menuProgramPathText; shellMenuItem.MenuParameter = menuParameter; + shellMenuItem.IsAlwaysRunAsAdministrator = isAlwaysRunAsAdministrator.HasValue && isAlwaysRunAsAdministrator.Value; shellMenuItem.FolderBackground = folderBackground.HasValue && folderBackground.Value; shellMenuItem.FolderDesktop = folderDesktop.HasValue && folderDesktop.Value; shellMenuItem.FolderDirectory = folderDirectory.HasValue && folderDirectory.Value; diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/CREATE_PROCESS_FLAGS.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/CREATE_PROCESS_FLAGS.cs deleted file mode 100644 index bdf93aa..0000000 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/CREATE_PROCESS_FLAGS.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; - -namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32 -{ - /// - /// 进程创建标志 - /// CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW 和 CreateProcessWithTokenW 函数使用以下进程创建标志。 可以在任意组合中指定它们, - /// - [Flags] - public enum CREATE_PROCESS_FLAGS - { - /// - /// 无任何标志 - /// - None = 0x0, - - /// - /// 与作业关联的进程的子进程不与作业相关联。 - /// 如果调用进程未与作业关联,则此常量不起作用。 如果调用进程与作业相关联,则作业必须设置 JOB_OBJECT_LIMIT_BREAKAWAY_OK 限制。 - /// - CREATE_BREAKAWAY_FROM_JOB = 0x01000000, - - /// - /// 新进程不会继承调用进程的错误模式。 相反,新进程会获取默认错误模式。 - /// 此功能对于禁用硬错误的多线程 shell 应用程序尤其有用。 - /// 默认行为是让新进程继承调用方的错误模式。 设置此标志会更改默认行为。 - /// - CREATE_DEFAULT_ERROR_MODE = 0x04000000, - - /// - /// 新进程具有新控制台,而不是继承其父控制台, (默认) 。 有关详细信息,请参阅 “创建控制台”。 - /// 此标志不能与 DETACHED_PROCESS一起使用。 - /// - CREATE_NEW_CONSOLE = 0x00000010, - - /// - /// 新进程是新进程组的根进程。 进程组包括此根进程的子代的所有进程。 新进程组的进程标识符与进程标识符相同,该标识符在 lpProcessInformation 参数中返回。 GenerateConsoleCtrlEvent 函数使用进程组,以便向一组控制台进程发送 CTRL+BREAK 信号。 - /// 如果指定此标志,将为新进程组中的所有进程禁用 CTRL+C 信号。 - /// 如果使用 CREATE_NEW_CONSOLE 指定,则忽略此标志。 - /// - CREATE_NEW_PROCESS_GROUP = 0x00000200, - - /// - /// 此过程是一个在没有控制台窗口的情况下运行的控制台应用程序。 因此,未设置应用程序的控制台句柄。 - /// T如果应用程序不是控制台应用程序,或者将其与 CREATE_NEW_CONSOLE 或 DETACHED_PROCESS 一起使用,则忽略此标志。 - /// - CREATE_NO_WINDOW = 0x08000000, - - /// - /// 进程将作为受保护的进程运行。 系统限制对受保护进程和受保护进程的线程的访问。 有关如何与受保护进程交互的详细信息,请参阅 进程安全性和访问权限。 - /// 若要激活受保护的进程,二进制文件必须具有特殊的签名。 此签名由 Microsoft 提供,但目前不适用于非 Microsoft 二进制文件。 目前有四个受保护的进程:媒体基础、音频引擎、Windows错误报告和系统。 还必须对加载到这些二进制文件的组件进行签名。 多媒体公司可以利用前两个受保护的流程。 有关详细信息,请参阅 受保护的媒体路径概述。 - /// Windows Server 2003 和 Windows XP:不支持此值。 - /// - CREATE_PROTECTED_PROCESS = 0x00040000, - - /// - /// 允许调用方执行绕过通常自动应用于进程的进程限制的子进程。 - /// - CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, - - /// - /// 此标志允许在Virtualization-Based安全环境中运行的安全进程启动。 - /// - CREATE_SECURE_PROCESS = 0x00400000, - - /// - /// 仅当启动基于 16 位Windows的应用程序时,此标志才有效。 如果已设置,新进程将在专用虚拟 DOS 计算机中运行 (VDM) 。 默认情况下,所有基于 16 位Windows的应用程序在单个共享 VDM 中作为线程运行。 单独运行的优点是,崩溃只会终止单个 VDM;在不同 VM 中运行的任何其他程序将继续正常运行。 此外,在单独的 VM 中运行的基于 16 位Windows的应用程序具有单独的输入队列。 这意味着,如果一个应用程序暂时停止响应,则单独的 VM 中的应用程序将继续接收输入。 单独运行的缺点是,需要更多内存才能执行此操作。 仅当用户请求 16 位应用程序应在自己的 VDM 中运行时,才应使用此标志。 - /// - CREATE_SEPARATE_WOW_VDM = 0x00000800, - - /// - /// 仅当启动基于 16 位Windows的应用程序时,标志才有效。 如果 WIN.INI Windows 部分中的 DefaultSeparateVDM 开关为 TRUE,则此标志将替代该开关。 新进程在共享虚拟 DOS 计算机中运行。 - /// - CREATE_SHARED_WOW_VDM = 0x00001000, - - /// - /// 新进程的主线程处于挂起状态创建,在调用 ResumeThread 函数之前不会运行。 - /// - CREATE_SUSPENDED = 0x00000004, - - /// - /// 如果设置了此标志, 则 lpEnvironment 指向的环境块使用 Unicode 字符。 否则,环境块使用 ANSI 字符。 - /// - CREATE_UNICODE_ENVIRONMENT = 0x00000400, - - /// - /// 调用线程启动并调试新进程。 它可以使用 WaitForDebugEvent 函数接收所有相关的调试事件。 - /// - DEBUG_ONLY_THIS_PROCESS = 0x00000002, - - /// - /// 调用线程启动并调试由新进程创建的新进程和所有子进程。 它可以使用 WaitForDebugEvent 函数接收所有相关的调试事件。 - /// 使用 DEBUG_PROCESS 的进程将成为调试链的根目录。 这一点一直持续到链中的另一个进程通过 DEBUG_PROCESS 创建。 - /// 如果此标志与 DEBUG_ONLY_THIS_PROCESS 结合使用,则调用方仅调试新进程,而不调试任何子进程。 - /// - DEBUG_PROCESS = 0x00000001, - - /// - /// 对于控制台进程,新进程不会 (默认) 继承其父级的控制台。 新进程稍后可以调用 AllocConsole 函数来创建控制台。 有关详细信息,请参阅 “创建控制台”。 - /// 此值不能与 CREATE_NEW_CONSOLE 一起使用。 - /// - DETACHED_PROCESS = 0x00000008, - - /// - /// 此过程是使用扩展的启动信息创建的; lpStartupInfo 参数指定 STARTUPINFOEX 结构。 - /// Windows Server 2003 和 Windows XP:不支持此值。 - /// - EXTENDED_STARTUPINFO_PRESENT = 0x00080000, - - /// - /// 进程继承其父级的相关性。 如果父进程具有多个 处理器组中的线程,新进程将继承父组使用的任意组的组相对相关性。 - /// Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP:不支持此值。 - /// - INHERIT_PARENT_AFFINITY = 0x00010000 - } -} diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/Kernel32Library.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/Kernel32Library.cs index 472da31..c0c4848 100644 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/Kernel32Library.cs +++ b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/Kernel32Library.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32 { @@ -7,90 +6,6 @@ public static partial class Kernel32Library { private const string Kernel32 = "kernel32.dll"; - /// - /// 关闭打开的对象句柄。 - /// - /// 打开对象的有效句柄。 - /// - /// 如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 - /// 如果应用程序在调试器下运行,则如果函数收到无效的句柄值或伪句柄值,该函数将引发异常。 - /// 如果两次关闭句柄,或者对 FindFirstFile 函数返回的句柄调用 CloseHandle,而不是调用 FindClose 函数,则可能会出现这种情况。 - /// - [LibraryImport(Kernel32, EntryPoint = "CloseHandle", SetLastError = false), PreserveSig] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool CloseHandle(IntPtr hObject); - - /// - /// 创建新进程及其主线程。 新进程在调用进程的安全上下文中运行。 - /// 如果调用进程正在模拟其他用户,则新进程将令牌用于调用进程,而不是模拟令牌。 若要在模拟令牌表示的用户的安全上下文中运行新进程,请使用 CreateProcessAsUser 或 CreateProcessWithLogonW 函数。 - /// - /// - /// 要执行的模块的名称。 此模块可以是基于 Windows 的应用程序。 它可以是某种其他类型的模块 (例如 MS-DOS 或 OS/2) (如果本地计算机上提供了相应的子系统)。 - /// 字符串可以指定要执行的模块的完整路径和文件名,也可以指定部分名称。 对于部分名称,函数使用当前驱动器和当前目录来完成规范。 函数不会使用搜索路径。 此参数必须包含文件扩展名;不采用默认扩展名。 - /// 参数可以为 NULL。 在这种情况下,模块名称必须是 lpCommandLine 字符串中第一个空格分隔的标记。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名结束和参数开始的位置;否则,文件名不明确。 - /// 如果可执行模块是 16 位应用程序, lpApplicationName 应为 NULL, lpCommandLine 指向的字符串应指定可执行模块及其参数。 - /// 若要运行批处理文件,必须启动命令解释器;将 lpApplicationName 设置为 cmd.exe 并将 lpCommandLine 设置为以下参数:/c 加上批处理文件的名称。 - /// - /// - /// 要执行的命令行。 - /// 此字符串的最大长度为 32,767 个字符,包括 Unicode 终止 null 字符。 如果 lpApplicationName 为 NULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 个字符。 - /// 此函数的 Unicode 版本 CreateProcessW 可以修改此字符串的内容。 因此,此参数不能是指向只读内存(的指针,例如 const 变量或文本字符串) 。 如果此参数是常量字符串,该函数可能会导致访问冲突。 - /// 参数可以为 NULL。 在这种情况下,函数使用 lpApplicationName 指向的字符串作为命令行。 - /// 如果 lpApplicationName 和 lpCommandLine 均为非 NULL,则 lpApplicationName 指向的以 null 结尾的字符串将指定要执行的模块, 而 lpCommandLine 指向的以 null 结尾的字符串将指定命令行。 新进程可以使用 GetCommandLine 检索整个命令行。 用 C 编写的控制台进程可以使用 argc 和 argv 参数来分析命令行。 由于 argv[0] 是模块名称,因此 C 程序员通常将模块名称重复为命令行中的第一个标记。 - /// 如果 lpApplicationName 为 NULL,则命令行的第一个空格分隔标记将指定模块名称。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名结束和参数开始的位置 (请参阅 lpApplicationName 参数) 的说明。 如果文件名不包含扩展名,则追加.exe。 因此,如果文件扩展名为 .com,则此参数必须包含 .com 扩展名。 如果文件名以不带扩展名的句点 (.) 结尾,或者文件名包含路径,则不会追加.exe。 如果文件名不包含目录路径,系统会按以下顺序搜索可执行文件: - /// 1.从中加载应用程序的目录。 - /// 2.父进程的当前目录。 - /// 3.32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。 - /// 4.16 位 Windows 系统目录。 没有获取此目录的路径的函数,但会对其进行搜索。 此目录的名称为 System。 - /// 5.Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。 - /// 6.PATH 环境变量中列出的目录。 请注意,此函数不会搜索应用程序路径注册表项指定的每个 应用程序 路径。 若要在搜索序列中包含此每个应用程序的路径,请使用 ShellExecute 函数。 - /// 系统向命令行字符串添加一个终止 null 字符,以将文件名与参数分开。 这会将原始字符串划分为两个字符串以供内部处理。 - /// - /// - /// 指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的新进程对象的句柄是否可以由子进程继承。 如果 lpProcessAttributes 为 NULL,则不能继承句柄。 - /// 结构的 lpSecurityDescriptor 成员为新进程指定安全描述符。 如果 lpProcessAttributes 为 NULL 或 lpSecurityDescriptor 为 NULL,则进程将获取默认安全描述符。 进程的默认安全描述符中的 ACL 来自创建者的主令牌。Windowsxp: 进程的默认安全描述符中的 ACL 来自创建者的主要令牌或模拟令牌。 此行为随 Windows XP SP2 和 Windows Server 2003 更改。 - /// - /// - /// 指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的新线程对象的句柄是否可以由子进程继承。 如果 lpProcessAttributes 为 NULL,则不能继承句柄。 - /// 结构的 lpSecurityDescriptor 成员指定主线程的安全描述符。 如果 lpThreadAttributes 为 NULL 或 lpSecurityDescriptor 为 NULL,则线程获取默认安全描述符。 线程的默认安全描述符中的 ACL 来自进程令牌。Windowsxp: 线程的默认安全描述符中的 ACL 来自创建者的主令牌或模拟令牌。 此行为随 Windows XP SP2 和 Windows Server 2003 更改。 - /// - /// - /// 如果此参数为 TRUE,则调用进程中的每个可继承句柄都由新进程继承。 如果参数为 FALSE,则不继承句柄。 请注意,继承的句柄与原始句柄具有相同的值和访问权限。 有关可继承句柄的其他讨论,请参阅备注。 - /// 终端服务: 不能跨会话继承句柄。 此外,如果此参数为 TRUE,则必须在与调用方相同的会话中创建进程。 - /// 受保护的流程灯(PPL) 进程: 当 PPL 进程创建非 PPL 进程时,将阻止泛型句柄继承,因为不允许将PROCESS_DUP_HANDLE从非 PPL 进程转换为 PPL 进程。 请参阅 进程安全性和访问权限 - /// - /// - /// 控制优先级类和进程的创建的标志。 有关值的列表,请参阅 进程创建标志。 - /// 此参数还控制新进程的优先级类,该类用于确定进程线程的计划优先级。 有关值的列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS ,除非创建过程的优先级类 IDLE_PRIORITY_CLASS 或 BELOW_NORMAL_PRIORITY_CLASS。 在这种情况下,子进程接收调用进程的默认优先级类。 - /// 如果 dwCreationFlags 参数的值为 0: - /// 1.进程同时继承调用方和父级控制台的错误模式。 - /// 2.假定新进程的环境块包含 ANSI 字符(请参阅 lpEnvironment 参数以获取) 的其他信息。 - /// 3.基于 16 位 Windows 的应用程序在共享的虚拟 DOS 计算机中运行, (VDM) 。 - /// - /// - /// 指向新进程的环境块的指针。 如果此参数为 NULL,则新进程使用调用进程的 环境。环境块由以 null 结尾的字符串的以 null 结尾的块组成。 - /// 每个字符串采用以下格式:名字=value\0 - /// 由于等号用作分隔符,因此不得在环境变量的名称中使用。环境块可以包含 Unicode 或 ANSI 字符。 如果 lpEnvironment 指向的环境块包含 Unicode 字符,请确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。如果进程的环境块的总大小超过 32,767 个字符,则此函数的 ANSI 版本 CreateProcessA 将失败。 - /// 请注意,ANSI 环境块以两个零字节结尾:一个字节用于最后一个字符串,另一个用于终止该块。 Unicode 环境块以四个零字节结尾:两个作为最后一个字符串,另外两个用于终止该块。 - /// - /// - /// 进程当前目录的完整路径。 字符串还可以指定 UNC 路径。如果此参数为 NULL,则新进程将具有与调用进程相同的当前驱动器和目录。 (此功能主要用于需要启动应用程序并指定其初始驱动器和工作目录的 shell。) - /// - /// - /// 指向 STARTUPINFO 或 STARTUPINFOEX 结构的指针。若要设置扩展属性,请使用 STARTUPINFOEX 结构并在 dwCreationFlags 参数中指定 EXTENDED_STARTUPINFO_PRESENT。当不再需要 STARTUPINFO 或 STARTUPINFOEX 中的句柄时,必须使用 CloseHandle 关闭它们。 - /// 调用方负责确保 STARTUPINFO 中的标准句柄字段包含有效的句柄值。 即使 dwFlags 成员指定了 STARTF_USESTDHANDLES,这些字段也保持不变地复制到子进程,而无需验证。 不正确的值可能导致子进程行为不端或崩溃。 使用应用程序验证程序运行时验证工具检测无效句柄。 - /// - /// - /// 指向 PROCESS_INFORMATION 结构的指针,该结构接收有关新进程的标识信息。当不再需要 PROCESS_INFORMATION 中的句柄时,必须使用 CloseHandle 将其关闭。 - /// - /// - /// 如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 - /// 请注意,函数在进程完成初始化之前返回 。 如果找不到所需的 DLL 或无法初始化,则进程将终止。 若要获取进程的终止状态,请调用 GetExitCodeProcess。 - /// - [LibraryImport(Kernel32, EntryPoint = "CreateProcessW", SetLastError = false, StringMarshalling = StringMarshalling.Utf16), PreserveSig] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool CreateProcess([MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, CREATE_PROCESS_FLAGS dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); - /// /// 获取调用进程的包路径。 /// @@ -99,12 +14,5 @@ public static partial class Kernel32Library /// 如果函数成功,则返回 ERROR_SUCCESS。 否则,函数将返回错误代码。 [LibraryImport(Kernel32, EntryPoint = "GetCurrentPackagePath", SetLastError = false), PreserveSig] public static unsafe partial uint GetCurrentPackagePath(ref int length, char* path); - - /// - /// 检索创建调用进程时指定的 STARTUPINFO 结构的内容。 - /// - /// 指向接收启动信息的 STARTUPINFO 结构的指针。 - [LibraryImport(Kernel32, EntryPoint = "GetStartupInfoW", SetLastError = false), PreserveSig] - public static partial void GetStartupInfo(out STARTUPINFO lpStartupInfo); } } diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/PROCESS_INFORMATION.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/PROCESS_INFORMATION.cs deleted file mode 100644 index 3fbe6b7..0000000 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/PROCESS_INFORMATION.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32 -{ - /// - /// 包含有关新创建的进程及其主线程的信息。 它与 CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW 或 CreateProcessWithTokenW 函数一起使用。 - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct PROCESS_INFORMATION - { - /// - /// 新创建的进程的句柄。 句柄用于在对进程对象执行操作的所有函数中指定进程。 - /// - public IntPtr hProcess; - - /// - /// 新创建的进程的主线程的句柄。 句柄用于在线程对象上执行操作的所有函数中指定线程。 - /// - public IntPtr hThread; - - /// - /// 可用于标识进程的值。 从创建进程到进程的所有句柄关闭并释放进程对象为止,该值有效;此时,可以重复使用标识符。 - /// - public int dwProcessId; - - /// - /// 可用于标识线程的值。 在线程创建到线程的所有句柄关闭且线程对象释放之前,该值有效;此时,可以重复使用标识符。 - /// - public int dwThreadId; - } -} diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTF.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTF.cs deleted file mode 100644 index dd2cd4c..0000000 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTF.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; - -namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32 -{ - /// - /// 一个位域,用于确定进程创建窗口时是否使用某些 STARTUPINFO 成员。 - /// - [Flags] - public enum STARTF - { - /// - /// 无标志 - /// - None = 0x0, - - /// - /// 指示在调用 CreateProcessAsUser 后,游标处于反馈模式两秒。 (“鼠标”控制面板实用工具中的“指针”选项卡) 显示“在后台工作”光标。 - /// 如果在这两秒内,进程进行第一次 GUI 调用,则系统会为进程再提供 5 秒。 如果在这五秒内进程显示窗口,则系统会为进程再提供 5 秒的时间以完成该窗口的绘制。 - /// 无论进程是否正在绘制,系统在首次调用 GetMessage 后关闭反馈游标。 - /// - STARTF_FORCEONFEEDBACK = 0x00000040, - - /// - /// 指示在进程启动时,反馈游标被强制关闭。 将显示“普通选择”游标。 - /// - STARTF_FORCEOFFFEEDBACK = 0x00000080, - - /// - /// 指示进程创建的任何窗口都不能固定在任务栏上。 - /// 此标志必须与 STARTF_TITLEISAPPID 结合使用。 - /// - STARTF_PREVENTPINNING = 0x00002000, - - /// - /// 指示进程应在全屏模式下运行,而不是在窗口模式下运行。 - /// 此标志仅适用于在 x86 计算机上运行的控制台应用程序。 - /// - STARTF_RUNFULLSCREEN = 0x00000020, - - /// - /// lpTitle 成员包含 AppUserModelID。 此标识符控制任务栏和 “开始” 菜单显示应用程序的方式,并使它能够与正确的快捷方式和跳转列表相关联。 通常,应用程序将使用 SetCurrentProcessExplicitAppUserModelID 和 GetCurrentProcessExplicitAppUserModelID 函数,而不是设置此标志。 有关详细信息,请参阅 应用程序用户模型 ID。 - /// 如果使用 STARTF_PREVENTPINNING,则无法将应用程序窗口固定到任务栏上。 应用程序使用任何 AppUserModelID 相关的窗口属性仅覆盖该窗口的此设置。 - /// 此标志不能与 STARTF_TITLEISLINKNAME 一起使用。 - /// - STARTF_TITLEISAPPID = 0x00001000, - - /// - /// lpTitle 成员包含用户调用启动此过程的快捷文件 (.lnk) 的路径。 调用指向已启动的应用程序的 .lnk 文件时,通常由 shell 设置此值。 大多数应用程序不需要设置此值。 - /// 此标志不能与 STARTF_TITLEISAPPID 一起使用。 - /// - STARTF_TITLEISLINKNAME = 0x00000800, - - /// - /// 命令行来自不受信任的源。 有关详细信息,请参阅“备注”。 - /// - STARTF_UNTRUSTEDSOURCE = 0x00008000, - - /// - /// dwXCountChars 和 dwYCountChars 员包含其他信息。 - /// - STARTF_USECOUNTCHARS = 0x00000008, - - /// - /// dwFillAttribute 成员包含其他信息。 - /// - STARTF_USEFILLATTRIBUTE = 0x00000010, - - /// - /// hStdInput 成员包含其他信息。 - /// 此标志不能与 STARTF_USESTDHANDLES 一起使用。 - /// - STARTF_USEHOTKEY = 0x00000200, - - /// - /// dwX 和 dwY 成员包含其他信息。 - /// - STARTF_USEPOSITION = 0x00000004, - - /// - /// wShowWindow 成员包含其他信息。 - /// - STARTF_USESHOWWINDOW = 0x00000001, - - /// - /// dwXSize 和 dwYSize 成员包含其他信息。 - /// - STARTF_USESIZE = 0x00000002, - - /// - /// hStdInput, hStdOutput 和 hStdError 成员包含其他信息。 - /// 如果在调用某个进程创建函数时指定此标志,则句柄必须可继承,并且该函数的 bInheritHandles 参数必须设置为 TRUE。 有关详细信息,请参阅 句柄继承。 - /// 如果在调用 GetStartupInfo 函数时指定了此标志,则这些成员是进程创建期间指定的句柄值或 INVALID_HANDLE_VALUE。 - /// 不再需要句柄时,必须使用 CloseHandle 关闭句柄。 - /// 此标志不能与 STARTF_USEHOTKEY 一起使用。 - /// - STARTF_USESTDHANDLES = 0x00000100 - } -} diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTUPINFO.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTUPINFO.cs deleted file mode 100644 index 206401e..0000000 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Kernel32/STARTUPINFO.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using WindowsToolsShellExtension.WindowsAPI.PInvoke.User32; - -namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Kernel32 -{ - /// - /// 指定创建时进程的主窗口的窗口工作站、桌面、标准句柄和外观。 - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public partial struct STARTUPINFO - { - /// - /// 结构大小(以字节为单位)。 - /// - public int cb; - - /// - /// 保留;必须为 NULL。 - /// - public IntPtr lpReserved; - - /// - /// 桌面的名称,或此过程的桌面和窗口工作站的名称。 字符串中的反斜杠指示字符串包括桌面和窗口工作站名称。有关详细信息,请参阅 与桌面的线程连接。 - /// - public IntPtr lpDesktop; - - /// - /// 对于控制台进程,如果创建新的控制台窗口,则这是标题栏中显示的标题。 如果为 NULL,则可执行文件的名称将改为用作窗口标题。 对于不创建新控制台窗口的 GUI 或控制台进程,此参数必须为 NULL。 - /// - public IntPtr lpTitle; - - /// - /// 如果 dwFlags 指定 STARTF_USEPOSITION,则如果创建新窗口(以像素为单位),则此成员是窗口左上角的 x 偏移量。 否则,将忽略此成员。 - /// 偏移量来自屏幕左上角。 对于 GUI 进程,新进程首次调用 CreateWindowEx 以创建重叠窗口(如果 CreateWindowEx 的 x 参数CW_USEDEFAULT)时,将使用指定的位置。 - /// - public int dwX; - - /// - /// 如果 dwFlags 指定 STARTF_USEPOSITION,则如果创建新窗口(以像素为单位),则此成员是窗口左上角的 y 偏移量。 否则,将忽略此成员。 - /// 偏移量来自屏幕左上角。 对于 GUI 进程,新进程首次调用 CreateWindowEx 以创建重叠窗口(如果 CreateWindowEx 的 y 参数CW_USEDEFAULT)时,将使用指定的位置。 - /// - public int dwY; - - /// - /// 如果 dwFlags 指定 STARTF_USESIZE,则如果创建新窗口(以像素为单位),则此成员是窗口的宽度。 否则,将忽略此成员。 - /// 对于 GUI 进程,这仅在新进程调用 CreateWindowEx 时首次调用 CreateWindow 以创建重叠窗口(如果 CreateWindowEx 的 nWidth 参数CW_USEDEFAULT)。 - /// - public int dwXSize; - - /// - /// 如果 dwFlags 指定 STARTF_USESIZE,则如果创建新窗口(以像素为单位),则此成员是窗口的高度。 否则,将忽略此成员。 - /// 对于 GUI 进程,仅当新进程调用 CreateWindowEx 以创建重叠窗口(如果 CreateWindowEx 的 nHeight 参数CW_USEDEFAULT)时,才使用此方法。 - /// - public int dwYSize; - - /// - /// 如果 dwFlags 指定 STARTF_USECOUNTCHARS,如果在控制台进程中创建了一个新的控制台窗口,则此成员以字符列指定屏幕缓冲区宽度。 否则,将忽略此成员。 - /// - public int dwXCountChars; - - /// - /// 如果 dwFlags 指定 STARTF_USECOUNTCHARS,如果在控制台进程中创建了一个新的控制台窗口,则此成员在字符行中指定屏幕缓冲区高度。 否则,将忽略此成员。 - /// - public int dwYCountChars; - - /// - /// 如果 dwFlags 指定 STARTF_USEFILLATTRIBUTE,则如果控制台应用程序中创建了新的控制台窗口,则此成员是初始文本和背景色。 否则,将忽略此成员。 - /// 此值可以是以下值的任意组合:FOREGROUND_BLUE、FOREGROUND_GREEN、FOREGROUND_RED、FOREGROUND_INTENSITY、BACKGROUND_BLUE、BACKGROUND_GREEN、BACKGROUND_RED和BACKGROUND_INTENSITY。 例如,以下值组合在白色背景上生成红色文本: - /// FOREGROUND_RED| BACKGROUND_RED| BACKGROUND_GREEN| BACKGROUND_BLUE - /// - public uint dwFillAttribute; - - /// - /// 一个位字段,用于确定进程创建窗口时是否使用某些 STARTUPINFO 成员。 此成员可以是以下一个或多个值。 - /// - public STARTF dwFlags; - - /// - /// If dwFlags 指定 STARTF_USESHOWWINDOW, 则此成员可以是在 ShowWindow 函数的 nCmdShow 参数中指定的任何值,但 SW_SHOWDEFAULT 除外。 否则,将忽略此成员。 - /// 对于 GUI 进程,首次调用 ShowWindow 时,将忽略其 nCmdShow 参数 wShowWindow 指定默认值。 在对 ShowWindow 的后续调用中,如果将 ShowWindow 的 nCmdShow 参数设置为 SW_SHOWDEFAULT,将使用 wShowWindow 成员。 - /// - public WindowShowStyle wShowWindow; - - /// - /// 保留供 C 运行时使用;必须为零。 - /// - public ushort cbReserved2; - - /// - /// 保留供 C 运行时使用;必须为 NULL。 - /// - public IntPtr lpReserved2; - - /// - /// 如果 dwFlags 指定 STARTF_USESTDHANDLES, 则此成员是进程的标准输入句柄。 如果未指定 STARTF_USESTDHANDLES,则标准输入的默认值为键盘缓冲区 - /// 如果 dwFlags 指定 STARTF_USEHOTKEY, 则此成员指定一个热键值,该值作为 WM_SETHOTKEY 消息的 wParam 参数发送到拥有该过程的应用程序创建的第一个符合条件的顶级窗口。如果使用 WindowStyle.WS_POPUP 窗口样式创建窗口,则它不符合条件,除非还设置了 WindowStyleEx.WS_EX_APPWINDOW 扩展窗口样式。 有关详细信息,请参阅 CreateWindowEx。 - /// 否则,将忽略此成员。 - /// - public IntPtr hStdInput; - - /// - /// 如果 dwFlags 指定 STARTF_USESTDHANDLES, 则此成员是进程的标准输出句柄。 否则,将忽略此成员,标准输出的默认值为控制台窗口的缓冲区。 - /// 如果进程从任务栏或跳转列表启动,系统将 hStdOutput 设置为包含用于启动进程的任务栏或跳转列表的监视器的句柄。 有关详细信息,请参阅“备注”。 - /// Windows 7、Windows Server 2008 R2、Windows Vista、Windows Server 2008、Windows XP 和 Windows Server 2003: 此行为是在Windows 8和Windows Server 2012中引入的。 - /// - public IntPtr hStdOutput; - - /// - /// 如果 dwFlags 指定 STARTF_USESTDHANDLES, 则此成员是进程的标准错误句柄。 否则,将忽略此成员,标准错误的默认值为控制台窗口的缓冲区。 - /// - public IntPtr hStdError; - } -} diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shell32/Shell32Library.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shell32/Shell32Library.cs index 27f3c3f..01667ad 100644 --- a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shell32/Shell32Library.cs +++ b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shell32/Shell32Library.cs @@ -10,6 +10,19 @@ public static partial class Shell32Library { public const string Shell32 = "shell32.dll"; + /// + /// 对指定文件执行操作。 + /// + /// 用于显示 UI 或错误消息的父窗口的句柄。 如果操作未与窗口关联,则此值可以为 NULL 。 + /// 指向以 null 结尾的字符串(在本例中称为 谓词)的指针,指定要执行的操作。 可用谓词集取决于特定的文件或文件夹。 通常,对象的快捷菜单中可用的操作是可用的谓词。 + /// 指向 以 null 结尾的字符串的指针,该字符串指定要对其执行指定谓词的文件或对象。 若要指定 Shell 命名空间对象,请传递完全限定分析名称。 请注意,并非所有对象都支持所有谓词。 例如,并非所有文档类型都支持“print”谓词。 如果将相对路径用于 lpDirectory 参数,请不要对 lpFile 使用相对路径。 + /// 如果 lpFile 指定可执行文件,则此参数是指向以 null 结尾的字符串的指针,该字符串指定要传递给应用程序的参数。 此字符串的格式由要调用的谓词决定。 如果 lpFile 指定文档文件, 则 lpParameters 应为 NULL。 + /// 指向 以 null 结尾的字符串的指针,该字符串指定操作) 目录的默认 (。 如果此值为 NULL,则使用当前工作目录。 如果在 lpFile 中提供了相对路径,请不要对 lpDirectory 使用相对路径。 + /// 指定应用程序在打开时如何显示应用程序的标志。 如果 lpFile 指定文档文件,则标志将直接传递给关联的应用程序。 由应用程序决定如何处理它。 它可以是在 ShowWindow 函数的 nCmdShow 参数中指定的任何值。 + /// + [LibraryImport(Shell32, EntryPoint = "ShellExecuteW", SetLastError = false, StringMarshalling = StringMarshalling.Utf16)] + public static partial nint ShellExecute(nint hwnd, [MarshalAs(UnmanagedType.LPWStr)] string lpOperation, [MarshalAs(UnmanagedType.LPWStr)] string lpFile, string lpParameters, [MarshalAs(UnmanagedType.LPWStr)] string lpDirectory, int nShowCmd); + /// /// 检索由文件夹的 KNOWNFOLDERID 标识的已知文件夹的完整路径。 /// diff --git a/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shlwapi/ShlwapiLibrary.cs b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shlwapi/ShlwapiLibrary.cs new file mode 100644 index 0000000..e3cca28 --- /dev/null +++ b/WindowsToolsShellExtension/WindowsAPI/PInvoke/Shlwapi/ShlwapiLibrary.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; + +namespace WindowsToolsShellExtension.WindowsAPI.PInvoke.Shlwapi +{ + /// + /// Shlwapi.dll 函数库 + /// + public static partial class ShlwapiLibrary + { + public const string Shlwapi = "shlwapi.dll"; + + /// + /// 尝试通过查询具有 GetWindow 方法的各种接口,从组件对象模型 (COM) 对象检索窗口句柄。 + /// + /// 指向 COM 对象的指针,此函数将尝试从中获取窗口句柄。 + /// 指向 HWND 的指针,此函数成功返回时接收窗口句柄。 如果未获取窗口句柄,此参数将设置为 NULL。 + /// 如果成功返回窗口句柄,则返回S_OK,否则返回 COM 错误代码。 如果未找到合适的接口,该函数将返回E_NOINTERFACE。 否则,该函数返回由相应接口的 GetWindow 方法返回的 HRESULT。 + [LibraryImport(Shlwapi, EntryPoint = "IUnknown_GetWindow", SetLastError = false), PreserveSig] + public static partial int IUnknown_GetWindow(IntPtr punk, out IntPtr phwnd); + } +}