Compare commits

..

11 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
2128505de8 fix xaml format 2025-12-24 15:23:50 +08:00
Shawn Yuan (from Dev Box)
0f4ead7069 update 2025-12-24 15:22:51 +08:00
Shawn Yuan (from Dev Box)
3749f3e87d update 2025-12-24 14:14:05 +08:00
Shawn Yuan (from Dev Box)
d341bd2ca6 update localization 2025-12-24 11:36:10 +08:00
Shawn Yuan (from Dev Box)
20dcb6fb47 add localization support 2025-12-24 11:17:17 +08:00
Shawn Yuan (from Dev Box)
72f84f9652 add model usage tag to ui 2025-12-24 11:01:01 +08:00
Shawn Yuan (from Dev Box)
64dafff7c4 add img size config 2025-12-24 10:20:49 +08:00
Shawn Yuan (from Dev Box)
927d190cf2 fix merge coflicts 2025-12-23 17:40:55 +08:00
Shawn Yuan (from Dev Box)
667800eb86 fix merge conflict 2025-12-23 17:16:43 +08:00
Shawn Yuan (from Dev Box)
35cab47465 Merge branch 'main' into shawn/APImprove2 2025-12-23 17:15:44 +08:00
Shawn Yuan (from Dev Box)
c1603b189f init 2025-12-23 17:12:17 +08:00
278 changed files with 1351 additions and 12177 deletions

View File

@@ -330,9 +330,6 @@ HHH
riday
YYY
# Unicode
precomposed
# GitHub issue/PR commands
azp
feedbackhub

View File

@@ -216,7 +216,6 @@ CImage
cla
CLASSDC
CLASSNOTAVAILABLE
CLEARTYPE
clickable
clickonce
CLIENTEDGE
@@ -254,7 +253,6 @@ colorhistory
colorhistorylimit
COLORKEY
colorref
Convs
comctl
comdlg
comexp
@@ -531,12 +529,9 @@ eyetracker
FANCYZONESDRAWLAYOUTTEST
FANCYZONESEDITOR
FARPROC
fdw
fdx
FErase
fesf
FFFF
FInc
Figma
FILEEXPLORER
fileexploreraddons
@@ -578,7 +573,6 @@ formatetc
FORPARSING
foundrylocal
FRAMECHANGED
FRestore
frm
FROMTOUCH
fsanitize
@@ -613,7 +607,6 @@ GETSCREENSAVERRUNNING
GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
gfx
GHND
gitmodules
GMEM
@@ -664,7 +657,6 @@ hdwwiz
Helpline
helptext
HGFE
hgdiobj
hglobal
hhk
HHmmssfff
@@ -712,7 +704,7 @@ hotlight
hotspot
HPAINTBUFFER
HRAWINPUT
hredraw
HREDRAW
hres
hresult
hrgn
@@ -890,7 +882,7 @@ LINKOVERLAY
LINQTo
listview
LIVEDRAW
livezoom
LIVEZOOM
LLKH
llkhf
LMEM
@@ -919,7 +911,6 @@ LPBITMAPINFOHEADER
LPCFHOOKPROC
LPCITEMIDLIST
LPCLSID
lpch
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
@@ -944,7 +935,6 @@ lptpm
LPTR
LPTSTR
lpv
LPrivate
LPW
lpwcx
lpwndpl
@@ -1359,7 +1349,6 @@ ppv
ppwsz
prc
Prefixer
Premul
prependpath
prepopulate
prevhost
@@ -1915,7 +1904,7 @@ valuegenerator
variantassignment
VARTYPE
vcamp
vcenter
VCENTER
vcgtq
VCINSTALLDIR
Vcpkg
@@ -1947,7 +1936,7 @@ vorrq
VOS
vpaddlq
vqsubq
vredraw
VREDRAW
vreinterpretq
VSC
VSCBD

2
.gitignore vendored
View File

@@ -358,4 +358,4 @@ src/common/Telemetry/*.etl
/src/settings-ui/Settings.UI/Assets/Settings/search.index.json
# PowerToysInstaller Build Temp Files
installer/*/*.wxs.bk
installer/*/*.wxs.bk

View File

@@ -131,8 +131,6 @@
"PowerToys.ImageResizer.exe",
"PowerToys.ImageResizer.dll",
"WinUI3Apps\\PowerToys.ImageResizerCLI.exe",
"WinUI3Apps\\PowerToys.ImageResizerCLI.dll",
"PowerToys.ImageResizerExt.dll",
"PowerToys.ImageResizerContextMenu.dll",
"ImageResizerContextMenuPackage.msix",
@@ -237,14 +235,6 @@
"PowerToys.CmdPalModuleInterface.dll",
"CmdPalKeyboardService.dll",
"PowerToys.ModuleContracts.dll",
"Awake.ModuleServices.dll",
"ColorPicker.ModuleServices.dll",
"Workspaces.ModuleServices.dll",
"Microsoft.CommandPalette.Extensions.dll",
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
"Microsoft.CmdPal.Ext.PowerToys.dll",
"Microsoft.CmdPal.Ext.PowerToys.exe",
"*Microsoft.CmdPal.UI_*.msix",
"PowerToys.DSC.dll",
@@ -368,13 +358,9 @@
"boost_regex-vc143-mt-x32-1_87.dll",
"boost_regex-vc143-mt-x64-1_87.dll",
"Microsoft.ML.OnnxRuntime.dll",
"UnitsNet.dll",
"UtfUnknown.dll",
"Wpf.Ui.dll",
"Shmuelie.WinRTServer.dll",
"ToolGood.Words.Pinyin.dll"
"Wpf.Ui.dll"
],
"SigningInfo": {
"Operations": [

View File

@@ -624,4 +624,4 @@ jobs:
- publish: $(JobOutputDirectory)
artifact: $(JobOutputArtifactName)-failure-$(System.JobAttempt)
displayName: Publish failure logs
condition: or(failed(), canceled())
condition: or(failed(), canceled())

View File

@@ -104,4 +104,4 @@ if ($totalFailure -gt 0) {
exit 1
}
exit 0
exit 0

View File

@@ -444,10 +444,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_ZoneWindowKeyUp</td>
<td>Occurs when a key is released while interacting with zones.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_CLICommand</td>
<td>Triggered when a FancyZones CLI command is executed, logging the command name and success status.</td>
</tr>
</table>
### FileExplorerAddOns

View File

@@ -37,7 +37,6 @@
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.5.250829002" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
@@ -95,7 +94,6 @@
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
<PackageVersion Include="SharpCompress" Version="0.37.2" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<!-- Don't update SkiaSharp.Views.WinUI to version 3.* branch as this brakes the HexBox control in Registry Preview. -->
<PackageVersion Include="SkiaSharp.Views.WinUI" Version="2.88.9" />
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
@@ -147,4 +145,4 @@
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1560,7 +1560,6 @@ SOFTWARE.
- ReverseMarkdown
- ScipBe.Common.Office.OneNote
- SharpCompress
- Shmuelie.WinRTServer
- SkiaSharp.Views.WinUI
- StreamJsonRpc
- StyleCop.Analyzers

View File

@@ -44,10 +44,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/PowerToys.ModuleContracts/PowerToys.ModuleContracts.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/SettingsAPI/SettingsAPI.vcxproj" Id="6955446d-23f7-4023-9bb3-8657f904af99" />
<Project Path="src/common/Themes/Themes.vcxproj" Id="98537082-0fdb-40de-abd8-0dc5a4269bab" />
<Project Path="src/common/UITestAutomation/UITestAutomation.csproj">
@@ -160,10 +156,6 @@
<Project Path="src/modules/alwaysontop/AlwaysOnTopModuleInterface/AlwaysOnTopModuleInterface.vcxproj" Id="48a0a19e-a0be-4256-acf8-cc3b80291af9" />
</Folder>
<Folder Name="/modules/awake/">
<Project Path="src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/awake/Awake/Awake.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -174,10 +166,6 @@
<Project Path="src/modules/cmdNotFound/CmdNotFoundModuleInterface/CmdNotFoundModuleInterface.vcxproj" Id="0014d652-901f-4456-8d65-06fc5f997fb0" />
</Folder>
<Folder Name="/modules/colorpicker/">
<Project Path="src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/colorPicker/ColorPicker/ColorPicker.vcxproj" Id="655c9af2-18d3-4da6-80e4-85504a7722ba">
<BuildDependency Project="src/common/logger/logger.vcxproj" />
</Project>
@@ -218,11 +206,6 @@
<Platform Solution="*|x64" Project="x64" />
<Deploy />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
<Deploy />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Registry/Microsoft.CmdPal.Ext.Registry.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -459,10 +442,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/imageresizer/Tests/">
<Project Path="src/modules/imageresizer/tests/ImageResizer.UnitTests.csproj">
@@ -953,10 +932,6 @@
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="2d604c07-51fc-46bb-9eb7-75aecc7f5e81" />
</Folder>
<Folder Name="/modules/Workspaces/">
<Project Path="src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />

View File

@@ -1,93 +0,0 @@
# CLI Conventions
This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.
## Library
Use the **System.CommandLine** library for CLI argument parsing. This is already defined in `Directory.Packages.props`:
```xml
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
```
Add the reference to your project:
```xml
<PackageReference Include="System.CommandLine" />
```
## Option Naming and Definition
- Use `--kebab-case` for long form (e.g., `--shrink-only`).
- Use single `-x` for short form (e.g., `-s`, `-w`).
- Define aliases as static readonly arrays: `["--silent", "-s"]`.
- Create options using `Option<T>` with descriptive help text.
- Add validators for options that require range or format checking.
## RootCommand Setup
- Create a `RootCommand` with a brief description.
- Add all options and arguments to the command.
## Parsing
- Use `Parser(rootCommand).Parse(args)` to parse CLI arguments.
- Extract option values using `parseResult.GetValueForOption()`.
- Note: Use `Parser` directly; `RootCommand.Parse()` may not be available with the pinned System.CommandLine version.
### Parse/Validation Errors
- On parse/validation errors, print error messages and usage, then exit with non-zero code.
## Examples
Reference implementations:
- Awake: `src/modules/Awake/Awake/Program.cs`
- ImageResizer: `src/modules/imageresizer/ui/Cli/`
## Help Output
- Provide a `PrintUsage()` method for custom help formatting if needed.
## Best Practices
1. **Consistency**: Follow existing module patterns.
2. **Documentation**: Always provide help text for each option.
3. **Validation**: Validate input and provide clear error messages.
4. **Atomicity**: Make one logical change per PR; avoid drive-by refactors.
5. **Build/Test Discipline**: Build and test synchronously, one terminal per operation.
6. **Style**: Follow repo analyzers (`.editorconfig`, StyleCop) and formatting rules.
## Logging Requirements
- Use `ManagedCommon.Logger` for consistent logging.
- Initialize logging early in `Main()`.
- Use dual output (console + log file) for errors and warnings to ensure visibility.
- Reference: `src/modules/imageresizer/ui/Cli/CliLogger.cs`
## Error Handling
### Exit Codes
- `0`: Success
- `1`: General error (parsing, validation, runtime)
- `2`: Invalid arguments (optional)
### Exception Handling
- Always wrap `Main()` in try-catch for unhandled exceptions.
- Log exceptions before exiting with non-zero code.
- Display user-friendly error messages to stderr.
- Preserve detailed stack traces in log files only.
## Testing Requirements
- Include tests for argument parsing, validation, and edge cases.
- Place CLI tests in module-specific test projects (e.g., `src/modules/[module]/tests/*CliTests.cs`).
## Signing and Deployment
- CLI executables are signed automatically in CI/CD.
- **New CLI tools**: Add your executable and dll to `.pipelines/ESRPSigning_core.json` in the signing list.
- CLI executables are deployed alongside their parent module (e.g., `C:\Program Files\PowerToys\modules\[ModuleName]\`).
- Use self-contained deployment (import `Common.SelfContained.props`).

View File

@@ -7,18 +7,11 @@
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="Microsoft_CommandPalette_Extensions_winmd" Guid="304AD25A-A986-4058-940E-61DB79EBD78C" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Microsoft_CommandPalette_Extensions_winmd" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="Microsoft.CommandPalette.Extensions.winmd" Source="$(var.BinDir)Microsoft.CommandPalette.Extensions.winmd" />
</Component>
<!-- Generated by generateFileComponents.ps1 -->
<!--BaseApplicationsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="BaseApplicationsComponentGroup">
<ComponentRef Id="Microsoft_CommandPalette_Extensions_winmd" />
</ComponentGroup>
</Fragment>

View File

@@ -173,4 +173,4 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
</ItemGroup>
</Target>
<Target Name="Restore" />
</Project>
</Project>

View File

@@ -10,8 +10,7 @@
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
IgnorableNamespaces="uap uap2 uap3 rescap desktop uap10 systemai com">
IgnorableNamespaces="uap uap2 uap3 rescap desktop uap10 systemai">
<Identity
Name="Microsoft.PowerToys.SparseApp"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
@@ -31,7 +30,6 @@
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.26226.0" />
</Dependencies>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
<systemai:Capability Name="systemAIModels"/>
<rescap:Capability Name="unvirtualizedResources"/>
@@ -68,42 +66,5 @@
AppListEntry="none">
</uap:VisualElements>
</Application>
<Application Id="PowerToys.CmdPalExtension" Executable="Microsoft.CmdPal.Ext.PowerToys.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="PowerToys.CommandPaletteExtension"
Description="PowerToys Command Palette Extension"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png"
AppListEntry="none">
</uap:VisualElements>
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Microsoft.CmdPal.Ext.PowerToys.exe" Arguments="-RegisterProcessAsComServer" DisplayName="PowerToys Command Palette Extension">
<com:Class Id="7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17" DisplayName="PowerToys Command Palette Extension" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.commandpalette"
Id="PowerToys"
PublicFolder="Public"
DisplayName="PowerToys"
Description="Surface PowerToys commands inside Command Palette">
<uap3:Properties>
<CmdPalProvider>
<Activation>
<CreateInstance ClassId="7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17" />
</Activation>
<SupportedInterfaces>
<Commands/>
</SupportedInterfaces>
</CmdPalProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
</Package>
</Package>

View File

@@ -2,10 +2,8 @@
// 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.IO;
using ManagedCommon;
namespace Common.UI
{
@@ -122,33 +120,28 @@ namespace Common.UI
}
}
// What about debug build? Should also consider debug build, maybe tray window message?
public static void OpenSettings(SettingsWindow window)
public static void OpenSettings(SettingsWindow window, bool mainExecutableIsOnTheParentFolder)
{
try
{
var exePath = Path.Combine(
PowerToysPathResolver.GetPowerToysInstallPath(),
"PowerToys.exe");
if (exePath == null || !File.Exists(exePath))
var directoryPath = System.AppContext.BaseDirectory;
if (mainExecutableIsOnTheParentFolder)
{
Logger.LogError($"Failed to find powertoys exe path, {exePath}");
return;
// Need to go into parent folder for PowerToys.exe. Likely a WinUI3 App SDK application.
directoryPath = Path.Combine(directoryPath, "..");
directoryPath = Path.Combine(directoryPath, "PowerToys.exe");
}
else
{
// PowerToys.exe is in the same path as the application.
directoryPath = Path.Combine(directoryPath, "PowerToys.exe");
}
var args = "--open-settings=" + SettingsWindowNameToString(window);
Process.Start(new ProcessStartInfo
{
FileName = exePath,
Arguments = args,
UseShellExecute = false,
});
Process.Start(new ProcessStartInfo(directoryPath) { Arguments = "--open-settings=" + SettingsWindowNameToString(window) });
}
catch (Exception ex)
catch
{
Logger.LogError(ex.Message);
// TODO(stefan): Log exception once unified logging is implemented
}
}
}

View File

@@ -39,7 +39,7 @@ namespace Microsoft.PowerToys.FilePreviewCommon
var softlineBreak = new Markdig.Extensions.Hardlines.SoftlineBreakAsHardlineExtension();
MarkdownPipelineBuilder pipelineBuilder;
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics().DisableHtml();
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics();
pipelineBuilder.Extensions.Add(extension);
pipelineBuilder.Extensions.Add(softlineBreak);

View File

@@ -1,168 +0,0 @@
// 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.IO;
using System.Runtime.Versioning;
using Microsoft.Win32;
namespace ManagedCommon
{
[SupportedOSPlatform("windows")]
public class PowerToysPathResolver
{
private const string PowerToysRegistryKey = @"Software\Classes\powertoys";
private const string PowerToysExe = "PowerToys.exe";
/// <summary>
/// Gets the PowerToys installation path by checking registry entries
/// </summary>
/// <returns>The path to PowerToys installation directory, or null if not found</returns>
public static string GetPowerToysInstallPath()
{
#if DEBUG
// In debug builds, resolve directly from the running process (no installer/registry involved).
return GetPathFromCurrentProcess();
#else
// Try to get path from Per-User installation first
string path = GetPathFromRegistry(RegistryHive.CurrentUser);
if (!string.IsNullOrEmpty(path))
{
return path;
}
// Fall back to Per-Machine installation
path = GetPathFromRegistry(RegistryHive.LocalMachine);
if (!string.IsNullOrEmpty(path))
{
return path;
}
return null;
#endif
}
private static string GetPathFromRegistry(RegistryHive hive)
{
try
{
using var baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64);
// First try to get path from the powertoys protocol registration
string path = GetPathFromProtocolRegistration(baseKey);
if (!string.IsNullOrEmpty(path))
{
return path;
}
}
catch (Exception)
{
// Ignore registry access errors
}
return null;
}
private static string GetPathFromProtocolRegistration(RegistryKey baseKey)
{
try
{
using var key = baseKey.OpenSubKey($@"{PowerToysRegistryKey}\shell\open\command");
if (key != null)
{
string command = key.GetValue(string.Empty)?.ToString();
if (!string.IsNullOrEmpty(command))
{
// Parse command like: "C:\Program Files\PowerToys\PowerToys.exe" "%1"
return ExtractPathFromCommand(command);
}
}
}
catch (Exception)
{
// Ignore registry access errors
}
return null;
}
private static string GetPathFromCurrentProcess()
{
try
{
// If we're running inside PowerToys.exe (dev/debug builds), use the executable location.
var processPath = Process.GetCurrentProcess().MainModule?.FileName;
if (!string.IsNullOrEmpty(processPath))
{
var processDir = Path.GetDirectoryName(processPath);
if (!string.IsNullOrEmpty(processDir) && File.Exists(Path.Combine(processDir, PowerToysExe)))
{
return processDir;
}
}
// As a fallback, walk up from AppContext.BaseDirectory to find PowerToys.exe.
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory != null)
{
var candidate = Path.Combine(directory.FullName, PowerToysExe);
if (File.Exists(candidate))
{
return directory.FullName;
}
directory = directory.Parent;
}
}
catch
{
// Ignore reflection/process permission errors; caller will see null and handle accordingly.
}
return null;
}
private static string ExtractPathFromCommand(string command)
{
if (string.IsNullOrEmpty(command))
{
return null;
}
try
{
// Handle quoted paths: "C:\Program Files\PowerToys\PowerToys.exe" "%1"
if (command.StartsWith('\"'))
{
int endQuote = command.IndexOf('\"', 1);
if (endQuote > 1)
{
string exePath = command.Substring(1, endQuote - 1);
if (File.Exists(exePath))
{
return Path.GetDirectoryName(exePath);
}
}
}
else
{
// Handle unquoted paths (less common)
string[] parts = command.Split(' ');
if (parts.Length > 0 && File.Exists(parts[0]))
{
return Path.GetDirectoryName(parts[0]);
}
}
}
catch (Exception)
{
// Ignore path parsing errors
}
return null;
}
}
}

View File

@@ -1,47 +0,0 @@
// 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 Common.UI;
namespace PowerToys.ModuleContracts;
/// <summary>
/// Base contract for PowerToys modules exposed to the Command Palette.
/// </summary>
public interface IModuleService
{
/// <summary>
/// Gets module identifier (e.g., Workspaces, Awake).
/// </summary>
string Key { get; }
Task<OperationResult> LaunchAsync(CancellationToken cancellationToken = default);
Task<OperationResult> OpenSettingsAsync(CancellationToken cancellationToken = default);
}
/// <summary>
/// Helper base to reduce duplication for simple modules.
/// </summary>
public abstract class ModuleServiceBase : IModuleService
{
public abstract string Key { get; }
protected abstract SettingsDeepLink.SettingsWindow SettingsWindow { get; }
public abstract Task<OperationResult> LaunchAsync(CancellationToken cancellationToken = default);
public virtual Task<OperationResult> OpenSettingsAsync(CancellationToken cancellationToken = default)
{
try
{
SettingsDeepLink.OpenSettings(SettingsWindow);
return Task.FromResult(OperationResult.Ok());
}
catch (Exception ex)
{
return Task.FromResult(OperationResult.Fail($"Failed to open settings for {Key}: {ex.Message}"));
}
}
}

View File

@@ -1,30 +0,0 @@
// 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 PowerToys.ModuleContracts;
/// <summary>
/// Lightweight result type for module operations.
/// </summary>
public readonly record struct OperationResult(bool Success, string? Error = null)
{
public static OperationResult Ok() => new(true, null);
public static OperationResult Fail(string error) => new(false, error);
}
/// <summary>
/// Result type with a payload.
/// </summary>
public readonly record struct OperationResult<T>(bool Success, T? Value, string? Error = null);
/// <summary>
/// Factory helpers for creating operation results.
/// </summary>
public static class OperationResults
{
public static OperationResult<T> Ok<T>(T value) => new(true, value, null);
public static OperationResult<T> Fail<T>(string error) => new(false, default, error);
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common.UI\Common.UI.csproj" />
</ItemGroup>
</Project>

View File

@@ -75,62 +75,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE;
}
hstring Constants::AdvancedPasteShowUIEvent()
{
return CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_EVENT;
}
hstring Constants::AdvancedPasteTerminateAppMessage()
{
return CommonSharedConstants::ADVANCED_PASTE_TERMINATE_APP_MESSAGE;
}
hstring Constants::AlwaysOnTopPinEvent()
{
return CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT;
}
hstring Constants::FindMyMouseTriggerEvent()
{
return CommonSharedConstants::FIND_MY_MOUSE_TRIGGER_EVENT;
}
hstring Constants::MouseHighlighterTriggerEvent()
{
return CommonSharedConstants::MOUSE_HIGHLIGHTER_TRIGGER_EVENT;
}
hstring Constants::MouseCrosshairsTriggerEvent()
{
return CommonSharedConstants::MOUSE_CROSSHAIRS_TRIGGER_EVENT;
}
hstring Constants::CursorWrapTriggerEvent()
{
return CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT;
}
hstring Constants::LightSwitchToggleEvent()
{
return CommonSharedConstants::LIGHTSWITCH_TOGGLE_EVENT;
}
hstring Constants::ZoomItZoomEvent()
{
return CommonSharedConstants::ZOOMIT_ZOOM_EVENT;
}
hstring Constants::ZoomItDrawEvent()
{
return CommonSharedConstants::ZOOMIT_DRAW_EVENT;
}
hstring Constants::ZoomItBreakEvent()
{
return CommonSharedConstants::ZOOMIT_BREAK_EVENT;
}
hstring Constants::ZoomItLiveZoomEvent()
{
return CommonSharedConstants::ZOOMIT_LIVEZOOM_EVENT;
}
hstring Constants::ZoomItSnipEvent()
{
return CommonSharedConstants::ZOOMIT_SNIP_EVENT;
}
hstring Constants::ZoomItRecordEvent()
{
return CommonSharedConstants::ZOOMIT_RECORD_EVENT;
}
hstring Constants::ShowPowerOCRSharedEvent()
{
return CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT;

View File

@@ -23,20 +23,6 @@ namespace winrt::PowerToys::Interop::implementation
static hstring AdvancedPasteAdditionalActionMessage();
static hstring AdvancedPasteCustomActionMessage();
static hstring AdvancedPasteTerminateAppMessage();
static hstring AdvancedPasteShowUIEvent();
static hstring AlwaysOnTopPinEvent();
static hstring MeasureToolTriggerEvent();
static hstring FindMyMouseTriggerEvent();
static hstring MouseHighlighterTriggerEvent();
static hstring MouseCrosshairsTriggerEvent();
static hstring CursorWrapTriggerEvent();
static hstring LightSwitchToggleEvent();
static hstring ZoomItZoomEvent();
static hstring ZoomItDrawEvent();
static hstring ZoomItBreakEvent();
static hstring ZoomItLiveZoomEvent();
static hstring ZoomItSnipEvent();
static hstring ZoomItRecordEvent();
static hstring ShowPowerOCRSharedEvent();
static hstring TerminatePowerOCRSharedEvent();
static hstring MouseJumpShowPreviewEvent();
@@ -47,6 +33,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring PowerAccentExitEvent();
static hstring ShortcutGuideTriggerEvent();
static hstring RegistryPreviewTriggerEvent();
static hstring MeasureToolTriggerEvent();
static hstring GcodePreviewResizeEvent();
static hstring BgcodePreviewResizeEvent();
static hstring QoiPreviewResizeEvent();

View File

@@ -20,19 +20,6 @@ namespace PowerToys
static String AdvancedPasteAdditionalActionMessage();
static String AdvancedPasteCustomActionMessage();
static String AdvancedPasteTerminateAppMessage();
static String AdvancedPasteShowUIEvent();
static String AlwaysOnTopPinEvent();
static String FindMyMouseTriggerEvent();
static String MouseHighlighterTriggerEvent();
static String MouseCrosshairsTriggerEvent();
static String CursorWrapTriggerEvent();
static String LightSwitchToggleEvent();
static String ZoomItZoomEvent();
static String ZoomItDrawEvent();
static String ZoomItBreakEvent();
static String ZoomItLiveZoomEvent();
static String ZoomItSnipEvent();
static String ZoomItRecordEvent();
static String ShowPowerOCRSharedEvent();
static String TerminatePowerOCRSharedEvent();
static String MouseJumpShowPreviewEvent();
@@ -64,4 +51,4 @@ namespace PowerToys
static String ShowCmdPalEvent();
}
}
}
}

View File

@@ -40,8 +40,6 @@ namespace CommonSharedConstants
const wchar_t ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE[] = L"CustomAction";
const wchar_t ADVANCED_PASTE_TERMINATE_APP_MESSAGE[] = L"TerminateApp";
const wchar_t ADVANCED_PASTE_SHOW_UI_EVENT[] = L"Local\\PowerToys_AdvancedPaste_ShowUI";
// Path to the event used to show Color Picker
const wchar_t SHOW_COLOR_PICKER_SHARED_EVENT[] = L"Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525";
@@ -85,21 +83,12 @@ namespace CommonSharedConstants
const wchar_t TERMINATE_MOUSE_JUMP_SHARED_EVENT[] = L"Local\\TerminateMouseJumpEvent-252fa337-317f-4c37-a61f-99464c3f9728";
// Paths to the events used by other Mouse Utilities
const wchar_t FIND_MY_MOUSE_TRIGGER_EVENT[] = L"Local\\FindMyMouseTriggerEvent-5a9dc5f4-1c74-4f2f-a66f-1b9b6a2f9b23";
const wchar_t MOUSE_HIGHLIGHTER_TRIGGER_EVENT[] = L"Local\\MouseHighlighterTriggerEvent-1e3c9c3d-3fdf-4f9a-9a52-31c9b3c3a8f4";
const wchar_t MOUSE_CROSSHAIRS_TRIGGER_EVENT[] = L"Local\\MouseCrosshairsTriggerEvent-0d4c7f92-0a5c-4f5c-b64b-8a2a2f7e0b21";
const wchar_t CURSOR_WRAP_TRIGGER_EVENT[] = L"Local\\CursorWrapTriggerEvent-1f8452b5-4e6e-45b3-8b09-13f14a5900c9";
// Path to the event used by RegistryPreview
const wchar_t REGISTRY_PREVIEW_TRIGGER_EVENT[] = L"Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687";
// Path to the event used by MeasureTool
const wchar_t MEASURE_TOOL_TRIGGER_EVENT[] = L"Local\\MeasureToolEvent-3d46745f-09b3-4671-a577-236be7abd199";
// Path to the event used by LightSwitch
const wchar_t LIGHTSWITCH_TOGGLE_EVENT[] = L"Local\\PowerToys-LightSwitch-ToggleEvent-d8dc2f29-8c94-4ca1-8c5f-3e2b1e3c4f5a";
// Path to the event used by GcodePreviewHandler
const wchar_t GCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysGcodePreviewResizeEvent-6ff1f9bd-ccbd-4b24-a79f-40a34fb0317d";
@@ -141,12 +130,6 @@ namespace CommonSharedConstants
// Path to the events used by ZoomIt
const wchar_t ZOOMIT_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysZoomIt-RefreshSettingsEvent-f053a563-d519-4b0d-8152-a54489c13324";
const wchar_t ZOOMIT_EXIT_EVENT[] = L"Local\\PowerToysZoomIt-ExitEvent-36641ce6-df02-4eac-abea-a3fbf9138220";
const wchar_t ZOOMIT_ZOOM_EVENT[] = L"Local\\PowerToysZoomIt-ZoomEvent-1e4190d7-94bc-4ad5-adc0-9a8fd07cb393";
const wchar_t ZOOMIT_DRAW_EVENT[] = L"Local\\PowerToysZoomIt-DrawEvent-56338997-404d-4549-bd9a-d132b6766975";
const wchar_t ZOOMIT_BREAK_EVENT[] = L"Local\\PowerToysZoomIt-BreakEvent-17f2e63c-4c56-41dd-90a0-2d12f9f50c6b";
const wchar_t ZOOMIT_LIVEZOOM_EVENT[] = L"Local\\PowerToysZoomIt-LiveZoomEvent-390bf0c7-616f-47dc-bafe-a2d228add20d";
const wchar_t ZOOMIT_SNIP_EVENT[] = L"Local\\PowerToysZoomIt-SnipEvent-2fd9c211-436d-4f17-a902-2528aaae3e30";
const wchar_t ZOOMIT_RECORD_EVENT[] = L"Local\\PowerToysZoomIt-RecordEvent-74539344-eaad-4711-8e83-23946e424512";
// used from quick access window
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";

View File

@@ -3,128 +3,78 @@
#include <functional>
#include <thread>
#include <string>
#include <atomic>
#include <windows.h>
/// <summary>
/// A reusable utility class that listens for a named Windows event and invokes a callback when triggered.
/// Provides RAII-based resource management for event handles and the listener thread.
/// The thread is properly joined on destruction to ensure clean shutdown.
/// </summary>
class EventWaiter
{
public:
EventWaiter() = default;
EventWaiter(const EventWaiter&) = delete;
EventWaiter& operator=(const EventWaiter&) = delete;
EventWaiter(EventWaiter&&) = delete;
EventWaiter& operator=(EventWaiter&&) = delete;
~EventWaiter()
EventWaiter() {}
EventWaiter(const std::wstring& name, std::function<void(DWORD)> callback)
{
stop();
}
/// <summary>
/// Starts listening for the specified named event. When the event is signaled, the callback is invoked.
/// </summary>
/// <param name="name">The name of the Windows event to listen for.</param>
/// <param name="callback">The callback function to invoke when the event is triggered. Receives ERROR_SUCCESS on success.</param>
/// <returns>true if listening started successfully, false otherwise.</returns>
bool start(const std::wstring& name, std::function<void(DWORD)> callback)
{
if (m_listening)
{
return false;
}
m_exitThreadEvent = CreateEventW(nullptr, false, false, nullptr);
m_waitingEvent = CreateEventW(nullptr, false, false, name.c_str());
if (!m_exitThreadEvent || !m_waitingEvent)
{
cleanup();
return false;
}
m_listening = true;
m_eventThread = std::thread([this, cb = std::move(callback)]() {
HANDLE events[2] = { m_waitingEvent, m_exitThreadEvent };
while (m_listening)
// Create localExitThreadEvent and localWaitingEvent for capturing. We cannot capture 'this' as we implement move constructor.
auto localExitThreadEvent = exitThreadEvent = CreateEvent(nullptr, false, false, nullptr);
HANDLE localWaitingEvent = waitingEvent = CreateEvent(nullptr, false, false, name.c_str());
std::thread([=]() {
HANDLE events[2] = { localWaitingEvent, localExitThreadEvent };
while (true)
{
auto waitResult = WaitForMultipleObjects(2, events, false, INFINITE);
if (!m_listening)
{
break;
}
if (waitResult == WAIT_OBJECT_0 + 1)
{
// Exit event signaled
break;
}
if (waitResult == WAIT_FAILED)
{
cb(GetLastError());
callback(GetLastError());
continue;
}
if (waitResult == WAIT_OBJECT_0)
{
cb(ERROR_SUCCESS);
callback(ERROR_SUCCESS);
}
}
});
return true;
}).detach();
}
/// <summary>
/// Stops listening for the event and cleans up resources.
/// Waits for the listener thread to finish before returning.
/// Safe to call multiple times.
/// </summary>
void stop()
EventWaiter(EventWaiter&) = delete;
EventWaiter& operator=(EventWaiter&) = delete;
EventWaiter(EventWaiter&& a) noexcept
{
m_listening = false;
if (m_exitThreadEvent)
{
SetEvent(m_exitThreadEvent);
}
if (m_eventThread.joinable())
{
m_eventThread.join();
}
cleanup();
this->exitThreadEvent = a.exitThreadEvent;
this->waitingEvent = a.waitingEvent;
a.exitThreadEvent = nullptr;
a.waitingEvent = nullptr;
}
/// <summary>
/// Returns whether the listener is currently active.
/// </summary>
bool is_listening() const
EventWaiter& operator=(EventWaiter&& a) noexcept
{
return m_listening;
this->exitThreadEvent = a.exitThreadEvent;
this->waitingEvent = a.waitingEvent;
a.exitThreadEvent = nullptr;
a.waitingEvent = nullptr;
return *this;
}
~EventWaiter()
{
if (exitThreadEvent)
{
SetEvent(exitThreadEvent);
CloseHandle(exitThreadEvent);
}
if (waitingEvent)
{
CloseHandle(waitingEvent);
}
}
private:
void cleanup()
{
if (m_exitThreadEvent)
{
CloseHandle(m_exitThreadEvent);
m_exitThreadEvent = nullptr;
}
if (m_waitingEvent)
{
CloseHandle(m_waitingEvent);
m_waitingEvent = nullptr;
}
}
HANDLE m_exitThreadEvent = nullptr;
HANDLE m_waitingEvent = nullptr;
std::thread m_eventThread;
std::atomic_bool m_listening{ false };
HANDLE exitThreadEvent = nullptr;
HANDLE waitingEvent = nullptr;
};

View File

@@ -45,7 +45,6 @@
<Target Name="GenerateDscResourceJsonFiles" AfterTargets="Build" Condition="'$(CIBuild)' != 'true'">
<Message Text="Generating DSC resource JSON files to DSCModules subfolder..." Importance="high" />
<MakeDir Directories="$(TargetDir)DSCModules" />
<Exec Command="dotnet &quot;$(TargetPath)&quot; manifest --resource settings --outputDir &quot;$(TargetDir)DSCModules&quot;" />
</Target>
</Project>

View File

@@ -335,6 +335,7 @@
<converters:CountToVisibilityConverter x:Key="CountToVisibilityConverter" />
<converters:CountToInvertedVisibilityConverter x:Key="CountToInvertedVisibilityConverter" />
<converters:ServiceTypeToIconConverter x:Key="ServiceTypeToIconConverter" />
<converters:PasteAIUsageToStringConverter x:Key="PasteAIUsageToStringConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="PromptBoxGrid" Loaded="Grid_Loaded">
@@ -430,12 +431,20 @@
Grid.Row="1"
MinHeight="104"
MaxHeight="320">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.CustomFormatResult, Mode=OneWay}"
TextWrapping="Wrap" />
<StackPanel>
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.CustomFormatResult, Mode=OneWay}"
TextWrapping="Wrap"
Visibility="{x:Bind ViewModel.HasCustomFormatText, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<Image
HorizontalAlignment="Left"
Source="{x:Bind ViewModel.CustomFormatImageResult, Mode=OneWay}"
Stretch="Uniform"
Visibility="{x:Bind ViewModel.HasCustomFormatImage, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</ScrollViewer>
</Grid>
<Rectangle
@@ -602,20 +611,37 @@
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ServiceType, Mode=OneWay}" />
</StackPanel>
<Border
<StackPanel
Grid.Column="2"
Padding="2,0,2,0"
VerticalAlignment="Center"
BorderBrush="{ThemeResource ControlStrokeColorSecondary}"
BorderThickness="1"
CornerRadius="{StaticResource ControlCornerRadius}"
Visibility="{x:Bind IsLocalModel, Mode=OneWay}">
<TextBlock
x:Uid="LocalModelBadge"
AutomationProperties.AccessibilityView="Raw"
FontSize="10"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</Border>
Orientation="Horizontal"
Spacing="4">
<Border
Padding="2,0,2,0"
VerticalAlignment="Center"
BorderBrush="{ThemeResource ControlStrokeColorSecondary}"
BorderThickness="1"
CornerRadius="{StaticResource ControlCornerRadius}">
<TextBlock
AutomationProperties.AccessibilityView="Raw"
FontSize="10"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Usage, Mode=OneWay, Converter={StaticResource PasteAIUsageToStringConverter}}" />
</Border>
<Border
Padding="2,0,2,0"
VerticalAlignment="Center"
BorderBrush="{ThemeResource ControlStrokeColorSecondary}"
BorderThickness="1"
CornerRadius="{StaticResource ControlCornerRadius}"
Visibility="{x:Bind IsLocalModel, Mode=OneWay}">
<TextBlock
x:Uid="LocalModelBadge"
AutomationProperties.AccessibilityView="Raw"
FontSize="10"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</Border>
</StackPanel>
<!--<Border
Grid.Column="2"
Padding="2,0,2,0"

View File

@@ -164,7 +164,7 @@ namespace AdvancedPaste.Controls
return;
}
var flyout = FlyoutBase.GetAttachedFlyout(AIProviderButton);
var flyout = AIProviderButton.Flyout;
if (AIProviderListView.SelectedItem is not PasteAIProviderDefinition provider)
{
@@ -180,7 +180,6 @@ namespace AdvancedPaste.Controls
if (ViewModel.SetActiveProviderCommand.CanExecute(provider))
{
await ViewModel.SetActiveProviderCommand.ExecuteAsync(provider);
SyncProviderSelection();
}
flyout?.Hide();

View File

@@ -0,0 +1,30 @@
// 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 AdvancedPaste.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace AdvancedPaste.Converters;
public sealed partial class PasteAIUsageToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var usage = value switch
{
string s => PasteAIUsageExtensions.FromConfigString(s),
PasteAIUsage u => u,
_ => PasteAIUsage.ChatCompletion,
};
return ResourceLoaderInstance.ResourceLoader.GetString($"PasteAIUsage_{usage}");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -46,6 +46,13 @@ internal static class DataPackageHelpers
return dataPackage;
}
internal static DataPackage CreateFromImage(RandomAccessStreamReference imageStreamRef)
{
DataPackage dataPackage = new();
dataPackage.SetBitmap(imageStreamRef);
return dataPackage;
}
internal static async Task<DataPackage> CreateFromFileAsync(string fileName)
{
var storageFile = await StorageFile.GetFileFromPathAsync(fileName);

View File

@@ -168,6 +168,9 @@ namespace AdvancedPaste.Services.CustomActions
ModelPath = provider.ModelPath,
SystemPrompt = systemPrompt,
ModerationEnabled = provider.ModerationEnabled,
Usage = provider.UsageKind,
ImageWidth = provider.ImageWidth,
ImageHeight = provider.ImageHeight,
};
return providerConfig;

View File

@@ -28,5 +28,11 @@ namespace AdvancedPaste.Services.CustomActions
public string SystemPrompt { get; set; }
public bool ModerationEnabled { get; set; }
public PasteAIUsage Usage { get; set; }
public int ImageWidth { get; set; }
public int ImageHeight { get; set; }
}
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
@@ -16,6 +17,7 @@ using Microsoft.SemanticKernel.Connectors.Google;
using Microsoft.SemanticKernel.Connectors.MistralAI;
using Microsoft.SemanticKernel.Connectors.Ollama;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.TextToImage;
namespace AdvancedPaste.Services.CustomActions
{
@@ -73,6 +75,69 @@ namespace AdvancedPaste.Services.CustomActions
var executionSettings = CreateExecutionSettings();
var kernel = CreateKernel();
switch (_config.Usage)
{
case PasteAIUsage.TextToImage:
var imageDescription = string.IsNullOrWhiteSpace(prompt) ? inputText : $"{inputText}. {prompt}";
return await ProcessTextToImageAsync(kernel, imageDescription, cancellationToken);
case PasteAIUsage.ChatCompletion:
default:
var userMessageContent = $"""
User instructions:
{prompt}
Clipboard Content:
{inputText}
Output:
""";
return await ProcessChatCompletionAsync(kernel, request, userMessageContent, systemPrompt, cancellationToken);
}
}
private async Task<string> ProcessTextToImageAsync(Kernel kernel, string userMessageContent, CancellationToken cancellationToken)
{
#pragma warning disable SKEXP0001
var imageService = kernel.GetRequiredService<ITextToImageService>();
var width = _config.ImageWidth > 0 ? _config.ImageWidth : 1024;
var height = _config.ImageHeight > 0 ? _config.ImageHeight : 1024;
var settings = new OpenAITextToImageExecutionSettings
{
Size = (width, height),
};
var generatedImages = await imageService.GetImageContentsAsync(new TextContent(userMessageContent), settings, cancellationToken: cancellationToken);
if (generatedImages.Count == 0)
{
throw new InvalidOperationException("No image generated.");
}
var imageContent = generatedImages[0];
if (imageContent.Data.HasValue)
{
var base64 = Convert.ToBase64String(imageContent.Data.Value.ToArray());
return $"data:{imageContent.MimeType ?? "image/png"};base64,{base64}";
}
else if (imageContent.Uri != null)
{
using var client = new HttpClient();
var imageBytes = await client.GetByteArrayAsync(imageContent.Uri, cancellationToken);
var base64 = Convert.ToBase64String(imageBytes);
return $"data:image/png;base64,{base64}";
}
else
{
throw new InvalidOperationException("Generated image contains no data.");
}
#pragma warning restore SKEXP0001
}
private async Task<string> ProcessChatCompletionAsync(Kernel kernel, PasteAIRequest request, string userMessageContent, string systemPrompt, CancellationToken cancellationToken)
{
var executionSettings = CreateExecutionSettings();
var modelId = _config.Model;
IChatCompletionService chatService;
@@ -95,29 +160,20 @@ namespace AdvancedPaste.Services.CustomActions
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(systemPrompt);
if (imageBytes != null)
if (request.ImageBytes != null)
{
var collection = new ChatMessageContentItemCollection();
if (!string.IsNullOrWhiteSpace(inputText))
if (!string.IsNullOrWhiteSpace(request.InputText))
{
collection.Add(new TextContent($"Clipboard Content:\n{inputText}"));
collection.Add(new TextContent($"Clipboard Content:\n{request.InputText}"));
}
collection.Add(new ImageContent(imageBytes, request.ImageMimeType ?? "image/png"));
collection.Add(new TextContent($"User instructions:\n{prompt}\n\nOutput:"));
collection.Add(new ImageContent(request.ImageBytes, request.ImageMimeType ?? "image/png"));
collection.Add(new TextContent($"User instructions:\n{request.Prompt}\n\nOutput:"));
chatHistory.AddUserMessage(collection);
}
else
{
var userMessageContent = $"""
User instructions:
{prompt}
Clipboard Content:
{inputText}
Output:
""";
chatHistory.AddUserMessage(userMessageContent);
}
@@ -142,11 +198,31 @@ namespace AdvancedPaste.Services.CustomActions
switch (_serviceType)
{
case AIServiceType.OpenAI:
kernelBuilder.AddOpenAIChatCompletion(_config.Model, apiKey, serviceId: _config.Model);
if (_config.Usage == PasteAIUsage.TextToImage)
{
#pragma warning disable SKEXP0010
kernelBuilder.AddOpenAITextToImage(apiKey, modelId: _config.Model);
#pragma warning restore SKEXP0010
}
else
{
kernelBuilder.AddOpenAIChatCompletion(_config.Model, apiKey, serviceId: _config.Model);
}
break;
case AIServiceType.AzureOpenAI:
var deploymentName = string.IsNullOrWhiteSpace(_config.DeploymentName) ? _config.Model : _config.DeploymentName;
kernelBuilder.AddAzureOpenAIChatCompletion(deploymentName, RequireEndpoint(endpoint, _serviceType), apiKey, serviceId: _config.Model);
if (_config.Usage == PasteAIUsage.TextToImage)
{
#pragma warning disable SKEXP0010
kernelBuilder.AddAzureOpenAITextToImage(deploymentName, RequireEndpoint(endpoint, _serviceType), apiKey);
#pragma warning restore SKEXP0010
}
else
{
kernelBuilder.AddAzureOpenAIChatCompletion(deploymentName, RequireEndpoint(endpoint, _serviceType), apiKey, serviceId: _config.Model);
}
break;
case AIServiceType.Mistral:
kernelBuilder.AddMistralChatCompletion(_config.Model, apiKey: apiKey);

View File

@@ -372,4 +372,10 @@
<value>Unable to load Foundry Local model: {0}</value>
<comment>{0} is the model identifier. Do not translate {0}.</comment>
</data>
<data name="PasteAIUsage_ChatCompletion" xml:space="preserve">
<value>Chat completion</value>
</data>
<data name="PasteAIUsage_TextToImage" xml:space="preserve">
<value>Text to image</value>
</data>
</root>

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.InteropServices;
@@ -614,8 +615,40 @@ namespace AdvancedPaste.ViewModels
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasCustomFormatImage))]
[NotifyPropertyChangedFor(nameof(HasCustomFormatText))]
[NotifyPropertyChangedFor(nameof(CustomFormatImageResult))]
private string _customFormatResult;
public bool HasCustomFormatImage => CustomFormatResult?.StartsWith("data:image", StringComparison.OrdinalIgnoreCase) ?? false;
public bool HasCustomFormatText => !HasCustomFormatImage;
public ImageSource CustomFormatImageResult
{
get
{
if (HasCustomFormatImage && !string.IsNullOrEmpty(CustomFormatResult))
{
try
{
var base64Data = CustomFormatResult.Split(',')[1];
var bytes = Convert.FromBase64String(base64Data);
var stream = new System.IO.MemoryStream(bytes);
var image = new BitmapImage();
image.SetSource(stream.AsRandomAccessStream());
return image;
}
catch (Exception ex)
{
Logger.LogError("Failed to create image source from data URI", ex);
}
}
return null;
}
}
[RelayCommand]
public async Task PasteCustomAsync()
{
@@ -623,7 +656,25 @@ namespace AdvancedPaste.ViewModels
if (!string.IsNullOrEmpty(text))
{
await CopyPasteAndHideAsync(DataPackageHelpers.CreateFromText(text));
if (text.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
{
try
{
var base64Data = text.Split(',')[1];
var bytes = Convert.FromBase64String(base64Data);
var stream = new System.IO.MemoryStream(bytes);
var dataPackage = DataPackageHelpers.CreateFromImage(Windows.Storage.Streams.RandomAccessStreamReference.CreateFromStream(stream.AsRandomAccessStream()));
await CopyPasteAndHideAsync(dataPackage);
}
catch (Exception ex)
{
Logger.LogError("Failed to paste image from data URI", ex);
}
}
else
{
await CopyPasteAndHideAsync(DataPackageHelpers.CreateFromText(text));
}
}
}
@@ -661,7 +712,7 @@ namespace AdvancedPaste.ViewModels
[RelayCommand]
public void OpenSettings()
{
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.AdvancedPaste);
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.AdvancedPaste, true);
GetMainWindow()?.Close();
}
@@ -895,11 +946,6 @@ namespace AdvancedPaste.ViewModels
Logger.LogError("Failed to activate AI provider", ex);
return;
}
UpdateAIProviderActiveFlags();
OnPropertyChanged(nameof(AIProviders));
NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}
public async Task CancelPasteActionAsync()

View File

@@ -15,11 +15,9 @@
#include <common/utils/logger_helper.h>
#include <common/utils/winapi_error.h>
#include <common/utils/gpo.h>
#include <common/utils/EventWaiter.h>
#include <algorithm>
#include <cwctype>
#include <thread>
#include <vector>
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -103,9 +101,6 @@ private:
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
// Event listening for external triggers (e.g., from CmdPal extension)
EventWaiter m_triggerEventWaiter;
Hotkey parse_single_hotkey(const wchar_t* keyName, const winrt::Windows::Data::Json::JsonObject& settingsObject)
{
try
@@ -784,17 +779,6 @@ public:
Trace::AdvancedPaste_Enable(true);
m_enabled = true;
m_process_manager.start();
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
// Note: Use start() directly instead of constructor + move assignment to avoid dangling this pointer in the thread.
m_triggerEventWaiter.start(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_EVENT, [this](DWORD) {
// Same logic as hotkeyId == 1 (m_advanced_paste_ui_hotkey)
Logger::trace(L"AdvancedPaste ShowUI event triggered");
m_process_manager.start();
m_process_manager.bring_to_front();
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUIEvent");
});
};
void Disable(bool traceEvent)
@@ -803,9 +787,6 @@ public:
{
m_process_manager.stop();
// Stop event listening
m_triggerEventWaiter.stop();
if (traceEvent)
{
Trace::AdvancedPaste_Enable(false);

View File

@@ -146,7 +146,7 @@ public:
}
}
m_showEventWaiter.start(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT, [&](DWORD err) {
m_showEventWaiter = EventWaiter(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT, [&](int err) {
if (m_enabled && err == ERROR_SUCCESS)
{
Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT);
@@ -164,7 +164,7 @@ public:
}
});
m_showAdminEventWaiter.start(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT, [&](DWORD err) {
m_showAdminEventWaiter = EventWaiter(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT, [&](int err) {
if (m_enabled && err == ERROR_SUCCESS)
{
Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT);

View File

@@ -67,7 +67,7 @@ namespace Hosts
services.AddSingleton<IElevationHelper, ElevationHelper>();
services.AddSingleton<OpenSettingsFunction>(() =>
{
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Hosts);
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Hosts, true);
});
services.AddSingleton<MainViewModel, MainViewModel>();

View File

@@ -155,7 +155,7 @@ public:
}
}
m_showEventWaiter.start(CommonSharedConstants::SHOW_HOSTS_EVENT, [&](DWORD err)
m_showEventWaiter = EventWaiter(CommonSharedConstants::SHOW_HOSTS_EVENT, [&](int err)
{
if (m_enabled && err == ERROR_SUCCESS)
{
@@ -174,7 +174,7 @@ public:
}
});
m_showAdminEventWaiter.start(CommonSharedConstants::SHOW_HOSTS_ADMIN_EVENT, [&](DWORD err)
m_showAdminEventWaiter = EventWaiter(CommonSharedConstants::SHOW_HOSTS_ADMIN_EVENT, [&](int err)
{
if (m_enabled && err == ERROR_SUCCESS)
{

View File

@@ -168,6 +168,9 @@
<ClCompile>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(CoreLibraryDependencies);%(AdditionalDependencies);advapi32.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -219,4 +222,4 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -8,8 +8,6 @@
#include <codecvt>
#include <common/utils/logger_helper.h>
#include "ThemeHelper.h"
#include <thread>
#include <atomic>
extern "C" IMAGE_DOS_HEADER __ImageBase;
@@ -105,18 +103,12 @@ private:
HANDLE m_force_light_event_handle;
HANDLE m_force_dark_event_handle;
HANDLE m_manual_override_event_handle;
HANDLE m_toggle_event_handle{ nullptr };
std::thread m_toggle_thread;
std::atomic<bool> m_toggle_thread_running{ false };
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
Hotkey m_toggle_theme_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'D' };
void init_settings();
void ToggleTheme();
void StartToggleListener();
void StopToggleListener();
public:
LightSwitchInterface()
@@ -126,7 +118,6 @@ public:
m_force_light_event_handle = CreateDefaultEvent(L"POWERTOYS_LIGHTSWITCH_FORCE_LIGHT");
m_force_dark_event_handle = CreateDefaultEvent(L"POWERTOYS_LIGHTSWITCH_FORCE_DARK");
m_manual_override_event_handle = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
m_toggle_event_handle = CreateDefaultEvent(L"Local\\PowerToys-LightSwitch-ToggleEvent-d8dc2f29-8c94-4ca1-8c5f-3e2b1e3c4f5a");
init_settings();
};
@@ -139,8 +130,6 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Ensure worker threads/process handles are cleaned up before destruction
disable();
delete this;
}
@@ -455,8 +444,6 @@ public:
Logger::info(L"Light Switch process launched successfully (PID: {}).", pi.dwProcessId);
m_process = pi.hProcess;
CloseHandle(pi.hThread);
StartToggleListener();
}
// Disable the powertoy
@@ -482,8 +469,6 @@ public:
CloseHandle(m_process);
m_process = nullptr;
}
StopToggleListener();
}
// Returns if the powertoys is enabled
@@ -545,8 +530,31 @@ public:
}
else if (hotkeyId == 0)
{
// get current will return true if in light mode; otherwise false
Logger::info(L"[Light Switch] Hotkey triggered: Toggle Theme");
ToggleTheme();
if (g_settings.m_changeSystem)
{
SetSystemTheme(!GetCurrentSystemTheme());
}
if (g_settings.m_changeApps)
{
SetAppsTheme(!GetCurrentAppsTheme());
}
if (!m_manual_override_event_handle)
{
m_manual_override_event_handle = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
if (!m_manual_override_event_handle)
{
m_manual_override_event_handle = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
}
}
if (m_manual_override_event_handle)
{
SetEvent(m_manual_override_event_handle);
Logger::debug(L"[Light Switch] Manual override event set");
}
}
return true;
@@ -559,80 +567,8 @@ public:
{
return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
}
};
void LightSwitchInterface::ToggleTheme()
{
if (g_settings.m_changeSystem)
{
SetSystemTheme(!GetCurrentSystemTheme());
}
if (g_settings.m_changeApps)
{
SetAppsTheme(!GetCurrentAppsTheme());
}
if (!m_manual_override_event_handle)
{
m_manual_override_event_handle = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
if (!m_manual_override_event_handle)
{
m_manual_override_event_handle = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
}
}
if (m_manual_override_event_handle)
{
SetEvent(m_manual_override_event_handle);
Logger::debug(L"[Light Switch] Manual override event set");
}
}
void LightSwitchInterface::StartToggleListener()
{
if (m_toggle_thread_running || !m_toggle_event_handle)
{
return;
}
m_toggle_thread_running = true;
m_toggle_thread = std::thread([this]() {
while (m_toggle_thread_running)
{
const DWORD wait_result = WaitForSingleObject(m_toggle_event_handle, 500);
if (!m_toggle_thread_running)
{
break;
}
if (wait_result == WAIT_OBJECT_0)
{
ToggleTheme();
ResetEvent(m_toggle_event_handle);
}
}
});
}
void LightSwitchInterface::StopToggleListener()
{
if (!m_toggle_thread_running)
{
return;
}
m_toggle_thread_running = false;
if (m_toggle_event_handle)
{
SetEvent(m_toggle_event_handle);
}
if (m_toggle_thread.joinable())
{
m_toggle_thread.join();
}
}
std::wstring utf8_to_wstring(const std::string& str)
{
if (str.empty())
@@ -710,4 +646,4 @@ void LightSwitchInterface::init_settings()
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new LightSwitchInterface();
}
}

View File

@@ -149,7 +149,7 @@ public:
init_settings();
triggerEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::MEASURE_TOOL_TRIGGER_EVENT);
triggerEventWaiter.start(CommonSharedConstants::MEASURE_TOOL_TRIGGER_EVENT, [this](DWORD) {
triggerEventWaiter = EventWaiter(CommonSharedConstants::MEASURE_TOOL_TRIGGER_EVENT, [this](int) {
on_hotkey(0);
});
}

View File

@@ -6,7 +6,6 @@
#include "../../../common/utils/resources.h"
#include "../../../common/logger/logger.h"
#include "../../../common/utils/logger_helper.h"
#include "../../../common/interop/shared_constants.h"
#include <atomic>
#include <thread>
#include <vector>
@@ -109,12 +108,6 @@ private:
// Hotkey
Hotkey m_activationHotkey{};
// Event-driven trigger support (for CmdPal/automation)
HANDLE m_triggerEventHandle = nullptr;
HANDLE m_terminateEventHandle = nullptr;
std::thread m_eventThread;
std::atomic_bool m_listening{ false };
public:
// Constructor
CursorWrap()
@@ -128,8 +121,7 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Ensure hooks/threads/handles are torn down before deletion
disable();
StopMouseHook();
g_cursorWrapInstance = nullptr; // Clear global instance pointer
delete this;
}
@@ -203,54 +195,11 @@ public:
{
m_enabled = true;
Trace::EnableCursorWrap(true);
// Start listening for external trigger event so we can invoke the same logic as the activation hotkey.
m_triggerEventHandle = CreateEventW(nullptr, false, false, CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT);
m_terminateEventHandle = CreateEventW(nullptr, false, false, nullptr);
if (m_triggerEventHandle && m_terminateEventHandle)
{
m_listening = true;
m_eventThread = std::thread([this]() {
HANDLE handles[2] = { m_triggerEventHandle, m_terminateEventHandle };
// WH_MOUSE_LL callbacks are delivered to the thread that installed the hook.
// Ensure this thread has a message queue and pumps messages while the hook is active.
MSG msg;
PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started");
while (m_listening)
{
auto res = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT);
if (!m_listening)
{
break;
}
if (res == WAIT_OBJECT_0)
{
ToggleMouseHook();
}
else if (res == WAIT_OBJECT_0 + 1)
{
break;
}
else
{
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
StopMouseHook();
Logger::info("CursorWrap event listener stopped");
});
}
// Always start the mouse hook when the module is enabled
// This ensures cursor wrapping is active immediately after enabling
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started");
}
// Disable the powertoy
@@ -258,26 +207,8 @@ public:
{
m_enabled = false;
Trace::EnableCursorWrap(false);
m_listening = false;
if (m_terminateEventHandle)
{
SetEvent(m_terminateEventHandle);
}
if (m_eventThread.joinable())
{
m_eventThread.join();
}
if (m_triggerEventHandle)
{
CloseHandle(m_triggerEventHandle);
m_triggerEventHandle = nullptr;
}
if (m_terminateEventHandle)
{
CloseHandle(m_terminateEventHandle);
m_terminateEventHandle = nullptr;
}
StopMouseHook();
Logger::info("CursorWrap disabled - mouse hook stopped");
}
// Returns if the powertoys is enabled
@@ -309,19 +240,7 @@ public:
return false;
}
// Toggle on the thread that owns the WH_MOUSE_LL hook (the event listener thread).
if (m_triggerEventHandle)
{
return SetEvent(m_triggerEventHandle);
}
return false;
}
private:
void ToggleMouseHook()
{
// Toggle cursor wrapping.
// Toggle cursor wrapping
if (m_hookActive)
{
StopMouseHook();
@@ -334,8 +253,11 @@ public:
RunComprehensiveTests();
#endif
}
return true;
}
private:
// Load the settings file.
void init_settings()
{

View File

@@ -8,8 +8,6 @@
#include <common/utils/logger_helper.h>
#include <common/utils/color.h>
#include <common/utils/string_utils.h>
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
namespace
{
@@ -71,9 +69,6 @@ private:
// Find My Mouse specific settings
FindMyMouseSettings m_findMyMouseSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
// Load initial settings from the persisted values.
void init_settings();
@@ -91,8 +86,6 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Ensure threads/handles are cleaned up before destruction
disable();
delete this;
}
@@ -157,11 +150,6 @@ public:
m_enabled = true;
Trace::EnableFindMyMouse(true);
std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::FIND_MY_MOUSE_TRIGGER_EVENT, [this](DWORD) {
OnHotkeyEx();
});
}
// Disable the powertoy
@@ -170,8 +158,6 @@ public:
m_enabled = false;
Trace::EnableFindMyMouse(false);
FindMyMouseDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled
@@ -230,7 +216,7 @@ inline static uint8_t LegacyOpacityToAlpha(int overlayOpacityPercent)
overlayOpacityPercent = 100;
}
// Round to nearest integer (0255)
// Round to nearest integer (0<EFBFBD>255)
return static_cast<uint8_t>((overlayOpacityPercent * 255 + 50) / 100);
}
@@ -546,4 +532,4 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new FindMyMouse();
}
}

View File

@@ -4,8 +4,6 @@
#include "trace.h"
#include "MouseHighlighter.h"
#include "common/utils/color.h"
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
namespace
{
@@ -63,9 +61,6 @@ private:
// Mouse Highlighter specific settings
MouseHighlighterSettings m_highlightSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
public:
// Constructor
MouseHighlighter()
@@ -77,8 +72,6 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Tear down threads/handles before deletion to avoid abort() on joinable threads during shutdown
disable();
delete this;
}
@@ -139,11 +132,6 @@ public:
m_enabled = true;
Trace::EnableMouseHighlighter(true);
std::thread([=]() { MouseHighlighterMain(m_hModule, m_highlightSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::MOUSE_HIGHLIGHTER_TRIGGER_EVENT, [this](DWORD) {
OnHotkeyEx();
});
}
// Disable the powertoy
@@ -152,8 +140,6 @@ public:
m_enabled = false;
Trace::EnableMouseHighlighter(false);
MouseHighlighterDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled

View File

@@ -4,8 +4,7 @@
#include "trace.h"
#include "InclusiveCrosshairs.h"
#include "common/utils/color.h"
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
#include <atomic>
#include <thread>
#include <chrono>
#include <memory>
@@ -125,9 +124,6 @@ private:
// Mouse Pointer Crosshairs specific settings
InclusiveCrosshairsSettings m_inclusiveCrosshairsSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
public:
// Constructor
MousePointerCrosshairs()
@@ -141,9 +137,11 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Ensure all background threads/handles are torn down before destruction to avoid std::terminate/abort on joinable threads
disable();
UninstallKeyboardHook();
StopXTimer();
StopYTimer();
g_instance.store(nullptr, std::memory_order_release);
// Release shared state so worker threads (if any) exit when weak_ptr lock fails
m_state.reset();
delete this;
}
@@ -205,11 +203,6 @@ public:
m_enabled = true;
Trace::EnableMousePointerCrosshairs(true);
std::thread([=]() { InclusiveCrosshairsMain(m_hModule, m_inclusiveCrosshairsSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the activation hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::MOUSE_CROSSHAIRS_TRIGGER_EVENT, [this](DWORD) {
on_hotkey(0); // activation hotkey
});
}
// Disable the powertoy
@@ -222,8 +215,6 @@ public:
StopYTimer();
m_glideState = 0;
InclusiveCrosshairsDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled
@@ -910,4 +901,4 @@ private:
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new MousePointerCrosshairs();
}
}

View File

@@ -426,7 +426,7 @@ public partial class OCROverlay : Window
private void SettingsMenuItem_Click(object sender, RoutedEventArgs e)
{
WindowUtilities.CloseAllOCROverlays();
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.PowerOCR);
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.PowerOCR, false);
}
private static bool CheckIfCheckingOrUnchecking(object? sender)

View File

@@ -121,7 +121,7 @@ int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInst
else
{
auto mainThreadId = GetCurrentThreadId();
exitEventWaiter.start(CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, [mainThreadId, &window](DWORD err) {
exitEventWaiter = EventWaiter(CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, [mainThreadId, &window](int err) {
if (err != ERROR_SUCCESS)
{
Logger::error(L"Failed to wait for {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, get_last_error_or_default(err));

View File

@@ -37,7 +37,7 @@ public:
}
triggerEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT);
triggerEventWaiter.start(CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, [this](DWORD) {
triggerEventWaiter = EventWaiter(CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, [this](int) {
OnHotkeyEx();
});

View File

@@ -1,22 +0,0 @@
// 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 PowerToys.ModuleContracts;
using WorkspacesCsharpLibrary.Data;
namespace Workspaces.ModuleServices;
/// <summary>
/// Workspaces-specific operations.
/// </summary>
public interface IWorkspaceService : IModuleService
{
Task<OperationResult> LaunchWorkspaceAsync(string workspaceId, CancellationToken cancellationToken = default);
Task<OperationResult> LaunchEditorAsync(CancellationToken cancellationToken = default);
Task<OperationResult> SnapshotAsync(string? targetPath = null, CancellationToken cancellationToken = default);
Task<OperationResult<IReadOnlyList<ProjectWrapper>>> GetWorkspacesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -1,96 +0,0 @@
// 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 Common.UI;
using ManagedCommon;
using PowerToys.Interop;
using PowerToys.ModuleContracts;
using WorkspacesCsharpLibrary.Data;
namespace Workspaces.ModuleServices;
/// <summary>
/// Implementation of workspace actions for reuse across hosts.
/// </summary>
public sealed class WorkspaceService : ModuleServiceBase, IWorkspaceService
{
public static WorkspaceService Instance { get; } = new();
public override string Key => SettingsDeepLink.SettingsWindow.Workspaces.ToString();
protected override SettingsDeepLink.SettingsWindow SettingsWindow => SettingsDeepLink.SettingsWindow.Workspaces;
public override Task<OperationResult> LaunchAsync(CancellationToken cancellationToken = default)
{
// Treat launch as invoking the Workspaces editor.
return LaunchEditorAsync(cancellationToken);
}
public Task<OperationResult> LaunchEditorAsync(CancellationToken cancellationToken = default)
{
try
{
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent());
eventHandle.Set();
return Task.FromResult(OperationResult.Ok());
}
catch (Exception ex)
{
return Task.FromResult(OperationResult.Fail($"Failed to launch Workspaces editor: {ex.Message}"));
}
}
public Task<OperationResult> LaunchWorkspaceAsync(string workspaceId, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(workspaceId))
{
return Task.FromResult(OperationResult.Fail("Workspace id is required."));
}
try
{
var powertoysBaseDir = PowerToysPathResolver.GetPowerToysInstallPath();
if (string.IsNullOrEmpty(powertoysBaseDir))
{
return Task.FromResult(OperationResult.Fail("PowerToys installation path not found."));
}
var launcherPath = Path.Combine(powertoysBaseDir, "PowerToys.WorkspacesLauncher.exe");
var startInfo = new ProcessStartInfo(launcherPath)
{
Arguments = workspaceId,
UseShellExecute = true,
};
Process.Start(startInfo);
return Task.FromResult(OperationResult.Ok());
}
catch (Exception ex)
{
return Task.FromResult(OperationResult.Fail($"Failed to launch workspace: {ex.Message}"));
}
}
public Task<OperationResult> SnapshotAsync(string? targetPath = null, CancellationToken cancellationToken = default)
{
// Snapshot orchestration is not yet exposed via events; provide a clear failure for now.
return Task.FromResult(OperationResult.Fail("Snapshot is not implemented for Workspaces."));
}
public Task<OperationResult<IReadOnlyList<ProjectWrapper>>> GetWorkspacesAsync(CancellationToken cancellationToken = default)
{
try
{
var items = WorkspacesStorage.Load();
return Task.FromResult(OperationResults.Ok<IReadOnlyList<ProjectWrapper>>(items));
}
catch (Exception ex)
{
return Task.FromResult(OperationResults.Fail<IReadOnlyList<ProjectWrapper>>($"Failed to read workspaces: {ex.Message}"));
}
}
}

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\PowerToys.ModuleContracts\PowerToys.ModuleContracts.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,70 +0,0 @@
// 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.Text.Json.Serialization;
namespace WorkspacesCsharpLibrary.Data;
public struct ApplicationWrapper
{
public struct WindowPositionWrapper
{
[JsonPropertyName("x")]
public int X { get; set; }
[JsonPropertyName("y")]
public int Y { get; set; }
[JsonPropertyName("width")]
public int Width { get; set; }
[JsonPropertyName("height")]
public int Height { get; set; }
}
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("application")]
public string Application { get; set; }
[JsonPropertyName("application-path")]
public string ApplicationPath { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("package-full-name")]
public string PackageFullName { get; set; }
[JsonPropertyName("app-user-model-id")]
public string AppUserModelId { get; set; }
[JsonPropertyName("pwa-app-id")]
public string PwaAppId { get; set; }
[JsonPropertyName("command-line-arguments")]
public string CommandLineArguments { get; set; }
[JsonPropertyName("is-elevated")]
public bool IsElevated { get; set; }
[JsonPropertyName("can-launch-elevated")]
public bool CanLaunchElevated { get; set; }
[JsonPropertyName("minimized")]
public bool Minimized { get; set; }
[JsonPropertyName("maximized")]
public bool Maximized { get; set; }
[JsonPropertyName("position")]
public WindowPositionWrapper Position { get; set; }
[JsonPropertyName("monitor")]
public int Monitor { get; set; }
[JsonPropertyName("version")]
public string Version { get; set; }
}

View File

@@ -1,43 +0,0 @@
// 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.Text.Json.Serialization;
namespace WorkspacesCsharpLibrary.Data;
public struct MonitorConfigurationWrapper
{
public struct MonitorRectWrapper
{
[JsonPropertyName("top")]
public int Top { get; set; }
[JsonPropertyName("left")]
public int Left { get; set; }
[JsonPropertyName("width")]
public int Width { get; set; }
[JsonPropertyName("height")]
public int Height { get; set; }
}
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("instance-id")]
public string InstanceId { get; set; }
[JsonPropertyName("monitor-number")]
public int MonitorNumber { get; set; }
[JsonPropertyName("dpi")]
public int Dpi { get; set; }
[JsonPropertyName("monitor-rect-dpi-aware")]
public MonitorRectWrapper MonitorRectDpiAware { get; set; }
[JsonPropertyName("monitor-rect-dpi-unaware")]
public MonitorRectWrapper MonitorRectDpiUnaware { get; set; }
}

View File

@@ -1,11 +0,0 @@
// 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 WorkspacesCsharpLibrary.Data;
namespace WorkspacesCsharpLibrary.Data;
public class ProjectData : WorkspacesEditorData<ProjectWrapper>
{
}

View File

@@ -1,26 +0,0 @@
// 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.Collections.Generic;
namespace WorkspacesCsharpLibrary.Data;
public struct ProjectWrapper
{
public string Id { get; set; }
public string Name { get; set; }
public long CreationTime { get; set; }
public long LastLaunchedTime { get; set; }
public bool IsShortcutNeeded { get; set; }
public bool MoveExistingWindows { get; set; }
public List<MonitorConfigurationWrapper> MonitorConfiguration { get; set; }
public List<ApplicationWrapper> Applications { get; set; }
}

View File

@@ -1,27 +0,0 @@
// 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.Collections.Generic;
using WorkspacesCsharpLibrary.Utils;
using static WorkspacesCsharpLibrary.Data.WorkspacesData;
namespace WorkspacesCsharpLibrary.Data;
public class WorkspacesData : WorkspacesEditorData<WorkspacesListWrapper>
{
public string File => FolderUtils.DataFolder() + "\\workspaces.json";
public struct WorkspacesListWrapper
{
public List<ProjectWrapper> Workspaces { get; set; }
}
public enum OrderBy
{
LastViewed = 0,
Created = 1,
Name = 2,
Unknown = 3,
}
}

View File

@@ -1,38 +0,0 @@
// 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.CodeAnalysis;
using System.Text.Json;
using WorkspacesCsharpLibrary.Utils;
namespace WorkspacesCsharpLibrary.Data;
/// <summary>
/// Shared JSON serializer helper for Workspaces payloads.
/// </summary>
public class WorkspacesEditorData<T>
{
[RequiresUnreferencedCode("JSON serialization uses reflection-based serializer.")]
[RequiresDynamicCode("JSON serialization uses reflection-based serializer.")]
public T Read(string file)
{
IOUtils ioUtils = new();
string data = ioUtils.ReadFile(file);
return JsonSerializer.Deserialize<T>(data, WorkspacesJsonOptions.EditorOptions)!;
}
[RequiresUnreferencedCode("JSON serialization uses reflection-based serializer.")]
[RequiresDynamicCode("JSON serialization uses reflection-based serializer.")]
public string Serialize(T data)
{
return JsonSerializer.Serialize(data, WorkspacesJsonOptions.EditorOptions);
}
[RequiresUnreferencedCode("JSON serialization uses reflection-based serializer.")]
[RequiresDynamicCode("JSON serialization uses reflection-based serializer.")]
public T Deserialize(string json)
{
return JsonSerializer.Deserialize<T>(json, WorkspacesJsonOptions.EditorOptions)!;
}
}

View File

@@ -1,17 +0,0 @@
// 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.Text.Json;
using WorkspacesCsharpLibrary.Utils;
namespace WorkspacesCsharpLibrary.Data;
internal static class WorkspacesJsonOptions
{
internal static readonly JsonSerializerOptions EditorOptions = new()
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
WriteIndented = true,
};
}

View File

@@ -1,96 +0,0 @@
// 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.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace WorkspacesCsharpLibrary.Data;
/// <summary>
/// Lightweight reader for persisted workspaces.
/// </summary>
public static class WorkspacesStorage
{
public static IReadOnlyList<ProjectWrapper> Load()
{
var filePath = GetDefaultFilePath();
if (!File.Exists(filePath))
{
return [];
}
try
{
var json = File.ReadAllText(filePath);
var data = JsonSerializer.Deserialize(json, WorkspacesStorageJsonContext.Default.WorkspacesFile);
if (data?.Workspaces == null)
{
return [];
}
return data.Workspaces
.Where(ws => !string.IsNullOrWhiteSpace(ws.Id) && !string.IsNullOrWhiteSpace(ws.Name))
.Select(ws => new ProjectWrapper
{
Id = ws.Id!,
Name = ws.Name!,
Applications = ws.Applications ?? new List<ApplicationWrapper>(),
CreationTime = ws.CreationTime,
LastLaunchedTime = ws.LastLaunchedTime,
IsShortcutNeeded = ws.IsShortcutNeeded,
MoveExistingWindows = ws.MoveExistingWindows,
MonitorConfiguration = ws.MonitorConfiguration ?? new List<MonitorConfigurationWrapper>(),
})
.ToList()
.AsReadOnly();
}
catch
{
return Array.Empty<ProjectWrapper>();
}
}
public static string GetDefaultFilePath()
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return Path.Combine(localAppData, "Microsoft", "PowerToys", "Workspaces", "workspaces.json");
}
internal sealed class WorkspacesFile
{
public List<WorkspaceProject> Workspaces { get; set; } = new();
}
internal sealed class WorkspaceProject
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("applications")]
public List<ApplicationWrapper> Applications { get; set; } = new();
[JsonPropertyName("monitor-configuration")]
public List<MonitorConfigurationWrapper> MonitorConfiguration { get; set; } = new();
[JsonPropertyName("creation-time")]
public long CreationTime { get; set; }
[JsonPropertyName("last-launched-time")]
public long LastLaunchedTime { get; set; }
[JsonPropertyName("is-shortcut-needed")]
public bool IsShortcutNeeded { get; set; }
[JsonPropertyName("move-existing-windows")]
public bool MoveExistingWindows { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
// 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.Text.Json.Serialization;
namespace WorkspacesCsharpLibrary.Data;
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(WorkspacesStorage.WorkspacesFile))]
[JsonSerializable(typeof(WorkspacesStorage.WorkspaceProject))]
[JsonSerializable(typeof(ApplicationWrapper))]
[JsonSerializable(typeof(ApplicationWrapper.WindowPositionWrapper))]
[JsonSerializable(typeof(MonitorConfigurationWrapper))]
[JsonSerializable(typeof(MonitorConfigurationWrapper.MonitorRectWrapper))]
internal sealed partial class WorkspacesStorageJsonContext : JsonSerializerContext
{
}

View File

@@ -16,7 +16,7 @@ using Windows.Management.Deployment;
namespace WorkspacesCsharpLibrary.Models
{
public partial class BaseApplication : INotifyPropertyChanged, IDisposable
public class BaseApplication : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;

View File

@@ -1,18 +0,0 @@
// 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.Text.Json;
using WorkspacesCsharpLibrary.Utils;
namespace WorkspacesCsharpLibrary.Utils;
public class DashCaseNamingPolicy : JsonNamingPolicy
{
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
public override string ConvertName(string name)
{
return name.UpperCamelCaseToDashCase();
}
}

View File

@@ -1,27 +0,0 @@
// 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.IO;
namespace WorkspacesCsharpLibrary.Utils;
public class FolderUtils
{
public static string Desktop()
{
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
}
public static string Temp()
{
return Path.GetTempPath();
}
// Note: the same path should be used in SnapshotTool and Launcher
public static string DataFolder()
{
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\Workspaces";
}
}

View File

@@ -1,47 +0,0 @@
// 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.IO;
using System.IO.Abstractions;
using System.Threading.Tasks;
namespace WorkspacesCsharpLibrary.Utils;
public class IOUtils
{
private readonly IFileSystem _fileSystem = new FileSystem();
public void WriteFile(string fileName, string data)
{
_fileSystem.File.WriteAllText(fileName, data);
}
public string ReadFile(string fileName)
{
if (_fileSystem.File.Exists(fileName))
{
int attempts = 0;
while (attempts < 10)
{
try
{
using FileSystemStream inputStream = _fileSystem.File.Open(fileName, FileMode.Open);
using StreamReader reader = new(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
return data;
}
catch (Exception)
{
Task.Delay(10).Wait();
}
attempts++;
}
}
return string.Empty;
}
}

View File

@@ -1,18 +0,0 @@
// 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.Linq;
namespace WorkspacesCsharpLibrary.Utils;
public static class StringUtils
{
public static string UpperCamelCaseToDashCase(this string str)
{
// If it's a single letter variable, leave it as it is
return str.Length == 1
? str
: string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x : x.ToString())).ToLowerInvariant();
}
}

View File

@@ -1,13 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.WorkspacesCsharpLibrary</AssemblyTitle>
<AssemblyDescription>PowerToys Workspaces Csharp Library</AssemblyDescription>
<Description>PowerToys Workspaces Csharp Library</Description>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
@@ -17,8 +15,4 @@
<AssemblyName>PowerToys.WorkspacesCsharpLibrary</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
</Project>
</Project>

View File

@@ -2,12 +2,13 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace WorkspacesCsharpLibrary.Data;
public enum InvokePoint
namespace WorkspacesEditor.Data
{
EditorButton = 0,
Shortcut,
LaunchAndEdit,
CommandPaletteExtension,
/* sync with workspaces-common */
public enum InvokePoint
{
EditorButton = 0,
Shortcut,
LaunchAndEdit,
}
}

View File

@@ -0,0 +1,101 @@
// 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.Collections.Generic;
using static WorkspacesEditor.Data.ProjectData;
namespace WorkspacesEditor.Data
{
public class ProjectData : WorkspacesEditorData<ProjectWrapper>
{
public struct ApplicationWrapper
{
public struct WindowPositionWrapper
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public string Id { get; set; }
public string Application { get; set; }
public string ApplicationPath { get; set; }
public string Title { get; set; }
public string PackageFullName { get; set; }
public string AppUserModelId { get; set; }
public string PwaAppId { get; set; }
public string CommandLineArguments { get; set; }
public bool IsElevated { get; set; }
public bool CanLaunchElevated { get; set; }
public bool Minimized { get; set; }
public bool Maximized { get; set; }
public WindowPositionWrapper Position { get; set; }
public int Monitor { get; set; }
public string Version { get; set; }
}
public struct MonitorConfigurationWrapper
{
public struct MonitorRectWrapper
{
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public string Id { get; set; }
public string InstanceId { get; set; }
public int MonitorNumber { get; set; }
public int Dpi { get; set; }
public MonitorRectWrapper MonitorRectDpiAware { get; set; }
public MonitorRectWrapper MonitorRectDpiUnaware { get; set; }
}
public struct ProjectWrapper
{
public string Id { get; set; }
public string Name { get; set; }
public long CreationTime { get; set; }
public long LastLaunchedTime { get; set; }
public bool IsShortcutNeeded { get; set; }
public bool MoveExistingWindows { get; set; }
public List<MonitorConfigurationWrapper> MonitorConfiguration { get; set; }
public List<ApplicationWrapper> Applications { get; set; }
}
}
}

View File

@@ -2,10 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using WorkspacesCsharpLibrary.Data;
using WorkspacesCsharpLibrary.Utils;
using WorkspacesEditor.Utils;
namespace WorkspacesCsharpLibrary.Data
namespace WorkspacesEditor.Data
{
public class TempProjectData : ProjectData
{

View File

@@ -0,0 +1,30 @@
// 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.Collections.Generic;
using WorkspacesEditor.Utils;
using static WorkspacesEditor.Data.ProjectData;
using static WorkspacesEditor.Data.WorkspacesData;
namespace WorkspacesEditor.Data
{
public class WorkspacesData : WorkspacesEditorData<WorkspacesListWrapper>
{
public string File => FolderUtils.DataFolder() + "\\workspaces.json";
public struct WorkspacesListWrapper
{
public List<ProjectWrapper> Workspaces { get; set; }
}
public enum OrderBy
{
LastViewed = 0,
Created = 1,
Name = 2,
Unknown = 3,
}
}
}

View File

@@ -0,0 +1,33 @@
// 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.Text.Json;
using WorkspacesEditor.Utils;
namespace WorkspacesEditor.Data
{
public class WorkspacesEditorData<T>
{
protected JsonSerializerOptions JsonOptions
{
get => new()
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
WriteIndented = true,
};
}
public T Read(string file)
{
IOUtils ioUtils = new();
string data = ioUtils.ReadFile(file);
return JsonSerializer.Deserialize<T>(data, JsonOptions);
}
public string Serialize(T data)
{
return JsonSerializer.Serialize(data, JsonOptions);
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using ManagedCommon;
using WorkspacesCsharpLibrary.Data;
using WorkspacesEditor.Data;
using WorkspacesEditor.Utils;
namespace WorkspacesEditor.Models
@@ -226,7 +226,7 @@ namespace WorkspacesEditor.Models
}
}
public Project(ProjectWrapper project)
public Project(ProjectData.ProjectWrapper project)
{
Id = project.Id;
Name = project.Name;
@@ -237,7 +237,7 @@ namespace WorkspacesEditor.Models
Monitors = [];
Applications = [];
foreach (ApplicationWrapper app in project.Applications)
foreach (ProjectData.ApplicationWrapper app in project.Applications)
{
Models.Application newApp = new()
{
@@ -269,7 +269,7 @@ namespace WorkspacesEditor.Models
Applications.Add(newApp);
}
foreach (MonitorConfigurationWrapper monitor in project.MonitorConfiguration)
foreach (ProjectData.MonitorConfigurationWrapper monitor in project.MonitorConfiguration)
{
System.Windows.Rect dpiAware = new(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
System.Windows.Rect dpiUnaware = new(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);

View File

@@ -0,0 +1,18 @@
// 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.Text.Json;
namespace WorkspacesEditor.Utils
{
public class DashCaseNamingPolicy : JsonNamingPolicy
{
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
public override string ConvertName(string name)
{
return name.UpperCamelCaseToDashCase();
}
}
}

View File

@@ -0,0 +1,28 @@
// 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.IO;
namespace WorkspacesEditor.Utils
{
public class FolderUtils
{
public static string Desktop()
{
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
}
public static string Temp()
{
return Path.GetTempPath();
}
// Note: the same path should be used in SnapshotTool and Launcher
public static string DataFolder()
{
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\Workspaces";
}
}
}

View File

@@ -0,0 +1,52 @@
// 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.IO;
using System.IO.Abstractions;
using System.Threading.Tasks;
namespace WorkspacesEditor.Utils
{
public class IOUtils
{
private readonly IFileSystem _fileSystem = new FileSystem();
public IOUtils()
{
}
public void WriteFile(string fileName, string data)
{
_fileSystem.File.WriteAllText(fileName, data);
}
public string ReadFile(string fileName)
{
if (_fileSystem.File.Exists(fileName))
{
int attempts = 0;
while (attempts < 10)
{
try
{
using FileSystemStream inputStream = _fileSystem.File.Open(fileName, FileMode.Open);
using StreamReader reader = new(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
return data;
}
catch (Exception)
{
Task.Delay(10).Wait();
}
attempts++;
}
}
return string.Empty;
}
}
}

View File

@@ -6,9 +6,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ManagedCommon;
using WorkspacesCsharpLibrary.Data;
using WorkspacesCsharpLibrary.Utils;
using WorkspacesEditor.Data;
using WorkspacesEditor.Models;
using WorkspacesEditor.ViewModels;
@@ -81,7 +81,7 @@ namespace WorkspacesEditor.Utils
foreach (Project project in workspaces)
{
ProjectWrapper wrapper = new()
ProjectData.ProjectWrapper wrapper = new()
{
Id = project.Id,
Name = project.Name,
@@ -95,7 +95,7 @@ namespace WorkspacesEditor.Utils
foreach (Application app in project.Applications.Where(x => x.IsIncluded))
{
wrapper.Applications.Add(new ApplicationWrapper
wrapper.Applications.Add(new ProjectData.ApplicationWrapper
{
Id = app.Id,
Application = app.AppName,
@@ -110,7 +110,7 @@ namespace WorkspacesEditor.Utils
Version = app.Version,
Maximized = app.Maximized,
Minimized = app.Minimized,
Position = new ApplicationWrapper.WindowPositionWrapper
Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper
{
X = app.Position.X,
Y = app.Position.Y,
@@ -123,20 +123,20 @@ namespace WorkspacesEditor.Utils
foreach (MonitorSetup monitor in project.Monitors)
{
wrapper.MonitorConfiguration.Add(new MonitorConfigurationWrapper
wrapper.MonitorConfiguration.Add(new ProjectData.MonitorConfigurationWrapper
{
Id = monitor.MonitorName,
InstanceId = monitor.MonitorInstanceId,
MonitorNumber = monitor.MonitorNumber,
Dpi = monitor.Dpi,
MonitorRectDpiAware = new MonitorConfigurationWrapper.MonitorRectWrapper
MonitorRectDpiAware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper
{
Left = (int)monitor.MonitorDpiAwareBounds.Left,
Top = (int)monitor.MonitorDpiAwareBounds.Top,
Width = (int)monitor.MonitorDpiAwareBounds.Width,
Height = (int)monitor.MonitorDpiAwareBounds.Height,
},
MonitorRectDpiUnaware = new MonitorConfigurationWrapper.MonitorRectWrapper
MonitorRectDpiUnaware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper
{
Left = (int)monitor.MonitorDpiUnawareBounds.Left,
Top = (int)monitor.MonitorDpiUnawareBounds.Top,
@@ -163,7 +163,7 @@ namespace WorkspacesEditor.Utils
private bool AddWorkspaces(MainViewModel mainViewModel, WorkspacesData.WorkspacesListWrapper workspaces)
{
foreach (ProjectWrapper project in workspaces.Workspaces)
foreach (ProjectData.ProjectWrapper project in workspaces.Workspaces)
{
mainViewModel.Workspaces.Add(new Project(project));
}

View File

@@ -18,12 +18,12 @@ using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using WorkspacesCsharpLibrary;
using WorkspacesCsharpLibrary.Data;
using WorkspacesCsharpLibrary.Utils;
using WorkspacesEditor.Data;
using WorkspacesEditor.Models;
using WorkspacesEditor.Telemetry;
using WorkspacesEditor.Utils;
using static WorkspacesCsharpLibrary.Data.WorkspacesData;
using static WorkspacesEditor.Data.WorkspacesData;
namespace WorkspacesEditor.ViewModels
{

View File

@@ -78,15 +78,6 @@
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Data\WorkspacesData.cs" />
<Compile Remove="Data\ProjectData.cs" />
<Compile Remove="Data\WorkspacesEditorData`1.cs" />
<Compile Remove="Utils\IOUtils.cs" />
<Compile Remove="Utils\FolderUtils.cs" />
<Compile Remove="Utils\DashCaseNamingPolicy.cs" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
@@ -105,4 +96,4 @@
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>
</Project>

View File

@@ -9,7 +9,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using WorkspacesCsharpLibrary.Data;
using WorkspacesEditor.Data;
using WorkspacesEditor.Models;
using WorkspacesEditor.ViewModels;

View File

@@ -3,12 +3,15 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Workspaces.Data;
using static WorkspacesLauncherUI.Data.AppLaunchData;
using static WorkspacesLauncherUI.Data.AppLaunchInfosData;
namespace WorkspacesLauncherUI.Data
{
public class AppLaunchData : WorkspacesCsharpLibrary.Data.WorkspacesEditorData<AppLaunchDataWrapper>
public class AppLaunchData : WorkspacesUIData<AppLaunchDataWrapper>
{
public struct AppLaunchDataWrapper
{

View File

@@ -3,11 +3,14 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Workspaces.Data;
using static WorkspacesLauncherUI.Data.AppLaunchInfoData;
namespace WorkspacesLauncherUI.Data
{
public class AppLaunchInfoData : WorkspacesCsharpLibrary.Data.WorkspacesEditorData<AppLaunchInfoWrapper>
public class AppLaunchInfoData : WorkspacesUIData<AppLaunchInfoWrapper>
{
public struct AppLaunchInfoWrapper
{

View File

@@ -4,12 +4,15 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Workspaces.Data;
using static WorkspacesLauncherUI.Data.AppLaunchInfoData;
using static WorkspacesLauncherUI.Data.AppLaunchInfosData;
namespace WorkspacesLauncherUI.Data
{
public class AppLaunchInfosData : WorkspacesCsharpLibrary.Data.WorkspacesEditorData<AppLaunchInfoListWrapper>
public class AppLaunchInfosData : WorkspacesUIData<AppLaunchInfoListWrapper>
{
public struct AppLaunchInfoListWrapper
{

View File

@@ -0,0 +1,35 @@
// 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.Text.Json;
using WorkspacesLauncherUI.Utils;
namespace Workspaces.Data
{
public class WorkspacesUIData<T>
{
protected JsonSerializerOptions JsonOptions
{
get
{
return new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
WriteIndented = true,
};
}
}
public T Deserialize(string data)
{
return JsonSerializer.Deserialize<T>(data, JsonOptions);
}
public string Serialize(T data)
{
return JsonSerializer.Serialize(data, JsonOptions);
}
}
}

View File

@@ -6,11 +6,13 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using ManagedCommon;
using WorkspacesCsharpLibrary;
using WorkspacesLauncherUI.Data;
using WorkspacesLauncherUI.Models;
using WorkspacesLauncherUI.Utils;
namespace WorkspacesLauncherUI.ViewModels
{

View File

@@ -1,102 +1,102 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.WorkspacesLauncherUI</AssemblyTitle>
<AssemblyDescription>PowerToys Workspaces Editor</AssemblyDescription>
<Description>PowerToys Workspaces Editor</Description>
<OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
</PropertyGroup>
<PropertyGroup>
<ProjectGuid>{9C53CC25-0623-4569-95BC-B05410675EE3}</ProjectGuid>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup>
<AssemblyTitle>PowerToys.WorkspacesLauncherUI</AssemblyTitle>
<AssemblyDescription>PowerToys Workspaces Launcher UI</AssemblyDescription>
<Description>PowerToys Workspaces Launcher UI</Description>
<OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>..\Assets\Workspaces\Workspaces.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyName>PowerToys.WorkspacesLauncherUI</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Content Include="..\Assets\**\*.*">
<Link>Assets\Workspaces\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<PropertyGroup>
<ProjectGuid>{9C53CC25-0623-4569-95BC-B05410675EE3}</ProjectGuid>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>f935dc20-1cf0-11d0-adb9-00c04fd58a0b</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
<COMReference Include="Shell32">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.manifest" />
</ItemGroup>
<PropertyGroup>
<ApplicationIcon>..\Assets\Workspaces\Workspaces.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AssemblyName>PowerToys.WorkspacesLauncherUI</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ControlzEx" />
<PackageReference Include="ModernWpfUI" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Assets\**\*.*">
<Link>Assets\Workspaces\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>f935dc20-1cf0-11d0-adb9-00c04fd58a0b</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
<COMReference Include="Shell32">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ControlzEx" />
<PackageReference Include="ModernWpfUI" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

View File

@@ -201,7 +201,7 @@ public:
Logger::error(message.value());
}
}
m_toggleEditorEventWaiter.start(CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT, [&](DWORD err) {
m_toggleEditorEventWaiter = EventWaiter(CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT, [&](int err) {
if (err == ERROR_SUCCESS)
{
Logger::trace(L"{} event was signaled", CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT);

View File

@@ -369,4 +369,4 @@
<Import Project="..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets" Condition="Exists('..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</Project>
</Project>

View File

@@ -28,20 +28,6 @@
#include <common/utils/logger_helper.h>
#include <common/utils/winapi_error.h>
#include <common/utils/gpo.h>
#include <array>
#include <vector>
#endif // __ZOOMIT_POWERTOYS__
#ifdef __ZOOMIT_POWERTOYS__
enum class ZoomItCommand
{
Zoom,
Draw,
Break,
LiveZoom,
Snip,
Record,
};
#endif // __ZOOMIT_POWERTOYS__
namespace winrt
@@ -186,6 +172,7 @@ std::wstring g_RecordingSaveLocationGIF;
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
type_pGetMonitorInfo pGetMonitorInfo;
type_MonitorFromPoint pMonitorFromPoint;
type_pSHAutoComplete pSHAutoComplete;
@@ -7725,53 +7712,6 @@ HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
}
// Dispatch commands coming from the PowerToys IPC channel.
#ifdef __ZOOMIT_POWERTOYS__
void ZoomIt_DispatchCommand(ZoomItCommand cmd)
{
auto post_hotkey = [](WPARAM id)
{
if (g_hWndMain != nullptr)
{
PostMessage(g_hWndMain, WM_HOTKEY, id, 0);
}
};
switch (cmd)
{
case ZoomItCommand::Zoom:
if (g_hWndMain != nullptr)
{
PostMessage(g_hWndMain, WM_COMMAND, IDC_ZOOM, 0);
}
Trace::ZoomItActivateZoom();
break;
case ZoomItCommand::Draw:
post_hotkey(DRAW_HOTKEY);
Trace::ZoomItActivateDraw();
break;
case ZoomItCommand::Break:
post_hotkey(BREAK_HOTKEY);
Trace::ZoomItActivateBreak();
break;
case ZoomItCommand::LiveZoom:
post_hotkey(LIVE_HOTKEY);
Trace::ZoomItActivateLiveZoom();
break;
case ZoomItCommand::Snip:
post_hotkey(SNIP_HOTKEY);
Trace::ZoomItActivateSnip();
break;
case ZoomItCommand::Record:
post_hotkey(RECORD_HOTKEY);
Trace::ZoomItActivateRecord();
break;
default:
break;
}
}
#endif
//----------------------------------------------------------------------------
//
// WinMain
@@ -7806,6 +7746,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
// Initialize logger
LoggerHelpers::init_logger(L"ZoomIt", L"", LogSettings::zoomItLoggerName);
ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) {
if (err != ERROR_SUCCESS)
{
@@ -7964,63 +7905,27 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
#ifdef __ZOOMIT_POWERTOYS__
HANDLE m_reload_settings_event_handle = NULL;
HANDLE m_exit_event_handle = NULL;
HANDLE m_zoom_event_handle = NULL;
HANDLE m_draw_event_handle = NULL;
HANDLE m_break_event_handle = NULL;
HANDLE m_live_zoom_event_handle = NULL;
HANDLE m_snip_event_handle = NULL;
HANDLE m_record_event_handle = NULL;
std::thread m_event_triggers_thread;
if( g_StartedByPowerToys ) {
// Start a thread to listen to PowerToys Events.
m_reload_settings_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_REFRESH_SETTINGS_EVENT);
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_EXIT_EVENT);
m_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_ZOOM_EVENT);
m_draw_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_DRAW_EVENT);
m_break_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_BREAK_EVENT);
m_live_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_LIVEZOOM_EVENT);
m_snip_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_SNIP_EVENT);
m_record_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_RECORD_EVENT);
if (!m_reload_settings_event_handle || !m_exit_event_handle || !m_zoom_event_handle || !m_draw_event_handle || !m_break_event_handle || !m_live_zoom_event_handle || !m_snip_event_handle || !m_record_event_handle)
if (!m_reload_settings_event_handle || !m_exit_event_handle)
{
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
return 1;
}
const std::array<HANDLE, 8> event_handles{
m_reload_settings_event_handle,
m_exit_event_handle,
m_zoom_event_handle,
m_draw_event_handle,
m_break_event_handle,
m_live_zoom_event_handle,
m_snip_event_handle,
m_record_event_handle,
};
const DWORD handle_count = static_cast<DWORD>(event_handles.size());
m_event_triggers_thread = std::thread([event_handles, handle_count]() {
m_event_triggers_thread = std::thread([&]() {
MSG msg;
HANDLE event_handles[2] = {m_reload_settings_event_handle, m_exit_event_handle};
while (g_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(handle_count, event_handles.data(), false, INFINITE, QS_ALLINPUT);
if (dwEvt == WAIT_FAILED)
{
Logger::error(L"ZoomIt event wait failed. {}", get_last_error_or_default(GetLastError()));
break;
}
DWORD dwEvt = MsgWaitForMultipleObjects(2, event_handles, false, INFINITE, QS_ALLINPUT);
if (!g_running)
{
break;
}
if (dwEvt == WAIT_OBJECT_0 + handle_count)
{
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
continue;
}
switch (dwEvt)
{
case WAIT_OBJECT_0:
@@ -8033,28 +7938,19 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
case WAIT_OBJECT_0 + 1:
{
// Exit Event
Logger::trace(L"Received an exit event.");
PostMessage(g_hWndMain, WM_QUIT, 0, 0);
break;
}
case WAIT_OBJECT_0 + 2:
ZoomIt_DispatchCommand(ZoomItCommand::Zoom);
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
break;
case WAIT_OBJECT_0 + 3:
ZoomIt_DispatchCommand(ZoomItCommand::Draw);
default:
break;
case WAIT_OBJECT_0 + 4:
ZoomIt_DispatchCommand(ZoomItCommand::Break);
break;
case WAIT_OBJECT_0 + 5:
ZoomIt_DispatchCommand(ZoomItCommand::LiveZoom);
break;
case WAIT_OBJECT_0 + 6:
ZoomIt_DispatchCommand(ZoomItCommand::Snip);
break;
case WAIT_OBJECT_0 + 7:
ZoomIt_DispatchCommand(ZoomItCommand::Record);
break;
default: break;
}
}
});
@@ -8084,12 +7980,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
SetEvent(m_reload_settings_event_handle);
CloseHandle(m_reload_settings_event_handle);
CloseHandle(m_exit_event_handle);
CloseHandle(m_zoom_event_handle);
CloseHandle(m_draw_event_handle);
CloseHandle(m_break_event_handle);
CloseHandle(m_live_zoom_event_handle);
CloseHandle(m_snip_event_handle);
CloseHandle(m_record_event_handle);
m_event_triggers_thread.join();
}
#endif // __ZOOMIT_POWERTOYS__

View File

@@ -8,7 +8,6 @@
#include <common/utils/logger_helper.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <common/interop/shared_constants.h>
#include <shellapi.h>
#include <common/interop/shared_constants.h>

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\PowerToys.ModuleContracts\PowerToys.ModuleContracts.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,151 +0,0 @@
// 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.Text.Json;
using Common.UI;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerToys.ModuleContracts;
namespace Awake.ModuleServices;
/// <summary>
/// Provides CLI-based Awake control for reuse across hosts.
/// </summary>
public sealed class AwakeService : ModuleServiceBase, IAwakeService
{
public static AwakeService Instance { get; } = new();
public override string Key => SettingsDeepLink.SettingsWindow.Awake.ToString();
protected override SettingsDeepLink.SettingsWindow SettingsWindow => SettingsDeepLink.SettingsWindow.Awake;
public override Task<OperationResult> LaunchAsync(CancellationToken cancellationToken = default)
{
// Default launch -> indefinite, honoring Awake's own settings for display behavior.
return SetIndefiniteAsync(cancellationToken);
}
public AwakeState GetCurrentState()
{
var isRunning = IsAwakeProcessRunning();
var settings = ReadSettings();
if (settings is null)
{
return new AwakeState(isRunning, AwakeStateMode.Passive, false, null, null);
}
var mode = settings.Properties.Mode switch
{
AwakeMode.PASSIVE => AwakeStateMode.Passive,
AwakeMode.INDEFINITE => AwakeStateMode.Indefinite,
AwakeMode.TIMED => AwakeStateMode.Timed,
AwakeMode.EXPIRABLE => AwakeStateMode.Expirable,
_ => AwakeStateMode.Passive,
};
TimeSpan? duration = null;
DateTimeOffset? expiration = null;
switch (mode)
{
case AwakeStateMode.Timed:
duration = TimeSpan.FromHours(settings.Properties.IntervalHours) + TimeSpan.FromMinutes(settings.Properties.IntervalMinutes);
break;
case AwakeStateMode.Expirable:
expiration = settings.Properties.ExpirationDateTime;
break;
}
return new AwakeState(isRunning, mode, settings.Properties.KeepDisplayOn, duration, expiration);
}
public Task<OperationResult> SetIndefiniteAsync(CancellationToken cancellationToken = default)
{
return UpdateSettingsAsync(
settings =>
{
settings.Properties.Mode = AwakeMode.INDEFINITE;
},
cancellationToken);
}
public Task<OperationResult> SetTimedAsync(int minutes, CancellationToken cancellationToken = default)
{
if (minutes <= 0)
{
return Task.FromResult(OperationResult.Fail("Minutes must be greater than zero."));
}
return UpdateSettingsAsync(
settings =>
{
var totalMinutes = Math.Min(minutes, int.MaxValue);
settings.Properties.Mode = AwakeMode.TIMED;
settings.Properties.IntervalHours = (uint)(totalMinutes / 60);
settings.Properties.IntervalMinutes = (uint)(totalMinutes % 60);
},
cancellationToken);
}
public Task<OperationResult> SetOffAsync(CancellationToken cancellationToken = default)
{
return UpdateSettingsAsync(
settings =>
{
settings.Properties.Mode = AwakeMode.PASSIVE;
},
cancellationToken);
}
private static Task<OperationResult> UpdateSettingsAsync(Action<AwakeSettings> mutateSettings, CancellationToken cancellationToken)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var settingsUtils = SettingsUtils.Default;
var settings = settingsUtils.GetSettingsOrDefault<AwakeSettings>(AwakeSettings.ModuleName);
mutateSettings(settings);
settingsUtils.SaveSettings(JsonSerializer.Serialize(settings, AwakeServiceJsonContext.Default.AwakeSettings), AwakeSettings.ModuleName);
return Task.FromResult(OperationResult.Ok());
}
catch (OperationCanceledException)
{
return Task.FromResult(OperationResult.Fail("Awake update was cancelled."));
}
catch (Exception ex)
{
return Task.FromResult(OperationResult.Fail($"Failed to update Awake settings: {ex.Message}"));
}
}
private static bool IsAwakeProcessRunning()
{
try
{
return Process.GetProcessesByName("PowerToys.Awake").Length > 0;
}
catch
{
return false;
}
}
private static AwakeSettings? ReadSettings()
{
try
{
var settingsUtils = SettingsUtils.Default;
return settingsUtils.GetSettingsOrDefault<AwakeSettings>(AwakeSettings.ModuleName);
}
catch
{
return null;
}
}
}

View File

@@ -1,13 +0,0 @@
// 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.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Awake.ModuleServices;
[JsonSerializable(typeof(AwakeSettings))]
internal sealed partial class AwakeServiceJsonContext : JsonSerializerContext
{
}

View File

@@ -1,26 +0,0 @@
// 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 Awake.ModuleServices;
/// <summary>
/// Represents the current state of the Awake module.
/// </summary>
/// <param name="IsRunning">Whether the Awake process is currently running.</param>
/// <param name="Mode">The current Awake mode (Passive, Indefinite, Timed, Expirable).</param>
/// <param name="KeepDisplayOn">Whether the display is kept on.</param>
/// <param name="Duration">For timed mode, the configured duration.</param>
/// <param name="Expiration">For expirable mode, the expiration date/time.</param>
public readonly record struct AwakeState(bool IsRunning, AwakeStateMode Mode, bool KeepDisplayOn, TimeSpan? Duration, DateTimeOffset? Expiration);
/// <summary>
/// The mode of the Awake module.
/// </summary>
public enum AwakeStateMode
{
Passive = 0,
Indefinite = 1,
Timed = 2,
Expirable = 3,
}

View File

@@ -1,21 +0,0 @@
// 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 PowerToys.ModuleContracts;
namespace Awake.ModuleServices;
public interface IAwakeService : IModuleService
{
Task<OperationResult> SetIndefiniteAsync(CancellationToken cancellationToken = default);
Task<OperationResult> SetTimedAsync(int minutes, CancellationToken cancellationToken = default);
Task<OperationResult> SetOffAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets the current state of the Awake module.
/// </summary>
AwakeState GetCurrentState();
}

View File

@@ -28,13 +28,6 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern void FreeConsole();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);

View File

@@ -49,8 +49,5 @@ namespace Awake.Core.Native
// Menu Item Info Flags
internal const uint MNS_AUTO_DISMISS = 0x10000000;
internal const uint MIM_STYLE = 0x00000010;
// Attach Console
internal const int ATTACH_PARENT_PROCESS = -1;
}
}

View File

@@ -51,24 +51,6 @@ namespace Awake
private static async Task<int> Main(string[] args)
{
var rootCommand = BuildRootCommand();
Bridge.AttachConsole(Core.Native.Constants.ATTACH_PARENT_PROCESS);
var parseResult = rootCommand.Parse(args);
if (parseResult.Tokens.Any(t => t.Value.ToLowerInvariant() is "--help" or "-h" or "-?"))
{
// Print help and exit.
return rootCommand.Invoke(args);
}
if (parseResult.Errors.Count > 0)
{
// Shows errors and returns non-zero.
return rootCommand.Invoke(args);
}
_settingsUtils = SettingsUtils.Default;
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);
@@ -125,97 +107,116 @@ namespace Awake
Bridge.GetPwrCapabilities(out _powerCapabilities);
Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities, _serializerOptions));
return await rootCommand.InvokeAsync(args);
Logger.LogInfo("Parsing parameters...");
Option<bool> configOption = new(_aliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> displayOption = new(_aliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<uint> timeOption = new(_aliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
{
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
Option<int> pidOption = new(_aliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<string> expireAtOption = new(_aliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> parentPidOption = new(_aliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
timeOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !uint.TryParse(result.Tokens[0].Value, out _))
{
string errorMessage = $"Interval in --time-limit could not be parsed correctly. Check that the value is valid and doesn't exceed 4,294,967,295. Value used: {result.Tokens[0].Value}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
pidOption.AddValidator(result =>
{
if (result.Tokens.Count == 0)
{
return;
}
string tokenValue = result.Tokens[0].Value;
if (!int.TryParse(tokenValue, out int parsed))
{
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {tokenValue}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
return;
}
if (parsed <= 0)
{
string errorMessage = $"PID value in --pid must be a positive integer. Value used: {parsed}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
return;
}
// Process existence check. (We also re-validate just before binding.)
if (!ProcessExists(parsed))
{
string errorMessage = $"No running process found with an ID of {parsed}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
expireAtOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !DateTimeOffset.TryParse(result.Tokens[0].Value, out _))
{
string errorMessage = $"Date and time value in --expire-at could not be parsed correctly. Check that the value is valid date and time. Refer to https://aka.ms/powertoys/awake for format examples. Value used: {result.Tokens[0].Value}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
RootCommand? rootCommand =
[
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption,
parentPidOption,
];
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption);
return rootCommand.InvokeAsync(args).Result;
}
}
}
private static RootCommand BuildRootCommand()
{
Logger.LogInfo("Parsing parameters...");
Option<bool> configOption = new(_aliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> displayOption = new(_aliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<uint> timeOption = new(_aliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
{
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
Option<int> pidOption = new(_aliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<string> expireAtOption = new(_aliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> parentPidOption = new(_aliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
timeOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !uint.TryParse(result.Tokens[0].Value, out _))
{
string errorMessage = $"Interval in --time-limit could not be parsed correctly. Check that the value is valid and doesn't exceed 4,294,967,295. Value used: {result.Tokens[0].Value}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
pidOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !int.TryParse(result.Tokens[0].Value, out _))
{
string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {result.Tokens[0].Value}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
expireAtOption.AddValidator(result =>
{
if (result.Tokens.Count != 0 && !DateTimeOffset.TryParse(result.Tokens[0].Value, out _))
{
string errorMessage = $"Date and time value in --expire-at could not be parsed correctly. Check that the value is valid date and time. Refer to https://aka.ms/powertoys/awake for format examples. Value used: {result.Tokens[0].Value}.";
Logger.LogError(errorMessage);
result.ErrorMessage = errorMessage;
}
});
RootCommand? rootCommand =
[
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption,
parentPidOption,
];
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption);
return rootCommand;
}
private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception exception)
@@ -263,7 +264,6 @@ namespace Awake
if (pid == 0 && !useParentPid)
{
Logger.LogInfo("No PID specified. Allocating console...");
Bridge.FreeConsole();
AllocateLocalConsole();
}
else

View File

@@ -12,4 +12,4 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
</PropertyGroup>
</Project>
</Project>

View File

@@ -12,4 +12,4 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
</PropertyGroup>
</Project>
</Project>

Some files were not shown because too many files have changed in this diff Show More