mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-20 19:16:55 +01:00
Compare commits
57 Commits
shawn/AddI
...
template-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0f35c4dac | ||
|
|
1fc14a2d95 | ||
|
|
9aab0f3893 | ||
|
|
91b7a99e76 | ||
|
|
d38edf798d | ||
|
|
17668047bf | ||
|
|
7b0b284d40 | ||
|
|
9aca6d136f | ||
|
|
4b2ee60b42 | ||
|
|
e37a328624 | ||
|
|
66e96bbe9d | ||
|
|
e13d6a78aa | ||
|
|
73786cd2be | ||
|
|
4de4d5f310 | ||
|
|
f8c5ff8c0c | ||
|
|
d32ea86314 | ||
|
|
177f144e6d | ||
|
|
7b469f6327 | ||
|
|
94ace730c8 | ||
|
|
a08fc0921f | ||
|
|
995bbdc62d | ||
|
|
f822826cf1 | ||
|
|
97c1de8bf6 | ||
|
|
0f8cf94d90 | ||
|
|
620f67a3ba | ||
|
|
23bc278cc8 | ||
|
|
73e379238b | ||
|
|
4710b816b4 | ||
|
|
b8a0163419 | ||
|
|
06fcbdac40 | ||
|
|
d515c67def | ||
|
|
9439b6df41 | ||
|
|
a37add8f08 | ||
|
|
60deec6815 | ||
|
|
7e791f2815 | ||
|
|
2b0ecc2979 | ||
|
|
45cf3de15d | ||
|
|
bf8c548501 | ||
|
|
121c6c0712 | ||
|
|
e68526b8d8 | ||
|
|
995a699de7 | ||
|
|
5f6df35d8d | ||
|
|
cffdecbc1b | ||
|
|
a4d8405957 | ||
|
|
cdf66a70e9 | ||
|
|
9dcddfd4b8 | ||
|
|
503bcbdf2d | ||
|
|
52f2561937 | ||
|
|
dc30f3fd8e | ||
|
|
8f9a2c32cc | ||
|
|
bcd1583bb7 | ||
|
|
437211b0e9 | ||
|
|
ca211be443 | ||
|
|
066c3247ed | ||
|
|
e87d1fefe6 | ||
|
|
e6d541ad7a | ||
|
|
a0a6f990e9 |
11
.github/actions/spell-check/allow/code.txt
vendored
11
.github/actions/spell-check/allow/code.txt
vendored
@@ -95,6 +95,7 @@ OTP
|
||||
Yubi
|
||||
Yubico
|
||||
Perplexity
|
||||
Groq
|
||||
svgl
|
||||
|
||||
# KEYS
|
||||
@@ -328,3 +329,13 @@ FFF
|
||||
HHH
|
||||
riday
|
||||
YYY
|
||||
|
||||
# GitHub issue/PR commands
|
||||
azp
|
||||
feedbackhub
|
||||
needinfo
|
||||
reportbug
|
||||
|
||||
#ffmpeg
|
||||
crf
|
||||
nostdin
|
||||
|
||||
27
.github/actions/spell-check/expect.txt
vendored
27
.github/actions/spell-check/expect.txt
vendored
@@ -111,6 +111,7 @@ AUTORADIOBUTTON
|
||||
Autorun
|
||||
AUTOTICKS
|
||||
AUTOUPDATE
|
||||
autopf
|
||||
AValid
|
||||
AWAYMODE
|
||||
azcliversion
|
||||
@@ -141,8 +142,11 @@ BITSPIXEL
|
||||
bla
|
||||
BLACKFRAME
|
||||
BLENDFUNCTION
|
||||
blittable
|
||||
Blockquotes
|
||||
blt
|
||||
bluelightreduction
|
||||
bluelightreductionstate
|
||||
BLURBEHIND
|
||||
BLURREGION
|
||||
bmi
|
||||
@@ -221,6 +225,7 @@ clientside
|
||||
CLIPBOARDUPDATE
|
||||
CLIPCHILDREN
|
||||
CLIPSIBLINGS
|
||||
CLITo
|
||||
closesocket
|
||||
clp
|
||||
CLSCTX
|
||||
@@ -249,6 +254,7 @@ colorformat
|
||||
colorhistory
|
||||
colorhistorylimit
|
||||
COLORKEY
|
||||
colorref
|
||||
comctl
|
||||
comdlg
|
||||
comexp
|
||||
@@ -262,6 +268,8 @@ CONFIGW
|
||||
CONFLICTINGMODIFIERKEY
|
||||
CONFLICTINGMODIFIERSHORTCUT
|
||||
CONOUT
|
||||
Contoso
|
||||
coreclr
|
||||
constexpr
|
||||
contentdialog
|
||||
contentfiles
|
||||
@@ -728,9 +736,9 @@ HWNDPARENT
|
||||
HWNDPREV
|
||||
hyjiacan
|
||||
IAI
|
||||
icf
|
||||
ICONERROR
|
||||
ICONLOCATION
|
||||
icf
|
||||
IDCANCEL
|
||||
IDD
|
||||
idk
|
||||
@@ -746,6 +754,7 @@ IFACEMETHOD
|
||||
IFACEMETHODIMP
|
||||
ifd
|
||||
IGNOREUNKNOWN
|
||||
ignoreversion
|
||||
IGo
|
||||
iid
|
||||
IIM
|
||||
@@ -772,7 +781,9 @@ INITDIALOG
|
||||
INITGUID
|
||||
INITTOLOGFONTSTRUCT
|
||||
INLINEPREFIX
|
||||
Inno
|
||||
inlines
|
||||
Inno
|
||||
INPC
|
||||
inproc
|
||||
INPUTHARDWARE
|
||||
@@ -805,6 +816,7 @@ IPTC
|
||||
irow
|
||||
irprops
|
||||
isbi
|
||||
iscc
|
||||
isfinite
|
||||
iss
|
||||
issecret
|
||||
@@ -1072,6 +1084,7 @@ MVVMTK
|
||||
MWBEx
|
||||
MYICON
|
||||
NAMECHANGE
|
||||
Notavailable
|
||||
namespaceanddescendants
|
||||
nao
|
||||
NCACTIVATE
|
||||
@@ -1110,6 +1123,7 @@ NEWPLUSSHELLEXTENSIONWIN
|
||||
newrow
|
||||
nicksnettravels
|
||||
NIF
|
||||
nightlight
|
||||
NLog
|
||||
NLSTEXT
|
||||
NMAKE
|
||||
@@ -1435,6 +1449,7 @@ RECTDESTINATION
|
||||
rectp
|
||||
RECTSOURCE
|
||||
recyclebin
|
||||
recursesubdirs
|
||||
Redist
|
||||
Reencode
|
||||
REFCLSID
|
||||
@@ -1480,6 +1495,7 @@ rgh
|
||||
rgn
|
||||
rgs
|
||||
rguid
|
||||
rhk
|
||||
RIDEV
|
||||
RIGHTSCROLLBAR
|
||||
riid
|
||||
@@ -1585,6 +1601,7 @@ SHGDNF
|
||||
SHGFI
|
||||
SHIL
|
||||
shinfo
|
||||
shk
|
||||
shlwapi
|
||||
shobjidl
|
||||
SHORTCUTATLEAST
|
||||
@@ -1629,6 +1646,7 @@ SKIPOWNPROCESS
|
||||
sku
|
||||
SLGP
|
||||
sln
|
||||
slnx
|
||||
SMALLICON
|
||||
smartphone
|
||||
smileys
|
||||
@@ -1783,6 +1801,7 @@ TILEDWINDOW
|
||||
TILLSON
|
||||
timedate
|
||||
timediff
|
||||
timestamped
|
||||
timeunion
|
||||
timeutil
|
||||
TITLEBARINFO
|
||||
@@ -1794,6 +1813,7 @@ tlbimp
|
||||
tlc
|
||||
tmain
|
||||
TNP
|
||||
toolgood
|
||||
Toolhelp
|
||||
toolwindow
|
||||
TOPDOWNDIB
|
||||
@@ -1842,18 +1862,23 @@ uitests
|
||||
UITo
|
||||
ULONGLONG
|
||||
ums
|
||||
UMax
|
||||
UMin
|
||||
uncompilable
|
||||
UNCPRIORITY
|
||||
UNDNAME
|
||||
UNICODETEXT
|
||||
unins
|
||||
Uninstaller
|
||||
uninstalls
|
||||
Uniquifies
|
||||
unitconverter
|
||||
unittests
|
||||
UNLEN
|
||||
Uninitializes
|
||||
UNORM
|
||||
unremapped
|
||||
Unsubscribes
|
||||
unvirtualized
|
||||
unwide
|
||||
unzoom
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
"PowerToys.FancyZonesEditorCommon.dll",
|
||||
"PowerToys.FancyZonesModuleInterface.dll",
|
||||
"PowerToys.FancyZones.exe",
|
||||
"FancyZonesCLI.exe",
|
||||
"FancyZonesCLI.dll",
|
||||
|
||||
"PowerToys.GcodePreviewHandler.dll",
|
||||
"PowerToys.GcodePreviewHandler.exe",
|
||||
@@ -351,6 +353,11 @@
|
||||
"Microsoft.SemanticKernel.Connectors.Ollama.dll",
|
||||
"OllamaSharp.dll",
|
||||
|
||||
"boost_regex-vc143-mt-gd-x32-1_87.dll",
|
||||
"boost_regex-vc143-mt-gd-x64-1_87.dll",
|
||||
"boost_regex-vc143-mt-x32-1_87.dll",
|
||||
"boost_regex-vc143-mt-x64-1_87.dll",
|
||||
|
||||
"UnitsNet.dll",
|
||||
"UtfUnknown.dll",
|
||||
"Wpf.Ui.dll"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Param(
|
||||
# Using the default value of 1.7 for winAppSdkVersionNumber and useExperimentalVersion as false
|
||||
[Parameter(Mandatory=$False,Position=1)]
|
||||
[string]$winAppSdkVersionNumber = "1.7",
|
||||
[string]$winAppSdkVersionNumber = "1.8",
|
||||
|
||||
# When the pipeline calls the PS1 file, the passed parameters are converted to string type
|
||||
[Parameter(Mandatory=$False,Position=2)]
|
||||
@@ -16,32 +16,7 @@ Param(
|
||||
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
|
||||
)
|
||||
|
||||
function Update-NugetConfig {
|
||||
param (
|
||||
[string]$filePath = [System.IO.Path]::Combine($rootPath, "nuget.config")
|
||||
)
|
||||
|
||||
Write-Host "Updating nuget.config file"
|
||||
[xml]$xml = Get-Content -Path $filePath
|
||||
|
||||
# Add localpackages source into nuget.config
|
||||
$packageSourcesNode = $xml.configuration.packageSources
|
||||
$addNode = $xml.CreateElement("add")
|
||||
$addNode.SetAttribute("key", "localpackages")
|
||||
$addNode.SetAttribute("value", "localpackages")
|
||||
$packageSourcesNode.AppendChild($addNode) | Out-Null
|
||||
|
||||
# Remove <packageSourceMapping> tag and its content
|
||||
$packageSourceMappingNode = $xml.configuration.packageSourceMapping
|
||||
if ($packageSourceMappingNode) {
|
||||
$xml.configuration.RemoveChild($packageSourceMappingNode) | Out-Null
|
||||
}
|
||||
|
||||
# print nuget.config after modification
|
||||
$xml.OuterXml
|
||||
# Save the modified nuget.config file
|
||||
$xml.Save($filePath)
|
||||
}
|
||||
|
||||
function Read-FileWithEncoding {
|
||||
param (
|
||||
@@ -71,6 +46,132 @@ function Write-FileWithEncoding {
|
||||
$writer.Close()
|
||||
}
|
||||
|
||||
|
||||
function Add-NuGetSourceAndMapping {
|
||||
param (
|
||||
[xml]$Xml,
|
||||
[string]$Key,
|
||||
[string]$Value,
|
||||
[string[]]$Patterns
|
||||
)
|
||||
|
||||
# Ensure packageSources exists
|
||||
if (-not $Xml.configuration.packageSources) {
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
|
||||
}
|
||||
$sources = $Xml.configuration.packageSources
|
||||
|
||||
# Add/Update Source
|
||||
$sourceNode = $sources.SelectSingleNode("add[@key='$Key']")
|
||||
if (-not $sourceNode) {
|
||||
$sourceNode = $Xml.CreateElement("add")
|
||||
$sourceNode.SetAttribute("key", $Key)
|
||||
$sources.AppendChild($sourceNode) | Out-Null
|
||||
}
|
||||
$sourceNode.SetAttribute("value", $Value)
|
||||
|
||||
# Ensure packageSourceMapping exists
|
||||
if (-not $Xml.configuration.packageSourceMapping) {
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
|
||||
}
|
||||
$mapping = $Xml.configuration.packageSourceMapping
|
||||
|
||||
# Remove invalid packageSource nodes (missing key or empty key)
|
||||
$invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
|
||||
if ($invalidNodes) {
|
||||
foreach ($node in $invalidNodes) {
|
||||
$mapping.RemoveChild($node) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Add/Update Mapping Source
|
||||
$mappingSource = $mapping.SelectSingleNode("packageSource[@key='$Key']")
|
||||
if (-not $mappingSource) {
|
||||
$mappingSource = $Xml.CreateElement("packageSource")
|
||||
$mappingSource.SetAttribute("key", $Key)
|
||||
# Insert at top for priority
|
||||
if ($mapping.HasChildNodes) {
|
||||
$mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
|
||||
} else {
|
||||
$mapping.AppendChild($mappingSource) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Double check and force attribute
|
||||
if (-not $mappingSource.HasAttribute("key")) {
|
||||
$mappingSource.SetAttribute("key", $Key)
|
||||
}
|
||||
|
||||
# Update Patterns
|
||||
# RemoveAll() removes all child nodes AND attributes, so we must re-set the key afterwards
|
||||
$mappingSource.RemoveAll()
|
||||
$mappingSource.SetAttribute("key", $Key)
|
||||
|
||||
foreach ($pattern in $Patterns) {
|
||||
$pkg = $Xml.CreateElement("package")
|
||||
$pkg.SetAttribute("pattern", $pattern)
|
||||
$mappingSource.AppendChild($pkg) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-WinAppSdkSplitDependencies {
|
||||
Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
|
||||
$installDir = Join-Path $rootPath "localpackages\output"
|
||||
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
||||
|
||||
# Create a temporary nuget.config to avoid interference from the repo's config
|
||||
$tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
|
||||
Set-Content -Path $tempConfig -Value "<?xml version='1.0' encoding='utf-8'?><configuration><packageSources><clear /><add key='TempSource' value='$sourceLink' /></packageSources></configuration>"
|
||||
|
||||
try {
|
||||
# Extract BuildTools version from Directory.Packages.props to ensure we have the required version
|
||||
$dirPackagesProps = Join-Path $rootPath "Directory.Packages.props"
|
||||
if (Test-Path $dirPackagesProps) {
|
||||
$propsContent = Get-Content $dirPackagesProps -Raw
|
||||
if ($propsContent -match '<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="([^"]+)"') {
|
||||
$buildToolsVersion = $Matches[1]
|
||||
Write-Host "Downloading Microsoft.Windows.SDK.BuildTools version $buildToolsVersion..."
|
||||
$nugetArgsBuildTools = "install Microsoft.Windows.SDK.BuildTools -Version $buildToolsVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgsBuildTools" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Download package to inspect nuspec and keep it for the build
|
||||
$nugetArgs = "install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgs" | Out-Null
|
||||
|
||||
# Parse dependencies from the installed folders
|
||||
# Folder structure is typically {PackageId}.{Version}
|
||||
$directories = Get-ChildItem -Path $installDir -Directory
|
||||
$allLocalPackages = @()
|
||||
foreach ($dir in $directories) {
|
||||
# Match any package pattern: PackageId.Version
|
||||
if ($dir.Name -match "^(.+?)\.(\d+\..*)$") {
|
||||
$pkgId = $Matches[1]
|
||||
$pkgVer = $Matches[2]
|
||||
$allLocalPackages += $pkgId
|
||||
|
||||
$packageVersions[$pkgId] = $pkgVer
|
||||
Write-Host "Found dependency: $pkgId = $pkgVer"
|
||||
}
|
||||
}
|
||||
|
||||
# Update repo's nuget.config to use localpackages
|
||||
$nugetConfig = Join-Path $rootPath "nuget.config"
|
||||
$configData = Read-FileWithEncoding -Path $nugetConfig
|
||||
[xml]$xml = $configData.Content
|
||||
|
||||
Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $installDir -Patterns $allLocalPackages
|
||||
|
||||
$xml.Save($nugetConfig)
|
||||
Write-Host "Updated nuget.config with localpackages mapping."
|
||||
} catch {
|
||||
Write-Warning "Failed to resolve dependencies: $_"
|
||||
} finally {
|
||||
Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# Execute nuget list and capture the output
|
||||
if ($useExperimentalVersion) {
|
||||
# The nuget list for experimental versions will cost more time
|
||||
@@ -112,56 +213,36 @@ if ($latestVersion) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Update packages.config files
|
||||
Get-ChildItem -Path $rootPath -Recurse packages.config | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
$content = $file.Content
|
||||
if ($content -match 'package id="Microsoft.WindowsAppSDK"') {
|
||||
$newVersionString = 'package id="Microsoft.WindowsAppSDK" version="' + $WinAppSDKVersion + '"'
|
||||
$oldVersionString = 'package id="Microsoft.WindowsAppSDK" version="[-.0-9a-zA-Z]*"'
|
||||
$content = $content -replace $oldVersionString, $newVersionString
|
||||
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
|
||||
Write-Host "Modified " $_.FullName
|
||||
}
|
||||
}
|
||||
# Resolve dependencies for 1.8+
|
||||
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
|
||||
|
||||
Resolve-WinAppSdkSplitDependencies
|
||||
|
||||
# Update Directory.Packages.props file
|
||||
Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
$content = $file.Content
|
||||
if ($content -match '<PackageVersion Include="Microsoft.WindowsAppSDK"') {
|
||||
$newVersionString = '<PackageVersion Include="Microsoft.WindowsAppSDK" Version="' + $WinAppSDKVersion + '" />'
|
||||
$oldVersionString = '<PackageVersion Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*" />'
|
||||
$content = $content -replace $oldVersionString, $newVersionString
|
||||
$isModified = $false
|
||||
|
||||
foreach ($pkgId in $packageVersions.Keys) {
|
||||
$ver = $packageVersions[$pkgId]
|
||||
# Escape dots in package ID for regex
|
||||
$pkgIdRegex = $pkgId -replace '\.', '\.'
|
||||
|
||||
$newVersionString = "<PackageVersion Include=""$pkgId"" Version=""$ver"" />"
|
||||
$oldVersionString = "<PackageVersion Include=""$pkgIdRegex"" Version=""[-.0-9a-zA-Z]*"" />"
|
||||
|
||||
if ($content -match "<PackageVersion Include=""$pkgIdRegex""") {
|
||||
# Update existing package
|
||||
if ($content -notmatch [regex]::Escape($newVersionString)) {
|
||||
$content = $content -replace $oldVersionString, $newVersionString
|
||||
$isModified = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isModified) {
|
||||
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
|
||||
Write-Host "Modified " $_.FullName
|
||||
}
|
||||
}
|
||||
|
||||
# Update .vcxproj files
|
||||
Get-ChildItem -Path $rootPath -Recurse *.vcxproj | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
$content = $file.Content
|
||||
if ($content -match '\\Microsoft.WindowsAppSDK.') {
|
||||
$newVersionString = '\Microsoft.WindowsAppSDK.' + $WinAppSDKVersion
|
||||
$oldVersionString = '\\Microsoft.WindowsAppSDK.(?=[-.0-9a-zA-Z]*\d)[-.0-9a-zA-Z]*' #positive lookahead for at least a digit
|
||||
$content = $content -replace $oldVersionString, $newVersionString
|
||||
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
|
||||
Write-Host "Modified " $_.FullName
|
||||
}
|
||||
}
|
||||
|
||||
# Update .csproj files
|
||||
Get-ChildItem -Path $rootPath -Recurse *.csproj | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
$content = $file.Content
|
||||
if ($content -match 'PackageReference Include="Microsoft.WindowsAppSDK"') {
|
||||
$newVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="'+ $WinAppSDKVersion + '"'
|
||||
$oldVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*"'
|
||||
$content = $content -replace $oldVersionString, $newVersionString
|
||||
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
|
||||
Write-Host "Modified " $_.FullName
|
||||
}
|
||||
}
|
||||
|
||||
Update-NugetConfig
|
||||
|
||||
@@ -19,7 +19,7 @@ parameters:
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
displayName: "Enable MSBuild Caching"
|
||||
default: true
|
||||
default: false
|
||||
- name: runTests
|
||||
type: boolean
|
||||
displayName: "Run Tests"
|
||||
@@ -33,7 +33,7 @@ parameters:
|
||||
default: true
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: 1.7
|
||||
default: 1.8
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -192,14 +192,14 @@ jobs:
|
||||
displayName: Verify XAML formatting
|
||||
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
|
||||
displayName: Verify Nuget package versions for PowerToys.sln
|
||||
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
|
||||
displayName: Verify Nuget package versions for PowerToys.slnx
|
||||
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\BugReportTool\BugReportTool.sln'
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\StylesReportTool\StylesReportTool.sln'
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.sln'
|
||||
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.slnx'
|
||||
displayName: Verify ARM64 configurations
|
||||
|
||||
- ${{ if eq(parameters.enablePackageCaching, true) }}:
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
${{ else }}:
|
||||
displayName: Build PowerToys main project
|
||||
inputs:
|
||||
solution: 'PowerToys.sln'
|
||||
solution: 'PowerToys.slnx'
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore -graph
|
||||
@@ -275,7 +275,7 @@ jobs:
|
||||
displayName: Generate DSC artifacts for ARM64
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
|
||||
inputs:
|
||||
solution: PowerToys.sln
|
||||
solution: PowerToys.slnx
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
command: restore
|
||||
feedsToUse: config
|
||||
configPath: nuget.config
|
||||
restoreSolution: PowerToys.sln
|
||||
restoreSolution: PowerToys.slnx
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
# Build all UI test projects if no specific modules are specified
|
||||
@@ -129,4 +129,4 @@ jobs:
|
||||
- publish: $(JobOutputDirectory)
|
||||
artifact: $(JobOutputArtifactName)
|
||||
displayName: Publish UI Test artifacts
|
||||
condition: always()
|
||||
condition: always()
|
||||
|
||||
@@ -35,7 +35,7 @@ steps:
|
||||
- task: VSBuild@1
|
||||
displayName: Build Shared Support DLLs
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
|
||||
@@ -74,7 +74,7 @@ steps:
|
||||
- task: VSBuild@1
|
||||
displayName: 💻 Build VNext MSI
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
@@ -91,7 +91,7 @@ steps:
|
||||
- task: VSBuild@1
|
||||
displayName: 👤 Build VNext MSI
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysInstallerVNext
|
||||
@@ -142,7 +142,7 @@ steps:
|
||||
- task: VSBuild@1
|
||||
displayName: 💻 Build VNext Bootstrapper
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
@@ -159,7 +159,7 @@ steps:
|
||||
- task: VSBuild@1
|
||||
displayName: 👤 Build VNext Bootstrapper
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysBootstrapperVNext
|
||||
|
||||
@@ -19,39 +19,20 @@ steps:
|
||||
-useExperimentalVersion $${{ parameters.useExperimentalVersion }}
|
||||
-rootPath "$(build.sourcesdirectory)"
|
||||
|
||||
- script: echo $(WinAppSDKVersion)
|
||||
displayName: 'Display WinAppSDK Version Found'
|
||||
# - task: NuGetCommand@2
|
||||
# displayName: 'Restore NuGet packages (slnx)'
|
||||
# inputs:
|
||||
# command: 'restore'
|
||||
# feedsToUse: 'config'
|
||||
# nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
|
||||
# restoreSolution: '$(build.sourcesdirectory)\**\*.slnx'
|
||||
# includeNuGetOrg: false
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download WindowsAppSDK'
|
||||
inputs:
|
||||
buildType: 'specific'
|
||||
project: '55e8140e-57ac-4e5f-8f9c-c7c15b51929d'
|
||||
definition: '104083'
|
||||
buildVersionToDownload: 'latestFromBranch'
|
||||
branchName: 'refs/heads/release/${{ parameters.versionNumber }}-stable'
|
||||
artifactName: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
targetPath: '$(Build.SourcesDirectory)\localpackages'
|
||||
|
||||
- script: dir $(Build.SourcesDirectory)\localpackages\NugetPackages
|
||||
displayName: 'List downloaded packages'
|
||||
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Install WindowsAppSDK'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
arguments: >
|
||||
install "Microsoft.WindowsAppSDK"
|
||||
-Source "$(Build.SourcesDirectory)\localpackages\NugetPackages"
|
||||
-Version "$(WinAppSDKVersion)"
|
||||
-OutputDirectory "$(Build.SourcesDirectory)\localpackages\output"
|
||||
-FallbackSource "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
|
||||
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Restore NuGet packages'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Restore NuGet packages (dotnet)'
|
||||
inputs:
|
||||
command: 'restore'
|
||||
projects: '$(build.sourcesdirectory)\**\*.slnx'
|
||||
feedsToUse: 'config'
|
||||
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
|
||||
restoreSolution: '$(build.sourcesdirectory)\**\*.sln'
|
||||
includeNuGetOrg: false
|
||||
workingDirectory: '$(build.sourcesdirectory)'
|
||||
|
||||
@@ -27,7 +27,8 @@ $versionExceptions = @(
|
||||
"WyHash.dll",
|
||||
"Microsoft.Recognizers.Text.DataTypes.TimexExpression.dll",
|
||||
"ObjectModelCsProjection.dll",
|
||||
"RendererCsProjection.dll") -join '|';
|
||||
"RendererCsProjection.dll",
|
||||
"Microsoft.ML.OnnxRuntime.dll") -join '|';
|
||||
$nullVersionExceptions = @(
|
||||
"SkiaSharp.Views.WinUI.Native.dll",
|
||||
"libSkiaSharp.dll",
|
||||
@@ -52,7 +53,12 @@ $nullVersionExceptions = @(
|
||||
"System.Diagnostics.EventLog.Messages.dll",
|
||||
"Microsoft.Windows.Widgets.dll",
|
||||
"AdaptiveCards.ObjectModel.WinUI3.dll",
|
||||
"AdaptiveCards.Rendering.WinUI3.dll") -join '|';
|
||||
"AdaptiveCards.Rendering.WinUI3.dll",
|
||||
"boost_regex_vc143_mt_gd_x32_1_87.dll",
|
||||
"boost_regex_vc143_mt_gd_x64_1_87.dll",
|
||||
"boost_regex_vc143_mt_x32_1_87.dll",
|
||||
"boost_regex_vc143_mt_x64_1_87.dll"
|
||||
) -join '|';
|
||||
$totalFailure = 0;
|
||||
|
||||
Write-Host $DirPath;
|
||||
|
||||
@@ -121,6 +121,9 @@ PowerToys Awake is a tool to keep your computer awake.
|
||||
|
||||
Randy contributed Registry Preview and some very early conversations about keyboard remapping.
|
||||
|
||||
### [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon
|
||||
Kayla was a former lead for PowerToys and helped create multiple utilities, maintained the GitHub repo, and collaborated with the community to improve the overall product
|
||||
|
||||
### [@oldnewthing](https://github.com/oldnewthing) - Raymond Chen
|
||||
|
||||
Find My Mouse is based on Raymond Chen's SuperSonar.
|
||||
@@ -180,7 +183,6 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
|
||||
|
||||
## PowerToys core team
|
||||
|
||||
- [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Lead
|
||||
- [@craigloewen-msft](https://github.com/craigloewen-msft) - Craig Loewen - Product Manager
|
||||
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
|
||||
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
|
||||
@@ -209,6 +211,7 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
|
||||
## Former PowerToys core team members
|
||||
|
||||
- [@indierawk2k2](https://github.com/indierawk2k2) - Mike Harsh - Product Manager
|
||||
- [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Product Manager
|
||||
- [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang - Product Manager
|
||||
- [@plante-msft](https://github.com/plante-msft) - Connor Plante - Product Manager
|
||||
- [@joadoumie](https://github.com/joadoumie) - Jordi Adoumie - Product Manager
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<!-- Make angle-bracket includes external and turn off code analysis for them -->
|
||||
<TreatAngleIncludeAsExternal>true</TreatAngleIncludeAsExternal>
|
||||
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
|
||||
<DisableAnalyzeExternal>true</DisableAnalyzeExternal>
|
||||
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
@@ -111,13 +116,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Debug/Release props -->
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'"
|
||||
Label="Configuration">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'"
|
||||
Label="Configuration">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
|
||||
@@ -243,6 +243,10 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<th>Event Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdNotFound_EnableCmdNotFound</td>
|
||||
<td>Triggered when Command Not Found is enabled or disabled.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
|
||||
<td>Triggered when a Command Not Found is installed.</td>
|
||||
@@ -257,6 +261,62 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Command Palette
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<th>Event Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_BeginInvoke</td>
|
||||
<td>Triggered when the Command Palette is launched by the user.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_ColdLaunch</td>
|
||||
<td>Occurs when Command Palette starts for the first time (cold start).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_OpenPage</td>
|
||||
<td>Triggered when a page is opened within the Command Palette, tracking navigation depth.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_OpenUri</td>
|
||||
<td>Occurs when a URI is opened through the Command Palette, including whether it's a web URL.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_ReactivateInstance</td>
|
||||
<td>Triggered when an existing Command Palette instance is reactivated.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_RunCommand</td>
|
||||
<td>Logs when a command is executed through the Command Palette, including admin elevation status.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_RunQuery</td>
|
||||
<td>Triggered when a search query is performed, including result count and duration.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPalDismissedOnEsc</td>
|
||||
<td>Occurs when the Command Palette is dismissed by pressing the Escape key.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPalDismissedOnLostFocus</td>
|
||||
<td>Triggered when the Command Palette is dismissed due to losing focus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPalHotkeySummoned</td>
|
||||
<td>Logs when the Command Palette is summoned via hotkey, distinguishing between global and context-specific hotkeys.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPalInvokeResult</td>
|
||||
<td>Records the result type of a Command Palette invocation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPalProcessStarted</td>
|
||||
<td>Triggered when the Command Palette process is started.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Crop And Lock
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
@@ -735,6 +795,10 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<th>Event Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.NewPlus_ChangedTemplateLocation</td>
|
||||
<td>Triggered when the template folder location is changed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplate</td>
|
||||
<td>Triggered when an item from New+ is created (copied to the current directory).</td>
|
||||
@@ -743,6 +807,10 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplateResult</td>
|
||||
<td>Logs the success of item creation (copying).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.NewPlus_EventOpenTemplates</td>
|
||||
<td>Triggered when the templates folder is opened.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.NewPlus_EventShowTemplateItems</td>
|
||||
<td>Triggered when the New+ context menu flyout is displayed.</td>
|
||||
@@ -928,12 +996,8 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.ShortcutGuide_EnableGuide</td>
|
||||
<td>Triggered when Shortcut Guide is enabled.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.ShortcutGuide_HideGuide</td>
|
||||
<td>Occurs when Shortcut Guide is hidden from view.</td>
|
||||
<td>Microsoft.PowerToys.ShortcutGuide_GuideSession</td>
|
||||
<td>Logs a Shortcut Guide session including duration and how it was closed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.ShortcutGuide_Settings</td>
|
||||
|
||||
@@ -8,4 +8,20 @@
|
||||
<PropertyGroup Label="ManifestToolOverride">
|
||||
<ManifestTool Condition="Exists('$(WindowsSdkDir)bin\x64\mt.exe')">$(WindowsSdkDir)bin\x64\mt.exe</ManifestTool>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Auto-restore NuGet for native vcxproj (PackageReference) when building inside VS -->
|
||||
<Target Name="EnsureNuGetRestoreForVcxproj" BeforeTargets="PrepareForBuild" Condition="
|
||||
'$(BuildingInsideVisualStudio)' == 'true'
|
||||
and '$(DesignTimeBuild)' != 'true'
|
||||
and '$(RestoreInProgress)' != 'true'
|
||||
and '$(MSBuildProjectExtension)' == '.vcxproj'
|
||||
and '$(RestoreProjectStyle)' == 'PackageReference'
|
||||
and '$(MSBuildProjectExtensionsPath)' != ''
|
||||
and !Exists('$(MSBuildProjectExtensionsPath)project.assets.json')
|
||||
">
|
||||
|
||||
<Message Importance="normal" Text="NuGet assets missing for $(MSBuildProjectName); running Restore...; IntDir=$(IntDir); BaseIntermediateOutputPath=$(BaseIntermediateOutputPath)" />
|
||||
|
||||
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Restore" Properties="RestoreInProgress=true" BuildInParallel="false" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -7,6 +7,8 @@
|
||||
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
||||
<PackageVersion Include="AdaptiveCards.Rendering.WinUI3" Version="2.1.0-beta" />
|
||||
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.5" />
|
||||
<PackageVersion Include="boost" Version="1.87.0" TargetFramework="native" />
|
||||
<PackageVersion Include="boost_regex-vc143" Version="1.87.0" TargetFramework="native" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251101-build.2372" />
|
||||
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
@@ -38,6 +40,7 @@
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
|
||||
@@ -69,10 +72,12 @@
|
||||
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.231216.1"/>
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.251104000" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.39" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.251106002" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
@@ -111,6 +116,7 @@
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />
|
||||
@@ -118,6 +124,7 @@
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.56.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||
|
||||
32
NOTICE.md
32
NOTICE.md
@@ -75,6 +75,37 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
```
|
||||
|
||||
### ToolGood.Words.Pinyin
|
||||
|
||||
We use the ToolGood.Words.Pinyin NuGet package for converting Chinese characters to pinyin.
|
||||
|
||||
**Source**: [https://github.com/toolgood/ToolGood.Words.Pinyin](https://github.com/toolgood/ToolGood.Words.Pinyin)
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 ToolGood
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
|
||||
## Utility: Command Palette Built-in Extensions
|
||||
|
||||
### Calculator
|
||||
@@ -1532,6 +1563,7 @@ SOFTWARE.
|
||||
- SkiaSharp.Views.WinUI
|
||||
- StreamJsonRpc
|
||||
- StyleCop.Analyzers
|
||||
- ToolGood.Words.Pinyin
|
||||
- UnicodeInformation
|
||||
- UnitsNet
|
||||
- UTF.Unknown
|
||||
|
||||
3396
PowerToys.sln
3396
PowerToys.sln
File diff suppressed because it is too large
Load Diff
1045
PowerToys.slnx
Normal file
1045
PowerToys.slnx
Normal file
File diff suppressed because it is too large
Load Diff
34
doc/devdocs/commands.md
Normal file
34
doc/devdocs/commands.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Issue/PR commands
|
||||
|
||||
The PowerToys repository uses some special keywords to help manage issues and pull requests. Here is a list of the most important commands you can use in issue and PR descriptions or comments.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/azp run` | Triggers the Azure Pipelines CI build for the current PR. Useful if you want to re-run the build without creating a new commit. |
|
||||
| `/bugreport` / `/reportbug` | Adds a comment with a manual for the Bug Report Tool, which helps users collect logs and system information for debugging purposes. It requests to upload this file and adds the `Needs-Author-Feedback` label. |
|
||||
| `/feedbackhub` | Adds a comment with a link to the Feedback Hub app on Windows, where users can submit feedback about PowerToys. Closes the issue and adds the `Resolution-Please File on Feedback Hub` label. |
|
||||
| `/dup #...` / `/duplicate #...` / `/dup https://...` / `/duplicate https://...` | Marks the current issue as a duplicate of another issue. It closes the current issue and applies the `Resolution-Duplicate` label. Replace `#...` with the issue number or a link to the issue. |
|
||||
| `/needinfo` | Adds the `Needs-Author-Feedback` label to the issue or PR, indicating that more information is needed from the author. |
|
||||
| `/helped` | Closes the issue and adds the `Resolution-Helped User` label. Furthermore a comment is added with a link to the PowerToys user documentation. |
|
||||
| `/loc` | Adds a comment informing the user that the issue was forwarded to the localization team and will soon be fixed. It adds the `Loc-Sent To Team` label. |
|
||||
|
||||
## Defining new commands
|
||||
|
||||
Most of these commands are using the [Microsoft GitHub Policy Service](https://github.com/apps/microsoft-github-policy-service) bot. Its commands are defined in the [PowerToys policy configuration file](/.github/policies/resourceManagement.yml).
|
||||
|
||||
## Other automated tasks
|
||||
|
||||
### Automatic labeling
|
||||
|
||||
The bot can automatically apply the correct `product-...` label for any opened issue.
|
||||
|
||||
> [!NOTE]
|
||||
> This feature is currently only available for the Workspaces module as a test.
|
||||
|
||||
### The `Needs-Author-Feedback` label
|
||||
|
||||
If an issue has this label and had no activity for 5 days, the bot will post a comment reminding the author to provide the needed information. It also adds the `Status-No recent activity` label. If no further activity occurs for another 5 days, the bot will close the issue.
|
||||
|
||||
### Filtering users that want to contribute
|
||||
|
||||
If a user utters their intention to contribute (e.g., by using the phrase "I want to contribute" in an issue or PR), the bot will add a comment with a link to the ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769).
|
||||
@@ -134,7 +134,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
|
||||
|
||||
#### Locally compiling the installer
|
||||
|
||||
1. Open `installer\PowerToysSetup.sln`
|
||||
1. Open `installer\PowerToysSetup.slnx`
|
||||
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
|
||||
1. From the `Build` menu choose `Build Solution`.
|
||||
|
||||
@@ -144,9 +144,9 @@ To build the installer from the command line, run `Developer Command Prompt for
|
||||
|
||||
```
|
||||
git clean -xfd -e *exe -- .\installer\
|
||||
MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
|
||||
MSBuild -t:restore .\installer\PowerToysSetup.slnx -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
|
||||
MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
|
||||
```
|
||||
|
||||
### Supported arguments for the .EXE Bootstrapper installer
|
||||
|
||||
@@ -38,7 +38,7 @@ For C# modules, the settings are accessed through the `SettingsUtils` class in t
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
// Read settings
|
||||
var settings = SettingsUtils.GetSettings<ModuleSettings>("ModuleName");
|
||||
var settings = SettingsUtils.Default.GetSettings<ModuleSettings>("ModuleName");
|
||||
bool enabled = settings.Enabled;
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
// Write settings
|
||||
settings.Enabled = true;
|
||||
SettingsUtils.SaveSettings(settings.ToJsonString(), "ModuleName");
|
||||
SettingsUtils.Default.SaveSettings(settings.ToJsonString(), "ModuleName");
|
||||
```
|
||||
|
||||
## Settings Handling in Modules
|
||||
|
||||
@@ -19,7 +19,7 @@ You can build the entire solution from the command line, which is sometimes fast
|
||||
2. Navigate to the repository root directory
|
||||
3. Run the following command(don't forget to set the correct platform):
|
||||
```pwsh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx /tl /p:NuGetInteractive="true"
|
||||
```
|
||||
4. This process should complete in approximately 13-14 minutes for a full build
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ Or reach out to "tools\build\BUILD-GUIDELINES.md"
|
||||
### Sample plain msbuild command
|
||||
```powershell
|
||||
# Restore:
|
||||
msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m
|
||||
msbuild powertoys.slnx -t:restore -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# Build powertoys sln
|
||||
msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m
|
||||
# Build powertoys slnx
|
||||
msbuild powertoys.slnx -p:configuration=debug -p:platform=x64 -m
|
||||
|
||||
# dotnet project
|
||||
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
|
||||
@@ -122,7 +122,7 @@ Similar for attach to managed code.
|
||||
|
||||
| Task | Command / Action | Notes |
|
||||
|------|------------------|-------|
|
||||
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs |
|
||||
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.slnx` | Deep clean removes packages & build outputs |
|
||||
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
|
||||
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
|
||||
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
|
||||
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
- Exit PowerToys if it's running.
|
||||
|
||||
- Open `PowerToys.sln` in Visual Studio and build the solution.
|
||||
- Open `PowerToys.slnx` in Visual Studio and build the solution.
|
||||
|
||||
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
# Localization
|
||||
|
||||
> **NOTE**: THIS DOCUMENT IS OUTDATED.
|
||||
> Follow [issue 15243](https://github.com/microsoft/PowerToys/issues/15243) for updates.
|
||||
|
||||
## Table of Contents
|
||||
1. [Localization on the pipeline (CDPX)](#localization-on-the-pipeline-cdpx)
|
||||
1. [UWP Special case](#uwp-special-case)
|
||||
2. [Enabling localization on a new project](#enabling-localization-on-a-new-project)
|
||||
1. [C++](#c)
|
||||
2. [C#](#c-1)
|
||||
3. [UWP](#uwp)
|
||||
3. [Lcl Files](#lcl-files)
|
||||
4. [Possible Issues in localization PRs (LEGO)](#possible-issues-in-localization-prs-lego)
|
||||
5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project)
|
||||
|
||||
## Localization on the pipeline (CDPX)
|
||||
[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/main/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package.
|
||||
|
||||
The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/main/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail in the [Lcl files section](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules.
|
||||
|
||||
Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/main/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this.
|
||||
|
||||
The process and variables that can be tweaked on the pipeline are described in more detail on [onebranch (account required) under Localization](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization).
|
||||
|
||||
The localized resource dlls for C# projects are added to the MSI only for build on the pipeline. This is done by checking if the [`IsPipeline` variable is defined](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L804-L805), which gets defined before [building the installer on the pipeline](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/build-installer.cmd#L4). This is done because the localized resx files are only present on the pipeline, and not having this check would result in the installer project failing to build locally.
|
||||
|
||||
## Enabling localization on a new project
|
||||
To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root.
|
||||
|
||||
For example, for a project in the folder `src\path` where the resx file is present in `resources\Resources.resx`, the LocProject.json file will contain the following:
|
||||
```
|
||||
{
|
||||
"Projects": [
|
||||
{
|
||||
"LanguageSet": "Azure_Languages",
|
||||
"LocItems": [
|
||||
{
|
||||
"SourceFile": "src\\path\\resources\\Resources.resx",
|
||||
"CopyOption": "LangIDOnName",
|
||||
"OutputPath": "src\\path\\resources"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The rest of the steps depend on the project type and are covered in the sections below. The steps to add the localized files to the MSI can be found in [Enabling localized MSI for a new project](#Enabling-localized-MSI-for-a-new-project).
|
||||
|
||||
### C++
|
||||
C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project.
|
||||
|
||||
If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files.
|
||||
|
||||
After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event:
|
||||
```
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h ProjName.base.rc ProjName.rc" />
|
||||
</Target>
|
||||
```
|
||||
|
||||
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
|
||||
```
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
strings
|
||||
END
|
||||
|
||||
#endif
|
||||
```
|
||||
Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` values from each langId from the pipeline, these are hardcoded in the script (for each language) as done in [lines 50-77 of `convert-resx-to-rc.ps1`](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/tools/build/convert-resx-to-rc.ps1#L50-L77). **If any other languages are added in the future, this script will have to be updated.** In order to determine what are the language codes, you can open the rc file in Resource View, right click the string table and press `Insert Copy` and choose the corresponding language. This autogenerates the required code and can be used to figure out the language codes. The files also add the resource declarations to a resource.h file, starting from 101 by default(this can be changed by an optional argument). Since the output files will be generated in `Generated Files`, any includes in these two files will require an additional `..\` and wherever resource.h is used, it will have to be included as `Generated Files\resource.h`. While adding `resource.base.h` and `ProjName.base.rc` to the vcxproj, these should be modified to not participate in the build to avoid build errors:
|
||||
```
|
||||
<None Include="Resources.resx" />
|
||||
```
|
||||
|
||||
Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/Directory.Build.targets) for an example.
|
||||
|
||||
Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project.
|
||||
|
||||
### C#
|
||||
Since C# projects natively support `resx` files, the only step required here is to include all the resx files in the build. For .NET Core projects this is done automatically and the .csproj does not need to be modified. For other projects, the following line needs to be added:
|
||||
```
|
||||
<EmbeddedResource Include="Properties\Resources.*.resx" />
|
||||
```
|
||||
|
||||
**Note:** Building with localized resources may cause a build warning `Referenced assembly 'mscorlib.dll' targets a different processor` which is a VS bug. More details can be found in [PowerToys issue #7269](https://github.com/microsoft/PowerToys/issues/7269).
|
||||
|
||||
**Note:** If a project needs to be migrated from XAML resources to resx, the easiest way to convert the resources would be to change to format to `=` separates resources by either manually (by Ctrl+H on a text editor), or by a script, and then running [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) on `Developer Command Prompt for VS` to convert it to resx format.
|
||||
```
|
||||
<system:String x:Key="wox_plugin_calculator_plugin_name">Calculator</system:String>
|
||||
<system:String x:Key="wox_plugin_calculator_plugin_description">Allows to do mathematical calculations.(Try 5*3-2 in Wox)</system:String>
|
||||
<system:String x:Key="wox_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
|
||||
```
|
||||
to
|
||||
```
|
||||
wox_plugin_calculator_plugin_name=Calculator
|
||||
wox_plugin_calculator_plugin_description=Allows to do mathematical calculations.(Try 5*3-2 in Wox)
|
||||
wox_plugin_calculator_not_a_number=Not a number (NaN)
|
||||
```
|
||||
After adding the resx file to the project along with the resource generator, references to the strings will have to be replaced with `Properties.Resources.resName` rather than the custom APIs. Check [this PR](https://github.com/microsoft/PowerToys/pull/6165) for an example of the changes required.
|
||||
|
||||
### UWP
|
||||
UWP projects expect `resw` files rather than `resx` (the format is almost the same). Unlike other C# projects, the files are expected in the format `fullLangId\Resources.resw`. To include these files in the build, replace the following line in the csproj:
|
||||
```
|
||||
<PRIResource Include="Strings\en-us\Resources.resw" />
|
||||
```
|
||||
to
|
||||
```
|
||||
<PRIResource Include="Strings\*\Resources.resw" />
|
||||
```
|
||||
|
||||
## Lcl Files
|
||||
Lcl files contain all the resources that are present in the English resx file, along with a translation if it has been added.
|
||||
|
||||
For example, an entry for a resource in the lcl file looks like this:
|
||||
```
|
||||
<Item ItemId=";EditKeyboard_WindowName" ItemType="0;.resx" PsrId="211" Leaf="true">
|
||||
<Str Cat="Text">
|
||||
<Val><![CDATA[Remap keys]]></Val>
|
||||
<Tgt Cat="Text" Stat="Loc" Orig="New">
|
||||
<Val><![CDATA[Remapper des touches]]></Val>
|
||||
</Tgt>
|
||||
</Str>
|
||||
<Disp Icon="Str" />
|
||||
</Item>
|
||||
```
|
||||
The `<Tgt>` element would not be present in the initial commits of the lcl files, as only the English version of the string would be present.
|
||||
|
||||
**Note:** The CDPX Localization system has a fail-safe check on the lcl files, where if the English string value which is present inside `<Val><![CDATA[*]]></Val>` does not match the value present in the English Resources.resx file then the translated value will not be copied to the localized resx file. This is present so that obsolete translations would not be loaded when the English resource has changed, and the English string will be used rather than the obsolete translation.
|
||||
|
||||
## Possible Issues in localization PRs (LEGO)
|
||||
Since the LEGO PRs update some of the strings in LCL files at a time, there can be multiple PRs which modify the same files, leading to merge conflicts. In most cases this would show up on GitHub as a merge conflict, but sometimes a bad git merge may occur, and the file could end up with incorrect formatting, such as two `<Tgt>` elements for a single resource. These can be fixed by ensuring the elements follow the format described in [this section](#lcl-files). To catch such errors, the build farm should be run for every LEGO PR and if any error occurs in the localization step, we should check the corresponding resx/lcl files for conflicts.
|
||||
|
||||
## Enabling localized MSI for a new project
|
||||
For C++ and UWP projects no additional files are generated with localization that need to be added to the MSI. For C++ projects all the resources are added to the dll/exe, while for UWP projects they are added to the `resources.pri` file (which is present even for an unlocalized project). To verify if the localized resources are added to the `resources.pri` file the following steps can be done:
|
||||
- Open `Developer Command Prompt for VS`
|
||||
- After navigating to the folder containing the pri file, run the following command:
|
||||
|
||||
makepri.exe dump /if .\resources.pri
|
||||
- Check the contents of the `resources.pri.xml` file that is generated from the command. The last section of the file will contain the resources with the strings in all the languages:
|
||||
```
|
||||
<NamedResource name="GeneralSettings_RunningAsAdminText" uri="ms-resource://f4f787a5-f0ae-47a9-be89-5408b1dd2b47/Resources/GeneralSettings_RunningAsAdminText">
|
||||
<Candidate qualifiers="Language-FR" type="String">
|
||||
<Value>Running as administrator</Value>
|
||||
</Candidate>
|
||||
<Candidate qualifiers="Language-EN-US" isDefault="true" type="String">
|
||||
<Value>Running as administrator</Value>
|
||||
</Candidate>
|
||||
</NamedResource>
|
||||
```
|
||||
|
||||
For C# projects, satellite dlls are generated when the project is built. For a project named `ProjName`, files are created in the format `langId\ProjName.resources.dll` where `langId` is in the same format as the lcl files. The satellite dlls need to be included with the MSI, but they must be added only if the solution is built from the build farm, as the localized resx files will not be present on local machines (and that could cause local builds of the installer to fail).
|
||||
This can be done by adding the directory name of the project to [Product.wxs near line 806](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L806) and a resource component for the project can be created in [Product.wxs near lines 845-847](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L845-L847) in this format:
|
||||
```
|
||||
<Component Id="ProjName_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)ProjNameInstallFolder">
|
||||
<File Id="ProjName_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\ProjName\$(var.Language)\ProjName.resources.dll" />
|
||||
</Component>
|
||||
```
|
||||
|
||||
We should also ensure the new dlls are signed by the pipeline. Currently all dlls of the form [`*.resources.dll` are signed](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/pipeline.user.windows.yml#L68).
|
||||
|
||||
**Note:** The resource dlls should be added to the MSI project only after the initial commit with the lcl files has been done by the Localization team. Otherwise, the pipeline will fail as there wouldn't be any resx files to generate the dlls.
|
||||
@@ -86,7 +86,7 @@ The module provides a user interface for configuring settings in the PowerToys S
|
||||
### Building and Testing
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git`
|
||||
2. Open PowerToys.sln in Visual Studio
|
||||
2. Open PowerToys.slnx in Visual Studio
|
||||
3. Select the Release configuration and build the solution
|
||||
4. Run PowerToys.exe from the output directory to test the module
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ FancyZones is divided into several projects:
|
||||
```
|
||||
git clone https://github.com/microsoft/PowerToys.git
|
||||
```
|
||||
2. Open `PowerToys.sln` in Visual Studio
|
||||
2. Open `PowerToys.slnx` in Visual Studio
|
||||
3. Select the Release configuration and build the solution
|
||||
4. If you encounter build errors, try deleting the x64 output folder and rebuild
|
||||
|
||||
@@ -244,7 +244,7 @@ UI tests are implemented using [Windows Application Driver](https://github.com/m
|
||||
|
||||
- Exit PowerToys if it's running
|
||||
- Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests.
|
||||
- Open `PowerToys.sln` in Visual Studio and build the solution.
|
||||
- Open `PowerToys.slnx` in Visual Studio and build the solution.
|
||||
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
|
||||
|
||||
>Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process.
|
||||
|
||||
@@ -11,7 +11,7 @@ Keyboard Manager consists of two main components:
|
||||
## Development Environment Setup
|
||||
|
||||
1. Clone the PowerToys repository
|
||||
2. Open `PowerToys.sln` in Visual Studio
|
||||
2. Open `PowerToys.slnx` in Visual Studio
|
||||
3. Ensure all NuGet packages are restored
|
||||
4. Build the entire solution in Debug configuration
|
||||
|
||||
@@ -91,4 +91,4 @@ If you encounter issues with multiple instances, check the mutex logic in `Keybo
|
||||
|
||||
To debug both the Editor and Engine:
|
||||
1. Launch the Engine first in debug mode
|
||||
2. Attach the debugger to the Editor process when it starts
|
||||
2. Attach the debugger to the Editor process when it starts
|
||||
|
||||
@@ -92,7 +92,7 @@ The module’s settings are exposed in the PowerToys Settings UI. Options includ
|
||||
3. Build the solution:
|
||||
|
||||
```sh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx
|
||||
```
|
||||
|
||||
> Note: This may take some time.
|
||||
|
||||
@@ -53,7 +53,7 @@ The Screen Ruler module consists of several components:
|
||||
|
||||
### Building
|
||||
|
||||
1. Open PowerToys.sln in Visual Studio
|
||||
1. Open PowerToys.slnx in Visual Studio
|
||||
2. In the Solutions Configuration drop-down menu, select Release or Debug
|
||||
3. From the Build menu, choose Build Solution
|
||||
4. The executable app for Screen Ruler is named PowerToys.MeasureToolUI.exe
|
||||
|
||||
@@ -19,7 +19,7 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
|
||||
## Build and Debug Instructions
|
||||
|
||||
### Build
|
||||
1. Open PowerToys.sln in Visual Studio
|
||||
1. Open PowerToys.slnx in Visual Studio
|
||||
2. Select Release or Debug in the Solutions Configuration drop-down menu
|
||||
3. From the Build menu, choose Build Solution
|
||||
4. The executable is named PowerToys.ShortcutGuide.exe
|
||||
|
||||
@@ -38,6 +38,11 @@ Welcome to the PowerToys developer documentation. This documentation provides in
|
||||
- [Update Process](processes/update-process.md) - How PowerToys updates work
|
||||
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
|
||||
|
||||
## Other Resources
|
||||
|
||||
- [aka.ms links](akaLinks.md) - List of short links
|
||||
- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
|
||||
|
||||
## Fork, Clone, Branch and Create your PR
|
||||
|
||||
Once you've discussed your proposed feature/fix/etc. with a team member, and an approach or a spec has been written and approved, it's time to start development:
|
||||
@@ -80,7 +85,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
|
||||
|
||||
### Install Visual Studio dependencies
|
||||
|
||||
1. Open the `PowerToys.sln` file.
|
||||
1. Open the `PowerToys.slnx` file.
|
||||
1. If you see a dialog that says `install extra components` in the solution explorer pane, click `install`
|
||||
|
||||
### Get Submodules to compile
|
||||
@@ -93,7 +98,7 @@ We have submodules that need to be initialized before you can compile most parts
|
||||
|
||||
### Compiling Source Code
|
||||
|
||||
- Open `PowerToys.sln` in Visual Studio.
|
||||
- Open `PowerToys.slnx` in Visual Studio.
|
||||
- In the `Solutions Configuration` drop-down menu select `Release` or `Debug`.
|
||||
- From the `Build` menu choose `Build Solution`, or press <kbd>Control</kbd>+<kbd>Shift</kbd>+<kbd>b</kbd> on your keyboard.
|
||||
- The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under `x64\Release\`.
|
||||
@@ -107,10 +112,10 @@ Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains
|
||||
|
||||
The installer can only be compiled in `Release` mode; steps 1 and 2 must be performed before the MSI can be compiled.
|
||||
|
||||
1. Compile `PowerToys.sln`. Instructions are listed above.
|
||||
1. Compile `PowerToys.slnx`. Instructions are listed above.
|
||||
1. Compile `BugReportTool.sln` tool. Path from root: `tools\BugReportTool\BugReportTool.sln` (details listed below)
|
||||
1. Compile `StylesReportTool.sln` tool. Path from root: `tools\StylesReportTool\StylesReportTool.sln` (details listed below)
|
||||
1. Compile `PowerToysSetup.sln` Path from root: `installer\PowerToysSetup.sln` (details listed below)
|
||||
1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
|
||||
|
||||
See [Installer](core/installer.md) for more details on building and debugging the installer.
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
|
||||
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
|
||||
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
|
||||
| [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. |
|
||||
| [QuickAI](https://github.com/ruslanlap/PowerToysRun-QuickAi) | [ruslanlap](https://github.com/ruslanlap) | AI-powered assistance with instant, smart responses from multiple providers (Groq, Together, Fireworks, OpenRouter, Cohere) |
|
||||
|
||||
## Extending software plugins
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32414.318
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}"
|
||||
EndProject
|
||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstallerVNext", "PowerToysSetupVNext\PowerToysInstallerVNext.wixproj", "{B6E94700-DF38-41F6-A3FD-18B69674AB1E}"
|
||||
EndProject
|
||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapperVNext", "PowerToysSetupVNext\PowerToysBootstrapperVNext.wixproj", "{DA4E9744-80BE-424C-B0F5-AFD8757DB575}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActionsVNext", "PowerToysSetupCustomActionsVNext\PowerToysSetupCustomActionsVNext.vcxproj", "{B3A354B0-1E54-4B55-A962-FB5AF9330C19}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SilentFilesInUseBAFunction", "PowerToysSetupVNext\SilentFilesInUseBA\SilentFilesInUseBAFunction.vcxproj", "{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
|
||||
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64
|
||||
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64
|
||||
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.Build.0 = Debug|x64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.Build.0 = Debug|x64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.ActiveCfg = Release|x64
|
||||
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.Build.0 = Release|x64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.Build.0 = Debug|x64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.ActiveCfg = Release|x64
|
||||
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.Build.0 = Release|x64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.Build.0 = Debug|x64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.ActiveCfg = Release|x64
|
||||
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.Build.0 = Release|x64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.Build.0 = Debug|x64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.ActiveCfg = Release|x64
|
||||
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
22
installer/PowerToysSetup.slnx
Normal file
22
installer/PowerToysSetup.slnx
Normal file
@@ -0,0 +1,22 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="ARM64" />
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Project Path="../src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
|
||||
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
|
||||
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
<Project Path="PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
|
||||
<Project Path="PowerToysSetupVNext/PowerToysInstallerVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
|
||||
<Project Path="PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj" Id="f8b9f842-f5c3-4a2d-8c85-7f8b9e2b4f1d">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
</Solution>
|
||||
@@ -124,7 +124,7 @@
|
||||
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
|
||||
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallPackageIdentityMSIX" After="InstallFiles" Condition="NOT Installed" />
|
||||
<Custom Action="InstallPackageIdentityMSIX" After="InstallFiles" Condition="NOT Installed AND WINDOWSBUILDNUMBER >= 22000" />
|
||||
<Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Before="RemoveFiles" />
|
||||
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
|
||||
<!-- TODO: Use to activate embedded MSIX -->
|
||||
|
||||
@@ -16,4 +16,5 @@ public static partial class CLSID
|
||||
public static readonly Guid CollatorDataSource = new Guid("9E175B8B-F52A-11D8-B9A5-505054503030");
|
||||
public static readonly Guid ApplicationActivationManager = new Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C");
|
||||
public static readonly Guid VirtualDesktopManager = new("aa509086-5ca9-4c25-8f95-589d3c07b48a");
|
||||
public static readonly Guid DesktopWallpaper = new("C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ public static partial class Ole32
|
||||
CLSCTX dwClsContext,
|
||||
ref Guid riid,
|
||||
out IntPtr rReturnedComObject);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial int CoInitializeEx(nint pvReserved, uint dwCoInit);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial void CoUninitialize();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
399
src/common/UITestAutomation/ScreenRecording.cs
Normal file
399
src/common/UITestAutomation/ScreenRecording.cs
Normal file
@@ -0,0 +1,399 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for recording the screen during UI tests.
|
||||
/// Requires FFmpeg to be installed and available in PATH.
|
||||
/// </summary>
|
||||
internal class ScreenRecording : IDisposable
|
||||
{
|
||||
private readonly string outputDirectory;
|
||||
private readonly string framesDirectory;
|
||||
private readonly string outputFilePath;
|
||||
private readonly List<string> capturedFrames;
|
||||
private readonly SemaphoreSlim recordingLock = new(1, 1);
|
||||
private readonly Stopwatch recordingStopwatch = new();
|
||||
private readonly string? ffmpegPath;
|
||||
private CancellationTokenSource? recordingCancellation;
|
||||
private Task? recordingTask;
|
||||
private bool isRecording;
|
||||
private int frameCount;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetCursorInfo(out ScreenCapture.CURSORINFO pci);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool DrawIconEx(IntPtr hdc, int x, int y, IntPtr hIcon, int cx, int cy, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
|
||||
|
||||
private const int CURSORSHOWING = 0x00000001;
|
||||
private const int DESKTOPHORZRES = 118;
|
||||
private const int DESKTOPVERTRES = 117;
|
||||
private const int DINORMAL = 0x0003;
|
||||
private const int TargetFps = 15; // 15 FPS for good balance of quality and size
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScreenRecording"/> class.
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Directory where the recording will be saved.</param>
|
||||
public ScreenRecording(string outputDirectory)
|
||||
{
|
||||
this.outputDirectory = outputDirectory;
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
framesDirectory = Path.Combine(outputDirectory, $"frames_{timestamp}");
|
||||
outputFilePath = Path.Combine(outputDirectory, $"recording_{timestamp}.mp4");
|
||||
capturedFrames = new List<string>();
|
||||
frameCount = 0;
|
||||
|
||||
// Check if FFmpeg is available
|
||||
ffmpegPath = FindFfmpeg();
|
||||
if (ffmpegPath == null)
|
||||
{
|
||||
Console.WriteLine("FFmpeg not found. Screen recording will be disabled.");
|
||||
Console.WriteLine("To enable video recording, install FFmpeg: https://ffmpeg.org/download.html");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether screen recording is available (FFmpeg found).
|
||||
/// </summary>
|
||||
public bool IsAvailable => ffmpegPath != null;
|
||||
|
||||
/// <summary>
|
||||
/// Starts recording the screen.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task StartRecordingAsync()
|
||||
{
|
||||
await recordingLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (isRecording || !IsAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create frames directory
|
||||
Directory.CreateDirectory(framesDirectory);
|
||||
|
||||
recordingCancellation = new CancellationTokenSource();
|
||||
isRecording = true;
|
||||
recordingStopwatch.Start();
|
||||
|
||||
// Start the recording task
|
||||
recordingTask = Task.Run(() => RecordFrames(recordingCancellation.Token));
|
||||
|
||||
Console.WriteLine($"Started screen recording at {TargetFps} FPS");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to start recording: {ex.Message}");
|
||||
isRecording = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
recordingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops recording and encodes video.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task StopRecordingAsync()
|
||||
{
|
||||
await recordingLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!isRecording || recordingCancellation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal cancellation
|
||||
recordingCancellation.Cancel();
|
||||
|
||||
// Wait for recording task to complete
|
||||
if (recordingTask != null)
|
||||
{
|
||||
await recordingTask;
|
||||
}
|
||||
|
||||
recordingStopwatch.Stop();
|
||||
isRecording = false;
|
||||
|
||||
double duration = recordingStopwatch.Elapsed.TotalSeconds;
|
||||
Console.WriteLine($"Recording stopped. Captured {capturedFrames.Count} frames in {duration:F2} seconds");
|
||||
|
||||
// Encode to video
|
||||
await EncodeToVideoAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error stopping recording: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup();
|
||||
recordingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records frames from the screen.
|
||||
/// </summary>
|
||||
private void RecordFrames(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
int frameInterval = 1000 / TargetFps;
|
||||
var frameTimer = Stopwatch.StartNew();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var frameStart = frameTimer.ElapsedMilliseconds;
|
||||
|
||||
try
|
||||
{
|
||||
CaptureFrame();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error capturing frame: {ex.Message}");
|
||||
}
|
||||
|
||||
// Sleep for remaining time to maintain target FPS
|
||||
var frameTime = frameTimer.ElapsedMilliseconds - frameStart;
|
||||
var sleepTime = Math.Max(0, frameInterval - (int)frameTime);
|
||||
|
||||
if (sleepTime > 0)
|
||||
{
|
||||
Thread.Sleep(sleepTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when stopping
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error during recording: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a single frame.
|
||||
/// </summary>
|
||||
private void CaptureFrame()
|
||||
{
|
||||
IntPtr hdc = GetDC(IntPtr.Zero);
|
||||
int screenWidth = GetDeviceCaps(hdc, DESKTOPHORZRES);
|
||||
int screenHeight = GetDeviceCaps(hdc, DESKTOPVERTRES);
|
||||
ReleaseDC(IntPtr.Zero, hdc);
|
||||
|
||||
Rectangle bounds = new Rectangle(0, 0, screenWidth, screenHeight);
|
||||
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb))
|
||||
{
|
||||
using (Graphics g = Graphics.FromImage(bitmap))
|
||||
{
|
||||
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
|
||||
|
||||
ScreenCapture.CURSORINFO cursorInfo;
|
||||
cursorInfo.CbSize = Marshal.SizeOf<ScreenCapture.CURSORINFO>();
|
||||
if (GetCursorInfo(out cursorInfo) && cursorInfo.Flags == CURSORSHOWING)
|
||||
{
|
||||
IntPtr hdcDest = g.GetHdc();
|
||||
DrawIconEx(hdcDest, cursorInfo.PTScreenPos.X, cursorInfo.PTScreenPos.Y, cursorInfo.HCursor, 0, 0, 0, IntPtr.Zero, DINORMAL);
|
||||
g.ReleaseHdc(hdcDest);
|
||||
}
|
||||
}
|
||||
|
||||
string framePath = Path.Combine(framesDirectory, $"frame_{frameCount:D6}.jpg");
|
||||
bitmap.Save(framePath, ImageFormat.Jpeg);
|
||||
capturedFrames.Add(framePath);
|
||||
frameCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes captured frames to video using ffmpeg.
|
||||
/// </summary>
|
||||
private async Task EncodeToVideoAsync()
|
||||
{
|
||||
if (capturedFrames.Count == 0)
|
||||
{
|
||||
Console.WriteLine("No frames captured");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build ffmpeg command with proper non-interactive flags
|
||||
string inputPattern = Path.Combine(framesDirectory, "frame_%06d.jpg");
|
||||
|
||||
// -y: overwrite without asking
|
||||
// -nostdin: disable interaction
|
||||
// -loglevel error: only show errors
|
||||
// -stats: show encoding progress
|
||||
string args = $"-y -nostdin -loglevel error -stats -framerate {TargetFps} -i \"{inputPattern}\" -c:v libx264 -pix_fmt yuv420p -crf 23 \"{outputFilePath}\"";
|
||||
|
||||
Console.WriteLine($"Encoding {capturedFrames.Count} frames to video...");
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ffmpegPath!,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true, // Important: redirect stdin to prevent hanging
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process != null)
|
||||
{
|
||||
// Close stdin immediately to ensure FFmpeg doesn't wait for input
|
||||
process.StandardInput.Close();
|
||||
|
||||
// Read output streams asynchronously to prevent deadlock
|
||||
var outputTask = process.StandardOutput.ReadToEndAsync();
|
||||
var errorTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
// Wait for process to exit
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
// Get the output
|
||||
string stdout = await outputTask;
|
||||
string stderr = await errorTask;
|
||||
|
||||
if (process.ExitCode == 0 && File.Exists(outputFilePath))
|
||||
{
|
||||
var fileInfo = new FileInfo(outputFilePath);
|
||||
Console.WriteLine($"Video created: {outputFilePath} ({fileInfo.Length / 1024 / 1024:F1} MB)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"FFmpeg encoding failed with exit code {process.ExitCode}");
|
||||
if (!string.IsNullOrWhiteSpace(stderr))
|
||||
{
|
||||
Console.WriteLine($"FFmpeg error: {stderr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error encoding video: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds ffmpeg executable.
|
||||
/// </summary>
|
||||
private static string? FindFfmpeg()
|
||||
{
|
||||
// Check if ffmpeg is in PATH
|
||||
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty<string>();
|
||||
|
||||
foreach (var dir in pathDirs)
|
||||
{
|
||||
var ffmpegPath = Path.Combine(dir, "ffmpeg.exe");
|
||||
if (File.Exists(ffmpegPath))
|
||||
{
|
||||
return ffmpegPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Check common installation locations
|
||||
var commonPaths = new[]
|
||||
{
|
||||
@"C:\.tools\ffmpeg\bin\ffmpeg.exe",
|
||||
@"C:\ffmpeg\bin\ffmpeg.exe",
|
||||
@"C:\Program Files\ffmpeg\bin\ffmpeg.exe",
|
||||
@"C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe",
|
||||
@$"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Microsoft\WinGet\Links\ffmpeg.exe",
|
||||
};
|
||||
|
||||
foreach (var path in commonPaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the recorded video file.
|
||||
/// </summary>
|
||||
public string OutputFilePath => outputFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory containing recordings.
|
||||
/// </summary>
|
||||
public string OutputDirectory => outputDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up resources.
|
||||
/// </summary>
|
||||
private void Cleanup()
|
||||
{
|
||||
recordingCancellation?.Dispose();
|
||||
recordingCancellation = null;
|
||||
recordingTask = null;
|
||||
|
||||
// Clean up frames directory if it exists
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(framesDirectory))
|
||||
{
|
||||
Directory.Delete(framesDirectory, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to cleanup frames directory: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (isRecording)
|
||||
{
|
||||
StopRecordingAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
recordingLock.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,9 +130,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
/// <param name="args">Optional command line arguments to pass to the application.</param>
|
||||
public void StartExe(string appPath, string[]? args = null)
|
||||
public void StartExe(string appPath, string[]? args = null, string? enableModules = null)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
if (!string.IsNullOrEmpty(enableModules))
|
||||
{
|
||||
opts.AddAdditionalCapability("enableModules", enableModules);
|
||||
}
|
||||
|
||||
if (scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
@@ -169,27 +173,66 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
private void TryLaunchPowerToysSettings(AppiumOptions opts)
|
||||
{
|
||||
try
|
||||
if (opts.ToCapabilities().HasCapability("enableModules"))
|
||||
{
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
var modulesString = (string)opts.ToCapabilities().GetCapability("enableModules");
|
||||
var modulesArray = modulesString.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
SettingsConfigHelper.ConfigureGlobalModuleSettings(modulesArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsConfigHelper.ConfigureGlobalModuleSettings();
|
||||
}
|
||||
|
||||
const int maxTries = 3;
|
||||
const int delayMs = 5000;
|
||||
const int maxRetries = 3;
|
||||
|
||||
for (int tryCount = 1; tryCount <= maxTries; tryCount++)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileName = locationPath + runnerPath,
|
||||
Verb = "runas",
|
||||
Arguments = "--open-settings",
|
||||
};
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = locationPath + runnerPath,
|
||||
Verb = "runas",
|
||||
Arguments = "--open-settings",
|
||||
};
|
||||
|
||||
ExitExe(runnerProcessInfo.FileName);
|
||||
runner = Process.Start(runnerProcessInfo);
|
||||
ExitExe(runnerProcessInfo.FileName);
|
||||
|
||||
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
|
||||
// Verify process was killed
|
||||
string exeName = Path.GetFileNameWithoutExtension(runnerProcessInfo.FileName);
|
||||
var remainingProcesses = Process.GetProcessesByName(exeName);
|
||||
|
||||
// Exit CmdPal UI before launching new process if use installer for test
|
||||
ExitExeByName("Microsoft.CmdPal.UI");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
|
||||
runner = Process.Start(runnerProcessInfo);
|
||||
|
||||
if (WaitForWindowAndSetCapability(opts, "PowerToys Settings", delayMs, maxRetries))
|
||||
{
|
||||
// Exit CmdPal UI before launching new process if use installer for test
|
||||
ExitExeByName("Microsoft.CmdPal.UI");
|
||||
return;
|
||||
}
|
||||
|
||||
// Window not found, kill all PowerToys processes and retry
|
||||
if (tryCount < maxTries)
|
||||
{
|
||||
KillPowerToysProcesses();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (tryCount == maxTries)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to launch PowerToys Settings after {maxTries} attempts: {ex.Message}", ex);
|
||||
}
|
||||
|
||||
// Kill processes and retry
|
||||
KillPowerToysProcesses();
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to launch PowerToys Settings: Window not found after {maxTries} attempts.");
|
||||
}
|
||||
|
||||
private void TryLaunchCommandPalette(AppiumOptions opts)
|
||||
@@ -211,7 +254,10 @@ namespace Microsoft.PowerToys.UITest
|
||||
var process = Process.Start(processStartInfo);
|
||||
process?.WaitForExit();
|
||||
|
||||
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
|
||||
if (!WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10))
|
||||
{
|
||||
throw new TimeoutException("Failed to find Command Palette window after multiple attempts.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -219,7 +265,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
|
||||
private bool WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
|
||||
{
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
@@ -230,18 +276,16 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
var hexHwnd = window[0].HWnd.ToString("x");
|
||||
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries)
|
||||
{
|
||||
Thread.Sleep(delayMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -292,17 +336,17 @@ namespace Microsoft.PowerToys.UITest
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle exceptions if needed
|
||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
Console.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts now exe and takes control of it.
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
public void RestartScopeExe(string? enableModules = null)
|
||||
{
|
||||
ExitScopeExe();
|
||||
StartExe(locationPath + sessionPath, this.commandLineArgs);
|
||||
StartExe(locationPath + sessionPath, commandLineArgs, enableModules);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot()
|
||||
@@ -327,5 +371,31 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.ExitExe(winAppDriverProcessInfo.FileName);
|
||||
SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
}
|
||||
|
||||
private void KillPowerToysProcesses()
|
||||
{
|
||||
var powerToysProcessNames = new[] { "PowerToys", "Microsoft.CmdPal.UI" };
|
||||
|
||||
foreach (var processName in powerToysProcessNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processes = Process.GetProcessesByName(processName);
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
// Verify processes are actually gone
|
||||
var remainingProcesses = Process.GetProcessesByName(processName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KillPowerToysProcesses] Failed to kill process {processName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,18 @@ namespace Microsoft.PowerToys.UITest
|
||||
public class SettingsConfigHelper
|
||||
{
|
||||
private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
|
||||
private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
|
||||
private static readonly SettingsUtils SettingsUtils = SettingsUtils.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Configures global PowerToys settings to enable only specified modules and disable all others.
|
||||
/// </summary>
|
||||
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when modulesToEnable is null.</exception>
|
||||
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled. If null or empty, all modules will be disabled.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
|
||||
public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable)
|
||||
public static void ConfigureGlobalModuleSettings(params string[]? modulesToEnable)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(modulesToEnable);
|
||||
modulesToEnable ??= Array.Empty<string>();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
public string? ScreenshotDirectory { get; set; }
|
||||
|
||||
public string? RecordingDirectory { get; set; }
|
||||
|
||||
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
|
||||
|
||||
private readonly PowerToysModule scope;
|
||||
@@ -36,6 +38,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
private readonly string[]? commandLineArgs;
|
||||
private SessionHelper? sessionHelper;
|
||||
private System.Threading.Timer? screenshotTimer;
|
||||
private ScreenRecording? screenRecording;
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
|
||||
{
|
||||
@@ -65,12 +68,35 @@ namespace Microsoft.PowerToys.UITest
|
||||
CloseOtherApplications();
|
||||
if (IsInPipeline)
|
||||
{
|
||||
ScreenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
|
||||
string baseDirectory = this.TestContext.TestResultsDirectory ?? string.Empty;
|
||||
ScreenshotDirectory = Path.Combine(baseDirectory, "UITestScreenshots_" + Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(ScreenshotDirectory);
|
||||
|
||||
RecordingDirectory = Path.Combine(baseDirectory, "UITestRecordings_" + Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(RecordingDirectory);
|
||||
|
||||
// Take screenshot every 1 second
|
||||
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, ScreenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
|
||||
|
||||
// Start screen recording (requires FFmpeg)
|
||||
try
|
||||
{
|
||||
screenRecording = new ScreenRecording(RecordingDirectory);
|
||||
if (screenRecording.IsAvailable)
|
||||
{
|
||||
_ = screenRecording.StartRecordingAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
screenRecording = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to start screen recording: {ex.Message}");
|
||||
screenRecording = null;
|
||||
}
|
||||
|
||||
// Escape Popups before starting
|
||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||
}
|
||||
@@ -88,15 +114,36 @@ namespace Microsoft.PowerToys.UITest
|
||||
if (IsInPipeline)
|
||||
{
|
||||
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
Dispose();
|
||||
|
||||
// Stop screen recording
|
||||
if (screenRecording != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
screenRecording.StopRecordingAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to stop screen recording: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
|
||||
or UnitTestOutcome.Error
|
||||
or UnitTestOutcome.Unknown)
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
AddRecordingsToTestResultsDirectory();
|
||||
AddLogFilesToTestResultsDirectory();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clean up recording if test passed
|
||||
CleanupRecordingDirectory();
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
this.Session.Cleanup();
|
||||
@@ -106,6 +153,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
public void Dispose()
|
||||
{
|
||||
screenshotTimer?.Dispose();
|
||||
screenRecording?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -600,6 +648,47 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds screen recordings to test results directory when test fails.
|
||||
/// </summary>
|
||||
protected void AddRecordingsToTestResultsDirectory()
|
||||
{
|
||||
if (RecordingDirectory != null && Directory.Exists(RecordingDirectory))
|
||||
{
|
||||
// Add video files (MP4)
|
||||
var videoFiles = Directory.GetFiles(RecordingDirectory, "*.mp4");
|
||||
foreach (string file in videoFiles)
|
||||
{
|
||||
this.TestContext.AddResultFile(file);
|
||||
var fileInfo = new FileInfo(file);
|
||||
Console.WriteLine($"Added video recording: {Path.GetFileName(file)} ({fileInfo.Length / 1024 / 1024:F1} MB)");
|
||||
}
|
||||
|
||||
if (videoFiles.Length == 0)
|
||||
{
|
||||
Console.WriteLine("No video recording available (FFmpeg not found). Screenshots are still captured.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up recording directory when test passes.
|
||||
/// </summary>
|
||||
private void CleanupRecordingDirectory()
|
||||
{
|
||||
if (RecordingDirectory != null && Directory.Exists(RecordingDirectory))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(RecordingDirectory, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to cleanup recording directory: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies PowerToys log files to test results directory when test fails.
|
||||
/// Renames files to include the directory structure after \PowerToys.
|
||||
@@ -689,11 +778,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
public Session RestartScopeExe(string? enableModules = null)
|
||||
{
|
||||
this.sessionHelper!.RestartScopeExe();
|
||||
this.sessionHelper!.RestartScopeExe(enableModules);
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
|
||||
return;
|
||||
return Session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,9 +16,54 @@
|
||||
|
||||
namespace registry
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct on_exit
|
||||
{
|
||||
std::function<void()> f;
|
||||
|
||||
on_exit(std::function<void()> f) :
|
||||
f{ std::move(f) } {}
|
||||
~on_exit() { f(); }
|
||||
};
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
inline const wchar_t* getScopeName(HKEY scope)
|
||||
{
|
||||
if (scope == HKEY_LOCAL_MACHINE)
|
||||
{
|
||||
return L"HKLM";
|
||||
}
|
||||
else if (scope == HKEY_CURRENT_USER)
|
||||
{
|
||||
return L"HKCU";
|
||||
}
|
||||
else if (scope == HKEY_CLASSES_ROOT)
|
||||
{
|
||||
return L"HKCR";
|
||||
}
|
||||
else
|
||||
{
|
||||
return L"HK??";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace install_scope
|
||||
{
|
||||
const wchar_t INSTALL_SCOPE_REG_KEY[] = L"Software\\Classes\\powertoys\\";
|
||||
const wchar_t UNINSTALL_REG_KEY[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
|
||||
|
||||
// Bundle UpgradeCode from PowerToys.wxs (with braces as stored in registry)
|
||||
const wchar_t BUNDLE_UPGRADE_CODE[] = L"{6341382D-C0A9-4238-9188-BE9607E3FAB2}";
|
||||
|
||||
enum class InstallScope
|
||||
{
|
||||
@@ -26,8 +71,67 @@ namespace registry
|
||||
PerUser,
|
||||
};
|
||||
|
||||
// Helper function to find PowerToys bundle in Windows Uninstall registry by BundleUpgradeCode
|
||||
inline bool find_powertoys_bundle_in_uninstall_registry(HKEY rootKey)
|
||||
{
|
||||
HKEY uninstallKey{};
|
||||
if (RegOpenKeyExW(rootKey, UNINSTALL_REG_KEY, 0, KEY_READ, &uninstallKey) != ERROR_SUCCESS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
detail::on_exit closeUninstallKey{ [uninstallKey] { RegCloseKey(uninstallKey); } };
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
|
||||
// Enumerate all subkeys under Uninstall
|
||||
while (RegEnumKeyW(uninstallKey, index++, subKeyName, 256) == ERROR_SUCCESS)
|
||||
{
|
||||
HKEY productKey{};
|
||||
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ, &productKey) != ERROR_SUCCESS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
detail::on_exit closeProductKey{ [productKey] { RegCloseKey(productKey); } };
|
||||
|
||||
// Check BundleUpgradeCode value (specific to WiX Bundle installations)
|
||||
wchar_t bundleUpgradeCode[256]{};
|
||||
DWORD bundleUpgradeCodeSize = sizeof(bundleUpgradeCode);
|
||||
|
||||
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, nullptr,
|
||||
reinterpret_cast<LPBYTE>(bundleUpgradeCode), &bundleUpgradeCodeSize) == ERROR_SUCCESS)
|
||||
{
|
||||
if (_wcsicmp(bundleUpgradeCode, BUNDLE_UPGRADE_CODE) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const InstallScope get_current_install_scope()
|
||||
{
|
||||
// 1. Check HKCU Uninstall registry first (user-level bundle)
|
||||
// Note: MSI components are always in HKLM regardless of install scope,
|
||||
// but the Bundle entry will be in HKCU for per-user installations
|
||||
if (find_powertoys_bundle_in_uninstall_registry(HKEY_CURRENT_USER))
|
||||
{
|
||||
Logger::info(L"Found user-level PowerToys bundle via BundleUpgradeCode in HKCU");
|
||||
return InstallScope::PerUser;
|
||||
}
|
||||
|
||||
// 2. Check HKLM Uninstall registry (machine-level bundle)
|
||||
if (find_powertoys_bundle_in_uninstall_registry(HKEY_LOCAL_MACHINE))
|
||||
{
|
||||
Logger::info(L"Found machine-level PowerToys bundle via BundleUpgradeCode in HKLM");
|
||||
return InstallScope::PerMachine;
|
||||
}
|
||||
|
||||
// 3. Fallback to legacy custom registry key detection
|
||||
Logger::info(L"PowerToys bundle not found in Uninstall registry, falling back to legacy detection");
|
||||
|
||||
// Open HKLM key
|
||||
HKEY perMachineKey{};
|
||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
@@ -45,6 +149,7 @@ namespace registry
|
||||
&perUserKey) != ERROR_SUCCESS)
|
||||
{
|
||||
// both keys are missing
|
||||
Logger::warn(L"No PowerToys installation detected, defaulting to PerMachine");
|
||||
return InstallScope::PerMachine;
|
||||
}
|
||||
else
|
||||
@@ -96,47 +201,6 @@ namespace registry
|
||||
template<class>
|
||||
inline constexpr bool always_false_v = false;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct on_exit
|
||||
{
|
||||
std::function<void()> f;
|
||||
|
||||
on_exit(std::function<void()> f) :
|
||||
f{ std::move(f) } {}
|
||||
~on_exit() { f(); }
|
||||
};
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
inline const wchar_t* getScopeName(HKEY scope)
|
||||
{
|
||||
if (scope == HKEY_LOCAL_MACHINE)
|
||||
{
|
||||
return L"HKLM";
|
||||
}
|
||||
else if (scope == HKEY_CURRENT_USER)
|
||||
{
|
||||
return L"HKCU";
|
||||
}
|
||||
else if (scope == HKEY_CLASSES_ROOT)
|
||||
{
|
||||
return L"HKCR";
|
||||
}
|
||||
else
|
||||
{
|
||||
return L"HK??";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ValueChange
|
||||
{
|
||||
using value_t = std::variant<DWORD, std::wstring>;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace PowerToys.DSC.UnitTests.SettingsResourceTests;
|
||||
public abstract class SettingsResourceModuleTest<TSettingsConfig> : BaseDscTest
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
private readonly SettingsUtils _settingsUtils = new();
|
||||
private readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
|
||||
private TSettingsConfig _originalSettings;
|
||||
|
||||
protected TSettingsConfig DefaultSettings => new();
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace PowerToys.DSC.Models.FunctionData;
|
||||
public sealed class SettingsFunctionData<TSettingsConfig> : BaseFunctionData, ISettingsFunctionData
|
||||
where TSettingsConfig : ISettingsConfig, new()
|
||||
{
|
||||
private static readonly SettingsUtils _settingsUtils = new();
|
||||
private static readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
|
||||
private static readonly TSettingsConfig _settingsConfig = new();
|
||||
|
||||
private readonly SettingsResourceObject<TSettingsConfig> _input;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 AdvancedPaste.Converters;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Windows.UI;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.ConvertersTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class HexColorToColorConverterTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestConvert_ValidSixDigitHex_ReturnsColor()
|
||||
{
|
||||
Color? result = HexColorConverterHelper.ConvertHexColorToRgb("#FFBFAB");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var color = (Windows.UI.Color)result;
|
||||
Assert.AreEqual(255, color.R);
|
||||
Assert.AreEqual(191, color.G);
|
||||
Assert.AreEqual(171, color.B);
|
||||
Assert.AreEqual(255, color.A);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConvert_ValidThreeDigitHex_ReturnsColor()
|
||||
{
|
||||
Color? result = HexColorConverterHelper.ConvertHexColorToRgb("#abc");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var color = (Windows.UI.Color)result;
|
||||
|
||||
// #abc should expand to #aabbcc
|
||||
Assert.AreEqual(170, color.R); // 0xaa
|
||||
Assert.AreEqual(187, color.G); // 0xbb
|
||||
Assert.AreEqual(204, color.B); // 0xcc
|
||||
Assert.AreEqual(255, color.A);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConvert_NullOrEmpty_ReturnsNull()
|
||||
{
|
||||
Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(null));
|
||||
Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(string.Empty));
|
||||
Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(" "));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConvert_InvalidHex_ReturnsNull()
|
||||
{
|
||||
Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb("#GGGGGG"));
|
||||
Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb("#12345"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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 AdvancedPaste.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.HelpersTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class ClipboardItemHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow("#FFBFAB", true)]
|
||||
[DataRow("#000000", true)]
|
||||
[DataRow("#FFFFFF", true)]
|
||||
[DataRow("#fff", true)]
|
||||
[DataRow("#abc", true)]
|
||||
[DataRow("#123456", true)]
|
||||
[DataRow("#AbCdEf", true)]
|
||||
[DataRow("FFBFAB", false)] // Missing #
|
||||
[DataRow("#GGGGGG", false)] // Invalid hex characters
|
||||
[DataRow("#12345", false)] // Wrong length
|
||||
[DataRow("#1234567", false)] // Too long
|
||||
[DataRow("", false)]
|
||||
[DataRow(null, false)]
|
||||
[DataRow(" #FFF ", true)] // Whitespace should be trimmed
|
||||
[DataRow("Not a color", false)]
|
||||
[DataRow("#", false)]
|
||||
[DataRow("##FFFFFF", false)]
|
||||
public void TestIsRgbHexColor(string input, bool expected)
|
||||
{
|
||||
bool result = ClipboardItemHelper.IsRgbHexColor(input);
|
||||
Assert.AreEqual(expected, result, $"IsRgbHexColor(\"{input}\") should return {expected}");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<converters:DateTimeToFriendlyStringConverter x:Key="DateTimeToFriendlyStringConverter" />
|
||||
<converters:HexColorToBrushConverter x:Key="HexColorToBrushConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid ColumnSpacing="12">
|
||||
@@ -25,6 +26,26 @@
|
||||
Source="{x:Bind ClipboardItem.Image, Mode=OneWay}"
|
||||
Stretch="UniformToFill"
|
||||
Visibility="{x:Bind HasImage, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<!-- Color preview with text -->
|
||||
<Grid Visibility="{x:Bind HasColor, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Ellipse
|
||||
Grid.Column="0"
|
||||
Width="8"
|
||||
Height="8"
|
||||
Margin="8,0,8,0"
|
||||
Fill="{x:Bind ClipboardItem.Content, Mode=OneWay, Converter={StaticResource HexColorToBrushConverter}}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ClipboardItem.Content, Mode=OneWay}"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
<!-- Text preview -->
|
||||
<TextBlock
|
||||
Margin="8,0,0,0"
|
||||
|
||||
@@ -38,9 +38,11 @@ namespace AdvancedPaste.Controls
|
||||
|
||||
public bool HasImage => ContentImage is not null;
|
||||
|
||||
public bool HasText => !string.IsNullOrEmpty(ContentText) && !HasImage;
|
||||
public bool HasText => !string.IsNullOrEmpty(ContentText) && !HasImage && !HasColor;
|
||||
|
||||
public bool HasGlyph => !HasImage && !HasText && !string.IsNullOrEmpty(IconGlyph);
|
||||
public bool HasGlyph => !HasImage && !HasText && !HasColor && !string.IsNullOrEmpty(IconGlyph);
|
||||
|
||||
public bool HasColor => ClipboardItemHelper.IsRgbHexColor(ContentText);
|
||||
|
||||
public ClipboardHistoryItemPreviewControl()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace AdvancedPaste.Converters
|
||||
{
|
||||
public static class HexColorConverterHelper
|
||||
{
|
||||
public static Windows.UI.Color? ConvertHexColorToRgb(string hexColor)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove # if present
|
||||
var cleanHex = hexColor.TrimStart('#');
|
||||
|
||||
// Expand 3-digit hex to 6-digit (#ABC -> #AABBCC)
|
||||
if (cleanHex.Length == 3)
|
||||
{
|
||||
cleanHex = $"{cleanHex[0]}{cleanHex[0]}{cleanHex[1]}{cleanHex[1]}{cleanHex[2]}{cleanHex[2]}";
|
||||
}
|
||||
|
||||
if (cleanHex.Length == 6)
|
||||
{
|
||||
var r = System.Convert.ToByte(cleanHex.Substring(0, 2), 16);
|
||||
var g = System.Convert.ToByte(cleanHex.Substring(2, 2), 16);
|
||||
var b = System.Convert.ToByte(cleanHex.Substring(4, 2), 16);
|
||||
|
||||
return Windows.UI.Color.FromArgb(255, r, g, b);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid color format - return null
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace AdvancedPaste.Converters
|
||||
{
|
||||
public sealed partial class HexColorToBrushConverter : IValueConverter
|
||||
{
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is not string hexColor || string.IsNullOrWhiteSpace(hexColor))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Windows.UI.Color? color = HexColorConverterHelper.ConvertHexColorToRgb(hexColor);
|
||||
|
||||
return color != null ? new SolidColorBrush((Windows.UI.Color)color) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,20 +198,14 @@ namespace AdvancedPaste.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async void ClipboardHistory_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
|
||||
private void ClipboardHistory_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
|
||||
{
|
||||
if (args.InvokedItem is ClipboardItem item)
|
||||
if (args.InvokedItem is ClipboardItem item && item.Item is not null)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemClicked());
|
||||
if (!string.IsNullOrEmpty(item.Content))
|
||||
{
|
||||
ClipboardHelper.SetTextContent(item.Content);
|
||||
}
|
||||
else if (item.Image is not null)
|
||||
{
|
||||
RandomAccessStreamReference image = await item.Item.Content.GetBitmapAsync();
|
||||
ClipboardHelper.SetImageContent(image);
|
||||
}
|
||||
|
||||
// Use SetHistoryItemAsContent to set the clipboard content without creating a new history entry
|
||||
Clipboard.SetHistoryItemAsContent(item.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Models;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
@@ -10,8 +11,11 @@ using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace AdvancedPaste.Helpers
|
||||
{
|
||||
internal static class ClipboardItemHelper
|
||||
internal static partial class ClipboardItemHelper
|
||||
{
|
||||
// Compiled regex for better performance when checking multiple clipboard items
|
||||
private static readonly Regex HexColorRegex = HexColorCompiledRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ClipboardItem from current clipboard data.
|
||||
/// </summary>
|
||||
@@ -55,6 +59,31 @@ namespace AdvancedPaste.Helpers
|
||||
return clipboardItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if text is a valid RGB hex color (e.g., #FFBFAB or #fff).
|
||||
/// </summary>
|
||||
public static bool IsRgbHexColor(string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string trimmedText = text.Trim();
|
||||
if (trimmedText.Length > 7)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trimmedText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match #RGB or #RRGGBB format (case-insensitive)
|
||||
return HexColorRegex.IsMatch(trimmedText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BitmapImage from clipboard data.
|
||||
/// </summary>
|
||||
@@ -80,5 +109,8 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$")]
|
||||
private static partial Regex HexColorCompiledRegex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,13 +312,39 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
void read_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
void read_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
const auto settingsObject = settings.get_raw_json();
|
||||
|
||||
// Migrate Paste As Plain text shortcut
|
||||
Hotkey old_paste_as_plain_hotkey;
|
||||
bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey);
|
||||
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
|
||||
m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject);
|
||||
|
||||
if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED))
|
||||
{
|
||||
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
|
||||
}
|
||||
else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED))
|
||||
{
|
||||
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_is_ai_enabled = false;
|
||||
}
|
||||
|
||||
if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
|
||||
{
|
||||
m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (old_data_migrated)
|
||||
{
|
||||
m_paste_as_plain_hotkey = old_paste_as_plain_hotkey;
|
||||
@@ -405,31 +431,6 @@ private:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
|
||||
m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject);
|
||||
|
||||
if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED))
|
||||
{
|
||||
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
|
||||
}
|
||||
else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED))
|
||||
{
|
||||
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_is_ai_enabled = false;
|
||||
}
|
||||
|
||||
if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
|
||||
{
|
||||
m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Hosts.Settings
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
var defaultSettings = new HostsProperties();
|
||||
ShowStartupWarning = defaultSettings.ShowStartupWarning;
|
||||
LoopbackDuplicates = defaultSettings.LoopbackDuplicates;
|
||||
|
||||
@@ -50,6 +50,7 @@ enum class ScheduleMode
|
||||
Off,
|
||||
FixedHours,
|
||||
SunsetToSunrise,
|
||||
FollowNightLight,
|
||||
// add more later
|
||||
};
|
||||
|
||||
@@ -61,6 +62,8 @@ inline std::wstring ToString(ScheduleMode mode)
|
||||
return L"SunsetToSunrise";
|
||||
case ScheduleMode::FixedHours:
|
||||
return L"FixedHours";
|
||||
case ScheduleMode::FollowNightLight:
|
||||
return L"FollowNightLight";
|
||||
default:
|
||||
return L"Off";
|
||||
}
|
||||
@@ -72,6 +75,8 @@ inline ScheduleMode FromString(const std::wstring& str)
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
if (str == L"FixedHours")
|
||||
return ScheduleMode::FixedHours;
|
||||
if (str == L"FollowNightLight")
|
||||
return ScheduleMode::FollowNightLight;
|
||||
return ScheduleMode::Off;
|
||||
}
|
||||
|
||||
@@ -167,7 +172,9 @@ public:
|
||||
ToString(g_settings.m_scheduleMode),
|
||||
{ { L"Off", L"Disable the schedule" },
|
||||
{ L"FixedHours", L"Set hours manually" },
|
||||
{ L"SunsetToSunrise", L"Use sunrise/sunset times" } });
|
||||
{ L"SunsetToSunrise", L"Use sunrise/sunset times" },
|
||||
{ L"FollowNightLight", L"Follow Windows Night Light state" }
|
||||
});
|
||||
|
||||
// Integer spinners
|
||||
settings.add_int_spinner(
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
#include <utils/logger_helper.h>
|
||||
#include "LightSwitchStateManager.h"
|
||||
#include <LightSwitchUtils.h>
|
||||
#include <NightLightRegistryObserver.h>
|
||||
|
||||
SERVICE_STATUS g_ServiceStatus = {};
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
||||
HANDLE g_ServiceStopEvent = nullptr;
|
||||
static LightSwitchStateManager* g_stateManagerPtr = nullptr;
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
||||
@@ -168,7 +170,15 @@ static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateMan
|
||||
}
|
||||
|
||||
// Use shared helper (handles wraparound logic)
|
||||
bool shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
|
||||
bool shouldBeLight = false;
|
||||
if (s.scheduleMode == ScheduleMode::FollowNightLight)
|
||||
{
|
||||
shouldBeLight = !IsNightLightEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
|
||||
}
|
||||
|
||||
// Compare current system/apps theme
|
||||
bool currentSystemLight = GetCurrentSystemTheme();
|
||||
@@ -199,15 +209,40 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
// Initialization
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
static LightSwitchStateManager stateManager;
|
||||
g_stateManagerPtr = &stateManager;
|
||||
|
||||
LightSwitchSettings::instance().InitFileWatcher();
|
||||
|
||||
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent();
|
||||
|
||||
static std::unique_ptr<NightLightRegistryObserver> g_nightLightWatcher;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// after loading settings:
|
||||
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
|
||||
|
||||
if (nightLightNeeded && !g_nightLightWatcher)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
|
||||
|
||||
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
|
||||
HKEY_CURRENT_USER,
|
||||
NIGHT_LIGHT_REGISTRY_PATH,
|
||||
[]() {
|
||||
if (g_stateManagerPtr)
|
||||
g_stateManagerPtr->OnNightLightChange();
|
||||
});
|
||||
}
|
||||
else if (!nightLightNeeded && g_nightLightWatcher)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
|
||||
g_nightLightWatcher->Stop();
|
||||
g_nightLightWatcher.reset();
|
||||
}
|
||||
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
@@ -274,6 +309,31 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
ResetEvent(hSettingsChanged);
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
stateManager.OnSettingsChanged();
|
||||
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
|
||||
|
||||
if (nightLightNeeded && !g_nightLightWatcher)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
|
||||
|
||||
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
|
||||
HKEY_CURRENT_USER,
|
||||
NIGHT_LIGHT_REGISTRY_PATH,
|
||||
[]() {
|
||||
if (g_stateManagerPtr)
|
||||
g_stateManagerPtr->OnNightLightChange();
|
||||
});
|
||||
|
||||
stateManager.OnNightLightChange();
|
||||
}
|
||||
else if (!nightLightNeeded && g_nightLightWatcher)
|
||||
{
|
||||
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
|
||||
g_nightLightWatcher->Stop();
|
||||
g_nightLightWatcher.reset();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -285,6 +345,11 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
CloseHandle(hManualOverride);
|
||||
if (hParent)
|
||||
CloseHandle(hParent);
|
||||
if (g_nightLightWatcher)
|
||||
{
|
||||
g_nightLightWatcher->Stop();
|
||||
g_nightLightWatcher.reset();
|
||||
}
|
||||
|
||||
Logger::info(L"[LightSwitchService] Worker thread exiting cleanly.");
|
||||
return 0;
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<ClCompile Include="LightSwitchService.cpp" />
|
||||
<ClCompile Include="LightSwitchSettings.cpp" />
|
||||
<ClCompile Include="LightSwitchStateManager.cpp" />
|
||||
<ClCompile Include="NightLightRegistryObserver.cpp" />
|
||||
<ClCompile Include="SettingsConstants.cpp" />
|
||||
<ClCompile Include="ThemeHelper.cpp" />
|
||||
<ClCompile Include="ThemeScheduler.cpp" />
|
||||
@@ -88,6 +89,7 @@
|
||||
<ClInclude Include="LightSwitchSettings.h" />
|
||||
<ClInclude Include="LightSwitchStateManager.h" />
|
||||
<ClInclude Include="LightSwitchUtils.h" />
|
||||
<ClInclude Include="NightLightRegistryObserver.h" />
|
||||
<ClInclude Include="SettingsConstants.h" />
|
||||
<ClInclude Include="SettingsObserver.h" />
|
||||
<ClInclude Include="ThemeHelper.h" />
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<ClCompile Include="LightSwitchStateManager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NightLightRegistryObserver.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ThemeScheduler.h">
|
||||
@@ -62,6 +65,9 @@
|
||||
<ClInclude Include="LightSwitchUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NightLightRegistryObserver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
|
||||
@@ -19,7 +19,8 @@ enum class ScheduleMode
|
||||
{
|
||||
Off,
|
||||
FixedHours,
|
||||
SunsetToSunrise
|
||||
SunsetToSunrise,
|
||||
FollowNightLight,
|
||||
// Add more in the future
|
||||
};
|
||||
|
||||
@@ -31,6 +32,8 @@ inline std::wstring ToString(ScheduleMode mode)
|
||||
return L"FixedHours";
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
case ScheduleMode::FollowNightLight:
|
||||
return L"FollowNightLight";
|
||||
default:
|
||||
return L"Off";
|
||||
}
|
||||
@@ -42,6 +45,8 @@ inline ScheduleMode FromString(const std::wstring& str)
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
if (str == L"FixedHours")
|
||||
return ScheduleMode::FixedHours;
|
||||
if (str == L"FollowNightLight")
|
||||
return ScheduleMode::FollowNightLight;
|
||||
else
|
||||
return ScheduleMode::Off;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ void LightSwitchStateManager::OnSettingsChanged()
|
||||
void LightSwitchStateManager::OnTick(int currentMinutes)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_stateMutex);
|
||||
EvaluateAndApplyIfNeeded();
|
||||
if (_state.lastAppliedMode != ScheduleMode::FollowNightLight)
|
||||
{
|
||||
EvaluateAndApplyIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when manual override is triggered
|
||||
@@ -49,8 +52,38 @@ void LightSwitchStateManager::OnManualOverride()
|
||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||
|
||||
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
|
||||
(_state.isSystemLightActive ? L"light" : L"dark"),
|
||||
(_state.isAppsLightActive ? L"light" : L"dark"));
|
||||
(_state.isSystemLightActive ? L"light" : L"dark"),
|
||||
(_state.isAppsLightActive ? L"light" : L"dark"));
|
||||
}
|
||||
|
||||
EvaluateAndApplyIfNeeded();
|
||||
}
|
||||
|
||||
// Runs with the registry observer detects a change in Night Light settings.
|
||||
void LightSwitchStateManager::OnNightLightChange()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_stateMutex);
|
||||
|
||||
bool newNightLightState = IsNightLightEnabled();
|
||||
|
||||
// In Follow Night Light mode, treat a Night Light toggle as a boundary
|
||||
if (_state.lastAppliedMode == ScheduleMode::FollowNightLight && _state.isManualOverride)
|
||||
{
|
||||
Logger::info(L"[LightSwitchStateManager] Night Light changed while manual override active; "
|
||||
L"treating as a boundary and clearing manual override.");
|
||||
_state.isManualOverride = false;
|
||||
}
|
||||
|
||||
if (newNightLightState != _state.isNightLightActive)
|
||||
{
|
||||
Logger::info(L"[LightSwitchStateManager] Night Light toggled to {}",
|
||||
newNightLightState ? L"ON" : L"OFF");
|
||||
|
||||
_state.isNightLightActive = newNightLightState;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::debug(L"[LightSwitchStateManager] Night Light change event fired, but no actual change.");
|
||||
}
|
||||
|
||||
EvaluateAndApplyIfNeeded();
|
||||
@@ -77,9 +110,9 @@ void LightSwitchStateManager::SyncInitialThemeState()
|
||||
_state.isSystemLightActive = GetCurrentSystemTheme();
|
||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
|
||||
_state.isSystemLightActive ? L"light" : L"dark");
|
||||
_state.isSystemLightActive ? L"light" : L"dark");
|
||||
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
|
||||
_state.isAppsLightActive ? L"light" : L"dark");
|
||||
_state.isAppsLightActive ? L"light" : L"dark");
|
||||
}
|
||||
|
||||
static std::pair<int, int> update_sun_times(auto& settings)
|
||||
@@ -194,7 +227,15 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
||||
|
||||
_state.lastAppliedMode = _currentSettings.scheduleMode;
|
||||
|
||||
bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
|
||||
bool shouldBeLight = false;
|
||||
if (_currentSettings.scheduleMode == ScheduleMode::FollowNightLight)
|
||||
{
|
||||
shouldBeLight = !_state.isNightLightActive;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
|
||||
}
|
||||
|
||||
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
|
||||
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
|
||||
@@ -227,6 +268,3 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
||||
|
||||
_state.lastTickMinutes = now;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ struct LightSwitchState
|
||||
bool isManualOverride = false;
|
||||
bool isSystemLightActive = false;
|
||||
bool isAppsLightActive = false;
|
||||
bool isNightLightActive = false;
|
||||
int lastEvaluatedDay = -1;
|
||||
int lastTickMinutes = -1;
|
||||
|
||||
@@ -32,6 +33,9 @@ public:
|
||||
// Called when manual override is toggled (via shortcut or system change).
|
||||
void OnManualOverride();
|
||||
|
||||
// Called when night light changes in windows settings
|
||||
void OnNightLightChange();
|
||||
|
||||
// Initial sync at startup to align internal state with system theme
|
||||
void SyncInitialThemeState();
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "NightLightRegistryObserver.h"
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
#include <wtypes.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class NightLightRegistryObserver
|
||||
{
|
||||
public:
|
||||
NightLightRegistryObserver(HKEY root, const std::wstring& subkey, std::function<void()> callback) :
|
||||
_root(root), _subkey(subkey), _callback(std::move(callback)), _stop(false)
|
||||
{
|
||||
_thread = std::thread([this]() { this->Run(); });
|
||||
}
|
||||
|
||||
~NightLightRegistryObserver()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
_stop = true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_event)
|
||||
SetEvent(_event);
|
||||
}
|
||||
|
||||
if (_thread.joinable())
|
||||
_thread.join();
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_hKey)
|
||||
{
|
||||
RegCloseKey(_hKey);
|
||||
_hKey = nullptr;
|
||||
}
|
||||
|
||||
if (_event)
|
||||
{
|
||||
CloseHandle(_event);
|
||||
_event = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void Run()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (RegOpenKeyExW(_root, _subkey.c_str(), 0, KEY_NOTIFY, &_hKey) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!_event)
|
||||
{
|
||||
RegCloseKey(_hKey);
|
||||
_hKey = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (!_stop)
|
||||
{
|
||||
HKEY hKeyLocal = nullptr;
|
||||
HANDLE eventLocal = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_stop)
|
||||
break;
|
||||
|
||||
hKeyLocal = _hKey;
|
||||
eventLocal = _event;
|
||||
}
|
||||
|
||||
if (!hKeyLocal || !eventLocal)
|
||||
break;
|
||||
|
||||
if (_stop)
|
||||
break;
|
||||
|
||||
if (RegNotifyChangeKeyValue(hKeyLocal, FALSE, REG_NOTIFY_CHANGE_LAST_SET, eventLocal, TRUE) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
DWORD wait = WaitForSingleObject(eventLocal, INFINITE);
|
||||
if (_stop || wait == WAIT_FAILED)
|
||||
break;
|
||||
|
||||
ResetEvent(eventLocal);
|
||||
|
||||
if (!_stop && _callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
_callback();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_hKey)
|
||||
{
|
||||
RegCloseKey(_hKey);
|
||||
_hKey = nullptr;
|
||||
}
|
||||
|
||||
if (_event)
|
||||
{
|
||||
CloseHandle(_event);
|
||||
_event = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HKEY _root;
|
||||
std::wstring _subkey;
|
||||
std::function<void()> _callback;
|
||||
HANDLE _event = nullptr;
|
||||
HKEY _hKey = nullptr;
|
||||
std::thread _thread;
|
||||
std::atomic<bool> _stop;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
@@ -11,4 +11,7 @@ enum class SettingId
|
||||
Sunset_Offset,
|
||||
ChangeSystem,
|
||||
ChangeApps
|
||||
};
|
||||
};
|
||||
|
||||
constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||
constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <logger/logger.h>
|
||||
#include <utils/logger_helper.h>
|
||||
#include "ThemeHelper.h"
|
||||
#include <SettingsConstants.h>
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
@@ -10,7 +11,7 @@ static void ResetColorPrevalence()
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
PERSONALIZATION_REGISTRY_PATH,
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
@@ -31,7 +32,7 @@ void SetAppsTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
PERSONALIZATION_REGISTRY_PATH,
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
@@ -50,7 +51,7 @@ void SetSystemTheme(bool mode)
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
PERSONALIZATION_REGISTRY_PATH,
|
||||
0,
|
||||
KEY_SET_VALUE,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
@@ -79,7 +80,7 @@ bool GetCurrentSystemTheme()
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
PERSONALIZATION_REGISTRY_PATH,
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
@@ -98,7 +99,7 @@ bool GetCurrentAppsTheme()
|
||||
DWORD size = sizeof(value);
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
PERSONALIZATION_REGISTRY_PATH,
|
||||
0,
|
||||
KEY_READ,
|
||||
&hKey) == ERROR_SUCCESS)
|
||||
@@ -109,3 +110,30 @@ bool GetCurrentAppsTheme()
|
||||
|
||||
return value == 1; // true = light, false = dark
|
||||
}
|
||||
|
||||
bool IsNightLightEnabled()
|
||||
{
|
||||
HKEY hKey;
|
||||
const wchar_t* path = NIGHT_LIGHT_REGISTRY_PATH;
|
||||
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
// RegGetValueW will set size to the size of the data and we expect that to be at least 25 bytes (we need to access bytes 23 and 24)
|
||||
DWORD size = 0;
|
||||
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, nullptr, &size) != ERROR_SUCCESS || size < 25)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<BYTE> data(size);
|
||||
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, data.data(), &size) != ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return data[23] == 0x10 && data[24] == 0x00;
|
||||
}
|
||||
@@ -3,3 +3,4 @@ void SetSystemTheme(bool dark);
|
||||
void SetAppsTheme(bool dark);
|
||||
bool GetCurrentSystemTheme();
|
||||
bool GetCurrentAppsTheme();
|
||||
bool IsNightLightEnabled();
|
||||
@@ -1,14 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="NuGet">
|
||||
<!-- Tell NuGet this is PackageReference style -->
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
|
||||
<!-- Tell NuGet we're a native project -->
|
||||
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
|
||||
|
||||
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
|
||||
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
|
||||
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
||||
@@ -31,6 +33,11 @@
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Microsoft.Windows.ImplementationLibrary" GeneratePathProperty="true" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
@@ -38,7 +45,6 @@
|
||||
<DesktopCompatible>true</DesktopCompatible>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
@@ -118,9 +124,6 @@
|
||||
<WarnAsError>true</WarnAsError>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
|
||||
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
|
||||
@@ -142,42 +145,5 @@
|
||||
<ResourceCompile Include="PowerToys.MeasureToolCore.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
</Project>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -73,6 +73,13 @@
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
|
||||
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<BuildProject>true</BuildProject>
|
||||
</ProjectReference>
|
||||
<CsWinRTInputs Include="$(OutputPath)\PowerToys.MeasureToolCore.winmd" />
|
||||
<None Include="$(OutputPath)\PowerToys.MeasureToolCore.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MeasureToolUI
|
||||
{
|
||||
public sealed class Settings
|
||||
{
|
||||
private static readonly SettingsUtils ModuleSettings = new();
|
||||
private static readonly SettingsUtils ModuleSettings = SettingsUtils.Default;
|
||||
|
||||
public MeasureToolMeasureStyle DefaultMeasureStyle
|
||||
{
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="NuGet">
|
||||
<!-- Tell NuGet this is PackageReference style -->
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
|
||||
<!-- Tell NuGet we're a native project -->
|
||||
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
|
||||
|
||||
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
|
||||
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
|
||||
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{e94fd11c-0591-456f-899f-efc0ca548336}</ProjectGuid>
|
||||
@@ -20,9 +23,12 @@
|
||||
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
|
||||
<!-- Force NuGet to treat this project strictly as packages.config style -->
|
||||
<RestoreProjectStyle>packages.config</RestoreProjectStyle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK.Foundation" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true"/>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
@@ -127,18 +133,18 @@
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="FindMyMouse.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- Deduplicate WindowsAppRuntimeAutoInitializer.cpp (added twice via transitive imports causing LNK4042). Remove all then add exactly once. -->
|
||||
<ItemGroup Condition="'$(PkgMicrosoft_WindowsAppSDK)'!=''">
|
||||
<!-- Remove any transitive inclusion first -->
|
||||
<ClCompile Remove="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp" />
|
||||
<!-- Re-add once, but disable PCH because the SDK file doesn't include our pch.h -->
|
||||
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<Target Name="FixWinAppSDKAutoInitializer" BeforeTargets="ClCompile" AfterTargets="WindowsAppRuntimeAutoInitializer">
|
||||
<ItemGroup>
|
||||
<!-- Remove ALL injected versions of the file -->
|
||||
<ClCompile Remove="@(ClCompile)" Condition="'%(Filename)' == 'WindowsAppRuntimeAutoInitializer'" />
|
||||
|
||||
<!-- Add ONE copy back manually -->
|
||||
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK_Foundation)\include\WindowsAppRuntimeAutoInitializer.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="RemoveManagedWebView2CoreFromNativeOutDir" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<_ToDelete Include="$(OutDir)Microsoft.Web.WebView2.Core.dll" />
|
||||
@@ -148,38 +154,4 @@
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
|
||||
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
|
||||
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -53,7 +53,7 @@ internal sealed class SettingsHelper
|
||||
lock (this.LockObject)
|
||||
{
|
||||
{
|
||||
var settingsUtils = new SettingsUtils();
|
||||
var settingsUtils = SettingsUtils.Default;
|
||||
|
||||
// set this to 1 to disable retries
|
||||
var remainingRetries = 5;
|
||||
|
||||
@@ -617,6 +617,8 @@ namespace MouseUtils.UITests
|
||||
|
||||
private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false)
|
||||
{
|
||||
Session = RestartScopeExe("FindMyMouse,MouseHighlighter,MouseJump,MousePointerCrosshairs,CursorWrap");
|
||||
|
||||
// this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
internal Settings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
|
||||
_watcher = SettingsHelper.GetFileWatcher("MouseWithoutBorders", "settings.json", () =>
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace PowerOCR.Settings
|
||||
[ImportingConstructor]
|
||||
public UserSettings(Helpers.IThrottledActionInvoker throttledActionInvoker)
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
ActivationShortcut = new SettingItem<string>(DefaultActivationShortcut);
|
||||
PreferredLanguage = new SettingItem<string>(string.Empty);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace WorkspacesEditor.Utils
|
||||
public class Settings
|
||||
{
|
||||
private const string WorkspacesModuleName = "Workspaces";
|
||||
private static readonly SettingsUtils _settingsUtils = new();
|
||||
private static readonly SettingsUtils _settingsUtils = SettingsUtils.Default;
|
||||
|
||||
public static WorkspacesSettings ReadSettings()
|
||||
{
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace WorkspacesEditor.ViewModels
|
||||
_orderByIndex = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||
settings.Properties.SortBy = (WorkspacesProperties.SortByProperty)value;
|
||||
settings.Save(new SettingsUtils());
|
||||
settings.Save(SettingsUtils.Default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Awake.Core
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_stateQueue = [];
|
||||
ModuleSettings = new SettingsUtils();
|
||||
ModuleSettings = SettingsUtils.Default;
|
||||
}
|
||||
|
||||
internal static void StartMonitor()
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Awake
|
||||
|
||||
private static async Task<int> Main(string[] args)
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
|
||||
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
@@ -16,6 +17,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
{
|
||||
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
||||
|
||||
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
|
||||
|
||||
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
|
||||
private CommandContextItemViewModel? _defaultCommandContextItemViewModel;
|
||||
|
||||
@@ -65,6 +68,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||
|
||||
public DataPackageView? DataPackage { get; private set; }
|
||||
|
||||
public List<IContextItemViewModel> AllCommands
|
||||
{
|
||||
get
|
||||
@@ -157,6 +162,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
// will never be able to load Hotkeys & aliases
|
||||
UpdateProperty(nameof(IsInitialized));
|
||||
|
||||
if (model is IExtendedAttributesProvider extendedAttributesProvider)
|
||||
{
|
||||
ExtendedAttributesProvider = new ExtensionObject<IExtendedAttributesProvider>(extendedAttributesProvider);
|
||||
var properties = extendedAttributesProvider.GetProperties();
|
||||
UpdateDataPackage(properties);
|
||||
}
|
||||
|
||||
Initialized |= InitializedState.Initialized;
|
||||
}
|
||||
|
||||
@@ -379,6 +391,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
UpdateProperty(nameof(SecondaryCommandName));
|
||||
UpdateProperty(nameof(HasMoreCommands));
|
||||
|
||||
break;
|
||||
case nameof(DataPackage):
|
||||
UpdateDataPackage(ExtendedAttributesProvider?.Unsafe?.GetProperties());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -431,6 +446,16 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
UpdateProperty(nameof(Icon));
|
||||
}
|
||||
|
||||
private void UpdateDataPackage(IDictionary<string, object?>? properties)
|
||||
{
|
||||
DataPackage =
|
||||
properties?.TryGetValue(WellKnownExtensionAttributes.DataPackage, out var dataPackageView) == true &&
|
||||
dataPackageView is DataPackageView view
|
||||
? view
|
||||
: null;
|
||||
UpdateProperty(nameof(DataPackage));
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
@@ -19,6 +19,8 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
|
||||
|
||||
public string Body { get; private set; } = string.Empty;
|
||||
|
||||
public ContentSize? Size { get; private set; } = ContentSize.Small;
|
||||
|
||||
// Metadata is an array of IDetailsElement,
|
||||
// where IDetailsElement = {IDetailsTags, IDetailsLink, IDetailsSeparator}
|
||||
public List<DetailsElementViewModel> Metadata { get; private set; } = [];
|
||||
@@ -40,6 +42,21 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
|
||||
UpdateProperty(nameof(Body));
|
||||
UpdateProperty(nameof(HeroImage));
|
||||
|
||||
if (model is IExtendedAttributesProvider provider)
|
||||
{
|
||||
if (provider.GetProperties()?.TryGetValue("Size", out var rawValue) == true)
|
||||
{
|
||||
if (rawValue is int sizeAsInt)
|
||||
{
|
||||
Size = (ContentSize)sizeAsInt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Size ??= ContentSize.Small;
|
||||
|
||||
UpdateProperty(nameof(Size));
|
||||
|
||||
var meta = model.Metadata;
|
||||
if (meta is not null)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
@@ -57,7 +58,7 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
||||
// because each call to GetProperties() is a cross process hop, and if you
|
||||
// marshal-by-value the property set, then you don't want to throw it away and
|
||||
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
|
||||
if (props?.TryGetValue("FontFamily", out var family) ?? false)
|
||||
if (props?.TryGetValue(WellKnownExtensionAttributes.FontFamily, out var family) ?? false)
|
||||
{
|
||||
FontFamily = family as string;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
|
||||
public string Section { get; private set; } = string.Empty;
|
||||
|
||||
public bool IsSectionOrSeparator { get; private set; }
|
||||
|
||||
public DetailsViewModel? Details { get; private set; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Details))]
|
||||
@@ -82,14 +84,18 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
}
|
||||
|
||||
UpdateTags(li.Tags);
|
||||
|
||||
Section = li.Section ?? string.Empty;
|
||||
|
||||
UpdateProperty(nameof(Section));
|
||||
IsSectionOrSeparator = IsSeparator(li);
|
||||
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
|
||||
|
||||
UpdateAccessibleName();
|
||||
}
|
||||
|
||||
private bool IsSeparator(IListItem item)
|
||||
{
|
||||
return item.Command is null;
|
||||
}
|
||||
|
||||
public override void SlowInitializeProperties()
|
||||
{
|
||||
base.SlowInitializeProperties();
|
||||
@@ -104,8 +110,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
{
|
||||
Details = new(extensionDetails, PageContext);
|
||||
Details.InitializeProperties();
|
||||
UpdateProperty(nameof(Details));
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
UpdateProperty(nameof(Details), nameof(HasDetails));
|
||||
}
|
||||
|
||||
AddShowDetailsCommands();
|
||||
@@ -135,14 +140,18 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
break;
|
||||
case nameof(model.Section):
|
||||
Section = model.Section ?? string.Empty;
|
||||
UpdateProperty(nameof(Section));
|
||||
IsSectionOrSeparator = IsSeparator(model);
|
||||
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
|
||||
break;
|
||||
case nameof(model.Details):
|
||||
case nameof(model.Command):
|
||||
IsSectionOrSeparator = IsSeparator(model);
|
||||
UpdateProperty(nameof(IsSectionOrSeparator));
|
||||
break;
|
||||
case nameof(Details):
|
||||
var extensionDetails = model.Details;
|
||||
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
|
||||
Details?.InitializeProperties();
|
||||
UpdateProperty(nameof(Details));
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
UpdateProperty(nameof(Details), nameof(HasDetails));
|
||||
UpdateShowDetailsCommand();
|
||||
break;
|
||||
case nameof(model.MoreCommands):
|
||||
@@ -194,8 +203,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
MoreCommands.Add(showDetailsContextItemViewModel);
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,8 +235,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
showDetailsContextItemViewModel.SlowInitializeProperties();
|
||||
MoreCommands.Add(showDetailsContextItemViewModel);
|
||||
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,4 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
public record DismissMessage()
|
||||
{
|
||||
}
|
||||
public record DismissMessage(bool ForceGoHome = false);
|
||||
|
||||
@@ -4,6 +4,4 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
public record NavigateBackMessage(bool FromBackspace = false)
|
||||
{
|
||||
}
|
||||
public record NavigateBackMessage(bool FromBackspace = false);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Used to navigate left in a grid view when pressing the Left arrow key in the SearchBox.
|
||||
/// </summary>
|
||||
public record NavigateLeftCommand;
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Used to navigate right in a grid view when pressing the Right arrow key in the SearchBox.
|
||||
/// </summary>
|
||||
public record NavigateRightCommand;
|
||||
@@ -3,13 +3,14 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public partial class SeparatorViewModel() :
|
||||
CommandItem,
|
||||
IContextItemViewModel,
|
||||
IFilterItemViewModel,
|
||||
ISeparatorContextItem,
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.CommandPalette.Extensions;
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ShellViewModel : ObservableObject,
|
||||
IDisposable,
|
||||
IRecipient<PerformCommandMessage>,
|
||||
IRecipient<HandleCommandResultMessage>
|
||||
{
|
||||
@@ -378,7 +379,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
{
|
||||
// Reset the palette to the main page and dismiss
|
||||
GoHome(withAnimation: false, focusSearch: false);
|
||||
WeakReferenceMessenger.Default.Send<DismissMessage>();
|
||||
WeakReferenceMessenger.Default.Send(new DismissMessage());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -398,7 +399,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
case CommandResultKind.Hide:
|
||||
{
|
||||
// Keep this page open, but hide the palette.
|
||||
WeakReferenceMessenger.Default.Send<DismissMessage>();
|
||||
WeakReferenceMessenger.Default.Send(new DismissMessage());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -460,4 +461,12 @@ public partial class ShellViewModel : ObservableObject,
|
||||
{
|
||||
_navigationCts?.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_handleInvokeTask?.Dispose();
|
||||
_navigationCts?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<!-- For MVVM Toolkit Partial Properties/AOT support -->
|
||||
<LangVersion>preview</LangVersion>
|
||||
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutputPath>
|
||||
<OutputPath>..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>$(RootNamespace).pri</ProjectPriFileName>
|
||||
|
||||
|
||||
<!-- Disable SA1313 for Primary Constructor fields conflict https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors -->
|
||||
<NoWarn>SA1313;</NoWarn>
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
@@ -8,13 +8,19 @@
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 rescap">
|
||||
|
||||
<!-- FOR PUBLISHING TO MICROSOFT STORE -->
|
||||
<!-- When you're ready to publish your extension to Microsoft Store,you'll need to
|
||||
change the values in the Identity & Properties tags below
|
||||
Name = replace with Microsoft Store's Package/Identity/Name
|
||||
Publisher = replace with Microsoft Store's Package/Identity/Publisher
|
||||
DisplayName = replace with the reserved name from Partner Center
|
||||
PublisherDisplayName = replace with Microsoft Store's Package/Properties/PublisherDisplayName
|
||||
Logo = Confirm that this image exist at the path
|
||||
-->
|
||||
<Identity
|
||||
Name="TemplateCmdPalExtension"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="0.0.1.0" />
|
||||
<!-- When you're ready to publish your extension, you'll need to change the
|
||||
Publisher= to match your own identity -->
|
||||
|
||||
<Properties>
|
||||
<DisplayName>TemplateDisplayName</DisplayName>
|
||||
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# Publication Setup
|
||||
|
||||
This folder contains tools to help you prepare your CmdPal extension for publication to the Microsoft Store and WinGet.
|
||||
|
||||
## Files and Folders in this Directory
|
||||
|
||||
### Scripts
|
||||
|
||||
- **`one-time-store-publishing-setup.ps1`** - Configure your project for Microsoft Store publishing (run once)
|
||||
- **`build-msix-bundles.ps1`** - Build MSIX packages and create bundles for Store submission
|
||||
- **`one-time-winget-publishing-setup.ps1`** - Configure your project for WinGet publishing (run once)
|
||||
|
||||
### Resource Folders
|
||||
|
||||
- **`microsoft-store-resources/`** - Contains files used for Microsoft Store publishing:
|
||||
- `bundle_mapping.txt` - Auto-generated file that maps MSIX files for bundle creation
|
||||
|
||||
- **`winget-resources/`** - Contains templates and scripts for WinGet publishing:
|
||||
- `build-exe.ps1` - Script to build standalone EXE installer
|
||||
- `setup-template.iss` - Inno Setup installer template
|
||||
- `release-extension.yml` - GitHub Actions workflow template (moved to `.github/workflows/` during setup)
|
||||
- `Backups/` - Backup copies of configuration files (created during setup)
|
||||
|
||||
## Microsoft Store Quick Start
|
||||
|
||||
1. Open PowerShell and navigate to the Publication folder:
|
||||
|
||||
```powershell
|
||||
cd <YourProject>\Publication
|
||||
```
|
||||
|
||||
2. Run the one-time setup script:
|
||||
|
||||
```powershell
|
||||
.\one-time-store-publishing-setup.ps1
|
||||
```
|
||||
|
||||
3. Follow the prompts to enter your Microsoft Store information from Partner Center:
|
||||
- Package Identity Name
|
||||
- Publisher Certificate
|
||||
- Display Name
|
||||
- Publisher Display Name
|
||||
|
||||
The script will update your `Package.appxmanifest` with Store-specific values.
|
||||
|
||||
4. Once configured, build your bundle:
|
||||
|
||||
```powershell
|
||||
.\build-msix-bundles.ps1
|
||||
```
|
||||
|
||||
This script will:
|
||||
- Build x64 and ARM64 MSIX packages
|
||||
- Automatically update `microsoft-store-resources\bundle_mapping.txt` with correct paths
|
||||
- Create a combined MSIX bundle
|
||||
- Display the bundle location when complete
|
||||
|
||||
5. Upload the resulting `.msixbundle` file from `microsoft-store-resources\` to Partner Center
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### makeappx.exe not found
|
||||
|
||||
The build script requires the Windows SDK. Install it via:
|
||||
|
||||
- Visual Studio Installer (Individual Components → Windows SDK)
|
||||
- [Standalone Windows SDK](https://developer.microsoft.com/windows/downloads/windows-sdk/)
|
||||
|
||||
### Build errors
|
||||
|
||||
Ensure you have:
|
||||
|
||||
- .NET 9.0 SDK installed
|
||||
- Windows SDK 10.0.26100.0 or compatible version
|
||||
- No other instances of Visual Studio building the project
|
||||
|
||||
### Bundle creation fails
|
||||
|
||||
Check that:
|
||||
|
||||
- Both x64 and ARM64 builds completed successfully
|
||||
- `microsoft-store-resources\bundle_mapping.txt` paths are correct (auto-updated by script)
|
||||
- No file locks on the MSIX files
|
||||
|
||||
## WinGet Quick Start
|
||||
|
||||
1. Open PowerShell and navigate to the Publication folder:
|
||||
|
||||
```powershell
|
||||
cd <YourProject>\Publication
|
||||
```
|
||||
|
||||
2. Run the one-time setup script:
|
||||
|
||||
```powershell
|
||||
.\one-time-winget-publishing-setup.ps1
|
||||
```
|
||||
|
||||
3. Follow the prompts to enter:
|
||||
- GitHub Repository URL (where releases will be published)
|
||||
- Developer/Publisher Name
|
||||
|
||||
The script will:
|
||||
- Configure `winget-resources\build-exe.ps1` with your extension details
|
||||
- Configure `winget-resources\setup-template.iss` with your extension information
|
||||
- Move `release-extension.yml` to `.github\workflows\` in your repository root
|
||||
|
||||
4. Commit and push changes to GitHub:
|
||||
|
||||
```powershell
|
||||
git add .
|
||||
git commit -m "Configure extension for WinGet publishing"
|
||||
git push
|
||||
```
|
||||
|
||||
5. Trigger the GitHub Action to build and release:
|
||||
|
||||
```powershell
|
||||
gh workflow run release-extension.yml --ref main -f "release_notes=**First Release of <ExtensionName> Extension for Command Palette**
|
||||
|
||||
The inaugural release of the <ExtensionName> for Command Palette..."
|
||||
```
|
||||
|
||||
Or create a release manually through the GitHub web interface.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Command Palette Extension Publishing Documentation](https://learn.microsoft.com/en-us/windows/powertoys/command-palette/publish-extension)
|
||||
- [Microsoft Store Publishing Guide](https://learn.microsoft.com/windows/apps/publish/)
|
||||
@@ -0,0 +1,570 @@
|
||||
# Build MSIX Bundles Script for CmdPal Extension
|
||||
# This script automates the process of building MSIX packages for x64 and ARM64 architectures
|
||||
# and creating an MSIX bundle for distribution
|
||||
# Version: 1.0
|
||||
|
||||
#Requires -Version 5.1
|
||||
|
||||
# Enable strict mode for better error detection
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " CmdPal Extension MSIX Builder" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Determine project root (parent of Publication folder)
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$projectName = Split-Path -Leaf $projectRoot
|
||||
|
||||
Write-Host "Project Configuration:" -ForegroundColor Yellow
|
||||
Write-Host " Project Root: $projectRoot" -ForegroundColor Gray
|
||||
Write-Host " Project Name: $projectName" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Verify we're in the right location
|
||||
$csprojPath = Join-Path $projectRoot "$projectName.csproj"
|
||||
$manifestPath = Join-Path $projectRoot "Package.appxmanifest"
|
||||
|
||||
if (-not (Test-Path $csprojPath)) {
|
||||
Write-Host "ERROR: Could not find .csproj file at: $csprojPath" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "This script must be run from the Publication folder within your project." -ForegroundColor Yellow
|
||||
Write-Host "Expected structure:" -ForegroundColor Gray
|
||||
Write-Host " <ProjectRoot>\" -ForegroundColor Gray
|
||||
Write-Host " <ProjectName>.csproj" -ForegroundColor Gray
|
||||
Write-Host " Publication\" -ForegroundColor Gray
|
||||
Write-Host " build-msix-bundles.ps1 (this script)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
Write-Host "ERROR: Could not find Package.appxmanifest at: $manifestPath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [OK] Project files validated" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Extract version from Package.appxmanifest
|
||||
Write-Host "Reading package information..." -ForegroundColor Cyan
|
||||
try {
|
||||
[xml]$manifest = Get-Content $manifestPath -ErrorAction Stop
|
||||
$packageName = $manifest.Package.Identity.Name
|
||||
$packageVersion = $manifest.Package.Identity.Version
|
||||
|
||||
Write-Host " Package Name: $packageName" -ForegroundColor White
|
||||
Write-Host " Version: $packageVersion" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Could not read Package.appxmanifest: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Ask user what to build
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Build Options" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "What would you like to build?" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " [1] x64 MSIX only" -ForegroundColor White
|
||||
Write-Host " [2] ARM64 MSIX only" -ForegroundColor White
|
||||
Write-Host " [3] Complete Bundle (x64 + ARM64 + Bundle file)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Enter your choice (1-3): " -ForegroundColor Yellow -NoNewline
|
||||
$buildChoice = Read-Host
|
||||
Write-Host ""
|
||||
|
||||
# Validate choice
|
||||
if ($buildChoice -notmatch '^[1-3]$') {
|
||||
Write-Host "ERROR: Invalid choice. Please enter 1, 2, or 3." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Determine what to build
|
||||
$buildX64 = $false
|
||||
$buildARM64 = $false
|
||||
$createBundle = $false
|
||||
|
||||
switch ($buildChoice) {
|
||||
"1" {
|
||||
$buildX64 = $true
|
||||
Write-Host "Building: x64 MSIX only" -ForegroundColor Cyan
|
||||
}
|
||||
"2" {
|
||||
$buildARM64 = $true
|
||||
Write-Host "Building: ARM64 MSIX only" -ForegroundColor Cyan
|
||||
}
|
||||
"3" {
|
||||
$buildX64 = $true
|
||||
$buildARM64 = $true
|
||||
$createBundle = $true
|
||||
Write-Host "Building: Complete Bundle (x64 + ARM64 + Bundle)" -ForegroundColor Cyan
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Clean previous builds (optional)
|
||||
Write-Host "Do you want to clean previous builds? (Y/N): " -ForegroundColor Yellow -NoNewline
|
||||
$cleanBuilds = Read-Host
|
||||
if ($cleanBuilds -match '^[Yy]') {
|
||||
Write-Host ""
|
||||
Write-Host "Cleaning previous builds..." -ForegroundColor Cyan
|
||||
|
||||
$appPackagesPath = Join-Path $projectRoot "AppPackages"
|
||||
if (Test-Path $appPackagesPath) {
|
||||
try {
|
||||
Remove-Item $appPackagesPath -Recurse -Force -ErrorAction Stop
|
||||
Write-Host " [OK] Cleaned AppPackages folder" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host " [WARNING] Could not clean AppPackages: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Clean old bundles in microsoft-store-resources folder
|
||||
$microsoftStoreResourcesPath = Join-Path $PSScriptRoot "microsoft-store-resources"
|
||||
if (Test-Path $microsoftStoreResourcesPath) {
|
||||
$oldBundles = Get-ChildItem $microsoftStoreResourcesPath -Filter "*.msixbundle" -ErrorAction SilentlyContinue
|
||||
if ($oldBundles) {
|
||||
foreach ($bundle in $oldBundles) {
|
||||
try {
|
||||
Remove-Item $bundle.FullName -Force -ErrorAction Stop
|
||||
Write-Host " [OK] Removed old bundle: $($bundle.Name)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host " [WARNING] Could not remove $($bundle.Name): $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Track built files for summary
|
||||
$builtFiles = @()
|
||||
$x64Msix = $null
|
||||
$arm64Msix = $null
|
||||
|
||||
# Build x64 MSIX (if requested)
|
||||
if ($buildX64) {
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Building x64 MSIX Package" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Running: dotnet build (x64)..." -ForegroundColor Yellow
|
||||
Write-Host "This may take a few seconds" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
$buildOutput = & dotnet build --configuration Release -p:GenerateAppxPackageOnBuild=true -p:Platform=x64 -p:AppxPackageDir="AppPackages\x64\" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: x64 build failed with exit code $LASTEXITCODE" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Build output:" -ForegroundColor Gray
|
||||
$buildOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
Pop-Location
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [SUCCESS] x64 build completed" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: x64 build failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# Build ARM64 MSIX (if requested)
|
||||
if ($buildARM64) {
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Building ARM64 MSIX Package" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Running: dotnet build (ARM64)..." -ForegroundColor Yellow
|
||||
Write-Host "This may take a few seconds" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
$buildOutput = & dotnet build --configuration Release -p:GenerateAppxPackageOnBuild=true -p:Platform=ARM64 -p:AppxPackageDir="AppPackages\ARM64\" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: ARM64 build failed with exit code $LASTEXITCODE" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Build output:" -ForegroundColor Gray
|
||||
$buildOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
Pop-Location
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [SUCCESS] ARM64 build completed" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: ARM64 build failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# Locate MSIX files (if bundle creation is needed or for summary)
|
||||
if ($createBundle -or $buildX64 -or $buildARM64) {
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Locating MSIX Files" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
$msixFiles = Get-ChildItem "AppPackages" -Recurse -Filter "*.msix" -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $msixFiles) {
|
||||
# Try alternate location
|
||||
Write-Host " MSIX files not found in AppPackages, checking bin folder..." -ForegroundColor Yellow
|
||||
$msixFiles = Get-ChildItem "bin" -Recurse -Filter "*.msix" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if ($buildX64 -and $buildARM64 -and $createBundle -and (-not $msixFiles -or $msixFiles.Count -lt 2)) {
|
||||
Write-Host "ERROR: Could not find both x64 and ARM64 MSIX files" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Expected files:" -ForegroundColor Gray
|
||||
Write-Host " - ${packageName}_${packageVersion}_x64.msix" -ForegroundColor Gray
|
||||
Write-Host " - ${packageName}_${packageVersion}_arm64.msix" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
if ($msixFiles) {
|
||||
Write-Host "Found files:" -ForegroundColor Yellow
|
||||
$msixFiles | ForEach-Object { Write-Host " - $($_.FullName)" -ForegroundColor Gray }
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($msixFiles) {
|
||||
Write-Host " Found MSIX files:" -ForegroundColor Green
|
||||
$msixFiles | ForEach-Object {
|
||||
$relativePath = $_.FullName -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
Write-Host " [OK] $relativePath" -ForegroundColor White
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Find specific x64 and ARM64 files
|
||||
if ($buildX64) {
|
||||
$x64Msix = $msixFiles | Where-Object { $_.Name -match "_x64\.msix$" } | Select-Object -First 1
|
||||
if ($x64Msix) {
|
||||
$builtFiles += $x64Msix.FullName
|
||||
}
|
||||
}
|
||||
|
||||
if ($buildARM64) {
|
||||
$arm64Msix = $msixFiles | Where-Object { $_.Name -match "_arm64\.msix$" } | Select-Object -First 1
|
||||
if ($arm64Msix) {
|
||||
$builtFiles += $arm64Msix.FullName
|
||||
}
|
||||
}
|
||||
|
||||
# Validate files for bundle creation
|
||||
if ($createBundle) {
|
||||
if (-not $x64Msix) {
|
||||
Write-Host "ERROR: Could not find x64 MSIX file" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not $arm64Msix) {
|
||||
Write-Host "ERROR: Could not find ARM64 MSIX file" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# Create bundle (if requested)
|
||||
if ($createBundle) {
|
||||
# Update bundle_mapping.txt
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Updating bundle_mapping.txt" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$microsoftStoreResourcesPath = Join-Path $PSScriptRoot "microsoft-store-resources"
|
||||
$bundleMappingPath = Join-Path $microsoftStoreResourcesPath "bundle_mapping.txt"
|
||||
|
||||
# Ensure microsoft-store-resources directory exists
|
||||
if (-not (Test-Path $microsoftStoreResourcesPath)) {
|
||||
Write-Host " Creating microsoft-store-resources folder..." -ForegroundColor Yellow
|
||||
try {
|
||||
New-Item -Path $microsoftStoreResourcesPath -ItemType Directory -Force | Out-Null
|
||||
Write-Host " [OK] Folder created" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not create folder: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Get relative paths from project root
|
||||
$x64RelativePath = $x64Msix.FullName -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
$arm64RelativePath = $arm64Msix.FullName -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
|
||||
# Create bundle mapping content
|
||||
$line1 = "`"$x64RelativePath`" `"$($x64Msix.Name)`""
|
||||
$line2 = "`"$arm64RelativePath`" `"$($arm64Msix.Name)`""
|
||||
$bundleMappingContent = "[Files]`r`n$line1`r`n$line2"
|
||||
|
||||
try {
|
||||
Set-Content -Path $bundleMappingPath -Value $bundleMappingContent -NoNewline -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] bundle_mapping.txt updated" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host " Content:" -ForegroundColor Gray
|
||||
Write-Host " [Files]" -ForegroundColor DarkGray
|
||||
Write-Host (' "' + $x64RelativePath + '" "' + $x64Msix.Name + '"') -ForegroundColor DarkGray
|
||||
Write-Host (' "' + $arm64RelativePath + '" "' + $arm64Msix.Name + '"') -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not update bundle_mapping.txt: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " Continuing with bundle creation..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Find makeappx.exe
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Creating MSIX Bundle" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Locating makeappx.exe..." -ForegroundColor Yellow
|
||||
|
||||
$arch = switch ($env:PROCESSOR_ARCHITECTURE) {
|
||||
"AMD64" { "x64" }
|
||||
"x86" { "x86" }
|
||||
"ARM64" { "arm64" }
|
||||
default { "x64" }
|
||||
}
|
||||
|
||||
Write-Host " Detected architecture: $arch" -ForegroundColor Gray
|
||||
|
||||
$makeappxPath = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\$arch\makeappx.exe" -ErrorAction SilentlyContinue |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $makeappxPath) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: makeappx.exe not found" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "makeappx.exe is part of the Windows SDK." -ForegroundColor Yellow
|
||||
Write-Host "Please install the Windows SDK from:" -ForegroundColor Yellow
|
||||
Write-Host " https://developer.microsoft.com/windows/downloads/windows-sdk/" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Or ensure the Windows SDK is installed with Visual Studio." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [OK] Found: $($makeappxPath.FullName)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Create bundle
|
||||
$bundleFileName = "${packageName}_${packageVersion}_Bundle.msixbundle"
|
||||
$bundleOutputPath = Join-Path $microsoftStoreResourcesPath $bundleFileName
|
||||
|
||||
Write-Host "Creating bundle: $bundleFileName" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
# Use absolute path to bundle_mapping.txt
|
||||
$bundleMappingAbsolute = Join-Path $microsoftStoreResourcesPath "bundle_mapping.txt"
|
||||
|
||||
# Verify the mapping file exists
|
||||
if (-not (Test-Path $bundleMappingAbsolute)) {
|
||||
Write-Host "ERROR: bundle_mapping.txt not found at: $bundleMappingAbsolute" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
$makeappxArgs = @(
|
||||
"bundle",
|
||||
"/v",
|
||||
"/f", "`"$bundleMappingAbsolute`"",
|
||||
"/p", "`"$bundleOutputPath`""
|
||||
)
|
||||
|
||||
Write-Host " Running: makeappx bundle /v /f `"$bundleMappingAbsolute`" /p `"$bundleOutputPath`"" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Run makeappx with proper quoting
|
||||
$bundleOutput = & $makeappxPath.FullName bundle /v /f $bundleMappingAbsolute /p $bundleOutputPath 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Bundle creation failed with exit code $LASTEXITCODE" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Output:" -ForegroundColor Gray
|
||||
$bundleOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
Write-Host ""
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [SUCCESS] Bundle created" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Bundle creation failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Pop-Location
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Verify bundle was created
|
||||
if (-not (Test-Path $bundleOutputPath)) {
|
||||
Write-Host "ERROR: Bundle file was not created at expected location" -ForegroundColor Red
|
||||
Write-Host " Expected: $bundleOutputPath" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Add bundle to built files
|
||||
$builtFiles += $bundleOutputPath
|
||||
}
|
||||
|
||||
# Final Summary
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host " BUILD COMPLETED SUCCESSFULLY!" -ForegroundColor Green
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Display what was built
|
||||
if ($buildChoice -eq "1") {
|
||||
Write-Host "Built: x64 MSIX Package" -ForegroundColor Cyan
|
||||
}
|
||||
elseif ($buildChoice -eq "2") {
|
||||
Write-Host "Built: ARM64 MSIX Package" -ForegroundColor Cyan
|
||||
}
|
||||
elseif ($buildChoice -eq "3") {
|
||||
Write-Host "Built: Complete Bundle (x64 + ARM64 + Bundle file)" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Package Information:" -ForegroundColor Yellow
|
||||
Write-Host " Name: $packageName" -ForegroundColor White
|
||||
Write-Host " Version: $packageVersion" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
# Display built files
|
||||
Write-Host "Built Files:" -ForegroundColor Yellow
|
||||
if ($x64Msix) {
|
||||
$x64Size = "{0:N2} MB" -f ((Get-Item $x64Msix.FullName).Length / 1MB)
|
||||
Write-Host " [x64 MSIX]" -ForegroundColor Cyan
|
||||
Write-Host " Location: $($x64Msix.FullName)" -ForegroundColor White
|
||||
Write-Host " Size: $x64Size" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($arm64Msix) {
|
||||
$arm64Size = "{0:N2} MB" -f ((Get-Item $arm64Msix.FullName).Length / 1MB)
|
||||
Write-Host " [ARM64 MSIX]" -ForegroundColor Cyan
|
||||
Write-Host " Location: $($arm64Msix.FullName)" -ForegroundColor White
|
||||
Write-Host " Size: $arm64Size" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($createBundle -and (Test-Path $bundleOutputPath)) {
|
||||
$bundleSize = "{0:N2} MB" -f ((Get-Item $bundleOutputPath).Length / 1MB)
|
||||
Write-Host " [MSIX Bundle]" -ForegroundColor Cyan
|
||||
Write-Host " Location: $bundleOutputPath" -ForegroundColor White
|
||||
Write-Host " Size: $bundleSize" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Display appropriate next steps based on what was built
|
||||
Write-Host "Next Steps:" -ForegroundColor Cyan
|
||||
if ($createBundle) {
|
||||
Write-Host " 1. Test the bundle by installing it locally" -ForegroundColor Gray
|
||||
Write-Host " 2. Upload the bundle to Microsoft Store Partner Center" -ForegroundColor Gray
|
||||
Write-Host " 3. Or distribute via other channels" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " 1. Test the MSIX package by installing it locally" -ForegroundColor Gray
|
||||
if ($buildX64) {
|
||||
Write-Host " 2. Build ARM64 package (option 2) or complete bundle (option 3)" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " 2. Build x64 package (option 1) or complete bundle (option 3)" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host " 3. Or distribute this individual package" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -0,0 +1,3 @@
|
||||
[Files]
|
||||
"AppPackages\x64\TemplateCmdPalExtension_0.0.1.0_x64_Test\TemplateCmdPalExtension_0.0.1.0_x64.msix" "TemplateCmdPalExtension_0.0.1.0_x64.msix"
|
||||
"AppPackages\ARM64\TemplateCmdPalExtension_0.0.1.0_arm64_Test\TemplateCmdPalExtension_0.0.1.0_arm64.msix" "TemplateCmdPalExtension_0.0.1.0_arm64.msix"
|
||||
@@ -0,0 +1,829 @@
|
||||
# One-Time Publication Setup Script for CmdPal Extension
|
||||
# This script collects Microsoft Store publication information and updates project files
|
||||
# Version: 1.1
|
||||
|
||||
#Requires -Version 5.1
|
||||
|
||||
# Enable strict mode for better error detection
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Microsoft Store Publication Setup" -ForegroundColor Cyan
|
||||
Write-Host " CmdPal Extension Publisher" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Path to the project files
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$csprojPath = Join-Path $projectRoot "TemplateCmdPalExtension.csproj"
|
||||
$manifestPath = Join-Path $projectRoot "Package.appxmanifest"
|
||||
|
||||
Write-Host "Validating project structure..." -ForegroundColor Cyan
|
||||
Write-Host " Project Root: $projectRoot" -ForegroundColor Gray
|
||||
|
||||
# Verify files exist with detailed error messages
|
||||
if (-not (Test-Path $csprojPath)) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Could not find .csproj file" -ForegroundColor Red
|
||||
Write-Host " Expected location: $csprojPath" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "This script must be run from the Publication folder within your project." -ForegroundColor Yellow
|
||||
Write-Host "Please navigate to: <YourProject>\Publication\" -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Could not find Package.appxmanifest file" -ForegroundColor Red
|
||||
Write-Host " Expected location: $manifestPath" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Your project structure may be incomplete or corrupted." -ForegroundColor Yellow
|
||||
Write-Host "Please ensure Package.appxmanifest exists in your project root." -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [OK] .csproj file found" -ForegroundColor Green
|
||||
Write-Host " [OK] Package.appxmanifest file found" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Create backup directory if it doesn't exist
|
||||
$backupDir = Join-Path $projectRoot "Publication\Backups"
|
||||
if (-not (Test-Path $backupDir)) {
|
||||
try {
|
||||
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created backup directory: $backupDir" -ForegroundColor Gray
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Could not create backup directory. Proceeding without backups." -ForegroundColor Yellow
|
||||
$backupDir = $null
|
||||
}
|
||||
}
|
||||
|
||||
# Create timestamped backups
|
||||
if ($backupDir) {
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
try {
|
||||
Copy-Item $csprojPath -Destination (Join-Path $backupDir "TemplateCmdPalExtension.csproj.$timestamp.bak") -Force
|
||||
Copy-Item $manifestPath -Destination (Join-Path $backupDir "Package.appxmanifest.$timestamp.bak") -Force
|
||||
Write-Host "Backup created: $timestamp" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Could not create backup files. Proceeding anyway." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "This script will collect information needed to publish your extension" -ForegroundColor White
|
||||
Write-Host "to the Microsoft Store. You can find this information in your" -ForegroundColor White
|
||||
Write-Host "Microsoft Partner Center account." -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "IMPORTANT: Have your Partner Center information ready before proceeding." -ForegroundColor Yellow
|
||||
Write-Host " - Package Identity Name" -ForegroundColor Gray
|
||||
Write-Host " - Publisher Certificate Name" -ForegroundColor Gray
|
||||
Write-Host " - Reserved App Name" -ForegroundColor Gray
|
||||
Write-Host " - Publisher Display Name" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "TIP: You can find this in Partner Center > Product Management > Product Identity" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Prompt to continue
|
||||
Write-Host "Do you want to continue? (Y/N): " -ForegroundColor Yellow -NoNewline
|
||||
$continue = Read-Host
|
||||
if ($continue -notmatch '^[Yy]') {
|
||||
Write-Host ""
|
||||
Write-Host "Setup cancelled by user." -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 0
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Function to validate package identity name format
|
||||
function Test-PackageIdentityName {
|
||||
param([string]$name)
|
||||
|
||||
# Package identity name rules:
|
||||
# - Between 3 and 50 characters
|
||||
# - Can contain: letters, numbers, periods, hyphens
|
||||
# - Cannot start/end with period
|
||||
# - Cannot have consecutive periods
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($name)) { return $false }
|
||||
if ($name.Length -lt 3 -or $name.Length -gt 50) { return $false }
|
||||
if ($name -match '^\.|\.$|\.\.') { return $false }
|
||||
if ($name -notmatch '^[a-zA-Z0-9.-]+$') { return $false }
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Function to validate publisher certificate format
|
||||
function Test-PublisherFormat {
|
||||
param([string]$publisher)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($publisher)) { return $false }
|
||||
|
||||
# Should start with CN= and follow distinguished name format
|
||||
if ($publisher -notmatch '^CN=.+') { return $false }
|
||||
|
||||
# Check for valid characters in DN
|
||||
if ($publisher -match '[<>]') { return $false }
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Function to validate display name
|
||||
function Test-DisplayName {
|
||||
param([string]$name)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($name)) { return $false }
|
||||
|
||||
# Display name should be reasonable length and not contain control characters
|
||||
if ($name.Length -lt 1 -or $name.Length -gt 256) { return $false }
|
||||
if ($name -match '[\x00-\x1F\x7F]') { return $false }
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Collect Microsoft Store Package Identity Name
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 1: Package Identity Name" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter Microsoft Store Package/Identity/Name:" -ForegroundColor Yellow
|
||||
Write-Host " Location: Partner Center > Product Identity > Package/Identity/Name" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Format Requirements:" -ForegroundColor Gray
|
||||
Write-Host " - 3-50 characters" -ForegroundColor DarkGray
|
||||
Write-Host " - Letters, numbers, periods, hyphens only" -ForegroundColor DarkGray
|
||||
Write-Host " - Cannot start/end with period or have consecutive periods" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host " Example: Publisher.MyAwesomeExtension" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$packageIdentityName = ""
|
||||
$maxAttempts = 3
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "Package Identity Name" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host ": " -NoNewline -ForegroundColor Yellow
|
||||
$packageIdentityName = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($packageIdentityName)) {
|
||||
Write-Host " [ERROR] Package Identity Name cannot be empty." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
elseif (-not (Test-PackageIdentityName $packageIdentityName)) {
|
||||
Write-Host " [ERROR] Invalid Package Identity Name format." -ForegroundColor Red
|
||||
Write-Host " Please ensure it meets the format requirements listed above." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] Package Identity Name accepted: $packageIdentityName" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Please verify your Partner Center information and try again." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Collect Microsoft Store Package Identity Publisher
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 2: Publisher Certificate" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter Microsoft Store Package/Identity/Publisher:" -ForegroundColor Yellow
|
||||
Write-Host " Location: Partner Center > Product Identity > Package/Identity/Publisher" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Format Requirements:" -ForegroundColor Gray
|
||||
Write-Host " - Must start with 'CN=' (Certificate Name)" -ForegroundColor DarkGray
|
||||
Write-Host " - This is the publisher certificate distinguished name" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host " Example: CN=12345678-1234-1234-1234-123456789012" -ForegroundColor DarkGray
|
||||
Write-Host " Example: CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$packageIdentityPublisher = ""
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "Publisher Certificate" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host ": " -NoNewline -ForegroundColor Yellow
|
||||
$packageIdentityPublisher = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($packageIdentityPublisher)) {
|
||||
Write-Host " [ERROR] Publisher cannot be empty." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
elseif (-not (Test-PublisherFormat $packageIdentityPublisher)) {
|
||||
if ($packageIdentityPublisher -notmatch '^CN=') {
|
||||
Write-Host " [ERROR] Publisher must start with 'CN='." -ForegroundColor Red
|
||||
Write-Host " Copy the entire string from Partner Center, including 'CN='." -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host " [ERROR] Invalid publisher format." -ForegroundColor Red
|
||||
Write-Host " Please ensure you copied the complete certificate name from Partner Center." -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] Publisher certificate accepted" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Please verify your Partner Center information and try again." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Collect Reserved Display Name
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 3: Display Name" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter the reserved Display Name from Partner Center:" -ForegroundColor Yellow
|
||||
Write-Host " Location: Partner Center > Product Management > Store Listing" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " This is the app name visible to users in the Microsoft Store." -ForegroundColor Gray
|
||||
Write-Host " It must match EXACTLY what you reserved in Partner Center." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Example: My Awesome CmdPal Extension" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$displayName = ""
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "Display Name" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host ": " -NoNewline -ForegroundColor Yellow
|
||||
$displayName = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($displayName)) {
|
||||
Write-Host " [ERROR] Display Name cannot be empty." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
elseif (-not (Test-DisplayName $displayName)) {
|
||||
Write-Host " [ERROR] Invalid Display Name." -ForegroundColor Red
|
||||
Write-Host " Display name must be 1-256 characters and cannot contain control characters." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] Display Name accepted: $displayName" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Please try again with valid information." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Collect Publisher Display Name
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 4: Publisher Display Name" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter Microsoft Store Package/Properties/PublisherDisplayName:" -ForegroundColor Yellow
|
||||
Write-Host " Location: Partner Center > Product Identity > Package/Properties" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " This is your company or developer name shown to users." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Example: Contoso Software Inc." -ForegroundColor DarkGray
|
||||
Write-Host " Example: Jessica Cha" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$publisherDisplayName = ""
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "Publisher Display Name" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host ": " -NoNewline -ForegroundColor Yellow
|
||||
$publisherDisplayName = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($publisherDisplayName)) {
|
||||
Write-Host " [ERROR] Publisher Display Name cannot be empty." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
elseif (-not (Test-DisplayName $publisherDisplayName)) {
|
||||
Write-Host " [ERROR] Invalid Publisher Display Name." -ForegroundColor Red
|
||||
Write-Host " Publisher name must be 1-256 characters and cannot contain control characters." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] Publisher Display Name accepted: $publisherDisplayName" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Please try again with valid information." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Check for required assets
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 5: Validating Required Assets" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Checking for Microsoft Store required asset images..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
$assetsPath = Join-Path $projectRoot "Assets"
|
||||
|
||||
# Check if Assets folder exists
|
||||
if (-not (Test-Path $assetsPath)) {
|
||||
Write-Host " [ERROR] Assets folder not found at: $assetsPath" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host " Please create the Assets folder and add the required images." -ForegroundColor Yellow
|
||||
Write-Host " Press any key to continue anyway (you'll need to add assets later)..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
$requiredAssets = @(
|
||||
@{ Name = "StoreLogo.png"; Size = "50x50"; Description = "Store logo for listings" },
|
||||
@{ Name = "Square150x150Logo.scale-200.png"; Size = "300x300"; Description = "Medium tile" },
|
||||
@{ Name = "Square44x44Logo.scale-200.png"; Size = "88x88"; Description = "App list icon" },
|
||||
@{ Name = "Wide310x150Logo.scale-200.png"; Size = "620x300"; Description = "Wide tile" },
|
||||
@{ Name = "SplashScreen.scale-200.png"; Size = "1240x600"; Description = "Splash screen" },
|
||||
@{ Name = "StoreLogo.scale-100.png"; Size = "50x50"; Description = "Store logo (100% scale)" }
|
||||
)
|
||||
|
||||
$missingAssets = @()
|
||||
$foundAssets = @()
|
||||
|
||||
Write-Host " Asset Validation Results:" -ForegroundColor Cyan
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host ("{0,-45} {1,-15} {2}" -f "File", "Size", "Status") -ForegroundColor Gray
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host ("{0,-45} {1,-15} {2}" -f "----", "----", "------") -ForegroundColor DarkGray
|
||||
|
||||
foreach ($asset in $requiredAssets) {
|
||||
$assetPath = Join-Path $assetsPath $asset.Name
|
||||
$statusPrefix = " "
|
||||
|
||||
if (Test-Path $assetPath) {
|
||||
try {
|
||||
$fileInfo = Get-Item $assetPath
|
||||
$fileSize = "{0:N2} KB" -f ($fileInfo.Length / 1KB)
|
||||
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host ("{0,-45} {1,-15} " -f $asset.Name, $asset.Size) -NoNewline -ForegroundColor White
|
||||
Write-Host "[OK]" -ForegroundColor Green
|
||||
|
||||
$foundAssets += $asset.Name
|
||||
}
|
||||
catch {
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host ("{0,-45} {1,-15} " -f $asset.Name, $asset.Size) -NoNewline -ForegroundColor White
|
||||
Write-Host "[WARNING]" -ForegroundColor Yellow
|
||||
Write-Host " (File exists but couldn't read properties)" -ForegroundColor DarkGray
|
||||
$foundAssets += $asset.Name
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host ("{0,-45} {1,-15} " -f $asset.Name, $asset.Size) -NoNewline -ForegroundColor White
|
||||
Write-Host "[MISSING]" -ForegroundColor Red
|
||||
Write-Host " ($($asset.Description))" -ForegroundColor DarkGray
|
||||
$missingAssets += $asset
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " Summary: " -NoNewline -ForegroundColor Cyan
|
||||
Write-Host "$($foundAssets.Count) of $($requiredAssets.Count) assets found" -ForegroundColor White
|
||||
|
||||
# Auto-fix: Copy StoreLogo.scale-100.png to StoreLogo.png if needed
|
||||
$storeLogoPath = Join-Path $assetsPath "StoreLogo.png"
|
||||
$storeLogoScaledPath = Join-Path $assetsPath "StoreLogo.scale-100.png"
|
||||
|
||||
if (-not (Test-Path $storeLogoPath) -and (Test-Path $storeLogoScaledPath)) {
|
||||
Write-Host ""
|
||||
Write-Host " [AUTO-FIX] Creating StoreLogo.png from StoreLogo.scale-100.png..." -ForegroundColor Cyan
|
||||
try {
|
||||
Copy-Item $storeLogoScaledPath -Destination $storeLogoPath -Force -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] StoreLogo.png created successfully" -ForegroundColor Green
|
||||
|
||||
# Update the missing/found counts
|
||||
$missingAssets = $missingAssets | Where-Object { $_.Name -ne "StoreLogo.png" }
|
||||
if ($foundAssets -notcontains "StoreLogo.png") {
|
||||
$foundAssets += "StoreLogo.png"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not copy file: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingAssets.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host " [WARNING] $($missingAssets.Count) asset(s) missing" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " The Microsoft Store requires specific image assets for your app listing." -ForegroundColor Gray
|
||||
Write-Host " You'll need to add these before you can publish." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " TIP: Use the Windows App SDK project templates or design tools to create" -ForegroundColor Cyan
|
||||
Write-Host " properly sized assets. Each image must be exactly the size specified." -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] All required assets are present!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Updating Project Files..." -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "The script will now update your project files with the information you provided." -ForegroundColor White
|
||||
Write-Host "Original files have been backed up in the Publication\Backups folder." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Update Package.appxmanifest
|
||||
Write-Host "[1/2] Updating Package.appxmanifest..." -ForegroundColor Cyan
|
||||
|
||||
try {
|
||||
$manifestContent = Get-Content $manifestPath -Raw -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not read Package.appxmanifest: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " The file may be locked by another process." -ForegroundColor Yellow
|
||||
Write-Host " Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Backup original content
|
||||
$manifestBackup = $manifestContent
|
||||
$manifestUpdateCount = 0
|
||||
|
||||
# Update Identity element (Name and Publisher)
|
||||
Write-Host " Updating Identity Name..." -NoNewline -ForegroundColor Gray
|
||||
$identityNamePattern = '(?<=<Identity\s+Name=")[^"]*'
|
||||
if ($manifestContent -match $identityNamePattern) {
|
||||
$oldValue = $Matches[0]
|
||||
$identityNameUpdated = $manifestContent -replace $identityNamePattern, $packageIdentityName
|
||||
if ($identityNameUpdated -ne $manifestContent) {
|
||||
$manifestContent = $identityNameUpdated
|
||||
$manifestUpdateCount++
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
Write-Host " Changed from: '$oldValue'" -ForegroundColor DarkGray
|
||||
Write-Host " Changed to: '$packageIdentityName'" -ForegroundColor DarkGray
|
||||
}
|
||||
else {
|
||||
Write-Host " [NO CHANGE]" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [WARNING] Could not find Identity Name attribute" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host " Updating Publisher..." -NoNewline -ForegroundColor Gray
|
||||
$publisherPattern = '(?<=Publisher=")[^"]*(?=")'
|
||||
if ($manifestContent -match $publisherPattern) {
|
||||
$oldValue = $Matches[0]
|
||||
$identityPublisherUpdated = $manifestContent -replace $publisherPattern, $packageIdentityPublisher
|
||||
if ($identityPublisherUpdated -ne $manifestContent) {
|
||||
$manifestContent = $identityPublisherUpdated
|
||||
$manifestUpdateCount++
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
Write-Host " Changed from: '$oldValue'" -ForegroundColor DarkGray
|
||||
Write-Host " Changed to: '$packageIdentityPublisher'" -ForegroundColor DarkGray
|
||||
}
|
||||
else {
|
||||
Write-Host " [NO CHANGE]" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [WARNING] Could not find Publisher attribute" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Update Properties (DisplayName and PublisherDisplayName)
|
||||
Write-Host " Updating Display Name..." -NoNewline -ForegroundColor Gray
|
||||
$displayNamePattern = '(?<=<DisplayName>)[^<]*(?=</DisplayName>)'
|
||||
if ($manifestContent -match $displayNamePattern) {
|
||||
$oldValue = $Matches[0]
|
||||
$displayNameUpdated = $manifestContent -replace $displayNamePattern, $displayName
|
||||
if ($displayNameUpdated -ne $manifestContent) {
|
||||
$manifestContent = $displayNameUpdated
|
||||
$manifestUpdateCount++
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
Write-Host " Changed from: '$oldValue'" -ForegroundColor DarkGray
|
||||
Write-Host " Changed to: '$displayName'" -ForegroundColor DarkGray
|
||||
}
|
||||
else {
|
||||
Write-Host " [NO CHANGE]" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [WARNING] Could not find DisplayName element" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host " Updating Publisher Display Name..." -NoNewline -ForegroundColor Gray
|
||||
$publisherDisplayNamePattern = '(?<=<PublisherDisplayName>)[^<]*(?=</PublisherDisplayName>)'
|
||||
if ($manifestContent -match $publisherDisplayNamePattern) {
|
||||
$oldValue = $Matches[0]
|
||||
$publisherDisplayNameUpdated = $manifestContent -replace $publisherDisplayNamePattern, $publisherDisplayName
|
||||
if ($publisherDisplayNameUpdated -ne $manifestContent) {
|
||||
$manifestContent = $publisherDisplayNameUpdated
|
||||
$manifestUpdateCount++
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
Write-Host " Changed from: '$oldValue'" -ForegroundColor DarkGray
|
||||
Write-Host " Changed to: '$publisherDisplayName'" -ForegroundColor DarkGray
|
||||
}
|
||||
else {
|
||||
Write-Host " [NO CHANGE]" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [WARNING] Could not find PublisherDisplayName element" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Also update the VisualElements DisplayName
|
||||
Write-Host " Updating VisualElements Display Name..." -NoNewline -ForegroundColor Gray
|
||||
$visualElementsPattern = '(?<=<uap:VisualElements[^>]*DisplayName=")[^"]*'
|
||||
if ($manifestContent -match $visualElementsPattern) {
|
||||
$oldValue = $Matches[0]
|
||||
if ($oldValue -ne $displayName) {
|
||||
$visualElementsUpdated = $manifestContent -replace $visualElementsPattern, $displayName
|
||||
if ($visualElementsUpdated -ne $manifestContent) {
|
||||
$manifestContent = $visualElementsUpdated
|
||||
$manifestUpdateCount++
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " [NO CHANGE]" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [ALREADY SET]" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [SKIP]" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Write the updated manifest
|
||||
if ($manifestContent -ne $manifestBackup) {
|
||||
try {
|
||||
Set-Content -Path $manifestPath -Value $manifestContent -NoNewline -ErrorAction Stop
|
||||
Write-Host ""
|
||||
Write-Host " [SUCCESS] Package.appxmanifest updated ($manifestUpdateCount changes)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host " [ERROR] Could not write to Package.appxmanifest: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " The file may be read-only or locked by another process." -ForegroundColor Yellow
|
||||
Write-Host " Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
Write-Host " [INFO] No changes were needed for Package.appxmanifest" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Update .csproj file
|
||||
Write-Host "[2/2] Updating TemplateCmdPalExtension.csproj..." -ForegroundColor Cyan
|
||||
|
||||
try {
|
||||
$csprojContent = Get-Content $csprojPath -Raw -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not read .csproj file: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " The file may be locked by another process." -ForegroundColor Yellow
|
||||
Write-Host " Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Backup original content
|
||||
$csprojBackup = $csprojContent
|
||||
$csprojUpdateCount = 0
|
||||
|
||||
# Check if Store properties are commented or uncommented
|
||||
Write-Host " Checking Store property configuration..." -NoNewline -ForegroundColor Gray
|
||||
$storePropsCommentedPattern = '<!--\s*<AppxPackageIdentityName>YOUR_PACKAGE_IDENTITY_NAME_HERE</AppxPackageIdentityName>\s*<AppxPackagePublisher>YOUR_PACKAGE_IDENTITY_PUBLISHER_HERE</AppxPackagePublisher>\s*<AppxPackageVersion>[^<]*</AppxPackageVersion>\s*-->'
|
||||
|
||||
if ($csprojContent -match $storePropsCommentedPattern) {
|
||||
Write-Host " [COMMENTED]" -ForegroundColor Yellow
|
||||
Write-Host " Uncommenting and updating Store properties..." -ForegroundColor Gray
|
||||
|
||||
# Uncomment and update the Store-specific properties
|
||||
$replacement = "<AppxPackageIdentityName>$packageIdentityName</AppxPackageIdentityName>`n <AppxPackagePublisher>$packageIdentityPublisher</AppxPackagePublisher>`n <AppxPackageVersion>0.0.1.0</AppxPackageVersion>"
|
||||
|
||||
$csprojContent = $csprojContent -replace $storePropsCommentedPattern, $replacement
|
||||
$csprojUpdateCount++
|
||||
Write-Host " [OK] Store properties uncommented and updated" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " [UNCOMMENTED]" -ForegroundColor Green
|
||||
Write-Host " Updating existing Store property values..." -ForegroundColor Gray
|
||||
|
||||
# Try updating already-uncommented properties
|
||||
$identityNamePattern = '(?<=<AppxPackageIdentityName>)[^<]*(?=</AppxPackageIdentityName>)'
|
||||
if ($csprojContent -match $identityNamePattern) {
|
||||
$oldValue = $Matches[0]
|
||||
if ($oldValue -ne $packageIdentityName) {
|
||||
$csprojContent = $csprojContent -replace $identityNamePattern, $packageIdentityName
|
||||
$csprojUpdateCount++
|
||||
Write-Host " Updated AppxPackageIdentityName" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
$publisherPattern = '(?<=<AppxPackagePublisher>)[^<]*(?=</AppxPackagePublisher>)'
|
||||
if ($csprojContent -match $publisherPattern) {
|
||||
$oldValue = $Matches[0]
|
||||
if ($oldValue -ne $packageIdentityPublisher) {
|
||||
$csprojContent = $csprojContent -replace $publisherPattern, $packageIdentityPublisher
|
||||
$csprojUpdateCount++
|
||||
Write-Host " Updated AppxPackagePublisher" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Uncomment the PrepareAssets Target section (using (?s) for multi-line matching)
|
||||
Write-Host " Checking PrepareAssets Target..." -NoNewline -ForegroundColor Gray
|
||||
$targetPattern = '(?s)<!--\s*(<Target Name="PrepareAssets".*?</Target>)\s*-->'
|
||||
|
||||
if ($csprojContent -match $targetPattern) {
|
||||
Write-Host " [COMMENTED]" -ForegroundColor Yellow
|
||||
Write-Host " Uncommenting PrepareAssets Target..." -ForegroundColor Gray
|
||||
|
||||
$targetReplacement = '$1'
|
||||
$targetUpdated = $csprojContent -replace $targetPattern, $targetReplacement
|
||||
|
||||
if ($targetUpdated -ne $csprojContent) {
|
||||
$csprojContent = $targetUpdated
|
||||
$csprojUpdateCount++
|
||||
Write-Host " [OK] PrepareAssets Target uncommented" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " [WARNING] Could not uncomment PrepareAssets Target" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " [ALREADY UNCOMMENTED]" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Write the updated csproj
|
||||
if ($csprojContent -ne $csprojBackup) {
|
||||
try {
|
||||
Set-Content -Path $csprojPath -Value $csprojContent -NoNewline -ErrorAction Stop
|
||||
Write-Host ""
|
||||
Write-Host " [SUCCESS] TemplateCmdPalExtension.csproj updated ($csprojUpdateCount changes)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host " [ERROR] Could not write to .csproj file: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " The file may be read-only or locked by another process." -ForegroundColor Yellow
|
||||
Write-Host " Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
Write-Host " [INFO] No changes were needed for TemplateCmdPalExtension.csproj" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Display summary
|
||||
Write-Host "=================================================================" -ForegroundColor Cyan
|
||||
Write-Host " CONFIGURATION SUMMARY" -ForegroundColor White
|
||||
Write-Host "=================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Package Identity Name:" -ForegroundColor Gray
|
||||
Write-Host " $packageIdentityName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Publisher:" -ForegroundColor Gray
|
||||
|
||||
# Truncate publisher if too long
|
||||
$publisherDisplay = if ($packageIdentityPublisher.Length -gt 80) {
|
||||
$packageIdentityPublisher.Substring(0, 77) + "..."
|
||||
} else {
|
||||
$packageIdentityPublisher
|
||||
}
|
||||
Write-Host " $publisherDisplay" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Display Name:" -ForegroundColor Gray
|
||||
Write-Host " $displayName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Publisher Display Name:" -ForegroundColor Gray
|
||||
Write-Host " $publisherDisplayName" -ForegroundColor White
|
||||
Write-Host "=================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Display modified files
|
||||
Write-Host "Modified Files:" -ForegroundColor Yellow
|
||||
$manifestRelative = $manifestPath -replace [regex]::Escape($projectRoot), "."
|
||||
$csprojRelative = $csprojPath -replace [regex]::Escape($projectRoot), "."
|
||||
Write-Host " ✓ $manifestRelative" -ForegroundColor Green
|
||||
Write-Host " ✓ $csprojRelative" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
if ($backupDir) {
|
||||
Write-Host "Backup Location:" -ForegroundColor Yellow
|
||||
$backupRelative = $backupDir -replace [regex]::Escape($projectRoot), "."
|
||||
Write-Host " $backupRelative" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Asset status
|
||||
if ($missingAssets.Count -gt 0) {
|
||||
Write-Host "=================================================================" -ForegroundColor Red
|
||||
Write-Host " ACTION REQUIRED: Missing Assets" -ForegroundColor Yellow
|
||||
Write-Host "=================================================================" -ForegroundColor Red
|
||||
Write-Host " $($missingAssets.Count) required asset(s) are missing. Add them before publishing:" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
foreach ($asset in $missingAssets) {
|
||||
Write-Host " * $($asset.Name) ($($asset.Size))" -ForegroundColor White
|
||||
}
|
||||
|
||||
Write-Host "=================================================================" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Asset Creation Tips:" -ForegroundColor Cyan
|
||||
Write-Host " * Use PNG format with transparency where appropriate" -ForegroundColor Gray
|
||||
Write-Host " * Follow Microsoft Store asset guidelines" -ForegroundColor Gray
|
||||
Write-Host " * Reference: https://learn.microsoft.com/windows/apps/design/style/app-icons-and-logos" -ForegroundColor DarkCyan
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] All required assets are present" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Final success message with conditional messaging
|
||||
Write-Host "=================================================================" -ForegroundColor Green
|
||||
if ($missingAssets.Count -gt 0) {
|
||||
Write-Host " Setup Complete - Action Required" -ForegroundColor Yellow
|
||||
Write-Host "=================================================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " Your project has been configured for Microsoft Store publishing." -ForegroundColor White
|
||||
Write-Host " However, you need to add $($missingAssets.Count) missing asset(s) before publishing." -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host " Setup Completed Successfully!" -ForegroundColor Green
|
||||
Write-Host "=================================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host " Your extension is ready for Microsoft Store publishing!" -ForegroundColor White
|
||||
Write-Host " All configuration and assets are in place." -ForegroundColor Green
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Next Steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Build MSIX bundles by running:" -ForegroundColor Gray
|
||||
Write-Host " .\build-msix-bundles.ps1" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " 2. Upload the bundle to Microsoft Store Partner Center" -ForegroundColor Gray
|
||||
Write-Host " (Located in Publication\ folder after build)" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host " 3. Follow submission instructions at:" -ForegroundColor Gray
|
||||
Write-Host " https://learn.microsoft.com/windows/powertoys/command-palette/publish-extension" -ForegroundColor DarkCyan
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -0,0 +1,512 @@
|
||||
# One-Time WinGet Publication Setup Script for CmdPal Extension
|
||||
# This script collects information and updates files needed for WinGet publication via EXE installer
|
||||
# Version: 1.0
|
||||
|
||||
#Requires -Version 5.1
|
||||
|
||||
|
||||
# Enable strict mode for better error detection
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " WinGet Publication Setup (EXE Installer)" -ForegroundColor Cyan
|
||||
Write-Host " CmdPal Extension Publisher" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Path to the project files
|
||||
$publicationRoot = $PSScriptRoot
|
||||
$projectRoot = Split-Path -Parent $publicationRoot
|
||||
$projectName = Split-Path -Leaf $projectRoot
|
||||
$wingetResourcesPath = Join-Path $PSScriptRoot "winget-resources"
|
||||
|
||||
Write-Host "Validating project structure..." -ForegroundColor Cyan
|
||||
Write-Host " Publication Root: $publicationRoot" -ForegroundColor Gray
|
||||
Write-Host " Project Root: $projectRoot" -ForegroundColor Gray
|
||||
Write-Host " Project Name: $projectName" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Verify required files exist
|
||||
$csprojPath = Join-Path $projectRoot "$projectName.csproj"
|
||||
$manifestPath = Join-Path $projectRoot "Package.appxmanifest"
|
||||
$extensionCsPath = Join-Path $projectRoot "$projectName.cs"
|
||||
|
||||
$buildExePath = Join-Path $wingetResourcesPath "build-exe.ps1"
|
||||
$setupTemplatePath = Join-Path $wingetResourcesPath "setup-template.iss"
|
||||
$releaseYmlPath = Join-Path $wingetResourcesPath "release-extension.yml"
|
||||
|
||||
if (-not (Test-Path $csprojPath)) {
|
||||
Write-Host "ERROR: Could not find .csproj file at: $csprojPath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
Write-Host "ERROR: Could not find Package.appxmanifest at: $manifestPath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $buildExePath)) {
|
||||
Write-Host "ERROR: Could not find build-exe.ps1 at: $buildExePath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $setupTemplatePath)) {
|
||||
Write-Host "ERROR: Could not find setup-template.iss at: $setupTemplatePath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $releaseYmlPath)) {
|
||||
Write-Host "ERROR: Could not find release-extension.yml at: $releaseYmlPath" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " [OK] All required files found" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Create backup directory
|
||||
$backupDir = Join-Path $wingetResourcesPath "Backups"
|
||||
if (-not (Test-Path $backupDir)) {
|
||||
try {
|
||||
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created backup directory: $backupDir" -ForegroundColor Gray
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Could not create backup directory. Proceeding without backups." -ForegroundColor Yellow
|
||||
$backupDir = $null
|
||||
}
|
||||
}
|
||||
|
||||
# Create timestamped backups
|
||||
if ($backupDir) {
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
try {
|
||||
Copy-Item $buildExePath -Destination (Join-Path $backupDir "build-exe.ps1.$timestamp.bak") -Force
|
||||
Copy-Item $setupTemplatePath -Destination (Join-Path $backupDir "setup-template.iss.$timestamp.bak") -Force
|
||||
Copy-Item $releaseYmlPath -Destination (Join-Path $backupDir "release-extension.yml.$timestamp.bak") -Force
|
||||
Write-Host "Backups created: $timestamp" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Could not create backup files. Proceeding anyway." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
# Read existing project information
|
||||
Write-Host "Reading project information..." -ForegroundColor Cyan
|
||||
try {
|
||||
[xml]$manifest = Get-Content $manifestPath -ErrorAction Stop
|
||||
$packageName = $manifest.Package.Identity.Name
|
||||
$packageVersion = $manifest.Package.Identity.Version
|
||||
$displayName = $manifest.Package.Properties.DisplayName
|
||||
$publisherDisplayName = $manifest.Package.Properties.PublisherDisplayName
|
||||
|
||||
Write-Host " Current Package Name: $packageName" -ForegroundColor White
|
||||
Write-Host " Current Version: $packageVersion" -ForegroundColor White
|
||||
Write-Host " Current Display Name: $displayName" -ForegroundColor White
|
||||
Write-Host " Current Publisher: $publisherDisplayName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Could not read Package.appxmanifest: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Extract GUID/CLSID from extension class
|
||||
Write-Host "Reading extension GUID..." -ForegroundColor Cyan
|
||||
try {
|
||||
$extensionCsContent = Get-Content $extensionCsPath -Raw -ErrorAction Stop
|
||||
if ($extensionCsContent -match '\[Guid\("([A-F0-9-]+)"\)\]') {
|
||||
$extensionGuid = $Matches[1]
|
||||
Write-Host " Extension GUID: $extensionGuid" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host "ERROR: Could not find GUID in $projectName.cs" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Could not read $projectName.cs: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "This script will configure your extension for WinGet publication using EXE installer." -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "The following information will be collected:" -ForegroundColor White
|
||||
Write-Host " - GitHub Repository URL (for releases)" -ForegroundColor Gray
|
||||
Write-Host " - Developer/Publisher Name" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "The script will update:" -ForegroundColor Yellow
|
||||
Write-Host " - build-exe.ps1 (build script)" -ForegroundColor Gray
|
||||
Write-Host " - setup-template.iss (Inno Setup installer script)" -ForegroundColor Gray
|
||||
Write-Host " - release-extension.yml (GitHub Actions workflow)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Function to validate URL
|
||||
function Test-GitHubUrl {
|
||||
param([string]$url)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($url)) { return $false }
|
||||
if ($url -notmatch '^https://github\.com/[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+/?$') { return $false }
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Function to validate developer name
|
||||
function Test-DeveloperName {
|
||||
param([string]$name)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($name)) { return $false }
|
||||
if ($name.Length -lt 1 -or $name.Length -gt 256) { return $false }
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Prompt to continue
|
||||
Write-Host "Do you want to continue? (Y/N): " -ForegroundColor Yellow -NoNewline
|
||||
$continue = Read-Host
|
||||
if ($continue -notmatch '^[Yy]') {
|
||||
Write-Host ""
|
||||
Write-Host "Setup cancelled by user." -ForegroundColor Yellow
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 0
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Collect GitHub Repository URL
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 1: GitHub Repository URL" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter your GitHub repository URL:" -ForegroundColor Yellow
|
||||
Write-Host " This is where your extension's releases will be published." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " Format: https://github.com/username/repository" -ForegroundColor DarkGray
|
||||
Write-Host " Example: https://github.com/johndoe/MyAwesomeExtension" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$githubRepoUrl = ""
|
||||
$maxAttempts = 3
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "GitHub Repository URL" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host ": " -NoNewline -ForegroundColor Yellow
|
||||
$githubRepoUrl = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($githubRepoUrl)) {
|
||||
Write-Host " [ERROR] GitHub Repository URL cannot be empty." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
elseif (-not (Test-GitHubUrl $githubRepoUrl)) {
|
||||
Write-Host " [ERROR] Invalid GitHub URL format." -ForegroundColor Red
|
||||
Write-Host " Please use format: https://github.com/username/repository" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Host " [OK] GitHub Repository URL accepted: $githubRepoUrl" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Please try again with a valid GitHub URL." -ForegroundColor Red
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
exit 1
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Collect Developer Name
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Step 2: Developer/Publisher Name" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Enter your developer or publisher name:" -ForegroundColor Yellow
|
||||
Write-Host " This will appear in the EXE installer as the publisher." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " IMPORTANT: If you published to Microsoft Store, this should match" -ForegroundColor Yellow
|
||||
Write-Host " the PublisherDisplayName from your Store configuration." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " Example: John Doe" -ForegroundColor DarkGray
|
||||
Write-Host " Example: Contoso Software" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host " Current value from manifest: $publisherDisplayName" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$developerName = ""
|
||||
$attempt = 0
|
||||
|
||||
do {
|
||||
$attempt++
|
||||
Write-Host "Developer Name" -NoNewline -ForegroundColor Yellow
|
||||
if ($attempt -gt 1) {
|
||||
Write-Host " (Attempt $attempt of $maxAttempts)" -NoNewline -ForegroundColor Red
|
||||
}
|
||||
Write-Host " [press Enter to use default]: " -NoNewline -ForegroundColor Yellow
|
||||
$input = Read-Host
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($input)) {
|
||||
$developerName = $publisherDisplayName
|
||||
Write-Host " [OK] Using default: $developerName" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
elseif (-not (Test-DeveloperName $input)) {
|
||||
Write-Host " [ERROR] Invalid developer name." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
$developerName = $input
|
||||
Write-Host " [OK] Developer name accepted: $developerName" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
|
||||
if ($attempt -ge $maxAttempts) {
|
||||
Write-Host ""
|
||||
Write-Host "Maximum attempts reached. Using default: $publisherDisplayName" -ForegroundColor Yellow
|
||||
$developerName = $publisherDisplayName
|
||||
Write-Host ""
|
||||
break
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
# Update files
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Updating Configuration Files..." -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Update build-exe.ps1
|
||||
Write-Host "[1/3] Updating build-exe.ps1..." -ForegroundColor Cyan
|
||||
try {
|
||||
$buildExeContent = Get-Content $buildExePath -Raw -ErrorAction Stop
|
||||
|
||||
# Update ExtensionName default value
|
||||
$buildExeContent = $buildExeContent -replace '\[string\]\$ExtensionName = "UPDATE"', "[string]`$ExtensionName = `"$projectName`""
|
||||
|
||||
# Update Version default value
|
||||
$buildExeContent = $buildExeContent -replace '\[string\]\$Version = "UPDATE"', "[string]`$Version = `"$packageVersion`""
|
||||
|
||||
Set-Content -Path $buildExePath -Value $buildExeContent -NoNewline -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] build-exe.ps1 updated" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not update build-exe.ps1: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Update setup-template.iss
|
||||
Write-Host "[2/3] Updating setup-template.iss..." -ForegroundColor Cyan
|
||||
try {
|
||||
$setupTemplateContent = Get-Content $setupTemplatePath -Raw -ErrorAction Stop
|
||||
|
||||
# Update version
|
||||
$setupTemplateContent = $setupTemplateContent -replace '#define AppVersion ".*"', "#define AppVersion `"$packageVersion`""
|
||||
|
||||
# Update AppId GUID
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'AppId=\{\{GUID-HERE\}\}', "AppId={{$extensionGuid}}"
|
||||
|
||||
# Update AppName (DISPLAY_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'AppName=DISPLAY_NAME', "AppName=$displayName"
|
||||
|
||||
# Update AppPublisher (DEVELOPER_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'AppPublisher=DEVELOPER_NAME', "AppPublisher=$developerName"
|
||||
|
||||
# Update DefaultDirName (EXTENSION_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'DefaultDirName=\{autopf\}\\EXTENSION_NAME', "DefaultDirName={autopf}\$projectName"
|
||||
|
||||
# Update OutputBaseFilename (EXTENSION_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'OutputBaseFilename=EXTENSION_NAME-Setup', "OutputBaseFilename=$projectName-Setup"
|
||||
|
||||
# Update Icon name (DISPLAY_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'Name: "\{group\}\\DISPLAY_NAME"', "Name: `"{group}\$displayName`""
|
||||
|
||||
# Update Icon filename (EXTENSION_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'Filename: "\{app\}\\EXTENSION_NAME\.exe"', "Filename: `"{app}\$projectName.exe`""
|
||||
|
||||
# Update Registry CLSID entries
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'CLSID\\CLSID-HERE', "CLSID\{{$extensionGuid}}"
|
||||
$setupTemplateContent = $setupTemplateContent -replace '\{\{CLSID-HERE\}\}', "{{$extensionGuid}}"
|
||||
|
||||
# Update Registry ValueData (EXTENSION_NAME)
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'ValueData: "EXTENSION_NAME"', "ValueData: `"$projectName`""
|
||||
|
||||
# Update LocalServer32 ValueData
|
||||
$setupTemplateContent = $setupTemplateContent -replace 'ValueData: "\{app\}\\EXTENSION_NAME\.exe', "ValueData: `"{app}\$projectName.exe"
|
||||
|
||||
Set-Content -Path $setupTemplatePath -Value $setupTemplateContent -NoNewline -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] setup-template.iss updated" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not update setup-template.iss: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Update release-extension.yml
|
||||
Write-Host "[3/3] Updating release-extension.yml..." -ForegroundColor Cyan
|
||||
try {
|
||||
$releaseYmlContent = Get-Content $releaseYmlPath -Raw -ErrorAction Stop
|
||||
|
||||
# Update workflow name
|
||||
$releaseYmlContent = $releaseYmlContent -replace 'name: CmdPal Extension - Build EXE Installer', "name: $displayName - Build EXE Installer"
|
||||
|
||||
# Update environment variables with actual values
|
||||
$releaseYmlContent = $releaseYmlContent -replace "DISPLAY_NAME: \$\{\{ vars\.DISPLAY_NAME \|\| 'DISPLAY_NAME' \}\}", "DISPLAY_NAME: `${{ vars.DISPLAY_NAME || '$displayName' }}"
|
||||
$releaseYmlContent = $releaseYmlContent -replace "EXTENSION_NAME: \$\{\{ vars\.EXTENSION_NAME \|\| 'EXTENSION_NAME' \}\}", "EXTENSION_NAME: `${{ vars.EXTENSION_NAME || '$projectName' }}"
|
||||
$releaseYmlContent = $releaseYmlContent -replace "FOLDER_NAME: \$\{\{ vars\.FOLDER_NAME \|\| 'FOLDER_NAME' \}\}", "FOLDER_NAME: `${{ vars.FOLDER_NAME || '$projectName' }}"
|
||||
$releaseYmlContent = $releaseYmlContent -replace "GITHUB_REPO_URL: \$\{\{ vars\.GITHUB_REPO_URL \|\| 'GITHUB_REPO_URL' \}\}", "GITHUB_REPO_URL: `${{ vars.GITHUB_REPO_URL || '$githubRepoUrl' }}"
|
||||
|
||||
Set-Content -Path $releaseYmlPath -Value $releaseYmlContent -NoNewline -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] release-extension.yml updated" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not update release-extension.yml: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Display summary
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host " CONFIGURATION SUMMARY" -ForegroundColor White
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host " Extension Name:" -ForegroundColor Gray
|
||||
Write-Host " $projectName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Display Name:" -ForegroundColor Gray
|
||||
Write-Host " $displayName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Version:" -ForegroundColor Gray
|
||||
Write-Host " $packageVersion" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Developer:" -ForegroundColor Gray
|
||||
Write-Host " $developerName" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Extension GUID:" -ForegroundColor Gray
|
||||
Write-Host " $extensionGuid" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " GitHub Repository:" -ForegroundColor Gray
|
||||
Write-Host " $githubRepoUrl" -ForegroundColor White
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Display modified files
|
||||
Write-Host "Updated Files:" -ForegroundColor Yellow
|
||||
$buildExeRelative = $buildExePath -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
$setupTemplateRelative = $setupTemplatePath -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
$releaseYmlRelative = $releaseYmlPath -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
Write-Host " [OK] $buildExeRelative" -ForegroundColor Green
|
||||
Write-Host " [OK] $setupTemplateRelative" -ForegroundColor Green
|
||||
Write-Host " [OK] $releaseYmlRelative" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
if ($backupDir) {
|
||||
Write-Host "Backup Location:" -ForegroundColor Yellow
|
||||
$backupRelative = $backupDir -replace [regex]::Escape($projectRoot + "\"), ""
|
||||
Write-Host " $backupRelative" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Move files to correct locations
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Moving Files to Correct Locations" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "The following file will be moved:" -ForegroundColor Yellow
|
||||
Write-Host " - release-extension.yml → .github/workflows/ (2 levels up)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "The following files will remain in winget-resources:" -ForegroundColor Yellow
|
||||
Write-Host " - build-exe.ps1" -ForegroundColor Gray
|
||||
Write-Host " - setup-template.iss" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Calculate destination paths
|
||||
# From: TemplateCmdPalExtension/Publication/winget-resources/
|
||||
# release-extension.yml → TemplateCmdPalExtension/.github/workflows/ (2 levels up from Publication)
|
||||
|
||||
# GitHub workflows directory (2 levels up from Publication)
|
||||
$solutionRoot = Split-Path -Parent $projectRoot
|
||||
$githubWorkflowsDir = Join-Path $solutionRoot ".github\workflows"
|
||||
$releaseYmlDestination = Join-Path $githubWorkflowsDir "release-extension.yml"
|
||||
|
||||
Write-Host "Destination:" -ForegroundColor Yellow
|
||||
Write-Host " release-extension.yml → $releaseYmlDestination" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Move release-extension.yml
|
||||
Write-Host "Moving release-extension.yml..." -ForegroundColor Cyan
|
||||
try {
|
||||
if (-not (Test-Path $githubWorkflowsDir)) {
|
||||
Write-Host " Creating .github/workflows directory..." -ForegroundColor Gray
|
||||
New-Item -Path $githubWorkflowsDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
if (Test-Path $releaseYmlDestination) {
|
||||
Write-Host " [WARNING] Destination file exists, overwriting..." -ForegroundColor Yellow
|
||||
}
|
||||
Move-Item $releaseYmlPath -Destination $releaseYmlDestination -Force -ErrorAction Stop
|
||||
Write-Host " [SUCCESS] Moved to: $releaseYmlDestination" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] Could not move release-extension.yml: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Verify file was moved
|
||||
Write-Host "Verifying file..." -ForegroundColor Cyan
|
||||
|
||||
if (Test-Path $releaseYmlDestination) {
|
||||
Write-Host " [OK] release-extension.yml exists at destination" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " [ERROR] release-extension.yml NOT found at destination" -ForegroundColor Red
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Final instructions
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host " Setup Completed Successfully!" -ForegroundColor Green
|
||||
Write-Host "================================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Files have been configured:" -ForegroundColor Yellow
|
||||
Write-Host " Updated (in winget-resources):" -ForegroundColor Cyan
|
||||
Write-Host " $buildExePath" -ForegroundColor White
|
||||
Write-Host " $setupTemplatePath" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Moved to GitHub workflows:" -ForegroundColor Cyan
|
||||
Write-Host " $releaseYmlDestination" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Next Steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Review the configured files to ensure correctness" -ForegroundColor Gray
|
||||
Write-Host " 2. Add and commit files and push to Github" -ForegroundColor Gray
|
||||
Write-Host " 3. Follow instructions at https://learn.microsoft.com//windows/powertoys/command-palette/publish-extension" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -0,0 +1,114 @@
|
||||
# TEMPLATE: PowerShell Build Script for Command Palette Extensions
|
||||
#
|
||||
# To use this template for a new extension:
|
||||
# 1. Copy this file to your extension's project folder as "build-exe.ps1"
|
||||
# 2. Update in param():
|
||||
# - EXTENSION_NAME with your extension name (e.g., CmdPalMyExtension)
|
||||
# - VERSION with your extension version (e.g., 0.0.1.0)
|
||||
|
||||
|
||||
|
||||
param(
|
||||
[string]$ExtensionName = "UPDATE", # Change to your extension name
|
||||
[string]$Configuration = "Release",
|
||||
[string]$Version = "UPDATE", # Change to your version
|
||||
[string[]]$Platforms = @("x64", "arm64")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "Building $ExtensionName EXE installer..." -ForegroundColor Green
|
||||
Write-Host "Version: $Version" -ForegroundColor Yellow
|
||||
Write-Host "Platforms: $($Platforms -join ', ')" -ForegroundColor Yellow
|
||||
|
||||
# Get the project directory (two levels up from winget-resources)
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$ProjectDir = Split-Path -Parent (Split-Path -Parent $ScriptDir)
|
||||
$ProjectFile = "$ProjectDir\$ExtensionName.csproj"
|
||||
|
||||
Write-Host "Script directory: $ScriptDir" -ForegroundColor Cyan
|
||||
Write-Host "Project directory: $ProjectDir" -ForegroundColor Cyan
|
||||
|
||||
# Clean previous builds
|
||||
Write-Host "Cleaning previous builds..." -ForegroundColor Yellow
|
||||
if (Test-Path "$ProjectDir\bin") {
|
||||
Remove-Item -Path "$ProjectDir\bin" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (Test-Path "$ProjectDir\obj") {
|
||||
Remove-Item -Path "$ProjectDir\obj" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Restore packages
|
||||
Write-Host "Restoring NuGet packages..." -ForegroundColor Yellow
|
||||
dotnet restore $ProjectFile
|
||||
|
||||
# Build for each platform
|
||||
foreach ($Platform in $Platforms) {
|
||||
Write-Host "`n=== Building $Platform ===" -ForegroundColor Cyan
|
||||
|
||||
# Build and publish
|
||||
Write-Host "Building and publishing $Platform application..." -ForegroundColor Yellow
|
||||
dotnet publish $ProjectFile `
|
||||
--configuration $Configuration `
|
||||
--runtime "win-$Platform" `
|
||||
--self-contained true `
|
||||
--output "$ProjectDir\bin\$Configuration\win-$Platform\publish"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "Build failed for $Platform with exit code: $LASTEXITCODE"
|
||||
continue
|
||||
}
|
||||
# Check if files were published
|
||||
$publishDir = "$ProjectDir\bin\$Configuration\win-$Platform\publish"
|
||||
$fileCount = (Get-ChildItem -Path $publishDir -Recurse -File).Count
|
||||
Write-Host "✅ Published $fileCount files to $publishDir" -ForegroundColor Green
|
||||
|
||||
# Create platform-specific setup script
|
||||
Write-Host "Creating installer script for $Platform..." -ForegroundColor Yellow
|
||||
$setupTemplate = Get-Content "$ScriptDir\setup-template.iss" -Raw
|
||||
|
||||
# Update version
|
||||
$setupScript = $setupTemplate -replace '#define AppVersion ".*"', "#define AppVersion `"$Version`""
|
||||
|
||||
# Update output filename to include platform suffix
|
||||
$setupScript = $setupScript -replace 'OutputBaseFilename=(.*?)\{#AppVersion\}', "OutputBaseFilename=`$1{#AppVersion}-$Platform"
|
||||
|
||||
# Update source path for the platform
|
||||
$setupScript = $setupScript -replace 'Source: "bin\\Release\\win-x64\\publish', "Source: `"bin\Release\win-$Platform\publish"
|
||||
|
||||
# Add architecture settings after [Setup] section
|
||||
if ($Platform -eq "arm64") {
|
||||
$setupScript = $setupScript -replace '(\[Setup\][^\[]*)(MinVersion=)', "`$1ArchitecturesAllowed=arm64`r`nArchitecturesInstallIn64BitMode=arm64`r`n`$2"
|
||||
} else {
|
||||
$setupScript = $setupScript -replace '(\[Setup\][^\[]*)(MinVersion=)', "`$1ArchitecturesAllowed=x64compatible`r`nArchitecturesInstallIn64BitMode=x64compatible`r`n`$2"
|
||||
}
|
||||
|
||||
$setupScript | Out-File -FilePath "$ProjectDir\setup-$Platform.iss" -Encoding UTF8
|
||||
|
||||
# Create installer with Inno Setup
|
||||
Write-Host "Creating $Platform installer with Inno Setup..." -ForegroundColor Yellow
|
||||
$InnoSetupPath = "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe"
|
||||
if (-not (Test-Path $InnoSetupPath)) {
|
||||
$InnoSetupPath = "${env:ProgramFiles}\Inno Setup 6\iscc.exe"
|
||||
}
|
||||
|
||||
if (Test-Path $InnoSetupPath) {
|
||||
& $InnoSetupPath "$ProjectDir\setup-$Platform.iss"
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$installer = Get-ChildItem "$ProjectDir\bin\$Configuration\installer\*-$Platform.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($installer) {
|
||||
$sizeMB = [math]::Round($installer.Length / 1MB, 2)
|
||||
Write-Host "✅ Created $Platform installer: $($installer.Name) ($sizeMB MB)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning "Installer file not found for $Platform"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Inno Setup failed for $Platform with exit code: $LASTEXITCODE"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Inno Setup not found at expected locations"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n🎉 Build completed successfully!" -ForegroundColor Green
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user