diff --git a/PowerToys.sln b/PowerToys.sln index e34779c5bb..ac70abbfe9 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11206.111 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" ProjectSection(ProjectDependencies) = postProject @@ -374,8 +374,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AlwaysOnTop", "AlwaysOnTop" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlwaysOnTop", "src\modules\alwaysontop\AlwaysOnTop\AlwaysOnTop.vcxproj", "{1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlwaysOnTopModuleInterface", "src\modules\alwaysontop\AlwaysOnTopModuleInterface\AlwaysOnTopModuleInterface.vcxproj", "{48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.WebSearch", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.WebSearch\Community.PowerToys.Run.Plugin.WebSearch.csproj", "{9F94B303-5E21-4364-9362-64426F8DB932}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MousePointerCrosshairs", "src\modules\MouseUtils\MousePointerCrosshairs\MousePointerCrosshairs.vcxproj", "{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}" @@ -443,8 +441,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{F05E590D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostsEditor.UnitTests", "src\modules\Hosts\Hosts.Tests\HostsEditor.UnitTests.csproj", "{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HostsModuleInterface", "src\modules\Hosts\HostsModuleInterface\HostsModuleInterface.vcxproj", "{B41B888C-7DB8-4747-B262-4062E05A230D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileLocksmith", "FileLocksmith", "{AB82E5DD-C32D-4F28-9746-2C780846188E}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\modules\FileLocksmith\FileLocksmithExt\FileLocksmithExt.vcxproj", "{57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}" @@ -834,6 +830,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LanguageModelProvider", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UI.ViewModels.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UI.ViewModels.UnitTests\Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj", "{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunnerV2", "src\RunnerV2\RunnerV2\RunnerV2.csproj", "{20C43796-E14D-47B2-843A-843CAC9C0D28}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlwaysOnTopModuleInterface", "src\modules\alwaysontop\AlwaysOnTopModuleInterface\AlwaysOnTopModuleInterface.csproj", "{2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -1610,14 +1610,6 @@ Global {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|ARM64.Build.0 = Release|ARM64 {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|x64.ActiveCfg = Release|x64 {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|x64.Build.0 = Release|x64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|ARM64.Build.0 = Debug|ARM64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|x64.ActiveCfg = Debug|x64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|x64.Build.0 = Debug|x64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|ARM64.ActiveCfg = Release|ARM64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|ARM64.Build.0 = Release|ARM64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|x64.ActiveCfg = Release|x64 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|x64.Build.0 = Release|x64 {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|ARM64.ActiveCfg = Debug|ARM64 {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|ARM64.Build.0 = Debug|ARM64 {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x64.ActiveCfg = Debug|x64 @@ -1826,14 +1818,6 @@ Global {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|ARM64.Build.0 = Release|ARM64 {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.ActiveCfg = Release|x64 {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.Build.0 = Release|x64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.Build.0 = Debug|ARM64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.ActiveCfg = Debug|x64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.Build.0 = Debug|x64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.ActiveCfg = Release|ARM64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.Build.0 = Release|ARM64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.ActiveCfg = Release|x64 - {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.Build.0 = Release|x64 {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.Build.0 = Debug|ARM64 {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x64.ActiveCfg = Debug|x64 @@ -3036,6 +3020,22 @@ Global {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.Build.0 = Release|ARM64 {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.ActiveCfg = Release|x64 {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.Build.0 = Release|x64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Debug|ARM64.Build.0 = Debug|ARM64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Debug|x64.ActiveCfg = Debug|x64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Debug|x64.Build.0 = Debug|x64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Release|ARM64.ActiveCfg = Release|ARM64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Release|ARM64.Build.0 = Release|ARM64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Release|x64.ActiveCfg = Release|x64 + {20C43796-E14D-47B2-843A-843CAC9C0D28}.Release|x64.Build.0 = Release|x64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Debug|ARM64.Build.0 = Debug|ARM64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Debug|x64.ActiveCfg = Debug|x64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Debug|x64.Build.0 = Debug|x64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Release|ARM64.ActiveCfg = Release|ARM64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Release|ARM64.Build.0 = Release|ARM64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Release|x64.ActiveCfg = Release|x64 + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3150,7 +3150,6 @@ Global {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704} {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} {9F94B303-5E21-4364-9362-64426F8DB932} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E} = {322566EF-20DC-43A6-B9F8-616AF942579A} {F7C8C0F1-5431-4347-89D0-8E5354F93CF2} = {2F305555-C296-497E-AC20-5FA1B237996A} @@ -3182,7 +3181,6 @@ Global {31D1C81D-765F-4446-AA62-E743F6325049} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A} = {1C48CD47-D610-463A-A53C-AF82DD6C47E7} - {B41B888C-7DB8-4747-B262-4062E05A230D} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} {AB82E5DD-C32D-4F28-9746-2C780846188E} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE} = {AB82E5DD-C32D-4F28-9746-2C780846188E} {E69B044A-2F8A-45AA-AD0B-256C59421807} = {AB82E5DD-C32D-4F28-9746-2C780846188E} @@ -3367,6 +3365,7 @@ Global {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {45354F4F-1414-45CE-B600-51CD1209FD19} = {1AFB6476-670D-4E80-A464-657E01DFF482} {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} + {2CF11A08-1A1F-4F75-BC41-F982FCF26D2F} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/RunnerV2/RunnerV2/Helpers/ElevationHelper.cs b/src/RunnerV2/RunnerV2/Helpers/ElevationHelper.cs new file mode 100644 index 0000000000..80dc82631e --- /dev/null +++ b/src/RunnerV2/RunnerV2/Helpers/ElevationHelper.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static RunnerV2.NativeMethods; + +namespace RunnerV2.Helpers +{ + internal static partial class ElevationHelper + { + private static bool? _cachedValue; + + internal static bool IsProcessElevated(bool useCachedValue = true) + { + if (_cachedValue is not null && useCachedValue) + { + return _cachedValue.Value; + } + + bool elevated = false; + if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKENQUERY, out nint token)) + { + TokenElevation elevation = default; + if (GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TOKEN_ELEVATION, ref elevation, (uint)Marshal.SizeOf(elevation), out uint _)) + { + elevated = elevation.TokenIsElevated != 0; + } + + if (token != IntPtr.Zero) + { + CloseHandle(token); + } + } + + _cachedValue = elevated; + return elevated; + } + } +} diff --git a/src/RunnerV2/RunnerV2/Helpers/HotkeyManager.cs b/src/RunnerV2/RunnerV2/Helpers/HotkeyManager.cs new file mode 100644 index 0000000000..88901182de --- /dev/null +++ b/src/RunnerV2/RunnerV2/Helpers/HotkeyManager.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using ManagedCommon; +using static RunnerV2.NativeMethods; + +namespace RunnerV2.Helpers +{ + internal static partial class HotkeyManager + { + private static readonly Dictionary _hotkeyActions = []; + + public static void EnableHotkey(HotkeyEx hotkey, Action onHotkey) + { + if (_hotkeyActions.ContainsKey(hotkey)) + { + return; + } + + _hotkeyActions[hotkey] = onHotkey; + + if (!RegisterHotKey(Runner.RunnerHwnd, hotkey.GetHashCode(), hotkey.ModifiersMask, hotkey.VkCode)) + { + Console.WriteLine("Failed to register hotkey: " + hotkey); + var lastError = Marshal.GetLastWin32Error(); + Console.WriteLine("LastError: " + lastError); + } + } + + public static void DisableHotkey(HotkeyEx hotkey) + { + if (!_hotkeyActions.ContainsKey(hotkey)) + { + return; + } + + _hotkeyActions.Remove(hotkey); + UnregisterHotKey(IntPtr.Zero, hotkey.GetHashCode()); + } + + public static void ProcessHotkey(nuint hotkeyId) + { + ulong hashId = hotkeyId.ToUInt64(); + if (_hotkeyActions.Any(h => h.Key.GetHashCode() == (int)hashId)) + { + _hotkeyActions.First(h => h.Key.GetHashCode() == (int)hashId).Value(); + } + } + } +} diff --git a/src/RunnerV2/RunnerV2/Helpers/SettingsHelper.cs b/src/RunnerV2/RunnerV2/Helpers/SettingsHelper.cs new file mode 100644 index 0000000000..51e57e45b0 --- /dev/null +++ b/src/RunnerV2/RunnerV2/Helpers/SettingsHelper.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Text.Json; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using PowerToys.Interop; + +namespace RunnerV2.Helpers +{ + internal static class SettingsHelper + { + private static Process? _process; + private static TwoWayPipeMessageIPCManaged? _ipc; + private static SettingsUtils _settingsUtils = new(); + + public static void OpenSettingsWindow(bool showOobeWindow = false, bool showScoobeWindow = false, bool showFlyout = false, Point? flyoutPosition = null, string? additionalArguments = null) + { + if (_process is not null && _ipc is not null && !_process.HasExited) + { + if (showFlyout) + { + _ipc.Send(@"{""ShowYourself"": ""flyout""}"); + } + else + { + _ipc.Send($@"{{""ShowYourself"": ""{additionalArguments ?? "Dashboard"}""}}"); + } + + return; + } + + _ipc?.End(); + _ipc = null; + + // Arg 1: Executable path + string executablePath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("No executable path found"), "WinUI3Apps", "PowerToys.Settings.exe"); + + // Arg 2,3: Pipe names + Pipe settingsPipe = new(); + Pipe powertoysPipe = new(); + + string powerToysPipeName = @"\\.\pipe\powertoys_runner_" + Guid.NewGuid(); + string settingsPipeName = @"\\.\pipe\powertoys_settings_" + Guid.NewGuid(); + + // Arg 4: Process pid + string currentProcessId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); + + // Arg 5: Settings theme + string theme = Program.GeneralSettings.Theme switch + { + "light" => "light", + "dark" => "dark", + "system" when ThemeHelpers.GetAppTheme() == AppTheme.Light => "light", + "system" when ThemeHelpers.GetAppTheme() == AppTheme.Dark => "dark", + _ => throw new NotImplementedException(), + }; + + // Arg 6: Elevated status + string isElevated = Program.GeneralSettings.IsElevated ? "true" : "false"; + + // Arg 7: Is user an administrator + string isAdmin = Program.GeneralSettings.IsAdmin ? "true" : "false"; + + // Arg 8: Show OOBE window + string showOobeArg = showOobeWindow ? "true" : "false"; + + // Arg 9: Show SCOOBE window + string showScoobeArg = showScoobeWindow ? "true" : "false"; + + // Arg 10: Show flyout + string showFlyoutArg = showFlyout ? "true" : "false"; + + // Arg 11: Are there additional settings window arguments + string areThereadditionalArgs = string.IsNullOrEmpty(additionalArguments) ? "false" : "true"; + + // Arg 12: Are there flyout position arguments + string areThereFlyoutPositionArgs = flyoutPosition.HasValue ? "true" : "false"; + + string executableArgs = $"{powerToysPipeName} {settingsPipeName} {currentProcessId} {theme} {isElevated} {isAdmin} {showOobeArg} {showScoobeArg} {showFlyoutArg} {areThereadditionalArgs} {areThereFlyoutPositionArgs}"; + + if (!string.IsNullOrEmpty(additionalArguments)) + { + executableArgs += $" {additionalArguments}"; + } + + if (flyoutPosition is not null) + { + executableArgs += $" {flyoutPosition.Value.X} {flyoutPosition.Value.Y}"; + } + + _process = Process.Start(executablePath, executableArgs); + + // Initialize listening to pipes + _ipc = new TwoWayPipeMessageIPCManaged(powerToysPipeName, settingsPipeName, OnSettingsMessageReceived); + _ipc.Start(); + } + + private static void OnSettingsMessageReceived(string message) + { + JsonDocument messageDocument = JsonDocument.Parse(message); + + foreach (var property in messageDocument.RootElement.EnumerateObject()) + { + switch (property.Name) + { + case "get_all_hotkey_conflicts": + // Todo: Handle hotkey conflict + break; + case "general": + _settingsUtils.SaveSettings(property.Value.ToString(), string.Empty); + foreach (IPowerToysModule module in Runner.LoadedModules) + { + module.OnSettingsChanged("general", property.Value); + Runner.ToggleModuleStateBasedOnEnabledProperty(module); + } + + break; + case string s: + _settingsUtils.SaveSettings(property.Value.ToString(), s); + + if (Runner.LoadedModules.Find(m => m.Name == s) is IPowerToysModule moduleFound) + { + moduleFound.OnSettingsChanged(s, property.Value); + } + else + { + // If no specific module was found, notify all enabled modules + foreach (IPowerToysModule module in Runner.LoadedModules.Where(m => m.Enabled)) + { + module.OnSettingsChanged(s, property.Value); + } + } + + break; + } + } + } + } +} diff --git a/src/RunnerV2/RunnerV2/Helpers/TrayIconManager.cs b/src/RunnerV2/RunnerV2/Helpers/TrayIconManager.cs new file mode 100644 index 0000000000..9927453f5a --- /dev/null +++ b/src/RunnerV2/RunnerV2/Helpers/TrayIconManager.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows.Forms; +using static RunnerV2.NativeMethods; + +namespace RunnerV2.Helpers +{ + internal static partial class TrayIconManager + { + internal static void StartTrayIcon() + { + NOTIFYICONDATA notifyicondata = new() + { + CbSize = (uint)Marshal.SizeOf(), + HWnd = Runner.RunnerHwnd, + UId = 1, + HIcon = Icon.ExtractAssociatedIcon(Environment.ProcessPath!)!.Handle, + UFlags = 0x0000001 | 0x00000002 | 0x4, + UCallbackMessage = (uint)WindowMessages.ICON_NOTIFY, + SzTip = "PowerToys Runner", + }; + + ChangeWindowMessageFilterEx(Runner.RunnerHwnd, 0x0111, 0x0001, IntPtr.Zero); + + Shell_NotifyIcon(NIMADD, ref notifyicondata); + } + + private enum TrayButton : uint + { + Settings = 1, + Documentation, + ReportBug, + Close, + } + + private static bool _doubleClickTimerRunning; + private static bool _doubleClickDetected; + + private static IntPtr _trayIconMenu; + + static TrayIconManager() + { + _trayIconMenu = CreatePopupMenu(); + AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Settings), "Settings\tDouble-click"); + AppendMenuW(_trayIconMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // separator + AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Documentation), "Documentation"); + AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.ReportBug), "Report a Bug"); + AppendMenuW(_trayIconMenu, 0x00000800u, UIntPtr.Zero, string.Empty); // separator + AppendMenuW(_trayIconMenu, 0u, new UIntPtr((uint)TrayButton.Close), "Close"); + } + + internal static void ProcessTrayIconMessage(long lParam) + { + switch (lParam) + { + case 0x0205: // WM_RBUTTONDBLCLK + case 0x007B: // WM_CONTEXTMENU + SetForegroundWindow(Runner.RunnerHwnd); + TrackPopupMenu(_trayIconMenu, 0x0004 | 0x0020, Cursor.Position.X, Cursor.Position.Y, 0, Runner.RunnerHwnd, IntPtr.Zero); + break; + case 0x0202: // WM_LBUTTONUP + if (_doubleClickTimerRunning) + { + break; + } + + _doubleClickTimerRunning = true; + Task.Delay(SystemInformation.DoubleClickTime).ContinueWith(_ => + { + if (!_doubleClickDetected) + { + SettingsHelper.OpenSettingsWindow(showFlyout: true, flyoutPosition: Cursor.Position); + } + + _doubleClickDetected = false; + _doubleClickTimerRunning = false; + }); + break; + case 0x0203: // WM_LBUTTONDBLCLK + _doubleClickDetected = true; + SettingsHelper.OpenSettingsWindow(); + break; + } + } + + internal static void ProcessTrayMenuCommand(nuint commandId) + { + switch ((TrayButton)commandId) + { + case TrayButton.Settings: + SettingsHelper.OpenSettingsWindow(); + break; + case TrayButton.Documentation: + Process.Start(new ProcessStartInfo + { + FileName = "https://aka.ms/PowerToysOverview", + UseShellExecute = true, + }); + break; + case TrayButton.ReportBug: + Process bugReportProcess = new(); + bugReportProcess.StartInfo = new ProcessStartInfo + { + FileName = "Tools\\PowerToys.BugReportTool.exe", + CreateNoWindow = true, + }; + + bugReportProcess.EnableRaisingEvents = true; + + EnableMenuItem(_trayIconMenu, (uint)TrayButton.ReportBug, 0x000000 | 0x00001); + + bugReportProcess.Exited += (sender, e) => + { + bugReportProcess.Dispose(); + EnableMenuItem(_trayIconMenu, (uint)TrayButton.ReportBug, 0x00000000); + }; + + bugReportProcess.Start(); + + break; + case TrayButton.Close: + Runner.Close(); + break; + } + } + } +} diff --git a/src/RunnerV2/RunnerV2/Models/SpecialMode.cs b/src/RunnerV2/RunnerV2/Models/SpecialMode.cs new file mode 100644 index 0000000000..178a34e4ca --- /dev/null +++ b/src/RunnerV2/RunnerV2/Models/SpecialMode.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace RunnerV2.Models +{ + internal enum SpecialMode + { + None, + Win32ToastNotificationCOMServer, + ToastNotificationHandler, + ReportSuccessfulUpdate, + } +} diff --git a/src/RunnerV2/RunnerV2/NativeMethods.cs b/src/RunnerV2/RunnerV2/NativeMethods.cs new file mode 100644 index 0000000000..34b4731345 --- /dev/null +++ b/src/RunnerV2/RunnerV2/NativeMethods.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace RunnerV2 +{ + internal static partial class NativeMethods + { + [LibraryImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); + + [LibraryImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetTokenInformation(IntPtr tokenHandle, TOKEN_INFORMATION_CLASS tokenInformationClass, ref TokenElevation tokenInformation, uint tokenInformationLength, out uint returnLength); + + [LibraryImport("Kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CloseHandle(IntPtr hObject); + + internal enum TOKEN_INFORMATION_CLASS + { + TOKEN_ELEVATION = 20, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TokenElevation + { + public uint TokenIsElevated; + } + + internal const int TOKENQUERY = 0x0008; + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool UnregisterHotKey(IntPtr hWnd, int id); + + [LibraryImport("user32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool AppendMenuW(IntPtr hMenu, uint uFlags, UIntPtr uIDNewItem, string lpNewItem); + + [LibraryImport("user32.dll", SetLastError = true)] + internal static partial IntPtr CreatePopupMenu(); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool SetForegroundWindow(IntPtr hWnd); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool TrackPopupMenu(IntPtr hMenu, uint uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); + + internal const uint NIMADD = 0x00000000; + + internal struct NOTIFYICONDATA + { + public uint CbSize; + public IntPtr HWnd; + public uint UId; + public uint UFlags; + public uint UCallbackMessage; + public IntPtr HIcon; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string SzTip; + public uint DwState; + public uint DwStateMask; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string SzInfo; + public uint UTimeoutOrVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string SzInfoTitle; + public uint DwInfoFlags; + public Guid GuidItem; + public IntPtr HBalloonIcon; + } + + [DllImport("shell32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool Shell_NotifyIcon(uint dwMessage, ref NOTIFYICONDATA lpdata); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, uint action, IntPtr pChangeFilterStruct); + + internal const uint CSVREDRAW = 0x0001; + internal const uint CSHREDRAW = 0x0002; + + internal const uint WSOVERLAPPEDWINDOW = 0x00CF0000; + internal const uint WSPOPUP = 0x80000000; + + internal const int CWUSEDEFAULT = unchecked((int)0x80000000); + + internal static readonly IntPtr IDCARROW = new(32512); + + [DllImport("user32.dll")] + internal static extern IntPtr GetMessageW(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool TranslateMessage(ref MSG lpMsg); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DispatchMessageW(ref MSG lpMsg); + + internal struct MSG + { + public IntPtr HWnd; + public uint Message; + public UIntPtr WParam; + public long LParam; + public ulong Time; + public Point Pt; + } + + internal enum WindowMessages : uint + { + COMMAND = 0x0111, + HOTKEY = 0x0312, + ICON_NOTIFY = 0x0800, + } + + [DllImport("user32.dll")] + internal static extern ushort RegisterClassW(ref WNDCLASS lpWndClass); + + [LibraryImport("user32.dll", SetLastError = false)] + internal static partial IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [LibraryImport("user32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial nint CreateWindowExW( + uint dwExStyle, + string lpClassName, + string lpWindowName, + uint dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + internal delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct WNDCLASS + { + public uint Style; + public WndProc LpfnWndProc; + public int CbClsExtra; + public int CbWndExtra; + public IntPtr HInstance; + public IntPtr HIcon; + public IntPtr HCursor; + public IntPtr HbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] + public string LpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] + public string LpszClassName; + } + } +} diff --git a/src/RunnerV2/RunnerV2/Program.cs b/src/RunnerV2/RunnerV2/Program.cs new file mode 100644 index 0000000000..99c5b0567c --- /dev/null +++ b/src/RunnerV2/RunnerV2/Program.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.PowerToys.Settings.UI.Library; +using PowerToys.GPOWrapperProjection; +using RunnerV2; +using RunnerV2.Helpers; +using RunnerV2.Models; +using Settings.UI.Library; + +internal sealed class Program +{ + private static readonly SettingsUtils _settingsUtils = new(); + private static GeneralSettings _generalSettings = _settingsUtils.GetSettings(); + + public static GeneralSettings GeneralSettings => _generalSettings; + + private static void Main(string[] args) + { + switch (ShouldRunInSpecialMode(args)) + { + case SpecialMode.None: + break; + default: + throw new NotImplementedException("Special modes are not implemented yet."); + } + + bool shouldOpenSettings = args.Any(s => s.StartsWith("--open-settings", StringComparison.InvariantCulture)); + bool shouldOpenSettingsToSpecificPage = args.Any(s => s.StartsWith("--open-settings=", StringComparison.InvariantCulture)); + + // Check if PowerToys is already running + if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1) + { + throw new NotImplementedException("Opening another instance window is not supported yet."); + } + + /* + * Todo: Data diagnotics + */ + + bool isElevated = ElevationHelper.IsProcessElevated(); + bool hasDontElevateArgument = args.Contains("--dont-elevate"); + bool runElevatedSetting = _generalSettings.RunElevated; + bool hasRestartedElevatedArgment = args.Contains("--restartedElevated"); + + Action afterInitializationAction = () => { }; + Version version = Assembly.GetExecutingAssembly().GetName().Version!; + + if ($"v{version.Major}.{version.Minor}.{version.Build}" != _settingsUtils.GetSettings(fileName: "last_version_run.json").LastVersion && (!_generalSettings.ShowWhatsNewAfterUpdates || GPOWrapper.GetDisableShowWhatsNewAfterUpdatesValue() != GpoRuleConfigured.Disabled)) + { + afterInitializationAction += () => + { + SettingsHelper.OpenSettingsWindow(showScoobeWindow: true); + }; + } + + if (!_settingsUtils.GetSettings(fileName: "oobe_settings.json").OpenedAtFirstLaunch) + { + afterInitializationAction += () => + { + SettingsHelper.OpenSettingsWindow(showOobeWindow: true); + }; + } + + // Set last version run + _settingsUtils.SaveSettings(new LastVersionRunSettings() { LastVersion = $"v{version.Major}.{version.Minor}.{version.Build}" }.ToJsonString(), fileName: "last_version_run.json"); + + switch ((isElevated, hasDontElevateArgument, runElevatedSetting, hasRestartedElevatedArgment)) + { + case (true, true, false, _): + // Todo: Scheudle restart as non elevated + throw new NotImplementedException(); + case (true, _, _, _): + case (_, _, false, _): + case (_, true, _, _): + case (false, _, _, true): + _ = Runner.Run(afterInitializationAction); + + // Todo: Save settings + break; + default: + // Todo: scheudle restart as elevated + throw new NotImplementedException(); + } + } + + private static SpecialMode ShouldRunInSpecialMode(string[] args) + { + // TODO + return SpecialMode.None; + } +} diff --git a/src/RunnerV2/RunnerV2/Runner.cs b/src/RunnerV2/RunnerV2/Runner.cs new file mode 100644 index 0000000000..d0b7b48a7c --- /dev/null +++ b/src/RunnerV2/RunnerV2/Runner.cs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using ManagedCommon; +using RunnerV2.Helpers; +using static RunnerV2.NativeMethods; + +namespace RunnerV2 +{ + internal static partial class Runner + { + public static nint RunnerHwnd { get; private set; } + + private const string TrayWindowClassName = "pt_tray_icon_window_class"; + + static Runner() + { + InitializeTrayWindow(); + } + + private static List _successfullyAddedModules = []; + + public static List LoadedModules => _successfullyAddedModules; + + internal static bool Run(Action afterInitializationAction) + { + // Todo: Start tray icon + TrayIconManager.StartTrayIcon(); + FrozenSet modulesToLoad = ["PowerToys.AlwaysOnTopModuleInterface.dll", "WinUI3Apps\\PowerToys.Hosts.dll"]; + + List failedModuleLoads = []; + + foreach (string module in modulesToLoad) + { + try + { + Assembly moduleAssembly = Assembly.LoadFrom(Path.GetFullPath(module)); + Type moduleInterfaceType = moduleAssembly.GetTypes().First(t => t.GetInterfaces().Any(i => i.Name.StartsWith(typeof(IPowerToysModule).Name, StringComparison.InvariantCulture))); + _successfullyAddedModules.Add((IPowerToysModule)Activator.CreateInstance(moduleInterfaceType)!); + } + catch (Exception e) + { + failedModuleLoads.Add(module); + Console.WriteLine($"Failed to load module {module}: {e.Message}"); + } + } + + if (failedModuleLoads.Count > 0) + { + MessageBox.Show("The following modules failed to load: \n- " + string.Join("\n- ", failedModuleLoads), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + foreach (IPowerToysModule module in _successfullyAddedModules) + { + ToggleModuleStateBasedOnEnabledProperty(module); + } + + afterInitializationAction(); + MessageLoop(); + + return true; + } + + private static void MessageLoop() + { + while (true) + { + if (GetMessageW(out MSG msg, IntPtr.Zero, 0, 0) != 0) + { + TranslateMessage(ref msg); + DispatchMessageW(ref msg); + + switch (msg.Message) + { + case (uint)WindowMessages.HOTKEY: + HotkeyManager.ProcessHotkey(msg.WParam); + break; + case (uint)WindowMessages.ICON_NOTIFY: + TrayIconManager.ProcessTrayIconMessage(msg.LParam); + break; + case (uint)WindowMessages.COMMAND: + TrayIconManager.ProcessTrayMenuCommand(msg.WParam); + break; + default: + break; + } + } + } + } + + [DoesNotReturn] + internal static void Close() + { + foreach (IPowerToysModule module in _successfullyAddedModules) + { + try + { + module.Disable(); + if (module.HotkeyEx is not null) + { + HotkeyManager.DisableHotkey(module.HotkeyEx); + } + } + catch (Exception e) + { + MessageBox.Show($"The module {module.Name} failed to unload: \n" + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + Environment.Exit(0); + } + + public static void ToggleModuleStateBasedOnEnabledProperty(IPowerToysModule module) + { + if ((module.Enabled && (module.GpoRuleConfigured != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)) || module.GpoRuleConfigured == PowerToys.GPOWrapper.GpoRuleConfigured.Enabled) + { + try + { + module.Enable(); + + /* Todo: conflict manager */ + + if (module.HotkeyEx is not null) + { + HotkeyManager.EnableHotkey(module.HotkeyEx, module.OnHotkey); + } + } + catch (Exception e) + { + MessageBox.Show($"The module {module.Name} failed to load: \n" + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + return; + } + + try + { + module.Disable(); + + if (module.HotkeyEx is not null) + { + HotkeyManager.DisableHotkey(module.HotkeyEx); + } + } + catch (Exception e) + { + MessageBox.Show($"The module {module.Name} failed to unload: \n" + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static void InitializeTrayWindow() + { + IntPtr hInstance = Process.GetCurrentProcess().MainModule!.BaseAddress; + IntPtr hCursor = Cursors.Arrow.Handle; + IntPtr hIcon = SystemIcons.Application.Handle; + + var wc = new WNDCLASS + { + HCursor = hCursor, + HInstance = hInstance, + LpszClassName = TrayWindowClassName, + Style = CSHREDRAW | CSVREDRAW, + LpfnWndProc = TrayIconWindowProc, + HIcon = hIcon, + HbrBackground = IntPtr.Zero, + LpszMenuName = string.Empty, + CbClsExtra = 0, + CbWndExtra = 0, + }; + + _ = RegisterClassW(ref wc); + + RunnerHwnd = CreateWindowExW( + 0, + wc.LpszClassName, + TrayWindowClassName, + WSOVERLAPPEDWINDOW | WSPOPUP, + CWUSEDEFAULT, + CWUSEDEFAULT, + CWUSEDEFAULT, + CWUSEDEFAULT, + IntPtr.Zero, + IntPtr.Zero, + wc.HInstance, + IntPtr.Zero); + + if (RunnerHwnd == IntPtr.Zero) + { + var err = Marshal.GetLastPInvokeError(); + MessageBox.Show($"CreateWindowExW failed. LastError={err}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static IntPtr TrayIconWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + TrayIconManager.ProcessTrayIconMessage(lParam.ToInt64()); + return DefWindowProcW(hWnd, msg, wParam, lParam); + } + } +} diff --git a/src/RunnerV2/RunnerV2/RunnerV2.csproj b/src/RunnerV2/RunnerV2/RunnerV2.csproj new file mode 100644 index 0000000000..f8ea6d2596 --- /dev/null +++ b/src/RunnerV2/RunnerV2/RunnerV2.csproj @@ -0,0 +1,20 @@ + + + + + WinExe + PowerToys Runner + PowerToys2 + ..\..\..\$(Platform)\$(Configuration) + enable + false + false + icon.ico + app.manifest + + + + + + + diff --git a/src/RunnerV2/RunnerV2/app.manifest b/src/RunnerV2/RunnerV2/app.manifest new file mode 100644 index 0000000000..8d3ce52af3 --- /dev/null +++ b/src/RunnerV2/RunnerV2/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + true/PM + PerMonitorV2 + + + diff --git a/src/RunnerV2/RunnerV2/icon.ico b/src/RunnerV2/RunnerV2/icon.ico new file mode 100644 index 0000000000..6cbba351a1 Binary files /dev/null and b/src/RunnerV2/RunnerV2/icon.ico differ diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index 6cb91a69ac..6effdfb581 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -66,5 +66,10 @@ namespace PowerToys.GPOWrapperProjection { return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue(); } + + public static GpoRuleConfigured GetDisableShowWhatsNewAfterUpdatesValue() + { + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetDisableShowWhatsNewAfterUpdatesValue(); + } } } diff --git a/src/common/ManagedCommon/HotkeyEx.cs b/src/common/ManagedCommon/HotkeyEx.cs new file mode 100644 index 0000000000..10ae8c5c55 --- /dev/null +++ b/src/common/ManagedCommon/HotkeyEx.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ManagedCommon +{ + public record HotkeyEx(ushort ModifiersMask, ushort VkCode); +} diff --git a/src/common/ManagedCommon/IPowerToysModule.cs b/src/common/ManagedCommon/IPowerToysModule.cs new file mode 100644 index 0000000000..d502079938 --- /dev/null +++ b/src/common/ManagedCommon/IPowerToysModule.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text.Json; +using PowerToys.GPOWrapper; + +namespace ManagedCommon +{ + public interface IPowerToysModule + { + public string Name { get; } + + public void Enable(); + + public void Disable(); + + public bool Enabled { get; } + + public GpoRuleConfigured GpoRuleConfigured { get; } + + public virtual HotkeyEx? HotkeyEx => null; + + public virtual Action OnHotkey => () => { }; + + public virtual Action OnSettingsChanged(string settingsKind, JsonElement jsonProperties) => () => { }; + } +} diff --git a/src/common/ManagedCommon/LanguageHelper.cs b/src/common/ManagedCommon/LanguageHelper.cs index 89b48e7ccb..b3a52722ed 100644 --- a/src/common/ManagedCommon/LanguageHelper.cs +++ b/src/common/ManagedCommon/LanguageHelper.cs @@ -18,7 +18,7 @@ namespace ManagedCommon internal sealed class OutGoingLanguageSettings { [JsonPropertyName("language")] - public string LanguageTag { get; set; } + public string? LanguageTag { get; set; } } public static string LoadLanguage() @@ -36,7 +36,7 @@ namespace ManagedCommon inputStream.Close(); reader.Dispose(); - return JsonSerializer.Deserialize(data, SourceGenerationContext.Default.OutGoingLanguageSettings).LanguageTag; + return JsonSerializer.Deserialize(data, SourceGenerationContext.Default.OutGoingLanguageSettings)!.LanguageTag!; } catch (Exception) { diff --git a/src/common/ManagedCommon/Logger.cs b/src/common/ManagedCommon/Logger.cs index 1173920340..22af2c560d 100644 --- a/src/common/ManagedCommon/Logger.cs +++ b/src/common/ManagedCommon/Logger.cs @@ -29,12 +29,12 @@ namespace ManagedCommon /// /// Gets the path to the log directory for the current version of the app. /// - public static string CurrentVersionLogDirectoryPath { get; private set; } + public static string? CurrentVersionLogDirectoryPath { get; private set; } /// /// Gets the path to the log directory for the app. /// - public static string AppLogDirectoryPath { get; private set; } + public static string? AppLogDirectoryPath { get; private set; } /// /// Initializes the logger and sets the path for logging. @@ -45,7 +45,7 @@ namespace ManagedCommon public static void InitializeLogger(string applicationLogPath, bool isLocalLow = false) { string versionedPath = LogDirectoryPath(applicationLogPath, isLocalLow); - string basePath = Path.GetDirectoryName(versionedPath); + string basePath = Path.GetDirectoryName(versionedPath)!; if (!Directory.Exists(versionedPath)) { diff --git a/src/common/ManagedCommon/ManagedCommon.csproj b/src/common/ManagedCommon/ManagedCommon.csproj index bd74253073..8be6b9bb5f 100644 --- a/src/common/ManagedCommon/ManagedCommon.csproj +++ b/src/common/ManagedCommon/ManagedCommon.csproj @@ -6,6 +6,7 @@ PowerToys ManagedCommon PowerToys.ManagedCommon + enable @@ -20,6 +21,7 @@ + diff --git a/src/common/ManagedCommon/ThemeHelpers.cs b/src/common/ManagedCommon/ThemeHelpers.cs index 181b3dbbc3..cfc332963c 100644 --- a/src/common/ManagedCommon/ThemeHelpers.cs +++ b/src/common/ManagedCommon/ThemeHelpers.cs @@ -24,7 +24,7 @@ namespace ManagedCommon // based on https://stackoverflow.com/questions/51334674/how-to-detect-windows-10-light-dark-mode-in-win32-application public static AppTheme GetAppTheme() { - int value = (int)Registry.GetValue($"{HKeyRoot}\\{HkeyWindowsPersonalizeTheme}", HValueAppTheme, 1); + int value = (int)Registry.GetValue($"{HKeyRoot}\\{HkeyWindowsPersonalizeTheme}", HValueAppTheme, 1)!; return (AppTheme)value; } diff --git a/src/common/ManagedCommon/ThemeListener.cs b/src/common/ManagedCommon/ThemeListener.cs index 945ab37deb..635d327452 100644 --- a/src/common/ManagedCommon/ThemeListener.cs +++ b/src/common/ManagedCommon/ThemeListener.cs @@ -24,7 +24,7 @@ namespace ManagedCommon /// /// An event that fires if the Theme changes. /// - public event ThemeChangedEvent ThemeChanged; + public event ThemeChangedEvent? ThemeChanged; private readonly ManagementEventWatcher watcher; @@ -33,7 +33,7 @@ namespace ManagedCommon var currentUser = WindowsIdentity.GetCurrent(); var query = new WqlEventQuery( $"SELECT * FROM RegistryValueChangeEvent WHERE Hive='HKEY_USERS' AND " + - $"KeyPath='{currentUser.User.Value}\\\\{ThemeHelpers.HkeyWindowsPersonalizeTheme.Replace("\\", "\\\\")}' AND ValueName='{ThemeHelpers.HValueAppTheme}'"); + $"KeyPath='{currentUser.User?.Value}\\\\{ThemeHelpers.HkeyWindowsPersonalizeTheme.Replace("\\", "\\\\")}' AND ValueName='{ThemeHelpers.HValueAppTheme}'"); watcher = new ManagementEventWatcher(query); watcher.EventArrived += Watcher_EventArrived; watcher.Start(); diff --git a/src/modules/Hosts/Hosts/Hosts.csproj b/src/modules/Hosts/Hosts/Hosts.csproj index cf595dd44b..b979a7604c 100644 --- a/src/modules/Hosts/Hosts/Hosts.csproj +++ b/src/modules/Hosts/Hosts/Hosts.csproj @@ -52,13 +52,6 @@ - - - PowerToys.GPOWrapper - $(OutDir) - false - - @@ -83,7 +76,7 @@ - + diff --git a/src/modules/Hosts/Hosts/ModuleInterface.cs b/src/modules/Hosts/Hosts/ModuleInterface.cs new file mode 100644 index 0000000000..d19bb88775 --- /dev/null +++ b/src/modules/Hosts/Hosts/ModuleInterface.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.IO; +using System.Reflection; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using PowerToys.GPOWrapper; + +namespace Hosts +{ + internal sealed class ModuleInterface : IPowerToysModule + { + public bool Enabled => new SettingsUtils().GetSettingsOrDefault().Enabled.Hosts; + + public GpoRuleConfigured GpoRuleConfigured => GpoRuleConfigured.NotConfigured; + + public string Name => "Hosts"; + + public void Disable() + { + foreach (var process in Process.GetProcessesByName("PowerToys.Hosts.exe")) + { + process.Kill(); + } + } + + public void Enable() + { + } + } +} diff --git a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.base.rc b/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.base.rc deleted file mode 100644 index 5fa3c8b90d..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.base.rc +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include "resource.h" -#include "../../../common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -1 VERSIONINFO -FILEVERSION FILE_VERSION -PRODUCTVERSION PRODUCT_VERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG -FILEFLAGS VS_FF_DEBUG -#else -FILEFLAGS 0x0L -#endif -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset - END -END diff --git a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj b/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj deleted file mode 100644 index f74481f2e0..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - 16.0 - {B41B888C-7DB8-4747-B262-4062E05A230D} - Win32Proj - HostsModuleInterface - HostsModuleInterface - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - - - - - - - - - - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ - PowerToys.HostsModuleInterface - - - true - - - false - - - - ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) - - - - - - - - - - - - - Create - - - - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {6955446d-23f7-4023-9bb3-8657f904af99} - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj.filters b/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj.filters deleted file mode 100644 index 004a9a4d07..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/HostsModuleInterface.vcxproj.filters +++ /dev/null @@ -1,59 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {875a08c6-f610-4667-bd0f-80171ed96072} - - - - - Source Files - - - Source Files - - - Source Files - - - - - Resource Files - - - Resource Files - - - Header Files - - - - - - Header Files - - - Generated Files - - - Header Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/src/modules/Hosts/HostsModuleInterface/Resource.resx b/src/modules/Hosts/HostsModuleInterface/Resource.resx deleted file mode 100644 index d0309bf10f..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/Resource.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Hosts File Editor - "Hosts" refer to the system hosts file, do not loc - - \ No newline at end of file diff --git a/src/modules/Hosts/HostsModuleInterface/dllmain.cpp b/src/modules/Hosts/HostsModuleInterface/dllmain.cpp deleted file mode 100644 index 993226ac2b..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/dllmain.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "pch.h" - -#include "trace.h" -#include -#include -#include -#include "Generated Files/resource.h" - -#include -#include -#include -#include -#include -#include -#include - -extern "C" IMAGE_DOS_HEADER __ImageBase; - -BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - Trace::RegisterProvider(); - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - case DLL_PROCESS_DETACH: - Trace::UnregisterProvider(); - break; - } - return TRUE; -} - -namespace -{ - // Name of the powertoy module. - inline const std::wstring ModuleKey = L"Hosts"; -} - -class HostsModuleInterface : public PowertoyModuleIface -{ -private: - bool m_enabled = false; - - std::wstring app_name; - - //contains the non localized key of the powertoy - std::wstring app_key; - - HANDLE m_hProcess = nullptr; - - HANDLE m_hShowEvent{}; - - HANDLE m_hShowAdminEvent{}; - - HANDLE m_hTerminateEvent{}; - - bool is_process_running() - { - return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; - } - - void bring_process_to_front() - { - auto enum_windows = [](HWND hwnd, LPARAM param) -> BOOL { - HANDLE process_handle = reinterpret_cast(param); - DWORD window_process_id = 0; - - GetWindowThreadProcessId(hwnd, &window_process_id); - if (GetProcessId(process_handle) == window_process_id) - { - SetForegroundWindow(hwnd); - return FALSE; - } - return TRUE; - }; - - EnumWindows(enum_windows, (LPARAM)m_hProcess); - } - - void launch_process(bool runas) - { - Logger::trace(L"Starting Hosts process"); - unsigned long powertoys_pid = GetCurrentProcessId(); - - std::wstring executable_args = L""; - executable_args.append(std::to_wstring(powertoys_pid)); - - SHELLEXECUTEINFOW sei{ sizeof(sei) }; - sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = L"WinUI3Apps\\PowerToys.Hosts.exe"; - sei.nShow = SW_SHOWNORMAL; - sei.lpParameters = executable_args.data(); - - if (runas) - { - sei.lpVerb = L"runas"; - } - - if (ShellExecuteExW(&sei)) - { - Logger::trace("Successfully started the Hosts process"); - } - else - { - Logger::error(L"Hosts failed to start. {}", get_last_error_or_default(GetLastError())); - } - - m_hProcess = sei.hProcess; - } - -public: - EventWaiter m_showEventWaiter; - - EventWaiter m_showAdminEventWaiter; - - HostsModuleInterface() - { - app_name = GET_RESOURCE_STRING(IDS_HOSTS_NAME); - app_key = ModuleKey; - LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::hostsLoggerName); - - m_hShowEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_HOSTS_EVENT); - if (!m_hShowEvent) - { - Logger::error(L"Failed to create show hosts event"); - auto message = get_last_error_message(GetLastError()); - if (message.has_value()) - { - Logger::error(message.value()); - } - } - - m_hShowAdminEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_HOSTS_ADMIN_EVENT); - if (!m_hShowAdminEvent) - { - Logger::error(L"Failed to create show hosts admin event"); - auto message = get_last_error_message(GetLastError()); - if (message.has_value()) - { - Logger::error(message.value()); - } - } - - m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::TERMINATE_HOSTS_EVENT); - if (!m_hTerminateEvent) - { - Logger::error(L"Failed to create terminate hosts event"); - auto message = get_last_error_message(GetLastError()); - if (message.has_value()) - { - Logger::error(message.value()); - } - } - - m_showEventWaiter = EventWaiter(CommonSharedConstants::SHOW_HOSTS_EVENT, [&](int err) - { - if (m_enabled && err == ERROR_SUCCESS) - { - Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_HOSTS_EVENT); - - if (is_process_running()) - { - bring_process_to_front(); - } - else - { - launch_process(false); - } - - Trace::ActivateEditor(); - } - }); - - m_showAdminEventWaiter = EventWaiter(CommonSharedConstants::SHOW_HOSTS_ADMIN_EVENT, [&](int err) - { - if (m_enabled && err == ERROR_SUCCESS) - { - Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_HOSTS_ADMIN_EVENT); - - if (is_process_running()) - { - bring_process_to_front(); - } - else - { - launch_process(true); - } - - Trace::ActivateEditor(); - } - }); - } - - ~HostsModuleInterface() - { - m_enabled = false; - } - - // Destroy the powertoy and free memory - virtual void destroy() override - { - Logger::trace("HostsModuleInterface::destroy()"); - - if (m_hShowEvent) - { - CloseHandle(m_hShowEvent); - m_hShowEvent = nullptr; - } - - if (m_hShowAdminEvent) - { - CloseHandle(m_hShowAdminEvent); - m_hShowAdminEvent = nullptr; - } - - if (m_hTerminateEvent) - { - CloseHandle(m_hTerminateEvent); - m_hTerminateEvent = nullptr; - } - - delete this; - } - - // Return the localized display name of the powertoy - virtual const wchar_t* get_name() override - { - return app_name.c_str(); - } - - // Return the non localized key of the powertoy, this will be cached by the runner - virtual const wchar_t* get_key() override - { - return app_key.c_str(); - } - - // Return the configured status for the gpo policy for the module - virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override - { - return powertoys_gpo::getConfiguredHostsFileEditorEnabledValue(); - } - - virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override - { - return false; - } - - virtual void call_custom_action(const wchar_t* /*action*/) override - { - } - - virtual void set_config(const wchar_t* /*config*/) override - { - } - - virtual bool is_enabled() override - { - return m_enabled; - } - - virtual void enable() - { - Logger::trace("HostsModuleInterface::enable()"); - m_enabled = true; - Trace::EnableHostsFileEditor(true); - }; - - virtual void disable() - { - Logger::trace("HostsModuleInterface::disable()"); - if (m_enabled) - { - if (m_hShowEvent) - { - ResetEvent(m_hShowEvent); - } - - if (m_hShowAdminEvent) - { - ResetEvent(m_hShowAdminEvent); - } - - SetEvent(m_hTerminateEvent); - WaitForSingleObject(m_hProcess, 1500); - TerminateProcess(m_hProcess, 1); - ResetEvent(m_hTerminateEvent); - } - - m_enabled = false; - Trace::EnableHostsFileEditor(false); - } -}; - -extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() -{ - return new HostsModuleInterface(); -} \ No newline at end of file diff --git a/src/modules/Hosts/HostsModuleInterface/packages.config b/src/modules/Hosts/HostsModuleInterface/packages.config deleted file mode 100644 index 09bfc449e2..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/modules/Hosts/HostsModuleInterface/pch.cpp b/src/modules/Hosts/HostsModuleInterface/pch.cpp deleted file mode 100644 index 1d9f38c57d..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/src/modules/Hosts/HostsModuleInterface/pch.h b/src/modules/Hosts/HostsModuleInterface/pch.h deleted file mode 100644 index 5cb4cbf823..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/pch.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN -#include diff --git a/src/modules/Hosts/HostsModuleInterface/resource.base.h b/src/modules/Hosts/HostsModuleInterface/resource.base.h deleted file mode 100644 index 4c6a75ef09..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/resource.base.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by AlwaysOnTopModuleInterface.rc - -////////////////////////////// -// Non-localizable - -#define FILE_DESCRIPTION "PowerToys Hosts Module" -#define INTERNAL_NAME "PowerToys.HostsModuleInterface" -#define ORIGINAL_FILENAME "PowerToys.HostsModuleInterface.dll" - -// Non-localizable -////////////////////////////// diff --git a/src/modules/Hosts/HostsModuleInterface/trace.cpp b/src/modules/Hosts/HostsModuleInterface/trace.cpp deleted file mode 100644 index 104075a829..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/trace.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "pch.h" -#include "trace.h" - -#include - -TRACELOGGING_DEFINE_PROVIDER( - g_hProvider, - "Microsoft.PowerToys", - // {38e8889b-9731-53f5-e901-e8a7c1753074} - (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), - TraceLoggingOptionProjectTelemetry()); - -// Log if the user has HostsFileEditor enabled or disabled -void Trace::EnableHostsFileEditor(const bool enabled) noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "HostsFileEditor_EnableHostsFileEditor", - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingBoolean(enabled, "Enabled")); -} - -// Log that the user tried to activate the editor -void Trace::ActivateEditor() noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "HostsFileEditor_Activate", - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} diff --git a/src/modules/Hosts/HostsModuleInterface/trace.h b/src/modules/Hosts/HostsModuleInterface/trace.h deleted file mode 100644 index f3dc6310b5..0000000000 --- a/src/modules/Hosts/HostsModuleInterface/trace.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -class Trace : public telemetry::TraceBase -{ -public: - // Log if the user has HostsFileEditor enabled or disabled - static void EnableHostsFileEditor(const bool enabled) noexcept; - - // Log that the user tried to activate the editor - static void ActivateEditor() noexcept; -}; diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.csproj b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.csproj new file mode 100644 index 0000000000..bab66629ab --- /dev/null +++ b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.csproj @@ -0,0 +1,18 @@ + + + + + PowerToys Alway on Top module interface + PowerToys.AlwaysOnTopModuleInterface + ..\..\..\..\$(Platform)\$(Configuration) + false + false + enable + + + + + + + + diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.rc b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.rc deleted file mode 100644 index 5fa3c8b90d..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.rc +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include "resource.h" -#include "../../../common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -1 VERSIONINFO -FILEVERSION FILE_VERSION -PRODUCTVERSION PRODUCT_VERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG -FILEFLAGS VS_FF_DEBUG -#else -FILEFLAGS 0x0L -#endif -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset - END -END diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj deleted file mode 100644 index 5f63a0e628..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj +++ /dev/null @@ -1,82 +0,0 @@ - - - - - 15.0 - {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9} - Win32Proj - alwaysontop - AlwaysOnTopModuleInterface - - - - DynamicLibrary - v143 - - - - - - - - - - - ..\..\..\..\$(Platform)\$(Configuration)\ - - - PowerToys.AlwaysOnTopModuleInterface - - - - _WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) - - - $(OutDir)$(TargetName)$(TargetExt) - gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) - - - - - - - - - - - - - Create - - - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {6955446d-23f7-4023-9bb3-8657f904af99} - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj.filters b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj.filters deleted file mode 100644 index 66fe083d3a..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj.filters +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Resource Files - - - - - {21926bf1-03b3-482d-8f60-8bc4fbfc6564} - - - {2f10207d-d8d1-4a42-8027-8ca597b3cb23} - - - {a4241930-ecae-44e2-be82-25eff2499fcd} - - - {8d479404-964b-4eb1-8fe8-554be3e68c9b} - - - - - - - - Resource Files - - - - - - \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/ModuleInterface.cs b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/ModuleInterface.cs new file mode 100644 index 0000000000..1b3bfb0b19 --- /dev/null +++ b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/ModuleInterface.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.InteropServices; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using PowerToys.GPOWrapper; + +namespace AlwaysOnTopModuleInterface +{ + public class ModuleInterface : IPowerToysModule + { + public bool Enabled => true; + + public string Name => "AlwaysOnTop"; + + public GpoRuleConfigured GpoRuleConfigured => GpoRuleConfigured.Unavailable; + + private Process? _process; + + private IntPtr pinEvent = CreateEventW(IntPtr.Zero, false, false, "Local\\AlwaysOnTopPinEvent-892e0aa2-cfa8-4cc4-b196-ddeb32314ce8"); + + public void Disable() + { + if (_process is not null && !_process.HasExited) + { + _process.Kill(); + } + + if (pinEvent != IntPtr.Zero) + { + CloseHandle(pinEvent); + pinEvent = IntPtr.Zero; + } + } + + public void Enable() + { + var psi = new ProcessStartInfo + { + FileName = "PowerToys.AlwaysOnTop.exe", + Arguments = Environment.ProcessId.ToString(CultureInfo.InvariantCulture), + UseShellExecute = true, + }; + + _process = Process.Start(psi); + } + + public HotkeyEx HotkeyEx => new(0x2 | 0x1, 0x54); // Ctrl + Alt + T + + public Action OnHotkey => () => + { + if (_process is not null && !_process.HasExited && pinEvent != IntPtr.Zero) + { + _ = SetEvent(pinEvent); + } + }; + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern IntPtr CreateEventW(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool SetEvent(IntPtr hEvent); + } +} diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp deleted file mode 100644 index 1ed96e79bd..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "pch.h" - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace NonLocalizable -{ - const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe"; -} - -namespace -{ - const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; - const wchar_t JSON_KEY_WIN[] = L"win"; - const wchar_t JSON_KEY_ALT[] = L"alt"; - const wchar_t JSON_KEY_CTRL[] = L"ctrl"; - const wchar_t JSON_KEY_SHIFT[] = L"shift"; - const wchar_t JSON_KEY_CODE[] = L"code"; - const wchar_t JSON_KEY_HOTKEY[] = L"hotkey"; - const wchar_t JSON_KEY_VALUE[] = L"value"; -} - -BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - Trace::AlwaysOnTop::RegisterProvider(); - break; - - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - Trace::AlwaysOnTop::UnregisterProvider(); - break; - } - return TRUE; -} - -class AlwaysOnTopModuleInterface : public PowertoyModuleIface -{ -public: - // Return the localized display name of the powertoy - virtual PCWSTR get_name() override - { - return app_name.c_str(); - } - - // Return the non localized key of the powertoy, this will be cached by the runner - virtual const wchar_t* get_key() override - { - return app_key.c_str(); - } - - // Return the configured status for the gpo policy for the module - virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override - { - return powertoys_gpo::getConfiguredAlwaysOnTopEnabledValue(); - } - - // Return JSON with the configuration options. - // These are the settings shown on the settings page along with their current values. - virtual bool get_config(wchar_t* buffer, int* buffer_size) override - { - HINSTANCE hinstance = reinterpret_cast(&__ImageBase); - - // Create a Settings object. - PowerToysSettings::Settings settings(hinstance, get_name()); - - return settings.serialize_to_buffer(buffer, buffer_size); - } - - // Passes JSON with the configuration settings for the powertoy. - // This is called when the user hits Save on the settings page. - virtual void set_config(const wchar_t* config) override - { - try - { - // Parse the input JSON string. - PowerToysSettings::PowerToyValues values = - PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); - - parse_hotkey(values); - // If you don't need to do any custom processing of the settings, proceed - // to persists the values calling: - values.save_to_settings_file(); - // Otherwise call a custom function to process the settings before saving them to disk: - // save_settings(); - } - catch (std::exception&) - { - // Improper JSON. - } - } - - virtual bool on_hotkey(size_t /*hotkeyId*/) override - { - if (m_enabled) - { - Logger::trace(L"AlwaysOnTop hotkey pressed"); - if (!is_process_running()) - { - Enable(); - } - - SetEvent(m_hPinEvent); - - return true; - } - - return false; - } - - virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override - { - if (m_hotkey.key) - { - if (hotkeys && buffer_size >= 1) - { - hotkeys[0] = m_hotkey; - } - - return 1; - } - else - { - return 0; - } - } - - // Enable the powertoy - virtual void enable() - { - Logger::info("AlwaysOnTop enabling"); - - Enable(); - } - - // Disable the powertoy - virtual void disable() - { - Logger::info("AlwaysOnTop disabling"); - - Disable(true); - } - - // Returns if the powertoy is enabled - virtual bool is_enabled() override - { - return m_enabled; - } - - // Destroy the powertoy and free memory - virtual void destroy() override - { - Disable(false); - delete this; - } - - AlwaysOnTopModuleInterface() - { - app_name = L"AlwaysOnTop"; //TODO: localize - app_key = NonLocalizable::ModuleKey; - m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT); - m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT); - init_settings(); - } - -private: - void Enable() - { - m_enabled = true; - - // Log telemetry - Trace::AlwaysOnTop::Enable(true); - - unsigned long powertoys_pid = GetCurrentProcessId(); - std::wstring executable_args = L""; - executable_args.append(std::to_wstring(powertoys_pid)); - ResetEvent(m_hPinEvent); - - SHELLEXECUTEINFOW sei{ sizeof(sei) }; - sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = NonLocalizable::ModulePath; - sei.nShow = SW_SHOWNORMAL; - sei.lpParameters = executable_args.data(); - if (ShellExecuteExW(&sei) == false) - { - Logger::error(L"Failed to start AlwaysOnTop"); - auto message = get_last_error_message(GetLastError()); - if (message.has_value()) - { - Logger::error(message.value()); - } - } - else - { - m_hProcess = sei.hProcess; - } - } - - void Disable(bool const traceEvent) - { - m_enabled = false; - ResetEvent(m_hPinEvent); - - // Log telemetry - if (traceEvent) - { - Trace::AlwaysOnTop::Enable(false); - } - - SetEvent(m_hTerminateEvent); - - // Wait for 1.5 seconds for the process to end correctly and stop etw tracer - WaitForSingleObject(m_hProcess, 1500); - - // If process is still running, terminate it - if (m_hProcess) - { - TerminateProcess(m_hProcess, 0); - m_hProcess = nullptr; - } - } - - void parse_hotkey(PowerToysSettings::PowerToyValues& settings) - { - auto settingsObject = settings.get_raw_json(); - if (settingsObject.GetView().Size()) - { - try - { - auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE); - m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); - m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); - m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); - m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL); - m_hotkey.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); - } - catch (...) - { - Logger::error("Failed to initialize AlwaysOnTop start shortcut"); - } - } - else - { - Logger::info("AlwaysOnTop settings are empty"); - } - } - - bool is_process_running() - { - return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; - } - - void init_settings() - { - try - { - // Load and parse the settings file for this PowerToy. - PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file(get_key()); - - parse_hotkey(settings); - } - catch (std::exception&) - { - Logger::warn(L"An exception occurred while loading the settings file"); - // Error while loading from the settings file. Let default values stay as they are. - } - } - - std::wstring app_name; - std::wstring app_key; //contains the non localized key of the powertoy - - bool m_enabled = false; - HANDLE m_hProcess = nullptr; - Hotkey m_hotkey; - - // Handle to event used to pin/unpin windows - HANDLE m_hPinEvent; - HANDLE m_hTerminateEvent; -}; - -extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() -{ - return new AlwaysOnTopModuleInterface(); -} diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/packages.config b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/packages.config deleted file mode 100644 index ff4b059648..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.cpp b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.cpp deleted file mode 100644 index 1d9f38c57d..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.h b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.h deleted file mode 100644 index 9dc1f70972..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/pch.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#include -#include -#include -#include -#include -#include diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/resource.h b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/resource.h deleted file mode 100644 index a4f258c95b..0000000000 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/resource.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by AlwaysOnTopModuleInterface.rc - -////////////////////////////// -// Non-localizable - -#define FILE_DESCRIPTION "PowerToys AlwaysOnTop Module" -#define INTERNAL_NAME "PowerToys.AlwaysOnTopModuleInterface" -#define ORIGINAL_FILENAME "PowerToys.AlwaysOnTopModuleInterface.dll" - -// Non-localizable -////////////////////////////// diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/targetver.h b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/targetver.h deleted file mode 100644 index 567cd346ef..0000000000 Binary files a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/targetver.h and /dev/null differ diff --git a/src/settings-ui/Settings.UI.Library/LastVersionRunSettings.cs b/src/settings-ui/Settings.UI.Library/LastVersionRunSettings.cs new file mode 100644 index 0000000000..879e80725d --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/LastVersionRunSettings.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Settings.UI.Library +{ + public class LastVersionRunSettings : ISettingsConfig + { + [JsonPropertyName("last_version")] + public string LastVersion { get; set; } + + public string GetModuleName() + { + return "LastVersionRun"; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/OOBESettings.cs b/src/settings-ui/Settings.UI.Library/OOBESettings.cs new file mode 100644 index 0000000000..99f90f11d9 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/OOBESettings.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Settings.UI.Library +{ + public class OOBESettings : ISettingsConfig + { + [JsonPropertyName("openedAtFirstLaunch")] + public bool OpenedAtFirstLaunch { get; set; } + + public string GetModuleName() + { + return "OOBE"; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index 057413a408..f9395ec05f 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -44,7 +44,7 @@ - PowerToys.GPOWrapper;PowerToys.ZoomItSettingsInterop + PowerToys.ZoomItSettingsInterop $(OutDir) false @@ -113,7 +113,7 @@ - +