[DSC] Implement Microsoft.PowerToys.Configure DSCResource & winget support (#30918)

* [DSC] Microsoft.PowerToys.Configure module + winget configuration file support

* f: fix for an incorrect directory id reference

* f: update comment

* f: address review comments

* f: file locksmith bug fix

* f: add explorer preview switches in samples

* f: remove debug

* Sign DSC files

* f: implement docs/samples generator

* [ci]Sign FancyZonesEditorCommon.dll

* Sign DSC files in the Generated folder

* f: address review comments

* f: update usable options

* f: add autogenerated sample

* [Installer] Don't use same GUID for different components

* [Installer]Don't remove folders shared by other modules

* Allow configuring PTRun MaximumNumberOfResults

* Remove all settings DSC sample. Just random data

* Allow configuring Hosts Run as Administrator

* Revert "[Installer]Don't remove folders shared by other modules"

This reverts commit 6da3d6cfd5.

* Add all PTRun plugins and Global and keyboard to DSC sample

* Fix issues with context menu modules not disabling

* Fix default enabled values when setting with DSC

* Fix tests regarding default modules in Settings

* Fix merge error

* Restart PowerToys process if we stopped it

---------

Co-authored-by: Andrey Nekrasov <1828123+yuyoyuppe@users.noreply.github.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Andrey Nekrasov 2024-04-02 01:09:47 +02:00 committed by GitHub
parent 818d3e3035
commit f23fa3f592
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
81 changed files with 2608 additions and 265 deletions

View File

@ -1029,6 +1029,7 @@ NOSIZE
NOTIFICATIONSDLL
NOTIFYICONDATAW
NOTIMPL
notlike
NOTOPMOST
NOTRACK
NOTSRCCOPY

2
.gitignore vendored
View File

@ -33,6 +33,8 @@ bld/
# Visual Studio 2017 auto generated files
Generated\ Files/
Generated/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

View File

@ -0,0 +1,51 @@
{
"Version": "1.0.0",
"UseMinimatch": false,
"SignBatches": [
{
"MatchedPath": [
"Microsoft.PowerToys.Configure.psm1",
"Microsoft.PowerToys.Configure.psd1"
],
"SigningInfo": {
"Operations": [
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolSign",
"Parameters": [
{
"parameterName": "OpusName",
"parameterValue": "Microsoft"
},
{
"parameterName": "OpusInfo",
"parameterValue": "http://www.microsoft.com"
},
{
"parameterName": "FileDigest",
"parameterValue": "/fd \"SHA256\""
},
{
"parameterName": "PageHash",
"parameterValue": "/NPH"
},
{
"parameterName": "TimeStamp",
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
],
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolVerify",
"Parameters": [],
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
}
}
]
}

View File

@ -349,6 +349,15 @@ extends:
batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_core.json'
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3
displayName: Sign DSC Powershell files
inputs:
ConnectedServiceName: 'Terminal/Console/WinAppDriver Team Code Signing Connection'
FolderPath: 'src/dsc/Microsoft.PowerToys.Configure'
signType: batchSigning
batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_DSC.json'
ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3
displayName: Sign x86 directshow VCM
inputs:

View File

@ -574,6 +574,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITests-FancyZonesEditor",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditorCommon", "src\modules\fancyzones\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj", "{C0974915-8A1D-4BF0-977B-9587D3807AB7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DSC", "DSC", "{557C4636-D7E1-4838-A504-7D19B725EE95}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Schema.Generator", "src\dsc\PowerToys.Settings.DSC.Schema.Generator\PowerToys.Settings.DSC.Schema.Generator.csproj", "{1D6893CB-BC0C-46A8-A76C-9728706CA51A}"
ProjectSection(ProjectDependencies) = postProject
{020A7474-3601-4160-A159-D7B70B77B15F} = {020A7474-3601-4160-A159-D7B70B77B15F}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@ -2536,6 +2543,18 @@ Global
{C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|x64.Build.0 = Release|x64
{C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|x86.ActiveCfg = Release|x64
{C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|x86.Build.0 = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|ARM64.Build.0 = Debug|ARM64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x64.ActiveCfg = Debug|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x64.Build.0 = Debug|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x86.ActiveCfg = Debug|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x86.Build.0 = Debug|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|ARM64.ActiveCfg = Release|ARM64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|ARM64.Build.0 = Release|ARM64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x64.ActiveCfg = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x64.Build.0 = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x86.ActiveCfg = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2748,6 +2767,7 @@ Global
{FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{3A9A791E-94A9-49F8-8401-C11CE288D5FB} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@ -0,0 +1,98 @@
# What is it
We would like to enable our users to use [`winget configure`](https://learn.microsoft.com/en-us/windows/package-manager/winget/configure) command to install PowerToys and configure its settings with a [Winget configuration file](https://learn.microsoft.com/en-us/windows/package-manager/configuration/create). For example:
```yaml
properties:
resources:
- resource: Microsoft.WinGet.DSC/WinGetPackage
directives:
description: Install PowerToys
allowPrerelease: true
settings:
id: PowerToys (Preview)
source: winget
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
ShortcutGuide:
Enabled: false
OverlayOpacity: 1
FancyZones:
Enabled: true
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
configurationVersion: 0.2.0
```
This should install PowerToys and make `PowerToysConfigure` resource available. We can use it in the same file.
# How it works
`PowerToysConfigure` is a [class-based DSC resource](https://learn.microsoft.com/en-us/powershell/dsc/concepts/class-based-resources?view=dsc-2.0). It looks up whether each setting was specified or not by checking whether it's `$null` or `0` for `enum`s and invokes `PowerToys.Settings.exe` with the updated value like so:
```
PowerToys.Settings.exe set <ModuleName>.<SettingName> <SettingValue>
```
So for the example the config above should perform 3 following invocations:
```
PowerToys.Settings.exe set ShortcutGuide.Enabled false
PowerToys.Settings.exe set FancyZones.Enabled true
PowerToys.Settings.exe set FancyZones.FancyzonesEditorHotkey "Shift+Ctrl+Alt+F"
```
`PowerToys.Settings` uses dotnet reflection capabilities to determine `SettingName` type and tries to convert the supplied `SettingValue` string accordingly. We use `ICmdReprParsable` for custom setting types.
# How DSC is implemented
We use `PowerToys.Settings.DSC.Schema.Generator` to generate the bulk of `PowerToysConfigure.psm1` file. It also uses dotnet reflection capabilities to inspect `PowerToys.Settings.UI.Lib.dll` assembly and generate properties for the modules we have. The actual generation is done as a `PowerToys.Settings.DSC.Schema.Generator.csproj` post-build action.
# Debugging DSC resources
First, make sure that PowerShell 7.4+ is installed. Then make sure that you have DSC installed:
```ps
Install-Module -Name PSDesiredStateConfiguration -RequiredVersion 2.0.7
```
After that, start a new `pwsh` session and `cd` to `src\dsc\Microsoft.PowerToys.Configure\Generated` directory. From there, you should execute:
```ps
$env:PSModulePath += ";$pwd"
```
Now build `PowerToys.sln` and **move** `src\dsc\Microsoft.PowerToys.Configure\Microsoft.PowerToys.Configure.psd1` temporarily to `src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\0.0.1\` folder, so it's located alongside with the generated `Microsoft.PowerToys.Configure.psm1`.
This will allow DSC to discover our DSC Resource module. See [PSModulePath](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_psmodulepath?view=powershell-7.4#long-description) for more info.
If everything works, you should see that your module is discovered by executing the following command:
```ps
Get-Module -ListAvailable | grep PowerToys
```
The resource itself should also be available:
```ps
Get-DSCResource | grep PowerToys
```
Otherwise, you can force-import the module to diagnose issues:
```
Import-Module .\Microsoft.PowerToys.Configure.psd1
```
If it's imported successfully, you could also try to invoke it directly:
```ps
Invoke-DscResource -Name PowerToysConfigure -Method Set -ModuleName Microsoft.PowerToys.Configure -Property @{ Debug = $true; Awake = @{ Enabled = $false; Mode = "TIMED"; IntervalMinutes = "10" } }
```
Note that we've supplied `Debug` option, so a `%TEMP\PowerToys.DSC.TestConfigure.txt` is created with the supplied properties, a current timestamp, and other debug output.
Finally, you can test it with winget by invoking it as such:
```ps
winget configure .\configuration.dsc.yaml --accept-configuration-agreements --disable-interactivity
```

View File

@ -46,6 +46,49 @@
</Component>
</DirectoryRef>
<?if $(var.PerUser) = "true" ?>
<DirectoryRef Id="PersonalFolder">
<Directory Id="WindowsPowerShellFolder" Name="PowerShell">
<Directory Id="PowerShellModulesFolder" Name="Modules">
<Directory Id="PowerToysDscFolder" Name="Microsoft.PowerToys.Configure">
<Directory Id="PowerToysDscVerFolder" Name="$(var.Version)">
<Component Id="PowerToysDSC" Win64="yes" Guid="4A033E3B-6590-43FD-8FBD-27F9DF557F7F">
<RegistryValue Root="HKCU"
Key="Software\[Manufacturer]\[ProductName]"
Name="DSCInstalled"
Type="integer"
Value="1"
KeyPath="yes"/>
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Microsoft.PowerToys.Configure.psd1" Id="PTConf.psd1" />
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version)\Microsoft.PowerToys.Configure.psm1" Id="PTConf.psm1" />
<RemoveFolder Id="RemoveThisFolder" On="uninstall" />
<RemoveFolder Id="RemovePowerToysDscVerFolder" Directory="PowerToysDscVerFolder" On="uninstall" />
<RemoveFolder Id="RemovePowerToysDscFolder" Directory="PowerToysDscFolder" On="uninstall" />
<RemoveFolder Id="RemovePowerShellModulesFolder" Directory="PowerShellModulesFolder" On="uninstall" />
<RemoveFolder Id="RemoveWindowsPowerShellFolder" Directory="WindowsPowerShellFolder" On="uninstall" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</DirectoryRef>
<?else?>
<DirectoryRef Id="ProgramFiles64Folder">
<Directory Id="WindowsPowerShellFolder" Name="WindowsPowerShell">
<Directory Id="PowerShellModulesFolder" Name="Modules">
<Directory Id="PowerToysDscFolder" Name="Microsoft.PowerToys.Configure">
<Directory Id="PowerToysDscVerFolder" Name="$(var.Version)">
<Component Id="PowerToysDSC" Win64="yes" Guid="C52AECA0-DA73-49B8-BB49-31EF6640FF1F">
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Microsoft.PowerToys.Configure.psd1" Id="PTConf.psd1" />
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version)\Microsoft.PowerToys.Configure.psm1" Id="PTConf.psm1" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</DirectoryRef>
<?endif?>
<DirectoryRef Id="ApplicationProgramsFolder">
<Component Id="PowerToysStartMenuShortcut" >
<Shortcut Id="ApplicationStartMenuShortcut"
@ -101,6 +144,7 @@
<ComponentRef Id="License_rtf" />
<ComponentRef Id="Notice_md" />
<ComponentRef Id="DesktopShortcut" />
<ComponentRef Id="PowerToysDSC" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -395,6 +395,7 @@
<Directory Id="ApplicationProgramsFolder" Name="PowerToys (Preview)"/>
</Directory>
<Directory Id="DesktopFolder" Name="Desktop" />
<Directory Id="PersonalFolder" Name="UserHomeDocuments" />
</Directory>
</Fragment>
</Wix>

View File

@ -20,7 +20,6 @@
#include <common/version/version.h>
using namespace System;
using namespace System::Runtime::InteropServices;
using System::Collections::Generic::List;
@ -40,11 +39,17 @@ public
delete _map;
}
String ^ GetKeyName(DWORD key) {
String ^ GetKeyName(DWORD key)
{
return gcnew String(_map->GetKeyName(key).c_str());
}
void Updatelayout()
DWORD GetKeyValue(String ^ name)
{
return _map->GetKeyFromName(msclr::interop::marshal_as<std::wstring>(name));
}
void Updatelayout()
{
_map->UpdateLayout();
}
@ -129,13 +134,13 @@ public
}
static List<String ^> ^ GetAllActiveMicrophoneDeviceNames() {
auto names = gcnew List<String ^>();
for (const auto& device : MicrophoneDevice::getAllActive())
{
names->Add(gcnew String(device->name().data()));
auto names = gcnew List<String ^>();
for (const auto& device : MicrophoneDevice::getAllActive())
{
names->Add(gcnew String(device->name().data()));
}
return names;
}
return names;
}
static List<String ^> ^
GetAllVideoCaptureDeviceNames() {

View File

@ -0,0 +1,83 @@
#
# Module manifest for module 'Microsoft.PowerToys.Configure'
#
# Generated by: Microsoft Corporation
#
# Generated on: 20.11.2023
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'Microsoft.PowerToys.Configure.psm1'
# Version number of this module.
ModuleVersion = '0.0.1'
# ID used to uniquely identify this module
GUID = '778ed7a1-489d-4dc9-b0f2-2da3b1fe14cb'
# Author of this module
Author = 'Microsoft Corporation'
# Company or vendor of this module
CompanyName = 'Microsoft'
# Copyright statement for this module
Copyright = '(c) Microsoft Corporation. All rights reserved.'
# Description of the functionality provided by this module
Description = 'The module enables settings configuration for an installed PowerToys application.'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = '*'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
DscResourcesToExport = @(
'PowerToysConfigure'
)
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
}

View File

@ -0,0 +1,23 @@
properties:
resources:
# - resource: Microsoft.WinGet.DSC/WinGetPackage
# directives:
# description: Install PowerToys
# allowPrerelease: true
# settings:
# id: PowerToys (Preview)
# source: winget
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
ShortcutGuide:
Enabled: false
OverlayOpacity: 50
FancyZones:
Enabled: true
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
FileLocksmith:
Enabled: false
configurationVersion: 0.2.0

View File

@ -0,0 +1,53 @@
properties:
resources:
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
PowerLauncher:
Enabled: true
Plugins:
- Name: "Calculator"
Disabled: false
- Name: "Folder"
Disabled: false
- Name: "History"
Disabled: false
- Name: "Windows Search"
Disabled: false
- Name: "OneNote"
Disabled: false
- Name: "PowerToys"
Disabled: false
- Name: "Program"
Disabled: false
ActionKeyword: "P:"
IsGlobal: false
- Name: "Registry Plugin"
Disabled: false
- Name: "Service"
Disabled: false
- Name: "Shell"
Disabled: false
- Name: "Windows System Commands"
Disabled: false
- Name: "Time and Date"
Disabled: false
- Name: "Unit Converter"
Disabled: false
- Name: "URI Handler"
Disabled: false
- Name: "Value Generator"
Disabled: false
- Name: "Visual Studio Code Workspaces"
Disabled: false
- Name: "Web Search"
Disabled: false
- Name: "Windows settings"
Disabled: false
- Name: "Windows Terminal"
Disabled: false
- Name: "Window Walker"
Disabled: false
configurationVersion: 0.2.0

View File

@ -0,0 +1,70 @@
properties:
resources:
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
AlwaysOnTop:
Enabled: false
Awake:
Enabled: false
ColorPicker:
Enabled: false
CropAndLock:
Enabled: false
EnvironmentVariables:
Enabled: false
FancyZones:
Enabled: false
FileLocksmith:
Enabled: false
ImageResizer:
Enabled: false
KeyboardManager:
Enabled: false
FindMyMouse:
Enabled: false
MouseHighlighter:
Enabled: false
MouseJump:
Enabled: false
MousePointerCrosshairs:
Enabled: false
MouseWithoutBorders:
Enabled: false
Peek:
Enabled: false
PowerRename:
Enabled: false
PowerLauncher:
Enabled: false
PowerAccent:
Enabled: false
PowerPreview:
EnableSvgPreview: false
EnableSvgThumbnail: false
EnableMdPreview: false
EnableMonacoPreview: false
EnablePdfPreview: false
EnablePdfThumbnail: false
EnableGcodePreview: false
EnableGcodeThumbnail: false
EnableStlThumbnail: false
EnableQoiPreview: false
EnableQoiThumbnail: false
PowerOcr:
Enabled: false
ShortcutGuide:
Enabled: false
VideoConference:
Enabled: false
MeasureTool:
Enabled: false
Hosts:
Enabled: false
PastePlain:
Enabled: false
RegistryPreview:
Enabled: false
configurationVersion: 0.2.0

View File

@ -0,0 +1,70 @@
properties:
resources:
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
AlwaysOnTop:
Enabled: true
Awake:
Enabled: true
ColorPicker:
Enabled: true
CropAndLock:
Enabled: true
EnvironmentVariables:
Enabled: true
FancyZones:
Enabled: true
FileLocksmith:
Enabled: true
ImageResizer:
Enabled: true
KeyboardManager:
Enabled: true
FindMyMouse:
Enabled: true
MouseHighlighter:
Enabled: true
MouseJump:
Enabled: true
MousePointerCrosshairs:
Enabled: true
MouseWithoutBorders:
Enabled: true
Peek:
Enabled: true
PowerRename:
Enabled: true
PowerLauncher:
Enabled: true
PowerAccent:
Enabled: true
PowerPreview:
EnableSvgPreview: true
EnableSvgThumbnail: true
EnableMdPreview: true
EnableMonacoPreview: true
EnablePdfPreview: true
EnablePdfThumbnail: true
EnableGcodePreview: true
EnableGcodeThumbnail: true
EnableStlThumbnail: true
EnableQoiPreview: true
EnableQoiThumbnail: true
PowerOcr:
Enabled: true
ShortcutGuide:
Enabled: true
VideoConference:
Enabled: true
MeasureTool:
Enabled: true
Hosts:
Enabled: true
PastePlain:
Enabled: true
RegistryPreview:
Enabled: true
configurationVersion: 0.2.0

View File

@ -0,0 +1,31 @@
// 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.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace PowerToys.Settings.DSC.Schema;
internal sealed class Common
{
private static string[] TypeParts(string name)
{
return Regex.Split(name.ToLower(CultureInfo.CurrentCulture), @"(?<!^)(?=[A-Z])|\.");
}
internal static bool InferIsBool(Type propertyInfo)
{
return TypeParts(propertyInfo.Name).Any(word => word.Equals("Bool", StringComparison.OrdinalIgnoreCase) || word.Equals("Boolean", StringComparison.OrdinalIgnoreCase));
}
internal static bool InferIsInt(Type propertyInfo)
{
return TypeParts(propertyInfo.Name).Any(word => word.Contains("Int", StringComparison.OrdinalIgnoreCase));
}
}

View File

@ -0,0 +1,357 @@
// 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.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using static PowerToys.Settings.DSC.Schema.Introspection;
namespace PowerToys.Settings.DSC.Schema;
internal sealed class DSCGeneration
{
private static readonly string DoubleNewLine = Environment.NewLine + Environment.NewLine;
private struct AdditionalPropertiesInfo
{
public string Name;
public string Type;
}
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } } };
private static string EmitEnumDefinition(Type type)
{
var values = string.Empty;
int i = 0;
foreach (var name in Enum.GetNames(type))
{
values += " " + name;
// Nullable enums seem to be not supported by winget, so the workaround is to always start with '1', because by default the values are initialized to zero. That allows us to use zero as a "lack of value" indicator.
if (i == 0)
{
values += " = 1";
}
values += Environment.NewLine;
i++;
}
return $$"""
enum {{type.Name}} {
{{values}}}
""";
}
private struct PropertyEmitInfo
{
public string Name;
public string Type;
public string Initializer;
public string EqualityOperator;
public string DefaultValue;
public PropertyEmitInfo(string name, Type property)
{
Name = name;
bool intLike = Common.InferIsInt(property);
bool boolLike = Common.InferIsBool(property);
var rawType = "string";
var isNullable = true;
DefaultValue = "$null";
EqualityOperator = "-ne";
Initializer = "= $null";
if (intLike)
{
rawType = "int";
isNullable = false;
}
else if (boolLike)
{
rawType = "bool";
isNullable = false;
}
else if (property.IsEnum)
{
rawType = property.Name;
isNullable = true;
Initializer = string.Empty;
DefaultValue = "0";
}
// For strings
else
{
EqualityOperator = "-notlike";
DefaultValue = "''";
}
// We must make all our properties nullable to be able to detect which of them weren't supplied
Type = isNullable ? rawType : $"Nullable[{rawType}]";
}
}
private static string EmitPropertyDefinition(PropertyEmitInfo info)
{
return $$"""
[DscProperty()] [{{info.Type}}]
${{info.Name}} {{info.Initializer}}
""";
}
private static string EmitPropertyApplyChangeStatements(string moduleName, PropertyEmitInfo info, string localPropertyName = null)
{
if (localPropertyName == null)
{
localPropertyName = info.Name;
}
return $$"""
if ($this.{{localPropertyName}} {{info.EqualityOperator}} {{info.DefaultValue}}) {
$Changes.Value += "set {{moduleName}}.{{info.Name}} `"$($this.{{localPropertyName}})`""
}
""";
}
private static string EmitModuleDefinition(SettingsStructure module)
{
bool generalSettings = module.Name == "GeneralSettings";
var properties = module.Properties
.Where(property => !property.Value.IsIgnored)
.Select(property => new PropertyEmitInfo(property.Key, property.Value.Type));
var propertyDefinitionsBlock = string.Empty;
var applyChangesBlock = string.Empty;
foreach (var property in properties)
{
var definition = EmitPropertyDefinition(property);
var applyChanges = EmitPropertyApplyChangeStatements(module.Name, property);
propertyDefinitionsBlock += definition + DoubleNewLine;
applyChangesBlock += applyChanges + DoubleNewLine;
}
bool hasAdditionalProperties = AdditionalPropertiesInfoPerModule.TryGetValue(module.Name, out var additionalPropertiesInfo);
// Enabled property of each module is contained in General settings
if (!generalSettings)
{
propertyDefinitionsBlock += $$"""
[DscProperty(Key)] [Nullable[bool]]
$Enabled = $null
""";
if (hasAdditionalProperties)
{
propertyDefinitionsBlock += $$"""
[DscProperty()] [{{additionalPropertiesInfo.Type}}]
${{additionalPropertiesInfo.Name}} = @()
""";
}
applyChangesBlock += EmitPropertyApplyChangeStatements("General.Enabled", new PropertyEmitInfo($"{module.Name}", typeof(bool)), "Enabled");
}
var additionalPropertiesCheckBlock = string.Empty;
if (hasAdditionalProperties)
{
additionalPropertiesCheckBlock = $$"""
if ($this.{{additionalPropertiesInfo.Name}}.Count -gt 0) {
$AdditionalPropertiesTmpPath = [System.IO.Path]::GetTempFileName()
$this.{{additionalPropertiesInfo.Name}} | ConvertTo-Json | Set-Content -Path $AdditionalPropertiesTmpPath
$Changes.Value += "setAdditional {{module.Name}} `"$AdditionalPropertiesTmpPath`""
}
""";
}
return $$"""
class {{module.Name}} {
{{propertyDefinitionsBlock}} ApplyChanges([ref]$Changes) {
{{applyChangesBlock}}
{{additionalPropertiesCheckBlock}}
}
}
""";
}
public static string EmitModuleFileContents(SettingsStructure[] moduleSettings, SettingsStructure generalSettings, string debugSettingsPath)
{
var enumsToEmit = new HashSet<Type>();
var modulesBlock = string.Empty;
var modulesResourcePropertiesBlock = string.Empty;
var applyModulesChangesBlock = string.Empty;
foreach (var module in moduleSettings.Append(generalSettings))
{
enumsToEmit.UnionWith(module.Properties
.Where(property => property.Value.Type.IsEnum)
.Select(property => property.Value.Type));
modulesBlock += EmitModuleDefinition(module);
applyModulesChangesBlock += $$"""
$this.{{module.Name}}.ApplyChanges([ref]$ChangesToApply)
""";
modulesResourcePropertiesBlock += $$"""
[DscProperty()]
[{{module.Name}}]${{module.Name}} = [{{module.Name}}]::new()
""";
}
var enumsBlock = string.Join(DoubleNewLine, enumsToEmit.Select(EmitEnumDefinition));
var version = interop.CommonManaged.GetProductVersion().Replace("v", string.Empty);
return $$"""
#region enums
enum PowerToysConfigureEnsure {
Absent
Present
}
{{enumsBlock}}
#endregion enums
#region DscResources
{{modulesBlock}}
[DscResource()]
class PowerToysConfigure {
[DscProperty(Key)] [PowerToysConfigureEnsure]
$Ensure = [PowerToysConfigureEnsure]::Present
[bool] $Debug = $false
{{modulesResourcePropertiesBlock}}
[string] GetPowerToysSettingsPath() {
# Obtain PowerToys install location
if ($this.Debug -eq $true) {
$SettingsExePath = "{{debugSettingsPath}}"
} else {
$installation = Get-WmiObject Win32_Product | Where-Object {$_.Name -eq "PowerToys (Preview)" -and $_.Version -eq "{{version}}"}
if ($installation) {
$SettingsExePath = Join-Path (Join-Path $installation.InstallLocation WinUI3Apps) PowerToys.Settings.exe
# Handle spaces in the path
$SettingsExePath = "`"$SettingsExePath`""
} else {
throw "PowerToys installation wasn't found."
}
}
return $SettingsExePath
}
[PowerToysConfigure] Get() {
$CurrentState = [PowerToysConfigure]::new()
$SettingsExePath = $this.GetPowerToysSettingsPath()
$SettingsTmpFilePath = [System.IO.Path]::GetTempFileName()
$SettingsToRequest = @{}
foreach ($module in $CurrentState.PSObject.Properties) {
$moduleName = $module.Name
# Skip utility properties
if ($moduleName -eq "Ensure" -or $moduleName -eq "Debug") {
continue
}
$moduleProperties = $module.Value
$propertiesArray = @()
foreach ($property in $moduleProperties.PSObject.Properties) {
$propertyName = $property.Name
# Skip Enabled properties - they should be requested from GeneralSettings
if ($propertyName -eq "Enabled") {
continue
}
$propertiesArray += $propertyName
}
$SettingsToRequest[$moduleName] = $propertiesArray
}
$settingsJson = $SettingsToRequest | ConvertTo-Json
$settingsJson | Set-Content -Path $SettingsTmpFilePath
Start-Process -FilePath $SettingsExePath -Wait -Args "get `"$SettingsTmpFilePath`""
$SettingsValues = Get-Content -Path $SettingsTmpFilePath -Raw
if ($this.Debug -eq $true) {
$TempFilePath = Join-Path -Path $env:TEMP -ChildPath "PowerToys.DSC.TestConfigure.txt"
Set-Content -Path "$TempFilePath" -Value ("Requested:`r`n" + $settingsJson + "`r`n" + "Got:`r`n" + $SettingsValues + "`r`n" + (Get-Date -Format "o")) -Force
}
$SettingsValues = $SettingsValues | ConvertFrom-Json
foreach ($module in $SettingsValues.PSObject.Properties) {
$moduleName = $module.Name
$obtainedModuleSettings = $module.Value
$moduleRef = $CurrentState.$moduleName
foreach ($property in $obtainedModuleSettings.PSObject.Properties) {
$propertyName = $property.Name
$moduleRef.$propertyName = $property.Value
}
}
Remove-Item -Path $SettingsTmpFilePath
return $CurrentState
}
[bool] Test() {
# NB: we must always assume that the configuration isn't applied, because changing some settings produce external side-effects
return $false
}
[void] Set() {
$SettingsExePath = $this.GetPowerToysSettingsPath()
$ChangesToApply = @()
{{applyModulesChangesBlock}}
if ($this.Debug -eq $true) {
$tmp_info = $ChangesToApply
# $tmp_info = $this | ConvertTo-Json -Depth 10
$TempFilePath = Join-Path -Path $env:TEMP -ChildPath "PowerToys.DSC.TestConfigure.txt"
Set-Content -Path "$TempFilePath" -Value ($tmp_info + "`r`n" + (Get-Date -Format "o")) -Force
}
# Stop any running PowerToys instances
Stop-Process -Name "PowerToys.Settings" -PassThru | Wait-Process
$PowerToysProcessStopped = Stop-Process -Name "PowerToys" -PassThru
$PowerToysProcessStopped | Wait-Process
foreach ($change in $ChangesToApply) {
Start-Process -FilePath $SettingsExePath -Wait -Args "$change"
}
# If the PowerToys was stopped, restart it.
if ($PowerToysProcessStopped -ne $null) {
Start-Process -FilePath $SettingsExePath
}
}
}
#endregion DscResources
""";
}
}

View File

@ -0,0 +1,79 @@
// 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.Linq;
using static PowerToys.Settings.DSC.Schema.Introspection;
namespace PowerToys.Settings.DSC.Schema;
internal sealed class DocumentationGeneration
{
private static readonly string IsAvailableSymbol = "✅";
private static readonly string IsUnavailableSymbol = "❌";
private static readonly string MissingValueIndicator = "—";
private static readonly string PropertySuffix = "Property";
private static string SimplifyPropertyType(string typeName)
{
if (typeName.EndsWith(PropertySuffix, StringComparison.InvariantCulture))
{
typeName = typeName.Remove(typeName.LastIndexOf(PropertySuffix, StringComparison.InvariantCulture), PropertySuffix.Length);
}
return typeName;
}
private static string EmitPropertyTableLine(string name, ModulePropertyStructure info)
{
bool isAvailable = !info.IsIgnored;
var availabilitySymbol = isAvailable ? IsAvailableSymbol : IsUnavailableSymbol;
var documentation = MissingValueIndicator;
if (info.Type.IsEnum)
{
documentation = "Possible values: ";
foreach (var enumValue in Enum.GetValues(info.Type))
{
documentation += enumValue.ToString() + ' ';
}
}
var propertyType = isAvailable ? SimplifyPropertyType(info.Type.Name) : MissingValueIndicator;
return $"| {name} | {propertyType} | {documentation} | {availabilitySymbol} |";
}
private static string EmitModulePropertiesTable(SettingsStructure module)
{
bool generalSettings = module.Name == "GeneralSettings";
var properties = module.Properties
.Where(p => !p.Value.IsIgnoredByJsonSerializer)
.Select(property => EmitPropertyTableLine(property.Key, property.Value)).Aggregate((acc, line) => string.Join(Environment.NewLine, [acc, line]));
var propertyDefinitionsBlock = string.Empty;
var applyChangesBlock = string.Empty;
return $$"""
### {{module.Name}}
| Name | Type | Description | Available |
| :--- | :--- | :--- | :--- |
{{properties}}
""";
}
public static string EmitDocumentationFileContents(SettingsStructure[] moduleSettings, SettingsStructure generalSettings)
{
var moduleTables = string.Empty;
foreach (var module in moduleSettings.Append(generalSettings))
{
moduleTables += EmitModulePropertiesTable(module);
}
return moduleTables;
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace PowerToys.Settings.DSC.Schema;
public class Introspection
{
public struct ModulePropertyStructure
{
public bool IsIgnoredByJsonSerializer;
public bool IsIgnoredByCmdConfigureAttribute;
public bool IsIgnored
{
get { return IsIgnoredByJsonSerializer || IsIgnoredByCmdConfigureAttribute; }
}
public Type Type;
}
public struct SettingsStructure
{
public string Name;
public Dictionary<string, ModulePropertyStructure> Properties;
}
private static bool IsModuleNameField(FieldInfo info)
{
return info != null && info.IsLiteral && !info.IsInitOnly
&& info.FieldType == typeof(string);
}
private static bool IsSettingsClassType(Type type)
{
return type.IsClass && type.FullName.EndsWith("Settings", StringComparison.InvariantCulture);
}
private static Dictionary<string, ModulePropertyStructure> ParseProperties(Type propertiesType)
{
return propertiesType.GetProperties().Select(property =>
{
var jsonIgnoreAttr = property.GetCustomAttribute<JsonIgnoreAttribute>();
var cmdIgnoreAttr = property.GetCustomAttribute<CmdConfigureIgnoreAttribute>();
return (property.Name, new ModulePropertyStructure
{
Type = property.PropertyType,
IsIgnoredByJsonSerializer = jsonIgnoreAttr != null,
IsIgnoredByCmdConfigureAttribute = cmdIgnoreAttr != null,
});
}).ToDictionary();
}
public static SettingsStructure ParseGeneralSettings(Assembly assembly)
{
return assembly
.GetTypes()
.Where(IsSettingsClassType)
.Where(type => type.Name == "GeneralSettings")
.Select(type => new SettingsStructure
{
Name = type.Name,
Properties = ParseProperties(type),
}).FirstOrDefault();
}
public static SettingsStructure[] ParseModuleSettings(Assembly assembly)
{
return assembly
.GetTypes()
.Where(IsSettingsClassType)
.Select(type => new
{
Properties = type.GetProperty("Properties"),
ModuleNameInfo = type.GetField("ModuleName", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy),
TypeName = type.Name,
})
.Where(x => x.Properties?.PropertyType.IsClass == true && IsModuleNameField(x.ModuleNameInfo))
.Select(x => new SettingsStructure
{
Name = x.TypeName.Replace("Settings", string.Empty),
Properties = ParseProperties(x.Properties.PropertyType),
})
.ToArray();
}
}

View File

@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Version.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>PowerToys.Settings.DSC.Schema</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<WindowsPackageType>None</WindowsPackageType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<SelfContained>true</SelfContained>
</PropertyGroup>
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
<PropertyGroup Condition="'$(Platform)'=='x64'">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<NoWarn></NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<WarningsNotAsErrors>CA1720</WarningsNotAsErrors>
<Optimize>False</Optimize>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<NoWarn></NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<WarningsNotAsErrors>CA1720</WarningsNotAsErrors>
<Optimize>true</Optimize>
</PropertyGroup>
<!-- See https://learn.microsoft.com/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component for more info -->
<PropertyGroup>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<PropertyGroup>
<GeneratedDSCModule>"$(ProjectDir)..\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(Version)\Microsoft.PowerToys.Configure.psm1"</GeneratedDSCModule>
</PropertyGroup>
<!-- The following sections assume that the machine we're building on is always x64. That means we won't be able to run/inspect arm64 executables, therefore we must always execute x64 generator. -->
<Target Name="PostBuildAction" AfterTargets="Build" Outputs="$(GeneratedDSCModule)" Condition="'$(Platform)'!='ARM64'">
<Exec Command="&quot;$(OutDir)$(AssemblyName).exe&quot; &quot;$(SolutionDir)x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll&quot; $(GeneratedDSCModule)" />
</Target>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Platform)'=='ARM64'">
<Exec Command="&quot;$(MSBuildToolsPath)\msbuild.exe&quot; PowerToys.sln -p:Configuration=&quot;$(Configuration)&quot; -p:Platform=&quot;x64&quot; -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="$(SolutionDir)" />
</Target>
</Project>

View File

@ -0,0 +1,86 @@
// 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.Reflection;
namespace PowerToys.Settings.DSC.Schema;
internal sealed class Program
{
public static int Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: Generator.exe <PowerToys.Settings.UI.Lib.dll path> <output path>");
return 1;
}
var dllPath = args[0];
var outputPath = args[1];
bool documentationMode = Path.GetExtension(outputPath) == ".md";
bool sampleMode = Path.GetExtension(outputPath) == ".yaml";
try
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var assembly = Assembly.LoadFrom(dllPath);
var moduleSettings = Introspection.ParseModuleSettings(assembly);
var generalSettings = Introspection.ParseGeneralSettings(assembly);
#if DEBUG
PrintUniquePropertyTypes(moduleSettings);
#endif
var outputFileContents = string.Empty;
if (documentationMode)
{
outputFileContents = DocumentationGeneration.EmitDocumentationFileContents(moduleSettings, generalSettings);
}
else if (sampleMode)
{
outputFileContents = SampleGeneration.EmitSampleFileContents(moduleSettings, generalSettings);
}
else
{
var debugSettingsPath = Path.Combine(Directory.GetParent(dllPath).FullName, "PowerToys.Settings.exe");
outputFileContents = DSCGeneration.EmitModuleFileContents(moduleSettings, generalSettings, debugSettingsPath);
}
File.WriteAllText(outputPath, outputFileContents);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 1;
}
return 0;
}
private static void PrintUniquePropertyTypes(Introspection.SettingsStructure[] moduleSettings)
{
Console.WriteLine("Detected the following module properties types:");
var propertyTypes = new HashSet<Type>();
foreach (var settings in moduleSettings)
{
Console.WriteLine($"{settings.Name}");
foreach (var (_, property) in settings.Properties)
{
if (!property.IsIgnored)
{
propertyTypes.Add(property.Type);
}
}
}
Console.WriteLine("\nDetected the following unique property types:");
foreach (var type in propertyTypes)
{
Console.WriteLine($"{type}");
}
}
}

View File

@ -0,0 +1,80 @@
// 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.Globalization;
using System.Linq;
using static PowerToys.Settings.DSC.Schema.Introspection;
namespace PowerToys.Settings.DSC.Schema;
internal sealed class SampleGeneration
{
private const int FixedSeed = 12345;
private static readonly Random _random = new Random(FixedSeed);
private static string EmitPropertySetter(string name, ModulePropertyStructure info)
{
var randomPropertyValue = "\"<string>\"";
if (Common.InferIsBool(info.Type))
{
randomPropertyValue = _random.Next(2) == 1 ? "true" : "false";
}
else if (Common.InferIsInt(info.Type))
{
randomPropertyValue = _random.Next(256).ToString(CultureInfo.InvariantCulture);
}
else if (info.Type.IsEnum)
{
var enumValues = Enum.GetValues(info.Type);
randomPropertyValue = enumValues.GetValue(_random.Next(enumValues.Length)).ToString();
}
return $" {name}: {randomPropertyValue}";
}
private static string EmitModulePropertiesSection(SettingsStructure module)
{
bool generalSettings = module.Name == "GeneralSettings";
var propertiesCollection = module.Properties
.Where(p => !p.Value.IsIgnored)
.Select(property => EmitPropertySetter(property.Key, property.Value))
.ToList();
string properties = propertiesCollection.Count != 0
? propertiesCollection.Aggregate((acc, line) => string.Join(Environment.NewLine, acc, line))
: string.Empty;
var propertyDefinitionsBlock = string.Empty;
var applyChangesBlock = string.Empty;
return $$"""
{{module.Name}}:
{{properties}}
""";
}
public static string EmitSampleFileContents(SettingsStructure[] moduleSettings, SettingsStructure generalSettings)
{
var moduleTables = $$"""
properties:
resources:
- resource: PowerToysConfigure
directives:
description: Configure PowerToys
settings:
""";
foreach (var module in moduleSettings.Append(generalSettings))
{
moduleTables += EmitModulePropertiesSection(module);
}
moduleTables += " configurationVersion: 0.2.0";
return moduleTables;
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="PowerToys.Settings.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@ -90,14 +90,12 @@ public:
}
m_enabled = true;
save_settings();
}
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
m_enabled = false;
save_settings();
}
virtual bool is_enabled() override
@ -123,7 +121,7 @@ public:
}
private:
bool m_enabled;
bool m_enabled = false;
bool m_extended_only;
void init_settings()
@ -136,7 +134,7 @@ private:
void save_settings()
{
auto& settings = FileLocksmithSettingsInstance();
settings.SetEnabled(m_enabled);
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
settings.SetExtendedContextMenuOnly(m_extended_only);
settings.Save();

View File

@ -18,10 +18,12 @@ static bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime)
FileLocksmithSettings::FileLocksmithSettings()
{
generalJsonFilePath = PTSettingsHelper::get_powertoys_general_save_file_location();
std::wstring savePath = PTSettingsHelper::get_module_save_folder_location(constants::nonlocalizable::PowerToyKey);
std::error_code ec;
jsonFilePath = savePath + constants::nonlocalizable::DataFilePath;
RefreshEnabledState();
Load();
}
@ -29,7 +31,6 @@ void FileLocksmithSettings::Save()
{
json::JsonObject jsonData;
jsonData.SetNamedValue(constants::nonlocalizable::JsonKeyEnabled, json::value(settings.enabled));
jsonData.SetNamedValue(constants::nonlocalizable::JsonKeyShowInExtendedContextMenu, json::value(settings.showInExtendedContextMenu));
json::to_file(jsonFilePath, jsonData);
@ -48,6 +49,32 @@ void FileLocksmithSettings::Load()
}
}
void FileLocksmithSettings::RefreshEnabledState()
{
// Load json settings from data file if it is modified in the meantime.
FILETIME lastModifiedTime{};
if (!(LastModifiedTime(generalJsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedGeneralSettingsTime) == 1))
return;
lastLoadedGeneralSettingsTime = lastModifiedTime;
auto json = json::from_file(generalJsonFilePath);
if (!json)
return;
const json::JsonObject& jsonSettings = json.value();
try
{
json::JsonObject modulesEnabledState;
json::get(jsonSettings, L"enabled", modulesEnabledState, json::JsonObject{});
json::get(modulesEnabledState, L"File Locksmith", settings.enabled, true);
}
catch (const winrt::hresult_error&)
{
}
}
void FileLocksmithSettings::Reload()
{
// Load json settings from data file if it is modified in the meantime.
@ -67,11 +94,6 @@ void FileLocksmithSettings::ParseJson()
const json::JsonObject& jsonSettings = json.value();
try
{
if (json::has(jsonSettings, constants::nonlocalizable::JsonKeyEnabled, json::JsonValueType::Boolean))
{
settings.enabled = jsonSettings.GetNamedBoolean(constants::nonlocalizable::JsonKeyEnabled);
}
if (json::has(jsonSettings, constants::nonlocalizable::JsonKeyShowInExtendedContextMenu, json::JsonValueType::Boolean))
{
settings.showInExtendedContextMenu = jsonSettings.GetNamedBoolean(constants::nonlocalizable::JsonKeyShowInExtendedContextMenu);

View File

@ -15,16 +15,10 @@ public:
return true;
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
return false;
Reload();
RefreshEnabledState();
return settings.enabled;
}
inline void SetEnabled(bool enabled)
{
settings.enabled = enabled;
Save();
}
inline bool GetShowInExtendedContextMenu() const
{
return settings.showInExtendedContextMenu;
@ -45,12 +39,15 @@ private:
bool showInExtendedContextMenu{ false };
};
void RefreshEnabledState();
void Reload();
void ParseJson();
Settings settings;
std::wstring generalJsonFilePath;
std::wstring jsonFilePath;
FILETIME lastLoadedTime;
FILETIME lastLoadedTime{};
FILETIME lastLoadedGeneralSettingsTime{};
};
FileLocksmithSettings& FileLocksmithSettingsInstance();

View File

@ -26,6 +26,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Utilities;
// 2023- Included in PowerToys.
// </history>
using Microsoft.Win32;
using Settings.UI.Library.Attributes;
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Properties.Setting.Values.#LoadIntSetting(System.String,System.Int32)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Properties.Setting.Values.#SaveSetting(System.String,System.Object)", Justification = "Dotnet port with style preservation")]
@ -766,6 +767,7 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
internal bool DrawMouseEx
{
get

View File

@ -11,7 +11,8 @@ namespace
{
const wchar_t c_imageResizerDataFilePath[] = L"\\image-resizer-settings.json";
const wchar_t c_rootRegPath[] = L"Software\\Microsoft\\ImageResizer";
const wchar_t c_enabled[] = L"Enabled";
const wchar_t c_enabled[] = L"enabled";
const wchar_t c_ImageResizer[] = L"Image Resizer";
unsigned int RegReadInteger(const std::wstring& valueName, unsigned int defaultValue)
{
@ -45,6 +46,7 @@ namespace
CSettings::CSettings()
{
generalJsonFilePath = PTSettingsHelper::get_powertoys_general_save_file_location();
std::wstring oldSavePath = PTSettingsHelper::get_module_save_folder_location(ImageResizerConstants::ModuleOldSaveFolderKey);
std::wstring savePath = PTSettingsHelper::get_module_save_folder_location(ImageResizerConstants::ModuleSaveFolderKey);
std::error_code ec;
@ -62,8 +64,6 @@ void CSettings::Save()
{
json::JsonObject jsonData;
jsonData.SetNamedValue(c_enabled, json::value(settings.enabled));
json::to_file(jsonFilePath, jsonData);
GetSystemTimeAsFileTime(&lastLoadedTime);
}
@ -82,6 +82,32 @@ void CSettings::Load()
}
}
void CSettings::RefreshEnabledState()
{
// Load json settings from data file if it is modified in the meantime.
FILETIME lastModifiedTime{};
if (!(LastModifiedTime(generalJsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedGeneralSettingsTime) == 1))
return;
lastLoadedGeneralSettingsTime = lastModifiedTime;
auto json = json::from_file(generalJsonFilePath);
if (!json)
return;
const json::JsonObject& jsonSettings = json.value();
try
{
json::JsonObject modulesEnabledState;
json::get(jsonSettings, c_enabled, modulesEnabledState, json::JsonObject{});
json::get(modulesEnabledState, c_ImageResizer, settings.enabled, true);
}
catch (const winrt::hresult_error&)
{
}
}
void CSettings::Reload()
{
// Load json settings from data file if it is modified in the meantime.
@ -106,10 +132,7 @@ void CSettings::ParseJson()
const json::JsonObject& jsonSettings = json.value();
try
{
if (json::has(jsonSettings, c_enabled, json::JsonValueType::Boolean))
{
settings.enabled = jsonSettings.GetNamedBoolean(c_enabled);
}
// NB: add any new settings here
}
catch (const winrt::hresult_error&)
{

View File

@ -1,5 +1,6 @@
#pragma once
#include "pch.h"
#include <common/utils/gpo.h>
class CSettings
@ -14,16 +15,10 @@ public:
return true;
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
return false;
Reload();
RefreshEnabledState();
return settings.enabled;
}
inline void SetEnabled(bool enabled)
{
settings.enabled = enabled;
Save();
}
void Save();
void Load();
@ -33,13 +28,16 @@ private:
bool enabled{ true };
};
void RefreshEnabledState();
void Reload();
void MigrateFromRegistry();
void ParseJson();
Settings settings;
std::wstring jsonFilePath;
std::wstring generalJsonFilePath;
FILETIME lastLoadedTime;
FILETIME lastLoadedGeneralSettingsTime{};
};
CSettings& CSettingsInstance();

View File

@ -37,7 +37,7 @@ class ImageResizerModule : public PowertoyModuleIface
{
private:
// Enabled by default
bool m_enabled = true;
bool m_enabled = false;
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
@ -101,7 +101,6 @@ public:
virtual void enable()
{
m_enabled = true;
CSettingsInstance().SetEnabled(m_enabled);
if (package::IsWin11OrGreater())
{
@ -121,7 +120,6 @@ public:
virtual void disable()
{
m_enabled = false;
CSettingsInstance().SetEnabled(m_enabled);
Trace::EnableImageResizer(m_enabled);
}

View File

@ -92,7 +92,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
_utilities.Add(new Utility(
UtilityKey.PowerOCR,
Resources.Text_Extractor,
generalSettings.Enabled.PowerOCR,
generalSettings.Enabled.PowerOcr,
(_) =>
{
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent());
@ -223,7 +223,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
case UtilityKey.ColorPicker: u.Enable(generalSettings.Enabled.ColorPicker); break;
case UtilityKey.FancyZones: u.Enable(generalSettings.Enabled.FancyZones); break;
case UtilityKey.Hosts: u.Enable(generalSettings.Enabled.Hosts); break;
case UtilityKey.PowerOCR: u.Enable(generalSettings.Enabled.PowerOCR); break;
case UtilityKey.PowerOCR: u.Enable(generalSettings.Enabled.PowerOcr); break;
case UtilityKey.MeasureTool: u.Enable(generalSettings.Enabled.MeasureTool); break;
case UtilityKey.ShortcutGuide: u.Enable(generalSettings.Enabled.ShortcutGuide); break;
case UtilityKey.RegistryPreview: u.Enable(generalSettings.Enabled.RegistryPreview); break;

View File

@ -266,20 +266,25 @@ namespace PowerLauncher
private static void UpdateSettings(PowerLauncherSettings settings)
{
var defaultPlugins = GetDefaultPluginsSettings().ToDictionary(x => x.Id);
var defaultPluginsByName = GetDefaultPluginsSettings().ToDictionary(x => x.Name);
foreach (PowerLauncherPluginSettings plugin in settings.Plugins)
{
if (defaultPlugins.TryGetValue(plugin.Id, out PowerLauncherPluginSettings value))
PowerLauncherPluginSettings value = null;
if ((plugin.Id != null && defaultPlugins.TryGetValue(plugin.Id, out value)) || (!string.IsNullOrEmpty(plugin.Name) && defaultPluginsByName.TryGetValue(plugin.Name, out value)))
{
var additionalOptions = CombineAdditionalOptions(value.AdditionalOptions, plugin.AdditionalOptions);
var enabledPolicyState = GPOWrapper.GetRunPluginEnabledValue(plugin.Id);
plugin.Name = value.Name;
var id = value.Id;
var name = value.Name;
var additionalOptions = plugin.AdditionalOptions != null ? CombineAdditionalOptions(value.AdditionalOptions, plugin.AdditionalOptions) : value.AdditionalOptions;
var enabledPolicyState = GPOWrapper.GetRunPluginEnabledValue(id);
plugin.Name = name;
plugin.Description = value.Description;
plugin.Author = value.Author;
plugin.IconPathDark = value.IconPathDark;
plugin.IconPathLight = value.IconPathLight;
plugin.EnabledPolicyUiState = (int)enabledPolicyState;
defaultPlugins[plugin.Id] = plugin;
defaultPlugins[plugin.Id].AdditionalOptions = additionalOptions;
defaultPlugins[id] = plugin;
defaultPlugins[id].AdditionalOptions = additionalOptions;
}
}

View File

@ -5,7 +5,6 @@
using System.Text.Json.Serialization;
using ManagedCommon;
using Wox.Plugin;
namespace Wox.Infrastructure.UserSettings

View File

@ -63,9 +63,6 @@ public:
static HRESULT s_CreateInstance(_In_opt_ IUnknown* punkOuter, _In_ REFIID riid, _Outptr_ void** ppv);
static bool SetEnabled(_In_ bool enabled);
static bool IsEnabled();
private:
~CPowerRenameMenu();

View File

@ -162,7 +162,7 @@ class PowerRenameModule : public PowertoyModuleIface
{
private:
// Enabled by default
bool m_enabled = true;
bool m_enabled = false;
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
@ -202,16 +202,13 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
save_settings();
}
// Disable the powertoy
virtual void disable()
{
Logger::info(L"PowerRename disabled");
m_enabled = false;
save_settings();
Logger::info(L"PowerRename disabled");
}
// Returns if the powertoy is enabled
@ -316,8 +313,6 @@ public:
void save_settings()
{
CSettingsInstance().SetEnabled(m_enabled);
CSettingsInstance().Save();
Trace::EnablePowerRename(m_enabled);
}

View File

@ -33,9 +33,11 @@ namespace
CSettings::CSettings()
{
generalJsonFilePath = PTSettingsHelper::get_powertoys_general_save_file_location();
std::wstring result = PTSettingsHelper::get_module_save_folder_location(PowerRenameConstants::ModuleKey);
jsonFilePath = result + std::wstring(c_powerRenameDataFilePath);
moduleJsonFilePath = result + std::wstring(c_powerRenameDataFilePath);
UIFlagsFilePath = result + std::wstring(c_powerRenameUIFlagsFilePath);
RefreshEnabledState();
Load();
}
@ -43,7 +45,6 @@ void CSettings::Save()
{
json::JsonObject jsonData;
jsonData.SetNamedValue(c_enabled, json::value(settings.enabled));
jsonData.SetNamedValue(c_showIconOnMenu, json::value(settings.showIconOnMenu));
jsonData.SetNamedValue(c_extendedContextMenuOnly, json::value(settings.extendedContextMenuOnly));
jsonData.SetNamedValue(c_persistState, json::value(settings.persistState));
@ -51,13 +52,13 @@ void CSettings::Save()
jsonData.SetNamedValue(c_maxMRUSize, json::value(settings.maxMRUSize));
jsonData.SetNamedValue(c_useBoostLib, json::value(settings.useBoostLib));
json::to_file(jsonFilePath, jsonData);
json::to_file(moduleJsonFilePath, jsonData);
GetSystemTimeAsFileTime(&lastLoadedTime);
}
void CSettings::Load()
{
if (!std::filesystem::exists(jsonFilePath))
if (!std::filesystem::exists(moduleJsonFilePath))
{
MigrateFromRegistry();
@ -71,20 +72,35 @@ void CSettings::Load()
}
}
void CSettings::Reload()
void CSettings::RefreshEnabledState()
{
// Load json settings from data file if it is modified in the meantime.
FILETIME lastModifiedTime{};
if (LastModifiedTime(jsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedTime) == 1)
if (!(LastModifiedTime(generalJsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedGeneralSettingsTime) == 1))
return;
lastLoadedGeneralSettingsTime = lastModifiedTime;
auto json = json::from_file(generalJsonFilePath);
if (!json)
return;
const json::JsonObject& jsonSettings = json.value();
try
{
json::JsonObject modulesEnabledState;
json::get(jsonSettings, L"enabled", modulesEnabledState, json::JsonObject{});
json::get(modulesEnabledState, L"PowerRename", settings.enabled, true);
}
catch (const winrt::hresult_error&)
{
Load();
}
}
void CSettings::MigrateFromRegistry()
{
settings.enabled = GetRegBoolean(c_enabled, true);
//settings.enabled = GetRegBoolean(c_enabled, true);
settings.showIconOnMenu = GetRegBoolean(c_showIconOnMenu, true);
settings.extendedContextMenuOnly = GetRegBoolean(c_extendedContextMenuOnly, false); // Disabled by default.
settings.persistState = GetRegBoolean(c_persistState, true);
@ -100,16 +116,12 @@ void CSettings::MigrateFromRegistry()
void CSettings::ParseJson()
{
auto json = json::from_file(jsonFilePath);
auto json = json::from_file(moduleJsonFilePath);
if (json)
{
const json::JsonObject& jsonSettings = json.value();
try
{
if (json::has(jsonSettings, c_enabled, json::JsonValueType::Boolean))
{
settings.enabled = jsonSettings.GetNamedBoolean(c_enabled);
}
if (json::has(jsonSettings, c_showIconOnMenu, json::JsonValueType::Boolean))
{
settings.showIconOnMenu = jsonSettings.GetNamedBoolean(c_showIconOnMenu);

View File

@ -15,16 +15,10 @@ public:
return true;
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
return false;
Reload();
RefreshEnabledState();
return settings.enabled;
}
inline void SetEnabled(bool enabled)
{
settings.enabled = enabled;
Save();
}
inline bool GetShowIconOnMenu() const
{
return settings.showIconOnMenu;
@ -112,7 +106,7 @@ private:
unsigned int flags{ 0 };
};
void Reload();
void RefreshEnabledState();
void MigrateFromRegistry();
void ParseJson();
@ -120,9 +114,11 @@ private:
void WriteFlags();
Settings settings;
std::wstring jsonFilePath;
std::wstring generalJsonFilePath;
std::wstring moduleJsonFilePath;
std::wstring UIFlagsFilePath;
FILETIME lastLoadedTime;
FILETIME lastLoadedTime{};
FILETIME lastLoadedGeneralSettingsTime{};
};
CSettings& CSettingsInstance();

View File

@ -0,0 +1,16 @@
// 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;
namespace Settings.UI.Library.Attributes;
/// <summary>
/// Adding this attribute to a property makes it not configurable from the command line.
/// Typical use cases:
/// - Property represents internal module state.
/// - Property has a type that is unwieldy to type as a command line string.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CmdConfigureIgnoreAttribute : Attribute;

View File

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -36,6 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public DateTimeOffset ExpirationDateTime { get; set; }
[JsonPropertyName("customTrayTimes")]
[CmdConfigureIgnoreAttribute]
public Dictionary<string, int> CustomTrayTimes { get; set; }
}

View File

@ -7,7 +7,7 @@ using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class BoolProperty
public record BoolProperty : ICmdLineRepresentable
{
public BoolProperty()
{
@ -22,9 +22,28 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("value")]
public bool Value { get; set; }
public static bool TryParseFromCmd(string cmd, out object result)
{
result = null;
if (!bool.TryParse(cmd, out bool value))
{
return false;
}
result = new BoolProperty { Value = value };
return true;
}
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
public bool TryToCmdRepresentable(out string result)
{
result = Value.ToString();
return true;
}
}
}

View File

@ -7,11 +7,13 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ColorPickerProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x43);
public ColorPickerProperties()
@ -43,6 +45,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("changecursor")]
[JsonConverter(typeof(BoolPropertyJsonConverter))]
[CmdConfigureIgnoreAttribute]
public bool ChangeCursor { get; set; }
[JsonPropertyName("copiedcolorrepresentation")]
@ -53,12 +56,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Property ColorHistory is not used, the color history is saved separately in the colorHistory.json file
[JsonPropertyName("colorhistory")]
[CmdConfigureIgnoreAttribute]
public List<string> ColorHistory { get; set; }
[JsonPropertyName("colorhistorylimit")]
[CmdConfigureIgnoreAttribute]
public int ColorHistoryLimit { get; set; }
[JsonPropertyName("visiblecolorformats")]
[CmdConfigureIgnoreAttribute]
public Dictionary<string, KeyValuePair<bool, string>> VisibleColorFormats { get; set; }
[JsonPropertyName("showcolorname")]

View File

@ -15,6 +15,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
private Action notifyEnabledChangedAction;
// Default values for enabled modules should match their expected "enabled by default" values.
// Otherwise, a run of DSC on clean settings will not match the expected default result.
public EnabledModules()
{
}
@ -55,7 +57,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private bool fileExplorerPreview = true;
[JsonPropertyName("File Explorer Preview")]
public bool FileExplorerPreview
public bool PowerPreview
{
get => fileExplorerPreview;
set
@ -116,7 +118,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool keyboardManager = true;
private bool keyboardManager; // defaulting to off
[JsonPropertyName("Keyboard Manager")]
public bool KeyboardManager
@ -183,7 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool awake;
private bool awake = true;
[JsonPropertyName("Awake")]
public bool Awake
@ -199,7 +201,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool mouseWithoutBorders = true;
private bool mouseWithoutBorders; // defaulting to off
[JsonPropertyName("MouseWithoutBorders")]
public bool MouseWithoutBorders
@ -247,7 +249,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool mouseJump = true;
private bool mouseJump; // defaulting to off
[JsonPropertyName("MouseJump")]
public bool MouseJump
@ -279,7 +281,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool mousePointerCrosshairs = true;
private bool mousePointerCrosshairs; // defaulting to off
[JsonPropertyName("MousePointerCrosshairs")]
public bool MousePointerCrosshairs
@ -295,7 +297,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool powerAccent;
private bool powerAccent; // defaulting to off
[JsonPropertyName("QuickAccent")]
public bool PowerAccent
@ -311,10 +313,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool powerOCR = true;
private bool powerOCR; // defaulting to off
[JsonPropertyName("TextExtractor")]
public bool PowerOCR
public bool PowerOcr
{
get => powerOCR;
set

View File

@ -4,6 +4,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -110,6 +111,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public BoolProperty FancyzonesMakeDraggedWindowTransparent { get; set; }
[JsonPropertyName("fancyzones_allowPopupWindowSnap")]
[CmdConfigureIgnore]
public BoolProperty FancyzonesAllowPopupWindowSnap { get; set; }
[JsonPropertyName("fancyzones_allowChildWindowSnap")]

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class FindMyMouseProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x46);
[JsonPropertyName("activation_method")]

View File

@ -8,6 +8,7 @@ using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -18,15 +19,18 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public bool Startup { get; set; }
// Gets or sets a value indicating whether the powertoy elevated.
[CmdConfigureIgnoreAttribute]
[JsonPropertyName("is_elevated")]
public bool IsElevated { get; set; }
// Gets or sets a value indicating whether powertoys should run elevated.
[JsonPropertyName("run_elevated")]
[CmdConfigureIgnoreAttribute]
public bool RunElevated { get; set; }
// Gets or sets a value indicating whether is admin.
[JsonPropertyName("is_admin")]
[CmdConfigureIgnoreAttribute]
public bool IsAdmin { get; set; }
// Gets or sets a value indicating whether is warnings of elevated apps enabled.
@ -39,16 +43,20 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// Gets or sets system theme name.
[JsonPropertyName("system_theme")]
[CmdConfigureIgnore]
public string SystemTheme { get; set; }
// Gets or sets powertoys version number.
[JsonPropertyName("powertoys_version")]
[CmdConfigureIgnore]
public string PowertoysVersion { get; set; }
[JsonPropertyName("action_name")]
[CmdConfigureIgnore]
public string CustomActionName { get; set; }
[JsonPropertyName("enabled")]
[CmdConfigureIgnore]
public EnabledModules Enabled { get; set; }
[JsonPropertyName("show_new_updates_toast_notification")]

View File

@ -3,10 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class GenericProperty<T>
public class GenericProperty<T> : ICmdLineRepresentable
{
[JsonPropertyName("value")]
public T Value { get; set; }
@ -20,5 +21,25 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public GenericProperty()
{
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "Adding ICmdLineRepresentable support")]
public static bool TryParseFromCmd(string cmd, out object result)
{
result = null;
if (ICmdLineRepresentable.TryParseFromCmdFor(typeof(T), cmd, out var value))
{
result = new GenericProperty<T> { Value = (T)value };
return true;
}
return false;
}
public bool TryToCmdRepresentable(out string result)
{
result = Value.ToString();
return true;
}
}
}

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
using Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.Library

View File

@ -2,14 +2,16 @@
// 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.Globalization;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class HotkeySettings
public record HotkeySettings : ICmdLineRepresentable
{
private const int VKTAB = 0x09;
@ -39,11 +41,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Code = code;
}
public HotkeySettings Clone()
{
return new HotkeySettings(Win, Ctrl, Alt, Shift, Code);
}
[JsonPropertyName("win")]
public bool Win { get; set; }
@ -176,5 +173,72 @@ namespace Microsoft.PowerToys.Settings.UI.Library
return false;
}
public static bool TryParseFromCmd(string cmd, out object result)
{
bool win = false, ctrl = false, alt = false, shift = false;
int code = 0;
var parts = cmd.Split('+');
foreach (var part in parts)
{
switch (part.Trim().ToLower(CultureInfo.InvariantCulture))
{
case "win":
win = true;
break;
case "ctrl":
ctrl = true;
break;
case "alt":
alt = true;
break;
case "shift":
shift = true;
break;
default:
if (!TryParseKeyCode(part, out code))
{
result = null;
return false;
}
break;
}
}
result = new HotkeySettings(win, ctrl, alt, shift, code);
return true;
}
private static bool TryParseKeyCode(string key, out int keyCode)
{
// ASCII symbol
if (key.Length == 1 && char.IsLetterOrDigit(key[0]))
{
keyCode = char.ToUpper(key[0], CultureInfo.InvariantCulture);
return true;
}
// VK code
else if (key.Length == 4 && key.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
return int.TryParse(key.AsSpan(2), NumberStyles.HexNumber, null, out keyCode);
}
// Alias
else
{
keyCode = (int)Utilities.Helper.GetKeyValue(key);
return keyCode != 0;
}
}
public bool TryToCmdRepresentable(out string result)
{
result = ToString();
result = result.Replace(" ", null);
return true;
}
}
}

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.ObjectModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -69,6 +70,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public StringProperty ImageresizerFileName { get; set; }
[JsonPropertyName("imageresizer_sizes")]
[CmdConfigureIgnoreAttribute]
public ImageResizerSizes ImageresizerSizes { get; set; }
[JsonPropertyName("imageresizer_keepDateModified")]
@ -78,6 +80,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public StringProperty ImageresizerFallbackEncoder { get; set; }
[JsonPropertyName("imageresizer_customSize")]
[CmdConfigureIgnoreAttribute]
public ImageResizerCustomSizeProperty ImageresizerCustomSize { get; set; }
public string ToJsonString()

View File

@ -3,13 +3,14 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
// Represents the configuration property of the settings that store Integer type.
public class IntProperty
public record IntProperty : ICmdLineRepresentable
{
public IntProperty()
{
@ -25,6 +26,19 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("value")]
public int Value { get; set; }
public static bool TryParseFromCmd(string cmd, out object result)
{
result = null;
if (!int.TryParse(cmd, out var value))
{
return false;
}
result = new IntProperty { Value = value };
return true;
}
// Returns a JSON version of the class settings configuration class.
public override string ToString()
{
@ -40,5 +54,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
throw new NotImplementedException();
}
public bool TryToCmdRepresentable(out string result)
{
result = Value.ToString(CultureInfo.InvariantCulture);
return true;
}
}
}

View File

@ -0,0 +1,126 @@
// 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.Globalization;
using System.Reflection;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// A helper interface to allow parsing property values from their command line representation.
/// </summary>
public interface ICmdLineRepresentable
{
public static abstract bool TryParseFromCmd(string cmd, out object result);
public abstract bool TryToCmdRepresentable(out string result);
public static sealed bool TryToCmdRepresentableFor(Type type, object value, out string result)
{
result = null;
if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
{
throw new ArgumentException($"{type} doesn't implement {nameof(ICmdLineRepresentable)}");
}
var method = type.GetMethod(nameof(TryToCmdRepresentable));
var parameters = new object[] { result };
if ((bool)method.Invoke(value, parameters))
{
result = (string)parameters[0];
return true;
}
return false;
}
public static sealed bool TryParseFromCmdFor(Type type, string cmd, out object result)
{
result = null;
if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
{
throw new ArgumentException($"{type} doesn't implement {nameof(ICmdLineRepresentable)}");
}
var method = type.GetMethod(nameof(TryParseFromCmd), BindingFlags.Static | BindingFlags.Public);
var parameters = new object[] { cmd, null };
if ((bool)method.Invoke(null, parameters))
{
result = parameters[1];
return true;
}
return false;
}
public static sealed object ParseFor(Type type, string cmdRepr)
{
if (type.IsEnum)
{
return Enum.Parse(type, cmdRepr);
}
else if (type.IsPrimitive)
{
if (type == typeof(bool))
{
return bool.Parse(cmdRepr.ToLowerInvariant());
}
else
{
// Converts numeric types like Uint32
return Convert.ChangeType(cmdRepr, type, CultureInfo.InvariantCulture);
}
}
else if (type.IsValueType && type == typeof(DateTimeOffset))
{
if (DateTimeOffset.TryParse(cmdRepr, out var structResult))
{
return structResult;
}
throw new ArgumentException($"Invalid DateTimeOffset format '{cmdRepr}'");
}
else if (type.IsClass)
{
if (type == typeof(string))
{
return cmdRepr;
}
else
{
TryParseFromCmdFor(type, cmdRepr, out var classResult);
return classResult;
}
}
throw new NotImplementedException($"Parsing type {type} is not supported yet");
}
public static string ToCmdRepr(Type type, object value)
{
if (type.IsEnum || type.IsPrimitive)
{
return value.ToString();
}
else if (type.IsValueType && type == typeof(DateTimeOffset))
{
return ((DateTimeOffset)value).ToString("o");
}
else if (type.IsClass)
{
if (type == typeof(string))
{
return (string)value;
}
else
{
TryToCmdRepresentableFor(type, value, out var result);
return result;
}
}
throw new NotImplementedException($"CmdRepr of {type} is not supported yet");
}
}

View File

@ -2,11 +2,12 @@
// 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.Globalization;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class KeyboardKeysProperty
public record KeyboardKeysProperty : ICmdLineRepresentable
{
public KeyboardKeysProperty()
{
@ -20,5 +21,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("value")]
public HotkeySettings Value { get; set; }
public static bool TryParseFromCmd(string cmd, out object result)
{
if (!HotkeySettings.TryParseFromCmd(cmd, out var hotkey))
{
result = null;
return false;
}
else
{
result = new KeyboardKeysProperty { Value = (HotkeySettings)hotkey };
return true;
}
}
public bool TryToCmdRepresentable(out string result)
{
return Value.TryToCmdRepresentable(out result);
}
}
}

View File

@ -5,16 +5,19 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class KeyboardManagerProperties
{
[JsonPropertyName("activeConfiguration")]
[CmdConfigureIgnoreAttribute]
public GenericProperty<string> ActiveConfiguration { get; set; }
// List of all Keyboard Configurations.
[JsonPropertyName("keyboardConfigurations")]
[CmdConfigureIgnoreAttribute]
public GenericProperty<List<string>> KeyboardConfigurations { get; set; }
public KeyboardManagerProperties()

View File

@ -4,12 +4,14 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
using Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MeasureToolProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x4D);
public MeasureToolProperties()
@ -35,6 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool PerColorChannelEdgeDetection { get; set; }
[CmdConfigureIgnore]
public IntProperty UnitsOfMeasure { get; set; }
public IntProperty PixelTolerance { get; set; }

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MouseHighlighterProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x48);
[JsonPropertyName("activation_shortcut")]
@ -20,6 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public StringProperty RightButtonClickColor { get; set; }
[JsonPropertyName("highlight_opacity")]
[CmdConfigureIgnore]
public IntProperty HighlightOpacity { get; set; }
[JsonPropertyName("always_color")]

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MouseJumpProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x44);
[JsonPropertyName("activation_shortcut")]

View File

@ -9,7 +9,7 @@ using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MouseJumpThumbnailSize : INotifyPropertyChanged
public record MouseJumpThumbnailSize : INotifyPropertyChanged, ICmdLineRepresentable
{
private int _width;
private int _height;
@ -64,5 +64,30 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static bool TryParseFromCmd(string cmd, out object result)
{
result = null;
var parts = cmd.Split('x');
if (parts.Length != 2)
{
return false;
}
if (int.TryParse(parts[0], out int width) && int.TryParse(parts[1], out int height))
{
result = new MouseJumpThumbnailSize { Width = width, Height = height };
return true;
}
return false;
}
public bool TryToCmdRepresentable(out string result)
{
result = $"{Width}x{Height}";
return true;
}
}
}

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MousePointerCrosshairsProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, true, false, 0x50); // Win + Alt + P
[JsonPropertyName("activation_shortcut")]

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -23,16 +24,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class MouseWithoutBordersProperties : ICloneable
{
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeySwitch2AllPC => new HotkeySettings();
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyLockMachine => new HotkeySettings(true, true, true, false, 0x4C);
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyReconnect => new HotkeySettings(true, true, true, false, 0x52);
[CmdConfigureIgnore]
public static HotkeySettings DefaultHotKeyToggleEasyMouse => new HotkeySettings(true, true, true, false, 0x45);
[CmdConfigureIgnore]
public StringProperty SecurityKey { get; set; }
[CmdConfigureIgnore]
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool UseService { get; set; }
@ -72,42 +79,54 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool ShowClipboardAndNetworkStatusMessages { get; set; }
[CmdConfigureIgnoreAttribute]
public List<string> MachineMatrixString { get; set; }
[CmdConfigureIgnoreAttribute]
public StringProperty MachinePool { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
[CmdConfigureIgnoreAttribute]
public bool MatrixOneRow { get; set; }
public IntProperty EasyMouse { get; set; }
[CmdConfigureIgnore]
public IntProperty MachineID { get; set; }
[CmdConfigureIgnoreAttribute]
public IntProperty LastX { get; set; }
[CmdConfigureIgnoreAttribute]
public IntProperty LastY { get; set; }
[CmdConfigureIgnoreAttribute]
public IntProperty PackageID { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
[CmdConfigureIgnoreAttribute]
public bool FirstRun { get; set; }
public IntProperty HotKeySwitchMachine { get; set; }
[ObsoleteAttribute("Use ToggleEasyMouseShortcut instead", false)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[CmdConfigureIgnoreAttribute]
public IntProperty HotKeyToggleEasyMouse { get; set; }
[ObsoleteAttribute("Use LockMachineShortcut instead", false)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[CmdConfigureIgnoreAttribute]
public IntProperty HotKeyLockMachine { get; set; }
[ObsoleteAttribute("Use ReconnectShortcut instead", false)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[CmdConfigureIgnoreAttribute]
public IntProperty HotKeyReconnect { get; set; }
[ObsoleteAttribute("Use Switch2AllPCShortcut instead", false)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[CmdConfigureIgnoreAttribute]
public IntProperty HotKeySwitch2AllPC { get; set; }
public HotkeySettings ToggleEasyMouseShortcut { get; set; }
@ -118,6 +137,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public HotkeySettings Switch2AllPCShortcut { get; set; }
[CmdConfigureIgnoreAttribute]
public IntProperty TCPPort { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
@ -126,8 +146,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public StringProperty Name2IP { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
[CmdConfigureIgnoreAttribute]
public bool FirstCtrlShiftS { get; set; }
[CmdConfigureIgnoreAttribute]
public StringProperty DeviceID { get; set; }
public MouseWithoutBordersProperties()

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class PastePlainProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, true, true, false, 0x56); // Ctrl+Win+Alt+V
public PastePlainProperties()

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class PeekProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(false, true, false, false, 0x20);
public PeekProperties()

View File

@ -4,15 +4,18 @@
using System.Text.Json.Serialization;
using ManagedCommon;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class PowerLauncherProperties
{
[JsonPropertyName("search_result_preference")]
[CmdConfigureIgnoreAttribute]
public string SearchResultPreference { get; set; }
[JsonPropertyName("search_type_preference")]
[CmdConfigureIgnoreAttribute]
public string SearchTypePreference { get; set; }
[JsonPropertyName("maximum_number_of_results")]
@ -22,18 +25,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public HotkeySettings OpenPowerLauncher { get; set; }
[JsonPropertyName("open_file_location")]
[CmdConfigureIgnoreAttribute]
public HotkeySettings OpenFileLocation { get; set; }
[JsonPropertyName("copy_path_location")]
[CmdConfigureIgnoreAttribute]
public HotkeySettings CopyPathLocation { get; set; }
[JsonPropertyName("open_console")]
[CmdConfigureIgnoreAttribute]
public HotkeySettings OpenConsole { get; set; }
[JsonPropertyName("override_win_r_key")]
[CmdConfigureIgnoreAttribute]
public bool OverrideWinkeyR { get; set; }
[JsonPropertyName("override_win_s_key")]
[CmdConfigureIgnoreAttribute]
public bool OverrideWinkeyS { get; set; }
[JsonPropertyName("ignore_hotkeys_in_fullscreen")]
@ -49,6 +57,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public Theme Theme { get; set; }
[JsonPropertyName("show_plugins_overview")]
[CmdConfigureIgnore]
public int ShowPluginsOverview { get; set; }
[JsonPropertyName("title_fontsize")]
@ -84,10 +93,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("generate_thumbnails_from_files")]
public bool GenerateThumbnailsFromFiles { get; set; }
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultOpenPowerLauncher => new HotkeySettings(false, false, true, false, 32);
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultOpenFileLocation => new HotkeySettings();
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultCopyPathLocation => new HotkeySettings();
public PowerLauncherProperties()

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class PowerOcrProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x54); // Win+Shift+T
public PowerOcrProperties()

View File

@ -2,7 +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 System;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@ -16,12 +18,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ShowIcon = new BoolProperty();
ExtendedContextMenuOnly = new BoolProperty();
UseBoostLib = new BoolProperty();
Enabled = new BoolProperty();
}
[ObsoleteAttribute("Now controlled from the general settings", false)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public BoolProperty Enabled { get; set; }
[JsonPropertyName("bool_persist_input")]
[CmdConfigureIgnoreAttribute]
public BoolProperty PersistState { get; set; }
[JsonPropertyName("bool_mru_enabled")]
@ -31,6 +35,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public IntProperty MaxMRUSize { get; set; }
[JsonPropertyName("bool_show_icon_on_menu")]
[CmdConfigureIgnoreAttribute]
public BoolProperty ShowIcon { get; set; }
[JsonPropertyName("bool_show_extended_menu")]

View File

@ -1,8 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Version.props" />
<Import Project="..\..\Version.props" />
<PropertyGroup>
<TargetFrameworks>net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<TargetFramework>net8.0-windows</TargetFramework>
<Version>$(Version).0</Version>
<Authors>Microsoft Corporation</Authors>
@ -20,42 +22,42 @@
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Include="backup_restore_settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<None Include="backup_restore_settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Resources.resx">
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Resources.resx">
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ShortcutGuideProperties
{
[CmdConfigureIgnore]
public HotkeySettings DefaultOpenShortcutGuide => new HotkeySettings(true, false, false, true, 0xBF);
public ShortcutGuideProperties()

View File

@ -8,7 +8,7 @@ using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
// Represents the configuration property of the settings that store string type.
public class StringProperty
public record StringProperty : ICmdLineRepresentable
{
public StringProperty()
{
@ -35,6 +35,18 @@ namespace Microsoft.PowerToys.Settings.UI.Library
return new StringProperty(v);
}
public static bool TryParseFromCmd(string cmd, out object result)
{
result = new StringProperty(cmd);
return true;
}
public bool TryToCmdRepresentable(out string result)
{
result = Value;
return true;
}
public static implicit operator StringProperty(string v)
{
return new StringProperty(v);

View File

@ -0,0 +1,81 @@
// 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.Linq;
using System.Reflection;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library;
public class CommandLineUtils
{
private static Type GetSettingsConfigType(string moduleName, Assembly settingsLibraryAssembly)
{
var settingsClassName = moduleName == "GeneralSettings" ? moduleName : moduleName + "Settings";
return settingsLibraryAssembly.GetType(typeof(CommandLineUtils).Namespace + "." + settingsClassName);
}
public static ISettingsConfig GetSettingsConfigFor(string moduleName, ISettingsUtils settingsUtils, Assembly settingsLibraryAssembly)
{
return GetSettingsConfigFor(GetSettingsConfigType(moduleName, settingsLibraryAssembly), settingsUtils);
}
/// Executes SettingsRepository<moduleSettingsType>.GetInstance(settingsUtils).SettingsConfig
public static ISettingsConfig GetSettingsConfigFor(Type moduleSettingsType, ISettingsUtils settingsUtils)
{
var genericSettingsRepositoryType = typeof(SettingsRepository<>);
var moduleSettingsRepositoryType = genericSettingsRepositoryType.MakeGenericType(moduleSettingsType);
// Note: GeneralSettings is only used here only to satisfy nameof constrains, i.e. the choice of this particular type doesn't have any special significance.
var getInstanceInfo = moduleSettingsRepositoryType.GetMethod(nameof(SettingsRepository<GeneralSettings>.GetInstance));
var settingsRepository = getInstanceInfo.Invoke(null, new object[] { settingsUtils });
var settingsConfigProperty = getInstanceInfo.ReturnType.GetProperty(nameof(SettingsRepository<GeneralSettings>.SettingsConfig));
return settingsConfigProperty.GetValue(settingsRepository) as ISettingsConfig;
}
public static Assembly GetSettingsAssembly()
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "PowerToys.Settings.UI.Lib");
}
public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
{
var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
return settingInfo.GetValue(properties);
}
public static object GetProperties(ISettingsConfig settingsConfig)
{
var settingsType = settingsConfig.GetType();
if (settingsType == typeof(GeneralSettings))
{
return settingsConfig;
}
var settingsConfigInfo = settingsType.GetProperty("Properties");
return settingsConfigInfo.GetValue(settingsConfig);
}
public static (PropertyInfo SettingInfo, object Properties) LocateSetting(string propertyName, ISettingsConfig settingsConfig)
{
var properties = GetProperties(settingsConfig);
var propertiesType = properties.GetType();
if (propertiesType == typeof(GeneralSettings) && propertyName.StartsWith("Enabled.", StringComparison.InvariantCulture))
{
var moduleNameToToggle = propertyName.Replace("Enabled.", string.Empty);
properties = propertiesType.GetProperty("Enabled").GetValue(properties);
propertiesType = properties.GetType();
propertyName = moduleNameToToggle;
}
return (propertiesType.GetProperty(propertyName), properties);
}
public static PropertyInfo GetSettingPropertyInfo(string propertyName, ISettingsConfig settingsConfig)
{
return LocateSetting(propertyName, settingsConfig).SettingInfo;
}
}

View File

@ -0,0 +1,78 @@
// 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.Globalization;
using System.Text.Json;
using System.Xml;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to get settings values by querying them from command line using the following syntax:
/// PowerToys.Settings.exe get <path to a json file containing a list of modules and their corresponding properties>
///
/// Example: PowerToys.Settings.exe get %TEMP%\properties.json
/// `properties.json` file contents:
/// {
/// "AlwaysOnTop": ["FrameEnabled", "FrameAccentColor"],
/// "FancyZones": ["FancyzonesShiftDrag", "FancyzonesShowOnAllMonitors"]
/// }
///
/// Upon PowerToys.Settings.exe completion, it'll update `properties.json` file to contain something like this:
/// {
/// "AlwaysOnTop": {
/// "FrameEnabled": true,
/// "FrameAccentColor": "#0099cc"
/// },
/// "FancyZones": {
/// "FancyzonesShiftDrag": true,
/// "FancyzonesShowOnAllMonitors": false
/// }
/// }
/// </summary>
public sealed class GetSettingCommandLineCommand
{
private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
public static string Execute(Dictionary<string, List<string>> settingNamesForModules)
{
var modulesSettings = new Dictionary<string, Dictionary<string, object>>();
var settingsAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsUtils = new SettingsUtils();
var enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;
foreach (var (moduleName, settings) in settingNamesForModules)
{
var moduleSettings = new Dictionary<string, object>();
if (moduleName != nameof(GeneralSettings))
{
moduleSettings.Add("Enabled", typeof(EnabledModules).GetProperty(moduleName).GetValue(enabledModules));
}
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsAssembly);
foreach (var settingName in settings)
{
var value = CommandLineUtils.GetPropertyValue(settingName, settingsConfig);
if (value != null)
{
var cmdReprValue = ICmdLineRepresentable.ToCmdRepr(value.GetType(), value);
moduleSettings.Add(settingName, cmdReprValue);
}
}
modulesSettings.Add(moduleName, moduleSettings);
}
return JsonSerializer.Serialize(modulesSettings, _serializerOptions);
}
}

View File

@ -103,6 +103,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
return LayoutMap.GetKeyName(key);
}
public static uint GetKeyValue(string key)
{
return LayoutMap.GetKeyValue(key);
}
public static string GetProductVersion()
{
return interop.CommonManaged.GetProductVersion();

View File

@ -0,0 +1,109 @@
// 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.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to set custom settings values by suppling them from command line using the following syntax:
/// PowerToys.Settings.exe setAdditional <module struct name> <path to a json file containing the properties>
/// </summary>
public sealed class SetAdditionalSettingsCommandLineCommand
{
private static readonly string KeyPropertyName = "Name";
private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
private struct AdditionalPropertyInfo
{
public string RootPropertyName;
public JsonValueKind RootObjectType;
}
private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { RootPropertyName = "Plugins", RootObjectType = JsonValueKind.Array } } };
private static void ExecuteRootArray(JsonElement.ArrayEnumerator properties, IEnumerable<object> currentPropertyValuesArray)
{
// In case it's an array of object -> combine the existing values with the provided
var currentPropertyValueType = currentPropertyValuesArray.FirstOrDefault()?.GetType();
object matchedElement = null;
foreach (var arrayElement in properties)
{
var newElementPropertyValues = new Dictionary<string, object>();
foreach (var elementProperty in arrayElement.EnumerateObject())
{
var elementPropertyName = elementProperty.Name;
var elementPropertyType = currentPropertyValueType.GetProperty(elementPropertyName).PropertyType;
var elemePropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString());
if (elementPropertyName == KeyPropertyName)
{
foreach (var currentElementValue in currentPropertyValuesArray)
{
var currentElementType = currentElementValue.GetType();
var keyPropertyNameInfo = currentElementType.GetProperty(KeyPropertyName);
var keyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue);
if (string.Equals(keyPropertyValue, elemePropertyValue))
{
matchedElement = currentElementValue;
break;
}
}
}
else
{
newElementPropertyValues.Add(elementPropertyName, elemePropertyValue);
}
}
if (matchedElement != null)
{
foreach (var overriddenProperty in newElementPropertyValues)
{
var propertyInfo = currentPropertyValueType.GetProperty(overriddenProperty.Key);
propertyInfo.SetValue(matchedElement, overriddenProperty.Value);
}
}
}
}
public static void Execute(string moduleName, JsonDocument settings, ISettingsUtils settingsUtils)
{
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
var settingsConfigType = settingsConfig.GetType();
if (!SupportedAdditionalPropertiesInfoForModules.TryGetValue(moduleName, out var additionalPropertiesInfo))
{
return;
}
var propertyValueInfo = settingsConfigType.GetProperty(additionalPropertiesInfo.RootPropertyName);
var currentPropertyValue = propertyValueInfo.GetValue(settingsConfig);
// For now, only a certain data shapes are supported
switch (additionalPropertiesInfo.RootObjectType)
{
case JsonValueKind.Array:
ExecuteRootArray(settings.RootElement.EnumerateArray(), currentPropertyValue as IEnumerable<object>);
break;
default:
throw new NotImplementedException();
}
settingsUtils.SaveSettings(settingsConfig.ToJsonString(), settingsConfig.GetModuleName());
}
}

View File

@ -0,0 +1,53 @@
// 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.Reflection;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to set settings values by suppling them from command line using the following syntax:
/// PowerToys.Settings.exe set <module struct name>.<field name> <field_value>
///
/// Example: PowerToys.Settings.exe set MeasureTool.MeasureCrossColor "#00FF00"
/// </summary>
public sealed class SetSettingCommandLineCommand
{
private static readonly char[] SettingNameSeparator = { '.' };
private static (string ModuleName, string PropertyName) ParseSettingName(string settingName)
{
var parts = settingName.Split(SettingNameSeparator, 2, StringSplitOptions.RemoveEmptyEntries);
return (parts[0], parts[1]);
}
public static void Execute(string settingName, string settingValue, ISettingsUtils settingsUtils)
{
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
var (moduleName, propertyName) = ParseSettingName(settingName);
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
var propertyInfo = CommandLineUtils.GetSettingPropertyInfo(propertyName, settingsConfig);
if (propertyInfo == null)
{
throw new ArgumentException($"Property '{propertyName}' wasn't found");
}
if (propertyInfo.PropertyType.GetCustomAttribute<CmdConfigureIgnoreAttribute>() != null)
{
throw new ArgumentException($"Property '{propertyName}' is explicitly ignored");
}
// Execute settingsConfig.Properties.<propertyName> = settingValue
var propertyValue = ICmdLineRepresentable.ParseFor(propertyInfo.PropertyType, settingValue);
var (settingInfo, properties) = CommandLineUtils.LocateSetting(propertyName, settingsConfig);
settingInfo.SetValue(properties, propertyValue);
settingsUtils.SaveSettings(settingsConfig.ToJsonString(), settingsConfig.GetModuleName());
}
}

View File

@ -4,11 +4,13 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class VideoConferenceConfigProperties
{
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultMuteCameraAndMicrophoneHotkey => new HotkeySettings()
{
Win = true,
@ -19,6 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Code = 81,
};
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultMuteMicrophoneHotkey => new HotkeySettings()
{
Win = true,
@ -29,6 +32,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Code = 65,
};
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultPushToTalkMicrophoneHotkey => new HotkeySettings()
{
Win = true,
@ -39,6 +43,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Code = 73,
};
[CmdConfigureIgnoreAttribute]
public HotkeySettings DefaultMuteCameraHotkey => new HotkeySettings()
{
Win = true,

View File

@ -9,10 +9,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
public class VideoConferenceSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "Video Conference";
public VideoConferenceSettings()
{
Version = "1";
Name = "Video Conference";
Name = ModuleName;
Properties = new VideoConferenceConfigProperties();
}

View File

@ -0,0 +1,75 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Settings.UI.UnitTests.Settings;
[TestClass]
public class ICmdReprParsableTests
{
[TestMethod]
public void KeyboardKeysPropertyParsing()
{
{
Assert.IsTrue(KeyboardKeysProperty.TryParseFromCmd("win+ctrl+Alt+sHifT+Q", out var hotkey));
Assert.AreEqual(new KeyboardKeysProperty { Value = new HotkeySettings(true, true, true, true, 0x51) }, hotkey);
}
{
Assert.IsTrue(KeyboardKeysProperty.TryParseFromCmd("CTRL+z", out var hotkey));
Assert.AreEqual(new KeyboardKeysProperty { Value = new HotkeySettings(false, true, false, false, 0x5A) }, hotkey);
}
{
Assert.IsTrue(KeyboardKeysProperty.TryParseFromCmd("shift+ALT+0x59", out var hotkey));
Assert.AreEqual(new KeyboardKeysProperty { Value = new HotkeySettings(false, false, true, true, 0x59) }, hotkey);
}
{
Assert.IsTrue(KeyboardKeysProperty.TryParseFromCmd("alt+Space", out var hotkey));
Assert.AreEqual(new KeyboardKeysProperty { Value = new HotkeySettings(false, false, true, false, 0x20) }, hotkey);
}
}
[TestMethod]
public void BoolPropertyParsing()
{
{
Assert.IsTrue(BoolProperty.TryParseFromCmd("True", out var result));
Assert.AreEqual(new BoolProperty(true), result);
}
{
Assert.IsTrue(BoolProperty.TryParseFromCmd("false", out var result));
Assert.AreEqual(new BoolProperty(false), result);
}
}
[TestMethod]
public void IntPropertyParsing()
{
{
Assert.IsTrue(IntProperty.TryParseFromCmd("123", out var result));
Assert.AreEqual(new IntProperty(123), result);
}
{
Assert.IsTrue(IntProperty.TryParseFromCmd("1500", out var result));
Assert.AreEqual(new IntProperty(1500), result);
Assert.AreNotEqual(new IntProperty(15), result);
}
}
[TestMethod]
public void MouseJumpThumbnailSizeParsing()
{
{
Assert.IsTrue(MouseJumpThumbnailSize.TryParseFromCmd("1920x1080", out var result));
Assert.AreEqual(new MouseJumpThumbnailSize { Width = 1920, Height = 1080 }, result);
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Abstractions.TestingHelpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static Microsoft.PowerToys.Settings.UI.Library.SetSettingCommandLineCommand;
namespace Settings.UI.UnitTests.Cmd;
[TestClass]
public class SetSettingCommandTests
{
private SettingsUtils settingsUtils;
[TestInitialize]
public void Setup()
{
settingsUtils = new SettingsUtils(new MockFileSystem());
}
private void SetSetting(Type moduleSettingsType, string settingName, string newValueStr)
{
var settings = CommandLineUtils.GetSettingsConfigFor(moduleSettingsType, settingsUtils);
var defaultValue = CommandLineUtils.GetPropertyValue(settingName, settings);
var qualifiedName = moduleSettingsType.Name.Replace("Settings", string.Empty) + "." + settingName;
var type = CommandLineUtils.GetSettingPropertyInfo(settingName, settings).PropertyType;
var newValue = ICmdLineRepresentable.ParseFor(type, newValueStr);
Execute(qualifiedName, newValueStr, settingsUtils);
Assert.AreNotEqual(defaultValue, newValue);
Assert.AreEqual(newValue, CommandLineUtils.GetPropertyValue(settingName, settings));
}
// Each setting has a different type.
[TestMethod]
[DataRow(typeof(PowerRenameSettings), nameof(PowerRenameProperties.MaxMRUSize), "123")]
[DataRow(typeof(FancyZonesSettings), nameof(FZConfigProperties.FancyzonesBorderColor), "#00FF00")]
[DataRow(typeof(MeasureToolSettings), nameof(MeasureToolProperties.ActivationShortcut), "Ctrl+Alt+Delete")]
[DataRow(typeof(AlwaysOnTopSettings), nameof(AlwaysOnTopProperties.SoundEnabled), "False")]
[DataRow(typeof(PowerAccentSettings), nameof(PowerAccentProperties.ShowUnicodeDescription), "true")]
[DataRow(typeof(AwakeSettings), nameof(AwakeProperties.Mode), "EXPIRABLE")]
[DataRow(typeof(AwakeSettings), nameof(AwakeProperties.ExpirationDateTime), "March 31, 2020 15:00 +00:00")]
[DataRow(typeof(PowerLauncherSettings), nameof(PowerLauncherProperties.MaximumNumberOfResults), "322")]
[DataRow(typeof(ColorPickerSettings), nameof(ColorPickerProperties.CopiedColorRepresentation), "RGB")]
public void SetModuleSetting(Type moduleSettingsType, string settingName, string newValueStr)
{
SetSetting(moduleSettingsType, settingName, newValueStr);
}
[DataRow(typeof(GeneralSettings), "Enabled.MouseWithoutBorders", "true")]
[DataRow(typeof(GeneralSettings), nameof(GeneralSettings.AutoDownloadUpdates), "true")]
[TestMethod]
public void SetGeneralSetting(Type moduleSettingsType, string settingName, string newValueStr)
{
SetSetting(moduleSettingsType, settingName, newValueStr);
}
}

View File

@ -237,10 +237,9 @@ namespace ViewModelTests
// Assert
Assert.IsTrue(modules.FancyZones);
Assert.IsTrue(modules.ImageResizer);
Assert.IsTrue(modules.FileExplorerPreview);
Assert.IsTrue(modules.PowerPreview);
Assert.IsTrue(modules.ShortcutGuide);
Assert.IsTrue(modules.PowerRename);
Assert.IsTrue(modules.KeyboardManager);
Assert.IsTrue(modules.PowerLauncher);
Assert.IsTrue(modules.ColorPicker);
}

View File

@ -67,7 +67,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.RegistryPreview: return generalSettingsConfig.Enabled.RegistryPreview;
case ModuleType.MeasureTool: return generalSettingsConfig.Enabled.MeasureTool;
case ModuleType.ShortcutGuide: return generalSettingsConfig.Enabled.ShortcutGuide;
case ModuleType.PowerOCR: return generalSettingsConfig.Enabled.PowerOCR;
case ModuleType.PowerOCR: return generalSettingsConfig.Enabled.PowerOcr;
default: return false;
}
}
@ -99,7 +99,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.RegistryPreview: generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break;
case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break;
case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break;
case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOCR = isEnabled; break;
case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break;
}
}

View File

@ -3,9 +3,15 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Common.UI;
using interop;
@ -42,8 +48,11 @@ namespace Microsoft.PowerToys.Settings.UI
ContainsFlyoutPosition,
}
// Quantity of arguments
private const int RequiredArgumentsQty = 12;
private const int RequiredArgumentsSetSettingQty = 4;
private const int RequiredArgumentsSetAdditionalSettingsQty = 4;
private const int RequiredArgumentsGetSettingQty = 3;
private const int RequiredArgumentsLaunchedFromRunnerQty = 12;
// Create an instance of the IPC wrapper.
private static TwoWayPipeMessageIPCManaged ipcmanager;
@ -100,6 +109,171 @@ namespace Microsoft.PowerToys.Settings.UI
}
}
private void OnLaunchedToSetSetting(string[] cmdArgs)
{
var settingName = cmdArgs[2];
var settingValue = cmdArgs[3];
try
{
SetSettingCommandLineCommand.Execute(settingName, settingValue, new SettingsUtils());
}
catch (Exception ex)
{
Logger.LogError($"SetSettingCommandLineCommand exception: '{settingName}' setting couldn't be set to {settingValue}", ex);
}
Exit();
}
private void OnLaunchedToSetAdditionalSetting(string[] cmdArgs)
{
var moduleName = cmdArgs[2];
var ipcFileName = cmdArgs[3];
try
{
using (var settings = JsonDocument.Parse(File.ReadAllText(ipcFileName)))
{
SetAdditionalSettingsCommandLineCommand.Execute(moduleName, settings, new SettingsUtils());
}
}
catch (Exception ex)
{
Logger.LogError($"SetAdditionalSettingsCommandLineCommand exception: couldn't set additional settings for '{moduleName}'", ex);
}
Exit();
}
private void OnLaunchedToGetSetting(string[] cmdArgs)
{
var ipcFileName = cmdArgs[2];
try
{
var requestedSettings = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(File.ReadAllText(ipcFileName));
File.WriteAllText(ipcFileName, GetSettingCommandLineCommand.Execute(requestedSettings));
}
catch (Exception ex)
{
Logger.LogError($"GetSettingCommandLineCommand exception", ex);
}
Exit();
}
private void OnLaunchedFromRunner(string[] cmdArgs)
{
// Skip the first argument which is prepended when launched by explorer
if (cmdArgs[0].EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) && cmdArgs[1].EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) && (cmdArgs.Length >= RequiredArgumentsLaunchedFromRunnerQty + 1))
{
cmdArgs = cmdArgs.Skip(1).ToArray();
}
_ = int.TryParse(cmdArgs[(int)Arguments.PTPid], out int powerToysPID);
PowerToysPID = powerToysPID;
IsElevated = cmdArgs[(int)Arguments.ElevatedStatus] == "true";
IsUserAnAdmin = cmdArgs[(int)Arguments.IsUserAdmin] == "true";
ShowOobe = cmdArgs[(int)Arguments.ShowOobeWindow] == "true";
ShowScoobe = cmdArgs[(int)Arguments.ShowScoobeWindow] == "true";
ShowFlyout = cmdArgs[(int)Arguments.ShowFlyout] == "true";
bool containsSettingsWindow = cmdArgs[(int)Arguments.ContainsSettingsWindow] == "true";
bool containsFlyoutPosition = cmdArgs[(int)Arguments.ContainsFlyoutPosition] == "true";
// To keep track of variable arguments
int currentArgumentIndex = RequiredArgumentsLaunchedFromRunnerQty;
if (containsSettingsWindow)
{
// Open specific window
StartupPage = GetPage(cmdArgs[currentArgumentIndex]);
currentArgumentIndex++;
}
int flyout_x = 0;
int flyout_y = 0;
if (containsFlyoutPosition)
{
// get the flyout position arguments
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_x);
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_y);
}
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Environment.Exit(0);
});
ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName], cmdArgs[(int)Arguments.PTPipeName], (string message) =>
{
if (IPCMessageReceivedCallback != null && message.Length > 0)
{
IPCMessageReceivedCallback(message);
}
});
ipcmanager.Start();
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
{
settingsWindow = new MainWindow();
settingsWindow.Activate();
settingsWindow.ExtendsContentIntoTitleBar = true;
settingsWindow.NavigateToSection(StartupPage);
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
// Need to call SetForegroundWindow to actually gain focus.
WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle());
}
else
{
// Create the Settings window hidden so that it's fully initialized and
// it will be ready to receive the notification if the user opens
// the Settings from the tray icon.
settingsWindow = new MainWindow(true);
if (ShowOobe)
{
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview);
oobeWindow.Activate();
oobeWindow.ExtendsContentIntoTitleBar = true;
SetOobeWindow(oobeWindow);
}
else if (ShowScoobe)
{
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
scoobeWindow.Activate();
scoobeWindow.ExtendsContentIntoTitleBar = true;
SetOobeWindow(scoobeWindow);
}
else if (ShowFlyout)
{
POINT? p = null;
if (containsFlyoutPosition)
{
p = new POINT(flyout_x, flyout_y);
}
ShellPage.OpenFlyoutCallback(p);
}
}
if (SelectedTheme() == ElementTheme.Default)
{
try
{
themeListener = new ThemeListener();
themeListener.ThemeChanged += (_) => HandleThemeChange();
}
catch (Exception ex)
{
Logger.LogError($"HandleThemeChange exception. Please install .NET 4.", ex);
}
}
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
@ -109,117 +283,21 @@ namespace Microsoft.PowerToys.Settings.UI
{
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs != null && cmdArgs.Length >= RequiredArgumentsQty)
if (cmdArgs?.Length >= RequiredArgumentsLaunchedFromRunnerQty)
{
// Skip the first argument which is prepended when launched by explorer
if (cmdArgs[0].EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) && cmdArgs[1].EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) && (cmdArgs.Length >= RequiredArgumentsQty + 1))
{
cmdArgs = cmdArgs.Skip(1).ToArray();
}
_ = int.TryParse(cmdArgs[(int)Arguments.PTPid], out int powerToysPID);
PowerToysPID = powerToysPID;
IsElevated = cmdArgs[(int)Arguments.ElevatedStatus] == "true";
IsUserAnAdmin = cmdArgs[(int)Arguments.IsUserAdmin] == "true";
ShowOobe = cmdArgs[(int)Arguments.ShowOobeWindow] == "true";
ShowScoobe = cmdArgs[(int)Arguments.ShowScoobeWindow] == "true";
ShowFlyout = cmdArgs[(int)Arguments.ShowFlyout] == "true";
bool containsSettingsWindow = cmdArgs[(int)Arguments.ContainsSettingsWindow] == "true";
bool containsFlyoutPosition = cmdArgs[(int)Arguments.ContainsFlyoutPosition] == "true";
// To keep track of variable arguments
int currentArgumentIndex = RequiredArgumentsQty;
if (containsSettingsWindow)
{
// Open specific window
StartupPage = GetPage(cmdArgs[currentArgumentIndex]);
currentArgumentIndex++;
}
int flyout_x = 0;
int flyout_y = 0;
if (containsFlyoutPosition)
{
// get the flyout position arguments
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_x);
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_y);
}
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Environment.Exit(0);
});
ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName], cmdArgs[(int)Arguments.PTPipeName], (string message) =>
{
if (IPCMessageReceivedCallback != null && message.Length > 0)
{
IPCMessageReceivedCallback(message);
}
});
ipcmanager.Start();
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
{
settingsWindow = new MainWindow();
settingsWindow.Activate();
settingsWindow.ExtendsContentIntoTitleBar = true;
settingsWindow.NavigateToSection(StartupPage);
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
// Need to call SetForegroundWindow to actually gain focus.
WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle());
}
else
{
// Create the Settings window hidden so that it's fully initialized and
// it will be ready to receive the notification if the user opens
// the Settings from the tray icon.
settingsWindow = new MainWindow(true);
if (ShowOobe)
{
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview);
oobeWindow.Activate();
oobeWindow.ExtendsContentIntoTitleBar = true;
SetOobeWindow(oobeWindow);
}
else if (ShowScoobe)
{
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
scoobeWindow.Activate();
scoobeWindow.ExtendsContentIntoTitleBar = true;
SetOobeWindow(scoobeWindow);
}
else if (ShowFlyout)
{
POINT? p = null;
if (containsFlyoutPosition)
{
p = new POINT(flyout_x, flyout_y);
}
ShellPage.OpenFlyoutCallback(p);
}
}
if (SelectedTheme() == ElementTheme.Default)
{
try
{
themeListener = new ThemeListener();
themeListener.ThemeChanged += (_) => HandleThemeChange();
}
catch (Exception ex)
{
Logger.LogError($"HandleThemeChange exception. Please install .NET 4.", ex);
}
}
OnLaunchedFromRunner(cmdArgs);
}
else if (cmdArgs?.Length == RequiredArgumentsSetSettingQty && cmdArgs[1] == "set")
{
OnLaunchedToSetSetting(cmdArgs);
}
else if (cmdArgs?.Length == RequiredArgumentsSetAdditionalSettingsQty && cmdArgs[1] == "setAdditional")
{
OnLaunchedToSetAdditionalSetting(cmdArgs);
}
else if (cmdArgs?.Length == RequiredArgumentsGetSettingQty && cmdArgs[1] == "get")
{
OnLaunchedToGetSetting(cmdArgs);
}
else
{
@ -417,7 +495,7 @@ namespace Microsoft.PowerToys.Settings.UI
case "QuickAccent": return typeof(PowerAccentPage);
case "FileExplorer": return typeof(PowerPreviewPage);
case "ShortcutGuide": return typeof(ShortcutGuidePage);
case "PowerOCR": return typeof(PowerOcrPage);
case "PowerOcr": return typeof(PowerOcrPage);
case "VideoConference": return typeof(VideoConferencePage);
case "MeasureTool": return typeof(MeasureToolPage);
case "Hosts": return typeof(HostsPage);

View File

@ -321,7 +321,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
// Tab and Shift+Tab are accessible keys and should not be displayed in the hotkey control.
if (internalSettings.Code > 0 && !internalSettings.IsAccessibleShortcut())
{
lastValidSettings = internalSettings.Clone();
lastValidSettings = internalSettings with { };
if (!ComboIsValid(lastValidSettings))
{
@ -436,7 +436,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
if (ComboIsValid(lastValidSettings))
{
HotkeySettings = lastValidSettings.Clone();
HotkeySettings = lastValidSettings with { };
}
PreviewKeysControl.ItemsSource = hotkeySettings.GetKeysList();

View File

@ -109,7 +109,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
else
{
_isEnabled = GeneralSettingsConfig.Enabled.PowerOCR;
_isEnabled = GeneralSettingsConfig.Enabled.PowerOcr;
}
}
@ -129,8 +129,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
// Set the status of PowerOCR in the general settings
GeneralSettingsConfig.Enabled.PowerOCR = value;
// Set the status of PowerOcr in the general settings
GeneralSettingsConfig.Enabled.PowerOcr = value;
var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());