Compare commits

...

18 Commits

Author SHA1 Message Date
Jaime Bernardo
8981e1a825 GPO line in General instead 2024-10-01 15:47:35 +01:00
Jaime Bernardo
e0c1a1d32b Fix spellcheck 2024-10-01 14:05:47 +01:00
Jaime Bernardo
3d413eff80 Update binary hashes 2024-10-01 13:59:34 +01:00
Jaime Bernardo
2720368d22 Update with latest repo changes 2024-10-01 13:57:57 +01:00
Jaime Bernardo
b0e4336c36 Address PR feedback 2024-09-30 11:35:56 +01:00
Jaime Bernardo
bd867c03fe Update README.md
Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
2024-09-30 11:32:39 +01:00
Jaime Bernardo
24d7e60d55 Mention ZoomIt 2024-09-27 17:12:07 +01:00
Jaime Bernardo
a0088b6335 0.85 changelog 2024-09-27 15:07:37 +01:00
Dustin L. Howett
474c6f7322 [Build]reintroduce variables.EnablePipelineCache (#35087)
* build: reintroduce variables.EnablePipelineCache
2024-09-27 10:39:42 +01:00
Dustin L. Howett
e79b0163b7 [ci]Disable recompilation for the NuGet packages (#35085)
-t:Pack is insufficient for packing a NuGet package after you've signed the DLLs.

Without -p:NoBuild=true, sometimes it will rebuild (or re-link) them for you.
2024-09-27 10:26:47 +01:00
Laszlo Nemeth
dca8b7ac35 [Workspaces] implement standalone app handling (#34948) 2024-09-26 19:54:16 +02:00
Den Delimarsky
49a828236a [Awake]PROMETHEAN_09082024 - tray icon fixes (#34717)
* Update with bug fixes for tray icon and support for parent process

* Process information enum

* Update the docs

* Fix spelling

* Make sure that PID is used in PT config flow

* Logic for checks based on #34148

* Update with link to PR

* Small cleanup

* Proper task segmentation in a function

* Cleanup the code

* Fix synchronization context issue

* Update planning doc

* Test disabling caching to see if that manages to pass CI
2024-09-26 15:25:30 +01:00
Stefan Markovic
3cdb30c647 [NewPlus]Set package version in CI (#35080) 2024-09-26 14:48:47 +01:00
Ethan Fang
6909887844 [GitHub]Adding New+ to the GitHub Issue Templates (#35072)
Updated bug_report.yml & translation_issue.yml
2024-09-26 11:24:03 +01:00
Stefan Markovic
5b616c9eed [General]Support language selection (#34971)
* Language setting

* spellcheck

* Set FileLocksmithContextMenu package version in AppManifest.xml

* Fix ambigious symbol build error

* Fix ambigious symbol build error #2

* Revert unneeded changes

* Improve perf

* try fix ci build
2024-09-25 21:20:15 +01:00
Dustin L. Howett
2b4b55cfeb release: do not publish symbols to the public by default (#35070)
This also moves the checkbox higher up so it is more prominent.
2024-09-25 13:15:18 -05:00
Ani
13c9ba9f81 [Settings][New+]Crash when running Dev build of Settings (#35066) 2024-09-25 17:44:14 +01:00
Seraphima Zykova
471db8bf9c [FancyZones] Allow snapping apps launched by Workspaces (#35067) 2024-09-25 18:36:36 +02:00
56 changed files with 1277 additions and 368 deletions

View File

@@ -65,6 +65,7 @@ body:
- Keyboard Manager
- Mouse Utilities
- Mouse Without Borders
- New+
- Peek
- PowerRename
- PowerToys Run

View File

@@ -39,6 +39,7 @@ body:
- Keyboard Manager
- Mouse Utilities
- Mouse Without Borders
- New+
- Peek
- PowerRename
- PowerToys Run

View File

@@ -367,6 +367,7 @@ dllmain
DNLEN
DONOTROUND
DONTVALIDATEPATH
DOPUS
dotnet
DPICHANGED
DPIs
@@ -1265,6 +1266,7 @@ PWSTR
pwsz
pwtd
QDC
QDir
qianlifeng
qit
QITAB
@@ -1557,7 +1559,7 @@ Stubless
STYLECHANGED
STYLECHANGING
subkeys
SUBLANG
sublang
subquery
Superbar
sut
@@ -1642,6 +1644,7 @@ toolkitconverters
Toolset
toolwindow
TOPDOWNDIB
TOTALCMD
TOUCHEVENTF
TOUCHINPUT
touchpad
@@ -1908,6 +1911,7 @@ XLoc
XNamespace
XPels
XPixel
xplorer
XResource
xsi
XStr

View File

@@ -10,6 +10,11 @@ resources:
# Expose all of these parameters for user configuration.
parameters:
- name: publishSymbolsToPublic
displayName: "Publish Symbols to **PUBLIC** (use only for Final Builds)"
type: boolean
default: false
- name: versionNumber
displayName: "Version Number"
type: string
@@ -28,11 +33,6 @@ parameters:
- x64
- arm64
- name: publishSymbolsToPublic
displayName: "Publish Symbols to **PUBLIC** (use only for Final Builds)"
type: boolean
default: true
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
extends:

View File

@@ -224,7 +224,7 @@ jobs:
inputs:
solution: '**\HostsUILib.csproj'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true -t:pack /bl:$(LogOutputDirectory)\build-hosts.binlog
msbuildArgs: /p:CIBuild=true;NoBuild=true -t:pack /bl:$(LogOutputDirectory)\build-hosts.binlog
configuration: $(BuildConfiguration)
msbuildArchitecture: x64
maximumCpuCount: true
@@ -237,7 +237,7 @@ jobs:
inputs:
solution: '**\EnvironmentVariablesUILib.csproj'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true -t:pack /bl:$(LogOutputDirectory)\build-env-var-editor.binlog
msbuildArgs: /p:CIBuild=true;NoBuild=true -t:pack /bl:$(LogOutputDirectory)\build-env-var-editor.binlog
configuration: $(BuildConfiguration)
msbuildArchitecture: x64
maximumCpuCount: true
@@ -250,7 +250,7 @@ jobs:
inputs:
solution: '**\RegistryPreviewUILib.csproj'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true -t:pack /bl:$(LogOutputDirectory)\build-registry-preview.binlog
msbuildArgs: /p:CIBuild=true;NoBuild=true -t:pack /bl:$(LogOutputDirectory)\build-registry-preview.binlog
configuration: $(BuildConfiguration)
msbuildArchitecture: x64
maximumCpuCount: true

View File

@@ -3,6 +3,9 @@ variables:
value: false
- name: EnablePipelineCache
value: true
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
- name: EnablePipelineCache
value: true
parameters:
- name: buildPlatforms

View File

@@ -58,3 +58,21 @@ $imageResizerContextMenuAppManifestReadFileLocation = $imageResizerContextMenuAp
$imageResizerContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
Write-Host "ImageResizerContextMenu version" $imageResizerContextMenuAppManifest.Package.Identity.Version
$imageResizerContextMenuAppManifest.Save($imageResizerContextMenuAppManifestWriteFileLocation);
# Set FileLocksmithContextMenu package version in AppManifest.xml
$fileLocksmithContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/FileLocksmith/FileLocksmithContextMenu/AppxManifest.xml';
$fileLocksmithContextMenuAppManifestReadFileLocation = $fileLocksmithContextMenuAppManifestWriteFileLocation;
[XML]$fileLocksmithContextMenuAppManifest = Get-Content $fileLocksmithContextMenuAppManifestReadFileLocation
$fileLocksmithContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
Write-Host "FileLocksmithContextMenu version" $fileLocksmithContextMenuAppManifest.Package.Identity.Version
$fileLocksmithContextMenuAppManifest.Save($fileLocksmithContextMenuAppManifestWriteFileLocation);
# Set NewPlusContextMenu package version in AppManifest.xml
$newPlusContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/NewPlus/NewShellExtensionContextMenu/AppxManifest.xml';
$newPlusContextMenuAppManifestReadFileLocation = $newPlusContextMenuAppManifestWriteFileLocation;
[XML]$newPlusContextMenuAppManifest = Get-Content $newPlusContextMenuAppManifestReadFileLocation
$newPlusContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
Write-Host "NewPlusContextMenu version" $newPlusContextMenuAppManifest.Package.Identity.Version
$newPlusContextMenuAppManifest.Save($newPlusContextMenuAppManifestWriteFileLocation);

View File

@@ -273,6 +273,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h
src\common\utils\HttpClient.h = src\common\utils\HttpClient.h
src\common\utils\json.h = src\common\utils\json.h
src\common\utils\language_helper.h = src\common\utils\language_helper.h
src\common\utils\logger_helper.h = src\common\utils\logger_helper.h
src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h

146
README.md
View File

@@ -22,10 +22,10 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) |
| [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) |
| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) |
| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) |
| [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) |
| [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
| [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
| [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
| [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
## Installing and running Microsoft PowerToys
@@ -41,19 +41,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.85%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.84%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysUserSetup-0.84.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysUserSetup-0.84.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysSetup-0.84.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysSetup-0.84.1-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.86%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.85%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.0/PowerToysUserSetup-0.85.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.0/PowerToysUserSetup-0.85.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.0/PowerToysSetup-0.85.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.0/PowerToysSetup-0.85.0-arm64.exe
| Description | Filename | sha256 hash |
|----------------|----------|-------------|
| Per user - x64 | [PowerToysUserSetup-0.84.1-x64.exe][ptUserX64] | 1CDAF3482B031D84DAE15188DE292FB44C5D211698089921040D94B256EBD3CA |
| Per user - ARM64 | [PowerToysUserSetup-0.84.1-arm64.exe][ptUserArm64] | E0207EF5147EE281D4F438E87A30586D8CAA24DE948950FF1B12E05454622CD9 |
| Machine wide - x64 | [PowerToysSetup-0.84.1-x64.exe][ptMachineX64] | 10DF9774DE1857051E135B9790A18A92C5C7F42587C733DEE991186E67231EE0 |
| Machine wide - ARM64 | [PowerToysSetup-0.84.1-arm64.exe][ptMachineArm64] | EB5DDA5EFBA17E813DBF24AFF668DDF5424ED3659234ABBC15441D478D812699 |
| Per user - x64 | [PowerToysUserSetup-0.85.0-x64.exe][ptUserX64] | 28A8BEA61040751287FF47C9BAC627A53A4670CFEA0C17B96EE947219E9A6EA9 |
| Per user - ARM64 | [PowerToysUserSetup-0.85.0-arm64.exe][ptUserArm64] | 2CA077E842B7C53BAFC75A25DBD16C1A4FCE20924C36FDA5AD8CF23CD836B855 |
| Machine wide - x64 | [PowerToysSetup-0.85.0-x64.exe][ptMachineX64] | 4A248AA914EEE339AA99D467FDFBDB1FCD7A49A8564DDBBB811D0EC69CEBAB75 |
| Machine wide - ARM64 | [PowerToysSetup-0.85.0-arm64.exe][ptMachineArm64] | B5FB04EAF44C4203E785411FF55025842B9C39D4970C0C934CB8ADBE79EF31AF |
This is our preferred method.
@@ -99,99 +99,117 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.84 - August 2024 Update
### 0.85 - September 2024 Update
In this release, we focused on adding a new utility (PowerToys Workspaces), Advanced paste custom actions feature, stability, and improvements.
In this release, we focused on new features, stability, and improvements.
**Highlights**
- New utility: PowerToys Workspaces - this utility can launch a set of applications to a custom layout and configuration on the desktop. App arrangements can be saved as a workspace and then relaunched with one click from the Workspaces Editor or from a desktop shortcut. In the editor, app configuration can be customized using CLI arguments and "launch as admin" modifiers, and app window sizes and positions can be updated as desired. This is our first public version of Workspaces and we are excited for you to try it out for yourself! Make sure to file issues you encounter on our GitHub so the team can continue to improve the utility.
- Known issues - the team is actively working on fixing these:
- Apps that launch as admin are unable to be repositioned to the desired layout.
- Border of "Remove" / "Add Back" app button in editor is not clearly visible on light themes.
- Added Awake --use-parent-pid CLI argument to attach to parent process. Thanks [@dend](https://github.com/dend)!
- Added custom actions - user-specified pre-defined prompts for the AI model. Additionally, actions (both standard and custom) are now searchable from prompt box and Ctrl + number in-app shortcuts are now applicable for first 9 search results.
- Ported all C++/CX code to C++/WinRT as part of a refactor and upgrade series aimed at enabling AOT (Ahead of Time) compilation for enhanced performance and reduced disk footprint.
- New utility: New+ - allows setting a personalized set of templates to quickly create files and folders from a File Explorer context menu. Thanks [@cgaarden](https://github.com/cgaarden)!
- Language selection - it's now possible to select which UI language should be used by PowerToys utilities.
- Lots of quality fixes for Workspaces, improving the number of supported applications.
- Reduced Peek memory usage by fixing image leaks. Thanks [@daverayment](https://github.com/daverayment)!
### General
- Added DSC support for ImageResizer resize sizes property.
- Added a general setting to select which UI language should be used in PowerToys utilities.
- Fixed internal code of some policies for Group Policy Objects, that were reading registry entries using the wrong internal functions, and structured code better to avoid future mistakes of the same kind. Thanks [@htcfreek](https://github.com/htcfreek)!
### Advanced Paste
- Added custom actions - user-specified pre-defined prompts for the AI model. Additionally, actions (both standard and custom) are now searchable from prompt box and Ctrl + number in-app shortcuts are now applicable for first 9 search results.
- Fixed some telemetry calls to signal Advanced Paste activation on the cases where a direct shortcut is being used without showing the UI.
- User-defined custom actions can only be used with AI turned on, so custom actions were disabled on Settings when AI is disabled and were hidden from the Advanced Paste UI.
### Awake
- Added --use-parent-pid CLI argument to attach to parent process and fixed issue causing tray icon to disappear. Thanks [@dend](https://github.com/dend)!
- Fixed tray icon behaviors, not appearing and showing incorrect time. Thanks [@dend](https://github.com/dend)!
### Hosts File Editor
### Environment Variables Editor
- Fixed save failure when the hosts file is hidden. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Added the `_NT_SYMBOL_PATH`, `_NT_ALT_SYMBOL_PATH` and `_NT_SYMCACHE_PATH` as variables that are shown as lists. Thanks [@chwarr](https://github.com/chwarr)!
### File Explorer add-ons
### FancyZones
- Fixed multiple preview form positioning issues causing floating, detached windows, CoreWebView2 related exception and process leak. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Allow snapping applications that were launched by Workspaces.
### Keyboard Manager
### File Locksmith
- Convert RemapBufferRow to a struct with descriptive field names. Thanks [@masaru-iritani](https://github.com/masaru-iritani)!
- Fixed issue causing stuck Ctrl key when shortcuts contain AltGr key.
- Fixed an issue causing File Locksmith to be triggered by unrelated verbs in the context menu.
### Mouse Pointer Crosshairs
- Allow crosshairs radius to be 0 pixels. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### New+
- New utility - Allows setting a personalized set of templates to quickly create files and folders from a File Explorer context menu. Thanks [@cgaarden](https://github.com/cgaarden)!
- Added missing entry for New+ policy state reporting in the Bug Report tool. Thanks [@htcfreek](https://github.com/htcfreek)!
- Added a policy for enabling/disabling whether filename extensions should be shown. Thanks [@htcfreek](https://github.com/htcfreek)!
### Peek
- Added long paths support. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
### Quick Accent
- Moved number superscripts and subscripts from Portuguese to all languages definition. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### PowerRename
- Updated the tooltip text of the replace box info button. Thanks [@Agnibaan](https://github.com/Agnibaan)!
- Properly show file's modified date instead of creation date in the file previewer. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed memory leak caused by unmanaged bitmap images not being freed. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed an issue causing Peek to not be displayed the first time when using a preview handler to display files. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Prevent tooltip in file previewer from overlapping with title bar controls. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed memory leaks in thumbnails and refactored image previewer. Thanks [@daverayment](https://github.com/daverayment)!
### PowerToys Run
- Fixed window positioning on start-up introduced in 0.83.
- Improved default web browser detection. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed volume ounces conversion to support both imperial and metric. Thanks [@GhostVaibhav](https://github.com/GhostVaibhav)!
- Fixed thread-safety issue causing results not to be shown on first launch.
- Improved the message boxes to be more specific when PowerToys Run failed to initialize itself or any plugin. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Use capital letters when showing degree results in the Unit Converter plugin. Thanks [@PesBandi](https://github.com/PesBandi)!
### Screen Ruler
### Quick Accent
- Added multiple measurements support for all measuring tools.
- Add the Middle Eastern Romanization character set. Thanks [@PesBandi](https://github.com/PesBandi)!
- Add the degree sign, integral and vertical ellipsis when "All Languages" is selected. Thanks [@rddunphy](https://github.com/rddunphy)!
### Settings
- Improved disabled animations InfoBar in Find My Mouse page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed the link to the Workspaces documentation. (This was a hotfix for 0.84)
- Fixed flyout issues after the Windows App SDK upgrade. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed initialization for the New+ settings page. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed enabled state of a control on the New+ settings page if the module is enabled by policy. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed a crash when cancelling the template folder selection in the New+ settings page.
### Workspaces
- New utility: PowerToys Workspaces - this utility can launch a set of applications to a custom layout and configuration on the desktop. App arrangements can be saved as a workspace and then relaunched with one click from the Workspaces Editor or from a desktop shortcut. In the editor, app configuration can be customized using CLI arguments and "launch as admin" modifiers, and app window sizes and positions can be updated as desired. This is our first public version of Workspaces and we are excited for you to try it out for yourself! Make sure to file issues you encounter on our GitHub so the team can continue to improve the utility.
- Fixed detecting and snapping applications like Discord. (This was a hotfix for 0.84)
- Fixed detecting and snapping applications like Steam. (This was a hotfix for 0.84)
- Fixed button visibility in the UI. (This was a hotfix for 0.84)
- Fixed an issue launching the wrong project when the editor was closed without saving or cancelling a new project.
- Properly handle repositioning windows running as administrator.
- Properly handle cases where the monitor where a workspace was saved is no longer present.
- Fixed the workspace launcher restarting itself in a loop without success.
- Properly handle standalone applications.
- Fixed issues causing icons to not show.
### Documentation
- Added ChatGPTPowerToys plugin mention to thirdPartyRunPlugins.md. Thanks [@ferraridavide](https://github.com/ferraridavide)!
- Fixed the thirdPartyRunPlugins.md entry for the RDP plugin. Thanks [@YisroelTech](https://github.com/YisroelTech)!
### Development
- Ported all C++/CX code to C++/WinRT.
- Moved Version.props import to Directory.Build.props.
- Extracted self-containment related .csproj properties to src/Common.SelfContained.props.
- Unused and obsolete dependencies cleanup. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Extracted CSWinRT related .csproj properties to src/Common.Dotnet.CsWinRT.props.
- Upgraded Microsoft.Windows.CsWinRT to 2.0.8 and updated verifyDepsJsonLibraryVersions.ps1 to unblock PRs.
- Explicitly Set NuGet Audit Mode to Direct in Directory.Build.props to revert changes made with VS 17.12 update. Thanks [@snickler](https://github.com/snickler)!
- Upgraded UnitsNet to 5.56.0.
- Upgraded Windows App SDK to 1.6.
- Upgraded the Target Platform Version to 10.0.22621.0.
- Added a bot trigger to automatically add a label to Workspaces issues. Thanks [@plante-msft](https://github.com/plante-msft)!
- Fixed a regular expression in the bot triggers for wanting to submit community contributions. Thanks [@PesBandi](https://github.com/PesBandi)!
- Fixed analyzer errors after the Visual Studio 17.12 update. Thanks [@snickler](https://github.com/snickler)!
- Fixed the TSA configuration for release CI builds.
- Refactored automated file component generation during installer builds.
- Rewrote the Azure Devops build system to be more modular and share more definitions between PR CI and Release CI.
- Fixed debugging of the New+ page of the Settings application when a settings file was not present.
- Fixed setting the version of the App Manifest in the File Locksmith and New+ context menu app packages.
- Fixed abstracted UI library nuget package signing on release CI.
- Removed build status from GitHub README.
#### What is being planned for version 0.84
#### What is being planned for version 0.86
For [v0.85][github-next-release-work], we'll work on the items below:
For [v0.86][github-next-release-work], we'll work on the items below:
- Stability / bug fixes
- Language selection
- New module: File Actions Menu
- New module: New+
- Integrate Sysinternals ZoomIt
## PowerToys Community

View File

@@ -10,13 +10,22 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
| Build ID | Build Date |
|:-------------------------------------------------------------------|:----------------|
| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
| Build ID | Build Date |
|:-------------------------------------------------------------------|:------------------|
| [`PROMETHEAN_09082024`](#PROMETHEAN_09082024-september-8-2024) | September 8, 2024 |
| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
### `PROMETHEAN_09082024` (September 8, 2024)
>[!NOTE]
>See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/34717)
- Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection.
- [#34148] Fixed a bug from the previous release that incorrectly synchronized threads for shell icon creation and initialized parent PID when it was not parented.
### `VISEGRADRELAY_08152024` (August 15, 2024)

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ManagedCommon
{
public static class LanguageHelper
{
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
public const string SettingsFile = "language.json";
internal sealed class OutGoingLanguageSettings
{
[JsonPropertyName("language")]
public string LanguageTag { get; set; }
}
public static string LoadLanguage()
{
FileSystem fileSystem = new FileSystem();
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var file = localAppDataDir + SettingsFilePath + SettingsFile;
if (fileSystem.File.Exists(file))
{
try
{
Stream inputStream = fileSystem.File.Open(file, FileMode.Open);
StreamReader reader = new StreamReader(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
reader.Dispose();
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data).LanguageTag;
}
catch (Exception)
{
}
}
return string.Empty;
}
}
}

View File

@@ -168,6 +168,11 @@
<Midl Include="LayoutMapManaged.idl" />
<Midl Include="TwoWayPipeMessageIPCManaged.idl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
<ImportGroup Label="ExtensionTargets" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@@ -0,0 +1,22 @@
#pragma once
#include <filesystem>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/utils/json.h>
namespace LanguageHelpers
{
inline std::wstring load_language()
{
std::filesystem::path languageJsonFilePath(PTSettingsHelper::get_root_save_folder_location() + L"\\language.json");
auto langJson = json::from_file(languageJsonFilePath.c_str());
if (!langJson.has_value())
{
return {};
}
std::wstring language = langJson->GetNamedString(L"language", L"").c_str();
return language;
}
}

View File

@@ -4,33 +4,204 @@
#include <string>
#include <atlstr.h>
// Get a string from the resource file
inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback)
#include <common/utils/language_helper.h>
inline std::wstring get_english_fallback_string(UINT resource_id, HINSTANCE instance)
{
wchar_t* text_ptr;
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
if (length == 0)
// Try to load en-us string as the first fallback.
WORD english_language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
ATL::CStringW english_string;
try
{
// Try to load en-us string as the first fallback.
WORD english_language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
ATL::CStringW english_string;
if (!english_string.LoadStringW(instance, resource_id, english_language))
{
return {};
}
}
catch (...)
{
return {};
}
return std::wstring(english_string);
}
inline std::wstring get_resource_string_language_override(UINT resource_id, HINSTANCE instance)
{
static std::wstring language = LanguageHelpers::load_language();
unsigned lang = LANG_ENGLISH;
unsigned sublang = SUBLANG_ENGLISH_US;
if (!language.empty())
{
// Language list taken from Resources.wxs
if (language == L"ar-SA")
{
lang = LANG_ARABIC;
sublang = SUBLANG_ARABIC_SAUDI_ARABIA;
}
else if (language == L"cs-CZ")
{
lang = LANG_CZECH;
sublang = SUBLANG_CZECH_CZECH_REPUBLIC;
}
else if (language == L"de-DE")
{
lang = LANG_GERMAN;
sublang = SUBLANG_GERMAN;
}
else if (language == L"en-US")
{
lang = LANG_ENGLISH;
sublang = SUBLANG_ENGLISH_US;
}
else if (language == L"es-ES")
{
lang = LANG_SPANISH;
sublang = SUBLANG_SPANISH;
}
else if (language == L"fa-IR")
{
lang = LANG_PERSIAN;
sublang = SUBLANG_PERSIAN_IRAN;
}
else if (language == L"fr-FR")
{
lang = LANG_FRENCH;
sublang = SUBLANG_FRENCH;
}
else if (language == L"he-IL")
{
lang = LANG_HEBREW;
sublang = SUBLANG_HEBREW_ISRAEL;
}
else if (language == L"hu-HU")
{
lang = LANG_HUNGARIAN;
sublang = SUBLANG_HUNGARIAN_HUNGARY;
}
else if (language == L"it-IT")
{
lang = LANG_ITALIAN;
sublang = SUBLANG_ITALIAN;
}
else if (language == L"ja-JP")
{
lang = LANG_JAPANESE;
sublang = SUBLANG_JAPANESE_JAPAN;
}
else if (language == L"ko-KR")
{
lang = LANG_KOREAN;
sublang = SUBLANG_KOREAN;
}
else if (language == L"nl-NL")
{
lang = LANG_DUTCH;
sublang = SUBLANG_DUTCH;
}
else if (language == L"pl-PL")
{
lang = LANG_POLISH;
sublang = SUBLANG_POLISH_POLAND;
}
else if (language == L"pt-BR")
{
lang = LANG_PORTUGUESE;
sublang = SUBLANG_PORTUGUESE_BRAZILIAN;
}
else if (language == L"pt-PT")
{
lang = LANG_PORTUGUESE;
sublang = SUBLANG_PORTUGUESE;
}
else if (language == L"ru-RU")
{
lang = LANG_RUSSIAN;
sublang = SUBLANG_RUSSIAN_RUSSIA;
}
else if (language == L"sv-SE")
{
lang = LANG_SWEDISH;
sublang = SUBLANG_SWEDISH;
}
else if (language == L"tr-TR")
{
lang = LANG_TURKISH;
sublang = SUBLANG_TURKISH_TURKEY;
}
else if (language == L"uk-UA")
{
lang = LANG_UKRAINIAN;
sublang = SUBLANG_UKRAINIAN_UKRAINE;
}
else if (language == L"zh-CN")
{
lang = LANG_CHINESE_SIMPLIFIED;
sublang = SUBLANG_CHINESE_SIMPLIFIED;
}
else if (language == L"zh-TW")
{
lang = LANG_CHINESE_TRADITIONAL;
sublang = SUBLANG_CHINESE_TRADITIONAL;
}
WORD languageID = MAKELANGID(lang, sublang);
ATL::CStringW result;
try
{
if (!english_string.LoadStringW(instance, resource_id, english_language))
if (!result.LoadStringW(instance, resource_id, languageID))
{
return fallback;
return {};
}
}
catch (...)
{
return fallback;
return {};
}
return std::wstring(english_string);
if (!result.IsEmpty())
{
return std::wstring(result);
}
}
return {};
}
// Get a string from the resource file
inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback)
{
// Try to load en-us string as the first fallback.
std::wstring english_string = get_english_fallback_string(resource_id, instance);
std::wstring language_override_resource = get_resource_string_language_override(resource_id, instance);
if (!language_override_resource.empty())
{
return language_override_resource;
}
else
{
return { text_ptr, static_cast<std::size_t>(length) };
wchar_t* text_ptr;
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
if (length == 0)
{
if (!english_string.empty())
{
return std::wstring(english_string);
}
else
{
return fallback;
}
}
else
{
return { text_ptr, static_cast<std::size_t>(length) };
}
}
}

View File

@@ -50,6 +50,12 @@ namespace AdvancedPaste
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>

View File

@@ -44,6 +44,12 @@ namespace EnvironmentVariables
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>

View File

@@ -7,6 +7,8 @@
#ifndef PCH_H
#define PCH_H
#include <atlbase.h>
// add headers that you want to pre-compile here
#include "framework.h"

View File

@@ -23,6 +23,12 @@ namespace FileLocksmithUI
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
Logger.InitializeLogger("\\File Locksmith\\FileLocksmithUI\\Logs");
this.InitializeComponent();

View File

@@ -38,6 +38,12 @@ namespace Hosts
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
Host.HostInstance = Microsoft.Extensions.Hosting.Host.

View File

@@ -24,6 +24,12 @@ namespace MeasureToolUI
{
Logger.InitializeLogger("\\Measure Tool\\MeasureToolUI\\Logs");
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
@@ -28,6 +29,19 @@ public partial class App : Application, IDisposable
{
Logger.InitializeLogger("\\TextExtractor\\Logs");
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
NativeThreadCTS = new CancellationTokenSource();
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
@@ -40,6 +41,20 @@ namespace WorkspacesEditor
Logger.InitializeLogger("\\Workspaces\\Logs");
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
const string appName = "Local\\PowerToys_Workspaces_Editor_InstanceMutex";
bool createdNew;
_instanceMutex = new Mutex(true, appName, out createdNew);

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using Common.UI;
@@ -41,6 +42,20 @@ namespace WorkspacesLauncherUI
Logger.InitializeLogger("\\Workspaces\\WorkspacesLauncherUI");
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
const string appName = "Local\\PowerToys_Workspaces_LauncherUI_InstanceMutex";
bool createdNew;
_instanceMutex = new Mutex(true, appName, out createdNew);

View File

@@ -19,6 +19,15 @@ namespace SnapshotUtils
namespace NonLocalizable
{
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
namespace FileManagers
{
const std::wstring FileExplorer = L"EXPLORER"; // windows explorer
const std::wstring TotalCommander = L"TOTALCMD"; // total commander
const std::wstring DirectoryOpus = L"DOPUS"; // directory opus
const std::wstring QDir = L"Q-DIR"; // Q-Dir
const std::wstring Xplorer2 = L"XPLORER2"; // Xplorer2
}
}
class WbemHelper
@@ -191,6 +200,17 @@ namespace SnapshotUtils
return res;
}
bool IsFileManagerApp(std::wstring processPath)
{
std::wstring appName = std::filesystem::path(processPath).stem();
std::transform(appName.begin(), appName.end(), appName.begin(), towupper);
return ((appName == NonLocalizable::FileManagers::FileExplorer) // windows explorer
|| (appName.starts_with(NonLocalizable::FileManagers::TotalCommander)) // total commander
|| (appName == NonLocalizable::FileManagers::DirectoryOpus) // directory opus
|| (appName == NonLocalizable::FileManagers::QDir) // Q-Dir
|| (appName.starts_with(NonLocalizable::FileManagers::Xplorer2))); // Xplorer2
}
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle)
{
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
@@ -269,25 +289,72 @@ namespace SnapshotUtils
if (!data.has_value() || data->name.empty())
{
Logger::info(L"Installed app not found: {}, try parent process", processPath);
bool standaloneApp = false;
bool steamLikeApp = false;
// try with parent process (fix for Steam)
auto parentPid = GetParentPid(pid);
auto parentProcessPath = get_process_path(parentPid);
if (!parentProcessPath.empty())
// check if original process is in the subfolder of the parent process which is a sign of an steam-like app
std::wstring processDir = std::filesystem::path(processPath).parent_path().c_str();
std::wstring parentProcessDir = std::filesystem::path(parentProcessPath).parent_path().c_str();
if (parentProcessPath == L"")
{
data = Utils::Apps::GetApp(parentProcessPath, installedApps);
if (!data.has_value() || data->name.empty())
if (processPath.ends_with(NonLocalizable::ApplicationFrameHost))
{
Logger::info(L"Installed parent app not found: {}", processPath);
// filter out ApplicationFrameHost.exe
continue;
}
processPath = parentProcessPath;
else
{
Logger::info(L"parent process unknown, the parent app is an already closed file manager app, it is a standalone app");
standaloneApp = true;
}
}
else if (processDir.starts_with(parentProcessDir))
{
Logger::info(L"parent process: {}, original process is in the subfolder of the parent process, it is a steam-like app", parentProcessPath);
steamLikeApp = true;
}
else if (IsFileManagerApp(parentProcessPath))
{
Logger::info(L"parent process: {}, The parent process is a known file manager app, it is a standalone app", parentProcessPath);
standaloneApp = true;
}
else
{
Logger::info(L"Parent process path not found");
continue;
Logger::info(L"parent process: {}, The parent process is NOT a known file manager app, it is a steam-like app", parentProcessPath);
steamLikeApp = true;
}
if (standaloneApp)
{
data = Utils::Apps::AppData{
.name = std::filesystem::path(processPath).stem(),
.installPath = processPath,
};
}
else if (steamLikeApp)
{
if (!parentProcessPath.empty())
{
data = Utils::Apps::GetApp(parentProcessPath, installedApps);
if (!data.has_value() || data->name.empty())
{
Logger::info(L"Installed parent app not found: {}", processPath);
continue;
}
processPath = parentProcessPath;
}
else
{
Logger::info(L"Parent process path not found");
continue;
}
}
}

View File

@@ -174,13 +174,15 @@ void WindowArranger::processWindow(HWND window)
}
auto data = Utils::Apps::GetApp(processPath, m_installedApps);
if (!data.has_value() || data->name.empty())
if (!data.has_value())
{
return;
}
auto iter = std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const auto& val)
{ return val.second.state == LaunchingState::Waiting && val.first.name == data.value().name; });
{
return val.second.state == LaunchingState::Waiting && !val.second.window && (val.first.name == data.value().name || val.first.path == data.value().installPath);
});
if (iter == m_launchingApps.end())
{
Logger::info(L"A window of {} is not in the project", processPath);

View File

@@ -17,6 +17,6 @@ namespace Awake.Core
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
// is representative of the date when the last change was made before
// the pull request is issued.
internal const string BuildId = "VISEGRADRELAY_08152024";
internal const string BuildId = "PROMETHEAN_09082024";
}
}

View File

@@ -6,17 +6,16 @@ using System;
using System.Collections.Generic;
using System.Threading;
namespace Awake.Core.Models
namespace Awake.Core.Threading
{
internal sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly Queue<Tuple<SendOrPostCallback, object>> queue =
new();
private readonly Queue<Tuple<SendOrPostCallback, object?>?> queue = new();
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
public override void Post(SendOrPostCallback d, object state)
#pragma warning restore CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
public override void Post(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
lock (queue)
{
queue.Enqueue(Tuple.Create(d, state));
@@ -28,7 +27,7 @@ namespace Awake.Core.Models
{
while (true)
{
Tuple<SendOrPostCallback, object> work;
Tuple<SendOrPostCallback, object?>? work;
lock (queue)
{
while (queue.Count == 0)
@@ -44,7 +43,14 @@ namespace Awake.Core.Models
break;
}
work.Item1(work.Item2);
try
{
work.Item1(work.Item2);
}
catch (Exception e)
{
Console.WriteLine("Error during execution: " + e.Message);
}
}
}
@@ -52,9 +58,7 @@ namespace Awake.Core.Models
{
lock (queue)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
queue.Enqueue(null); // Signal the end of the message loop
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
Monitor.Pulse(queue);
}
}

View File

@@ -9,10 +9,9 @@ using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Awake.Core.Models;
using Awake.Core.Native;
using Awake.Core.Threading;
using Awake.Properties;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -29,13 +28,13 @@ namespace Awake.Core
internal static class TrayHelper
{
private static NotifyIconData _notifyIconData;
private static IntPtr _trayMenu;
private static IntPtr _hiddenWindowHandle;
private static SingleThreadSynchronizationContext? _syncContext;
private static Thread? _mainThread;
private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; }
private static IntPtr _hiddenWindowHandle;
internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; }
static TrayHelper()
@@ -46,42 +45,35 @@ namespace Awake.Core
private static void ShowContextMenu(IntPtr hWnd)
{
if (TrayMenu != IntPtr.Zero)
if (TrayMenu == IntPtr.Zero)
{
Bridge.SetForegroundWindow(hWnd);
// Get the handle to the context menu associated with the tray icon
IntPtr hMenu = TrayMenu;
// Get the current cursor position
Bridge.GetCursorPos(out Models.Point cursorPos);
Bridge.ScreenToClient(hWnd, ref cursorPos);
MenuInfo menuInfo = new()
{
CbSize = (uint)Marshal.SizeOf(typeof(MenuInfo)),
FMask = Native.Constants.MIM_STYLE,
DwStyle = Native.Constants.MNS_AUTO_DISMISS,
};
Bridge.SetMenuInfo(hMenu, ref menuInfo);
// Display the context menu at the cursor position
Bridge.TrackPopupMenuEx(
hMenu,
Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON,
cursorPos.X,
cursorPos.Y,
hWnd,
IntPtr.Zero);
}
else
{
// Tray menu was not initialized. Log the issue.
// This is normal when operating in "standalone mode" - that is, detached
// from the PowerToys configuration file.
Logger.LogError("Tried to create a context menu while the TrayMenu object is a null pointer. Normal when used in standalone mode.");
return;
}
Bridge.SetForegroundWindow(hWnd);
// Get cursor position and convert it to client coordinates
Bridge.GetCursorPos(out Models.Point cursorPos);
Bridge.ScreenToClient(hWnd, ref cursorPos);
// Set menu information
var menuInfo = new MenuInfo
{
CbSize = (uint)Marshal.SizeOf<MenuInfo>(),
FMask = Native.Constants.MIM_STYLE,
DwStyle = Native.Constants.MNS_AUTO_DISMISS,
};
Bridge.SetMenuInfo(TrayMenu, ref menuInfo);
// Display the context menu at the cursor position
Bridge.TrackPopupMenuEx(
TrayMenu,
Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON,
cursorPos.X,
cursorPos.Y,
hWnd,
IntPtr.Zero);
}
public static void InitializeTray(Icon icon, string text)
@@ -89,8 +81,11 @@ namespace Awake.Core
IntPtr hWnd = IntPtr.Zero;
// Start the message loop asynchronously
Task.Run(() =>
_mainThread = new Thread(() =>
{
_syncContext = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(_syncContext);
RunOnMainThread(() =>
{
WndClassEx wcex = new()
@@ -137,74 +132,82 @@ namespace Awake.Core
Bridge.ShowWindow(hWnd, 0); // SW_HIDE
Bridge.UpdateWindow(hWnd);
Logger.LogInfo($"Created HWND for the window: {hWnd}");
SetShellIcon(hWnd, text, icon);
});
}).Wait();
Task.Run(() =>
{
RunOnMainThread(() =>
{
RunMessageLoop();
});
_syncContext!.BeginMessageLoop();
});
_mainThread.IsBackground = true;
_mainThread.Start();
}
internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add)
{
Logger.LogInfo($"Setting the shell icon.\nText: {text}\nAction: {action}");
int message = Native.Constants.NIM_ADD;
switch (action)
if (hWnd != IntPtr.Zero && icon != null)
{
case TrayIconAction.Update:
message = Native.Constants.NIM_MODIFY;
break;
case TrayIconAction.Delete:
message = Native.Constants.NIM_DELETE;
break;
case TrayIconAction.Add:
default:
break;
}
int message = Native.Constants.NIM_ADD;
if (action == TrayIconAction.Add || action == TrayIconAction.Update)
{
Logger.LogInfo($"Adding or updating tray icon. HIcon handle is {icon?.Handle}\nHWnd: {hWnd}");
_notifyIconData = new NotifyIconData
switch (action)
{
CbSize = Marshal.SizeOf(typeof(NotifyIconData)),
HWnd = hWnd,
UId = 1000,
UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE,
UCallbackMessage = (int)Native.Constants.WM_USER,
HIcon = icon?.Handle ?? IntPtr.Zero,
SzTip = text,
};
}
else if (action == TrayIconAction.Delete)
{
_notifyIconData = new NotifyIconData
case TrayIconAction.Update:
message = Native.Constants.NIM_MODIFY;
break;
case TrayIconAction.Delete:
message = Native.Constants.NIM_DELETE;
break;
case TrayIconAction.Add:
default:
break;
}
if (action == TrayIconAction.Add || action == TrayIconAction.Update)
{
CbSize = Marshal.SizeOf(typeof(NotifyIconData)),
HWnd = hWnd,
UId = 1000,
UFlags = 0,
};
}
_notifyIconData = new NotifyIconData
{
CbSize = Marshal.SizeOf(typeof(NotifyIconData)),
HWnd = hWnd,
UId = 1000,
UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE,
UCallbackMessage = (int)Native.Constants.WM_USER,
HIcon = icon?.Handle ?? IntPtr.Zero,
SzTip = text,
};
}
else if (action == TrayIconAction.Delete)
{
_notifyIconData = new NotifyIconData
{
CbSize = Marshal.SizeOf(typeof(NotifyIconData)),
HWnd = hWnd,
UId = 1000,
UFlags = 0,
};
}
if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData))
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}");
}
if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData))
{
int errorCode = Marshal.GetLastWin32Error();
Logger.LogInfo($"Could not set the shell icon. Action: {action} and error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}");
if (action == TrayIconAction.Delete)
throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}");
}
if (action == TrayIconAction.Delete)
{
_notifyIconData = default;
}
}
else
{
_notifyIconData = default;
Logger.LogInfo($"Cannot set the shell icon - parent window handle is zero or icon is not available. Text: {text} Action: {action}");
}
}
@@ -215,6 +218,8 @@ namespace Awake.Core
Bridge.TranslateMessage(ref msg);
Bridge.DispatchMessage(ref msg);
}
Logger.LogInfo("Message loop terminated.");
}
private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
@@ -295,30 +300,20 @@ namespace Awake.Core
internal static void RunOnMainThread(Action action)
{
var syncContext = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncContext);
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
syncContext.Post(
_ =>
{
try
_syncContext!.Post(
_ =>
{
action();
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
}
finally
{
syncContext.EndMessageLoop();
}
},
null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
syncContext.BeginMessageLoop();
try
{
Logger.LogInfo($"Thread execution is on: {Environment.CurrentManagedThreadId}");
action();
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
}
},
null);
}
internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys)
@@ -353,6 +348,7 @@ namespace Awake.Core
private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode)
{
TrayMenu = Bridge.CreatePopupMenu();
if (TrayMenu == IntPtr.Zero)
{
return;

View File

@@ -53,91 +53,110 @@ namespace Awake
{
_settingsUtils = new SettingsUtils();
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);
Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs"));
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher;
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1);
return 0;
}
if (!instantiated)
{
// Awake is already running - there is no need for us to process
// anything further
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1);
return 1;
}
Logger.LogInfo($"Launching {Core.Constants.AppName}...");
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
Logger.LogInfo($"Build: {Core.Constants.BuildId}");
Logger.LogInfo($"OS: {Environment.OSVersion}");
Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}");
TaskScheduler.UnobservedTaskException += (sender, args) =>
else
{
Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check!
args.SetObserved();
};
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1);
return 1;
}
else
{
Logger.LogInfo($"Launching {Core.Constants.AppName}...");
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
Logger.LogInfo($"Build: {Core.Constants.BuildId}");
Logger.LogInfo($"OS: {Environment.OSVersion}");
Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}");
// To make it easier to diagnose future issues, let's get the
// system power capabilities and aggregate them in the log.
Bridge.GetPwrCapabilities(out _powerCapabilities);
Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities));
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check!
args.SetObserved();
};
Logger.LogInfo("Parsing parameters...");
// To make it easier to diagnose future issues, let's get the
// system power capabilities and aggregate them in the log.
Bridge.GetPwrCapabilities(out _powerCapabilities);
Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities));
var configOption = new Option<bool>(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Logger.LogInfo("Parsing parameters...");
var displayOption = new Option<bool>(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var configOption = new Option<bool>(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var timeOption = new Option<uint>(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
{
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
var displayOption = new Option<bool>(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var pidOption = new Option<int>(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var timeOption = new Option<uint>(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
{
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
var expireAtOption = new Option<string>(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var pidOption = new Option<int>(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var parentPidOption = new Option<bool>(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
var expireAtOption = new Option<string>(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
RootCommand? rootCommand =
[
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption,
parentPidOption,
];
var parentPidOption = new Option<bool>(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption);
RootCommand? rootCommand =
[
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption,
parentPidOption,
];
return rootCommand.InvokeAsync(args).Result;
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption);
return rootCommand.InvokeAsync(args).Result;
}
}
}
private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e)
@@ -167,15 +186,11 @@ namespace Awake
if (pid == 0 && !useParentPid)
{
Logger.LogInfo("No PID specified. Allocating console...");
Manager.AllocateConsole();
_handler += new ConsoleEventHandler(ExitHandler);
Manager.SetConsoleControlHandler(_handler, true);
Trace.Listeners.Add(new ConsoleTraceListener());
AllocateLocalConsole();
}
else
{
Logger.LogInfo("Starting with PID binding.");
_startedFromPowerToys = true;
}
@@ -233,7 +248,7 @@ namespace Awake
}
catch (Exception ex)
{
Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}");
Logger.LogError($"There was a problem with the configuration file. Make sure it exists. {ex.Message}");
}
}
else if (pid != 0 || useParentPid)
@@ -294,38 +309,22 @@ namespace Awake
}
}
private static void AllocateLocalConsole()
{
Manager.AllocateConsole();
_handler += new ConsoleEventHandler(ExitHandler);
Manager.SetConsoleControlHandler(_handler, true);
Trace.Listeners.Add(new ConsoleTraceListener());
}
private static void ScaffoldConfiguration(string settingsPath)
{
try
{
var directory = Path.GetDirectoryName(settingsPath)!;
var fileName = Path.GetFileName(settingsPath);
_watcher = new FileSystemWatcher
{
Path = directory,
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
Filter = fileName,
};
var mergedObservable = Observable.Merge(
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h,
h => _watcher.Changed -= h),
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Created += h,
h => _watcher.Created -= h));
mergedObservable
.Throttle(TimeSpan.FromMilliseconds(25))
.SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange);
var settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
TrayHelper.SetTray(settings, _startedFromPowerToys);
SetupFileSystemWatcher(settingsPath);
InitializeSettings();
ProcessSettings();
}
catch (Exception ex)
@@ -334,6 +333,38 @@ namespace Awake
}
}
private static void SetupFileSystemWatcher(string settingsPath)
{
var directory = Path.GetDirectoryName(settingsPath)!;
var fileName = Path.GetFileName(settingsPath);
_watcher = new FileSystemWatcher
{
Path = directory,
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
Filter = fileName,
};
Observable.Merge(
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h,
h => _watcher.Changed -= h),
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Created += h,
h => _watcher.Created -= h))
.Throttle(TimeSpan.FromMilliseconds(25))
.SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange);
}
private static void InitializeSettings()
{
var settings = Manager.ModuleSettings?.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
TrayHelper.SetTray(settings, _startedFromPowerToys);
}
private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent)
{
try
@@ -351,7 +382,9 @@ namespace Awake
{
try
{
var settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? throw new InvalidOperationException("Settings are null.");
var settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName)
?? throw new InvalidOperationException("Settings are null.");
Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
switch (settings.Properties.Mode)
@@ -365,7 +398,7 @@ namespace Awake
break;
case AwakeMode.TIMED:
uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60);
uint computedTime = (settings.Properties.IntervalHours * 3600) + (settings.Properties.IntervalMinutes * 60);
Manager.SetTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
break;

View File

@@ -4,6 +4,7 @@
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Threading;
using System.Windows;
@@ -29,6 +30,19 @@ namespace ColorPickerUI
protected override void OnStartup(StartupEventArgs e)
{
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
NativeThreadCTS = new CancellationTokenSource();
ExitToken = NativeThreadCTS.Token;

View File

@@ -398,7 +398,7 @@ void FancyZones::WindowCreated(HWND window) noexcept
return;
}
if (!FancyZonesWindowProcessing::IsProcessable(window))
if (!FancyZonesWindowProcessing::IsProcessableAutomatically(window))
{
return;
}
@@ -1084,7 +1084,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
}
auto window = GetForegroundWindow();
if (!FancyZonesWindowProcessing::IsProcessable(window))
if (!FancyZonesWindowProcessing::IsProcessableManually(window))
{
return false;
}

View File

@@ -65,16 +65,28 @@ FancyZonesWindowProcessing::ProcessabilityType FancyZonesWindowProcessing::Defin
return ProcessabilityType::NotCurrentVirtualDesktop;
}
// Ignore windows launched by Workspaces
if (FancyZonesWindowProperties::IsLaunchedByWorkspaces(window))
{
return ProcessabilityType::LaunchedByWorkspaces;
}
return ProcessabilityType::Processable;
}
bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept
bool FancyZonesWindowProcessing::IsProcessableAutomatically(HWND window) noexcept
{
return DefineWindowType(window) == ProcessabilityType::Processable;
auto type = DefineWindowType(window);
if (type != ProcessabilityType::Processable)
{
return false;
}
// Ignore windows launched by Workspaces
if (FancyZonesWindowProperties::IsLaunchedByWorkspaces(window))
{
return false;
}
return true;
}
bool FancyZonesWindowProcessing::IsProcessableManually(HWND window) noexcept
{
auto type = DefineWindowType(window);
return type == ProcessabilityType::Processable;
}

View File

@@ -13,10 +13,10 @@ namespace FancyZonesWindowProcessing
NonProcessablePopupWindow,
ChildWindow,
Excluded,
NotCurrentVirtualDesktop,
LaunchedByWorkspaces
NotCurrentVirtualDesktop
};
ProcessabilityType DefineWindowType(HWND window) noexcept;
bool IsProcessable(HWND window) noexcept;
bool IsProcessableAutomatically(HWND window) noexcept;
bool IsProcessableManually(HWND window) noexcept;
}

View File

@@ -29,7 +29,7 @@ WindowMouseSnap::~WindowMouseSnap()
std::unique_ptr<WindowMouseSnap> WindowMouseSnap::Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
{
if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent() || !FancyZonesWindowProcessing::IsProcessable(window))
if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent() || !FancyZonesWindowProcessing::IsProcessableManually(window))
{
return nullptr;
}

View File

@@ -4,6 +4,8 @@
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/WindowUtils.h>
#include <modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h>
#include "Util.h"
#include <CppUnitTestLogger.h>
@@ -45,7 +47,8 @@ namespace FancyZonesUnitTests
Assert::IsTrue(IsIconic(window));
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Minimized, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ToolWindow)
@@ -53,7 +56,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", WS_EX_TOOLWINDOW);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::ToolWindow, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (InvisibleWindow)
@@ -63,7 +67,8 @@ namespace FancyZonesUnitTests
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // let ShowWindow finish
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NotVisible, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD(NonRootWindow)
@@ -75,7 +80,8 @@ namespace FancyZonesUnitTests
Assert::IsFalse(FancyZonesWindowUtils::IsRoot(window));
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonRootWindow, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (Popup_App)
@@ -83,7 +89,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW | WS_POPUP);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (Popup_Menu)
@@ -91,7 +98,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_POPUP | WS_TILED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonProcessablePopupWindow, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (Popup_MenuEdge)
@@ -99,7 +107,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_POPUP | WS_TILED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_THICKFRAME | WS_SIZEBOX);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonProcessablePopupWindow, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (Popup_Calculator)
@@ -107,7 +116,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_BORDER | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_GROUP | WS_POPUP | WS_POPUPWINDOW | WS_SIZEBOX | WS_TABSTOP | WS_TILEDWINDOW);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (Popup_CalculatorTopmost)
@@ -115,7 +125,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_BORDER | WS_CAPTION | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_OVERLAPPED | WS_POPUP | WS_POPUPWINDOW | WS_SIZEBOX | WS_SYSMENU | WS_THICKFRAME);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD(Popup_FacebookMessenger)
@@ -123,7 +134,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_GROUP | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_POPUP | WS_TABSTOP | WS_THICKFRAME);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ChildWindow_OptionDisabled)
@@ -142,7 +154,8 @@ namespace FancyZonesUnitTests
Assert::IsTrue(FancyZonesWindowUtils::HasVisibleOwner(window), L"Child window doesn't have visible owner");
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::ChildWindow, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ChildWindow_OptionEnabled)
@@ -159,7 +172,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, 0, parentWindow);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ExcludedApp_ByDefault)
@@ -168,7 +182,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"SysListView32");
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ExcludedApp_ByDefault_SplashScreen)
@@ -176,7 +191,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"MsoSplash");
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ExcludedApp_ByUser)
@@ -188,7 +204,18 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"Test_Excluded");
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (LaunchedByWorkspaces)
{
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW);
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
TEST_METHOD (ProcessableWindow)
@@ -196,7 +223,8 @@ namespace FancyZonesUnitTests
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW);
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableAutomatically(window));
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessableManually(window));
}
};
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Input;
@@ -55,6 +56,20 @@ namespace FancyZonesEditor
public App()
{
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
Logger.InitializeLogger("\\FancyZones\\Editor\\Logs");
// DebugModeCheck();

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Globalization;
using System.Text;
using System.Windows;
@@ -19,6 +20,19 @@ namespace ImageResizer
{
static App()
{
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException)
{
// error
}
Console.InputEncoding = Encoding.Unicode;
}

View File

@@ -46,6 +46,7 @@
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -1,9 +1,7 @@
#include "pch.h"
#include "Dialog.h"
using namespace winrt::Windows::Foundation;
IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle)
winrt::Windows::Foundation::IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle)
{
ContentDialog confirmationDialog;
confirmationDialog.XamlRoot(root);

View File

@@ -24,8 +24,6 @@
#include "EditorConstants.h"
#include <common/Themes/theme_listener.h>
using namespace winrt::Windows::Foundation;
static UINT g_currentDPI = DPIAware::DEFAULT_DPI;
LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM);
@@ -57,7 +55,7 @@ static void handleTheme()
}
}
static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
static winrt::Windows::Foundation::IAsyncOperation<bool> OrphanKeysConfirmationDialog(
KBMEditor::KeyboardManagerState& state,
const std::vector<DWORD>& keys,
XamlRoot root)
@@ -90,7 +88,7 @@ static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
co_return res == ContentDialogResult::Primary;
}
static IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings)
static winrt::Windows::Foundation::IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings)
{
ShortcutErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer);

View File

@@ -18,8 +18,6 @@
#include "EditorConstants.h"
#include <common/Themes/theme_listener.h>
using namespace winrt::Windows::Foundation;
static UINT g_currentDPI = DPIAware::DEFAULT_DPI;
LRESULT CALLBACK EditShortcutsWindowProc(HWND, UINT, WPARAM, LPARAM);
@@ -51,7 +49,7 @@ static void handleTheme()
}
}
static IAsyncAction OnClickAccept(
static winrt::Windows::Foundation::IAsyncAction OnClickAccept(
KBMEditor::KeyboardManagerState& keyboardManagerState,
XamlRoot root,
std::function<void()> ApplyRemappings)

View File

@@ -17,6 +17,7 @@ using PowerLauncher.Helper;
using PowerLauncher.Plugin;
using PowerLauncher.ViewModel;
using PowerToys.Interop;
using Windows.Globalization;
using Wox;
using Wox.Infrastructure;
using Wox.Infrastructure.Image;
@@ -54,6 +55,19 @@ namespace PowerLauncher
{
NativeThreadCTS = new CancellationTokenSource();
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
Log.Info($"Starting PowerToys Run with PID={Environment.ProcessId}", typeof(App));
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredPowerLauncherEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{

View File

@@ -3,9 +3,11 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using ManagedCommon;
namespace Wox.Plugin
{

View File

@@ -40,6 +40,12 @@ namespace Peek.UI
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
Logger.InitializeLogger("\\Peek\\Logs");

View File

@@ -9,6 +9,7 @@
#include <common/logger/logger.h>
#include <common/logger/logger_settings.h>
#include <common/utils/language_helper.h>
#include <common/utils/logger_helper.h>
#include <common/utils/gpo.h>
@@ -33,6 +34,12 @@ const std::wstring moduleName = L"PowerRename";
/// </summary>
App::App()
{
std::wstring appLanguage = LanguageHelpers::load_language();
if (!appLanguage.empty())
{
Microsoft::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(appLanguage);
}
InitializeComponent();
#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION

View File

@@ -37,4 +37,5 @@
#include <winrt/Microsoft.UI.Xaml.Shapes.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.Windows.ApplicationModel.Resources.h>
#include <winrt/Microsoft.Windows.Globalization.h>
#include <wil/cppwinrt_helpers.h>

View File

@@ -5,6 +5,7 @@
using System;
using System.Web;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
@@ -25,6 +26,13 @@ namespace RegistryPreview
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
}

View File

@@ -233,6 +233,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
}
}
else if (name == L"language")
{
constexpr const wchar_t* language_filename = L"\\language.json";
const std::wstring save_file_location = PTSettingsHelper::get_root_save_folder_location() + language_filename;
json::to_file(save_file_location, j);
}
}
return;
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class LanguageModel
{
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
public const string SettingsFile = "language.json";
public string Tag { get; set; }
public string ResourceID { get; set; }
public string Language { get; set; }
public static string LoadSetting()
{
FileSystem fileSystem = new FileSystem();
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var file = localAppDataDir + SettingsFilePath + SettingsFile;
if (fileSystem.File.Exists(file))
{
try
{
Stream inputStream = fileSystem.File.Open(file, FileMode.Open);
StreamReader reader = new StreamReader(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
reader.Dispose();
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data).LanguageTag;
}
catch (Exception)
{
}
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class OutGoingLanguageSettings
{
[JsonPropertyName("language")]
public string LanguageTag { get; set; }
public OutGoingLanguageSettings()
{
}
public OutGoingLanguageSettings(string language)
{
LanguageTag = language;
}
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@@ -78,6 +78,12 @@ namespace Microsoft.PowerToys.Settings.UI
{
Logger.InitializeLogger(@"\Settings\Logs");
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
UnhandledException += App_UnhandledException;

View File

@@ -229,6 +229,25 @@
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="Appearance_Behavior" IsEnabled="True">
<tkcontrols:SettingsCard x:Uid="LanguageHeader" HeaderIcon="{ui:FontIcon Glyph=&#xF2B7;}">
<ComboBox
x:Name="Languages_ComboBox"
MinWidth="{StaticResource SettingActionControlMinWidth}"
DisplayMemberPath="Language"
ItemsSource="{Binding Languages, Mode=TwoWay}"
SelectedIndex="{Binding LanguagesIndex, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="LanguageRestartInfo"
IsClosable="False"
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.LanguageChanged}"
IsTabStop="True"
Severity="Informational">
<InfoBar.ActionButton>
<Button x:Uid="LanguageRestartInfoButton" Click="Click_LanguageRestart" />
</InfoBar.ActionButton>
</InfoBar>
<tkcontrols:SettingsCard x:Uid="ColorModeHeader" HeaderIcon="{ui:FontIcon Glyph=&#xE790;}">
<tkcontrols:SettingsCard.Description>
<HyperlinkButton x:Uid="Windows_Color_Settings" Click="OpenColorsSettings_Click" />

View File

@@ -130,5 +130,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
string r = await Task.FromResult<string>(ShellGetFolder.GetFolderDialog(hwnd));
return r;
}
private void Click_LanguageRestart(object sender, RoutedEventArgs e)
{
ViewModel.Restart();
}
}
}

View File

@@ -59,7 +59,10 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
@@ -4380,4 +4383,85 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="OpenSettings.Content" xml:space="preserve">
<value>Open settings</value>
</data>
<data name="LanguageHeader.Header" xml:space="preserve">
<value>Application language</value>
</data>
<data name="LanguageHeader.Description" xml:space="preserve">
<value>PowerToys will use OS language by default.</value>
</data>
<data name="LanguageRestartInfo.Title" xml:space="preserve">
<value>Restart PowerToys for language change to take effect.</value>
</data>
<data name="LanguageRestartInfoButton.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="Default_Language" xml:space="preserve">
<value>Windows default</value>
</data>
<data name="Arabic_Saudi_Arabia_Language" xml:space="preserve">
<value>Arabic (Saudi Arabia)</value>
</data>
<data name="Czech_Language" xml:space="preserve">
<value>Czech</value>
</data>
<data name="German_Language" xml:space="preserve">
<value>German</value>
</data>
<data name="English_Language" xml:space="preserve">
<value>English</value>
</data>
<data name="Spanish_Language" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Persian_Farsi_Language" xml:space="preserve">
<value>Persian (Farsi)</value>
</data>
<data name="French_Language" xml:space="preserve">
<value>French</value>
</data>
<data name="Hebrew_Israel_Language" xml:space="preserve">
<value>Hebrew (Israel)</value>
</data>
<data name="Hungarian_Language" xml:space="preserve">
<value>Hungarian</value>
</data>
<data name="Italian_Language" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Japanese_Language" xml:space="preserve">
<value>Japanese</value>
</data>
<data name="Korean_Language" xml:space="preserve">
<value>Korean</value>
</data>
<data name="Dutch_Language" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Polish_Language" xml:space="preserve">
<value>Polish</value>
</data>
<data name="Portuguese_Brazil_Language" xml:space="preserve">
<value>Portuguese (Brazil)</value>
</data>
<data name="Portuguese_Portugal_Language" xml:space="preserve">
<value>Portuguese (Portugal)</value>
</data>
<data name="Russian_Language" xml:space="preserve">
<value>Russian</value>
</data>
<data name="Swedish_Language" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Turkish_Language" xml:space="preserve">
<value>Turkish</value>
</data>
<data name="Ukrainian_Language" xml:space="preserve">
<value>Ukrainian</value>
</data>
<data name="Chinese_Simplified_Language" xml:space="preserve">
<value>Chinese (Simplified)</value>
</data>
<data name="Chinese_Traditional_Language" xml:space="preserve">
<value>Chinese (Traditional)</value>
</data>
</root>

View File

@@ -3,10 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
@@ -145,8 +148,38 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction);
}
InitializeLanguages();
}
// Supported languages. Taken from Resources.wxs + default + en-US
private Dictionary<string, string> langTagsAndIds = new Dictionary<string, string>
{
{ string.Empty, "Default_language" },
{ "ar-SA", "Arabic_Saudi_Arabia_Language" },
{ "cs-CZ", "Czech_Language" },
{ "de-DE", "German_Language" },
{ "en-US", "English_Language" },
{ "es-ES", "Spanish_Language" },
{ "fa-IR", "Persian_Farsi_Language" },
{ "fr-FR", "French_Language" },
{ "he-IL", "Hebrew_Israel_Language" },
{ "hu-HU", "Hungarian_Language" },
{ "it-IT", "Italian_Language" },
{ "ja-JP", "Japanese_Language" },
{ "ko-KR", "Korean_Language" },
{ "nl-NL", "Dutch_Language" },
{ "pl-PL", "Polish_Language" },
{ "pt-BR", "Portuguese_Brazil_Language" },
{ "pt-PT", "Portuguese_Portugal_Language" },
{ "ru-RU", "Russian_Language" },
{ "sv-SE", "Swedish_Language" },
{ "tr-TR", "Turkish_Language" },
{ "uk-UA", "Ukrainian_Language" },
{ "zh-CN", "Chinese_Simplified_Language" },
{ "zh-TW", "Chinese_Traditional_Language" },
};
private static bool _isDevBuild;
private bool _startup;
private bool _isElevated;
@@ -177,6 +210,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private string _settingsBackupMessage;
private string _backupRestoreMessageSeverity;
private int _languagesIndex;
private int _initLanguagesIndex;
private bool _languageChanged;
// Gets or sets a value indicating whether run powertoys on start-up.
public bool Startup
{
@@ -740,6 +777,51 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public ObservableCollection<LanguageModel> Languages { get; } = new ObservableCollection<LanguageModel>();
public int LanguagesIndex
{
get
{
return _languagesIndex;
}
set
{
if (_languagesIndex != value)
{
_languagesIndex = value;
OnPropertyChanged(nameof(LanguagesIndex));
NotifyLanguageChanged();
if (_initLanguagesIndex != value)
{
LanguageChanged = true;
}
else
{
LanguageChanged = false;
}
}
}
}
public bool LanguageChanged
{
get
{
return _languageChanged;
}
set
{
if (_languageChanged != value)
{
_languageChanged = value;
OnPropertyChanged(nameof(LanguageChanged));
}
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null, bool reDoBackupDryRun = true)
{
// Notify UI of property change
@@ -996,5 +1078,31 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
NotifyPropertyChanged(nameof(IsDownloadAllowed));
}
}
private void InitializeLanguages()
{
var lang = LanguageModel.LoadSetting();
int i = 0;
foreach (var item in langTagsAndIds)
{
Languages.Add(new LanguageModel { Tag = item.Key, ResourceID = item.Value, Language = GetResourceString(item.Value) });
if (item.Key.Equals(lang, StringComparison.Ordinal))
{
_initLanguagesIndex = i;
LanguagesIndex = i;
}
i++;
}
}
private void NotifyLanguageChanged()
{
OutGoingLanguageSettings outsettings = new OutGoingLanguageSettings(langTagsAndIds.ElementAt(LanguagesIndex).Key);
SendConfigMSG(outsettings.ToString());
}
}
}

View File

@@ -207,6 +207,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
try
{
settings = settingsUtils.GetSettingsOrDefault<NewPlusSettings>(NewPlusSettings.ModuleName);
if (string.IsNullOrEmpty(settings.TemplateLocation))
{
// This can happen when running the DEBUG Settings application without first letting the runner create the default settings file.
settings.TemplateLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "PowerToys", "NewPlus", "Templates");
}
}
catch (Exception e)
{