mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-26 14:07:24 +01:00
Compare commits
18 Commits
stefan/lan
...
0.85_relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8981e1a825 | ||
|
|
e0c1a1d32b | ||
|
|
3d413eff80 | ||
|
|
2720368d22 | ||
|
|
b0e4336c36 | ||
|
|
bd867c03fe | ||
|
|
24d7e60d55 | ||
|
|
a0088b6335 | ||
|
|
474c6f7322 | ||
|
|
e79b0163b7 | ||
|
|
dca8b7ac35 | ||
|
|
49a828236a | ||
|
|
3cdb30c647 | ||
|
|
6909887844 | ||
|
|
5b616c9eed | ||
|
|
2b4b55cfeb | ||
|
|
13c9ba9f81 | ||
|
|
471db8bf9c |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -65,6 +65,7 @@ body:
|
||||
- Keyboard Manager
|
||||
- Mouse Utilities
|
||||
- Mouse Without Borders
|
||||
- New+
|
||||
- Peek
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
@@ -39,6 +39,7 @@ body:
|
||||
- Keyboard Manager
|
||||
- Mouse Utilities
|
||||
- Mouse Without Borders
|
||||
- New+
|
||||
- Peek
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
|
||||
6
.github/actions/spell-check/expect.txt
vendored
6
.github/actions/spell-check/expect.txt
vendored
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,9 @@ variables:
|
||||
value: false
|
||||
- name: EnablePipelineCache
|
||||
value: true
|
||||
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
|
||||
- name: EnablePipelineCache
|
||||
value: true
|
||||
|
||||
parameters:
|
||||
- name: buildPlatforms
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
146
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
50
src/common/ManagedCommon/LanguageHelper.cs
Normal file
50
src/common/ManagedCommon/LanguageHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
22
src/common/utils/language_helper.h
Normal file
22
src/common/utils/language_helper.h
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
49
src/settings-ui/Settings.UI.Library/LanguageModel.cs
Normal file
49
src/settings-ui/Settings.UI.Library/LanguageModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -229,6 +229,25 @@
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Appearance_Behavior" IsEnabled="True">
|
||||
<tkcontrols:SettingsCard x:Uid="LanguageHeader" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<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=}">
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<HyperlinkButton x:Uid="Windows_Color_Settings" Click="OpenColorsSettings_Click" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user