Compare commits

...

44 Commits

Author SHA1 Message Date
Den Delimarsky
e8ad4fa804 [Awake]Fix DAISY build issues (#34054)
This PR addresses some post-merge issues caught by @davidegiacometti,
including:

1. Separator in the context menu shown when not running from inside
PowerToys.
2. "Keep display on" setting not persisting across switches between
modes.
3. Awake not launching in standalone mode.

Additionally:

1. Exits are now properly handled in **timed** and **expirable**
keep-awake modes when running standalone. This ensures that Awake exists
after completion and doesn't switch to an in-actionable passive mode.
2. Tray tooltips now cover how much time is left on the timer.
3. Fixes #29354
4. Avoids a nasty memory leak because of re-instantiating of `Icon`
objects for every tray update.
5. Adds DPI awareness to the context menu (#16123)
2024-07-30 16:08:37 +01:00
Clint Rutkas
5d77874382 Correcting CppWinRT references (#34025)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

From the WinAppSdk 1.6 test upgrade, we caught a linking issue when we
retargetted to 22000. This is a screenshot from only touching
Cpp.Build.props without the fixes but the retarget.

![image](https://github.com/user-attachments/assets/03c0b592-d600-41f9-b8b4-c3976423003a)


![image](https://github.com/user-attachments/assets/b7928481-3ff2-44ba-889c-0370ef977643)

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2024-07-26 16:23:39 -07:00
stan-sz
5b1e5107ee [Deps]Use MSTest meta package instead of individual test packages (#34019)
## Summary of the Pull Request
This is a follow up on #33964 where using the MSTest meta package brings
in all necessary test dependencies as well as enabled MSTest.Analyzers
for common test code misconfigurations. Coverlet package has not been
used, thus removing.
2024-07-26 11:57:06 +01:00
Den Delimarsky
1be3b6c087 [Awake]Refactor and update version - DAISY023_04102024 (#32378)
Improves the following:

- Consolidates different code paths for easier maintenance.
- Removes the dependency on Windows Forms and creates the system tray
icon and handling through native Win32 APIs (massive thank you to
@BrianPeek for helping write the window creation logic and diagnosing
threading issues).
- Changing modes in Awake now triggers icon changes in the tray
(#11996). Massive thank you to @niels9001 for creating the icons.

Fixes the following:

- When in the UI and you select `0` as hours and `0` as minutes in
`TIMED` awake mode, the UI becomes non-responsive whenever you try to
get back to timed after it rolls back to `PASSIVE`. (#33630)
- Adds the option to keep track of Awake state through tray tooltip.
(#12714)

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
2024-07-25 17:09:17 +01:00
Fefe_du_973
63625a1cee [ColorPicker]Fixed Scrolling through the history in the color picker editor with a mouse wheel (#33551)
## Summary of the Pull Request
Fixed Scrolling through the history in the color picker editor with a
mouse wheel

## Detailed Description of the Pull Request / Additional comments
I added a mousewheel event listener on the HistoryColors ListView, then
I added two functions in the ColorEditorView.xaml.cs to handle the
Event, it checks the number of color in the history and handles the
scroll control accordingly
2024-07-25 17:06:12 +01:00
Heiko
4fee37c35a [AdvPaste]CSV parser: Handle control characters (quotation marks) correctly (#33986)
## Summary of the Pull Request

This PR fixes the csv parsing related to quotation marks according to
csv standard rules:
- An empty data value can be written as `""`. => Remove both quotation
marks.
- Enclosing data by starting and ending with `"` if they contain the
delimiter. => First and last quotation mark has to be removed.
- Escape quotation mark with second quotation mark. => Replace pairs of
two with a single one.

### Input
```csv
A,B,,"","my ""nice"" string","""zz""","""""double quotes"""""
```

### Before this PR (Wrong result)
```json
[
  [
    "A",
    "B",
    "",
    "\"\"",
    "\"my \"\"nice\"\" string\"",
    "\"\"\"zz\"\"\"",
    "\"\"\"\"\"double quotes\"\"\"\"\""
  ]
]
```

### After this PR (Correct result)
```json
[
  [
    "A",
    "B",
    "",
    "",
    "my \"nice\" string",
    "\"zz\"",
    "\"\"double quotes\"\""
  ]
]
```
2024-07-25 14:05:07 +01:00
Ani
d40367a860 [FileLocksmith]Show process files in modal dialog to fix crash (#33804)
## Summary of the Pull Request
To prevent a crash, show the individual files of a process within a
modal dialog rather than as inline text. Please see the linked issue for
more details.

## Detailed Description of the Pull Request / Additional comments
- Removed inline list of process files.
- Added "Show files" button to expander to show list of process files as
a modal dialog. This dialog has the same design as the one used to
display the list of selected folders within the same application.
- Added unhandled exception hander to application similar to our other
applications.


![image](https://github.com/user-attachments/assets/52eddfcc-5e10-40a3-94b2-68bbfb607f1d)

![image](https://github.com/user-attachments/assets/ff996e32-36f6-41a9-a9f0-6dda7a93d09a)
2024-07-25 13:46:20 +01:00
Ani
84def18ed5 [Peek][PreviewPane]Show Copy entry in right-click copy menu (#33845)
## Summary of the Pull Request
Fixes two bugs:
- Peek: Missing "Copy" menu-item for all WebView2 previewers.
- PreviewPane: Missing "Copy" menu-item for markdown files only.

## Detailed Description of the Pull Request / Additional comments
The issues are:
- Peek: 
- When not using Monaco (markdown, html) - the default WebView2 context
menu has been disabled. I have enabled it and then disabled ALL
menu-items other than "Copy" (such as "Back").
- When using Monaco + Release (other code files) - current code tries to
use the Monaco context menu, but it is somehow disabled at runtime. I
spent MANY hours trying to find out why but without success. It works
fine when I view the generated html + js files in a browser or in a
Debug build or in PreviewPane. But I couldn't find the root cause.
Trying to fix it by enabling the WebView2 context menu instead doesn't
work as for whatever reason, WebView2 doesn't generate a "Copy"
menu-item (it thinks there's no selected text when there is). So in this
case, the only thing I could get to work was generating context
menu-items via WebView2 callbacks that call JS functions. As a bonus,
this way of doing it also allows "Toggle text wrapping" to work.
- PreviewPane:
- Markdown - the default WebView2 context menu has been disabled. Like
for Peek, I have enabled it and then disabled ALL menu-items other than
"Copy" (such as "Back").
- Monaco (other code files) - this already just works fine, so I've left
it as is. I *could* make it work the same way as I've done for Peek for
consistency, but I've chosen to leave it as is since it works.
  

![image](https://github.com/user-attachments/assets/d758ada7-bb62-4f40-bef7-ad08ffb83786)

![image](https://github.com/user-attachments/assets/4e0baa7e-632f-412a-b2b1-b9f666277ca7)
2024-07-25 13:30:52 +01:00
David Federman
ac14ad3458 Update MSBuildCache to 0.1.283-preview (#33988)
Update MSBuildCache to 0.1.283-preview

Notable change is this one, which should avoid under-builds when the
build tooling updates: https://github.com/microsoft/MSBuildCache/pull/77

Full release notes:
*
[0.1.283-preview](https://github.com/microsoft/MSBuildCache/releases/tag/v0.1.283-preview)
*
[0.1.273-preview](https://github.com/microsoft/MSBuildCache/releases/tag/v0.1.273-preview)
2024-07-24 12:18:24 -07:00
Davide Giacometti
0a9e889b1b [Build][Installer]Fix NU1503 build warnings (#33938)
## Summary of the Pull Request

This PR aims for fix some CI build warnings.
## Detailed Description of the Pull Request / Additional comments

### Warning NU1503
Fix warning NU1503 for wix toolset projects marking them as restorable
and adding an empty restore target.
### No test result files matching '[ '**/*.trx' ]' were found.
Skip publishing of test results for ARM64 since pipeline isn't running
ARM64 tests

![image](https://github.com/user-attachments/assets/10a64ca2-2bff-42a8-ade9-07bd5ad88e3c)
2024-07-24 16:49:08 +01:00
walex999
7479ef6e65 fixed typo in readme (#33979)
There was a typo in the readme
replaced : finding
with : find

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
2024-07-23 10:28:00 -07:00
Vaibhav Sharma
3652e3627a [AdvPaste]Fix CSV parser supporting escape delimiter by enclosing in double quotes (#33874)
## Summary of the Pull Request
This PR fixes the CSV parser support for escaping delimiter by enclosing
it in quotes

## Detailed Description of the Pull Request / Additional comments
- This PR introduces a fix for the support of adding a delimiter to the
string and supporting it by enclosing it in `"`
2024-07-23 14:29:33 +01:00
Clint Rutkas
07c4972c2c [Deps]Upgrade testing frameworks to latest (#33964)
Upgrading testing frameworks to latest
2024-07-23 11:18:58 +01:00
Clint Rutkas
9e3ac70897 [Deps]Upgrade Microsoft.Data.Sqlite to 8.0.7 (#33963)
Updating to latest sqlite, can hold until .84

VS Code workspaces is the only item that uses this.
2024-07-23 11:04:25 +01:00
Ahmada Yusril
1b27500231 [PTRun][ValueGenerator]Add result entries showing how to use (#33490)
## Summary of the Pull Request
Added usage suggestion in PTRun Value Generator. 

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Added dropdown and give a basic description about the usage of value
generator

![image](https://github.com/microsoft/PowerToys/assets/24465401/2f9e01d1-1f5a-42b5-9234-f768b27124db)

Using fuzzy match to filter relevant queries

![image](https://github.com/microsoft/PowerToys/assets/24465401/dd0594bb-328a-4a8d-9d7c-e3b5732a8573)

---------

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
2024-07-22 16:31:32 +01:00
Heiko
16a1fb7981 [MWB][Enterprise] Add new policies and improve settings UI for the Transfer File setting (#33571)
## Summary of the Pull Request

### Improve settings page for TransferFile setting
The TransferFile setting depends on ShareClipboard setting. This is now
visually shown.

**Before the change:**

![image](https://github.com/microsoft/PowerToys/assets/61519853/735ad7bd-4f45-4914-8bbc-bc47170a9b47)
**After the change**

![image](https://github.com/microsoft/PowerToys/assets/61519853/78fb74c3-29a0-4779-8f0c-9df6c4161be1)


### New policies are added for MWB
Name | Supported states | Id | Behavior
------------ | ------------- | ------------ | -------------
Clipboard sharing enabled | disabled | MwbClipboardSharingEnabled |
Disables the feature if set to disabled.
File transfer enabled | disabled | MwbFileTransferEnabled | Disables the
feature if set to disabled.
Original user interface enabled | disabled | MwbUseOriginalUserInterface
| Disables the feature if set to disabled. |
Disallow blocking screensaver on other machines | enabled |
MwbDisallowBlockingScreensaver | Disables the feature if set to enabled.
|
Connect only in same subnet | enabled & disabled | MwbSameSubnetOnly |
Enables the feature if set to enabled.<br />Disables the feature if set
to disabled. |
Validate remote machine IP Address | enabled & disabled |
MwbValidateRemoteIp | Enables the feature if set to enabled.<br
/>Disables the feature if set to disabled.
Disable user defined IP Address mapping rules | enabled |
MwbDisableUserDefinedIpMappingRules | If enabled the user can't define
IP Address mapping rules.
Predefined IP Address mappings | enabled with multi-line text value |
MwbPolicyDefinedIpMappingRules | Allows admins to force define IP
Address mapping rules.

#### User Interface screenshots

![image](https://github.com/microsoft/PowerToys/assets/61519853/3d8a46c5-13f3-4a47-80a1-c0d242d8541c)

![image](https://github.com/microsoft/PowerToys/assets/61519853/44f4dc60-5106-45bf-9bb4-aa0bde9ef6fa)

![image](https://github.com/microsoft/PowerToys/assets/61519853/569be956-e889-442c-bdc9-e319ad3c19e3)
2024-07-22 15:49:45 +01:00
Heiko
ca97e01d59 [PTRun][System]Support automatic logon after reboot (#33740)
## Summary of the Pull Request
Adds support for auto-logon after reboot when using the reboot command
in system plugin.

The behavior after reboot depends on the system setting for auto-logon
after reboot/updates.

https://learn.microsoft.com/windows-server/administration/windows-commands/shutdown
2024-07-22 15:43:42 +01:00
Vaibhav Sharma
70d3d5f16e [PTRun][Registry]Allow interchangeable use of / instead of \ (#33309)
## Summary of the Pull Request
As the title suggests, the PR adds the feature of using / instead of \
in the Registry plugin of PT Run.
2024-07-22 15:14:10 +01:00
Dustin L. Howett
af6916a538 README: switch status badges over to shine-oss (#33929)
We are moving away from the `ms` organization.
2024-07-19 10:44:15 -07:00
Clint Rutkas
0c00106d5a Update CODEOWNERS
trying without ./
2024-07-18 15:10:04 -07:00
Clint Rutkas
96642b6525 Update CODEOWNERS w/ correct casing on markdown files (#33919)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2024-07-18 15:07:08 -07:00
Clint Rutkas
9bea986f3d Update CODEOWNERS (#33918)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Adjusting to root folder the main items. i was able to mod
code_of_conduct.md without a lock down.

https://github.com/microsoft/PowerToys/pull/33915

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2024-07-18 14:07:17 -07:00
Craig Loewen
6fdc86ed2d Removed similar issues bot GitHub Action (#33909)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Remove the GitHub action version of the Similar issues bot. This was the
prototype version, we have now developed a more robust version in
partnership with GitHub (currently in the form of a GitHub app) and are
installing that on the repo instead.
2024-07-18 10:27:24 -07:00
Ani
78d53ffb10 [PTRun]Fixed unstable startup position after moving to PerMonitorV2 (#33784)
## Summary of the Pull Request
Fixes an issue where PowerToys Run can sometimes start up in an
inconvenient position in a multi-monitor / multi-DPI setup.
2024-07-18 15:16:19 +01:00
Davide Giacometti
7d8af7bbbb [Build]Remove wildcards items in vcxproj (#33862)
## Summary of the Pull Request

Fix the solution warnings related to wildcards items in vcxproj.

## Detailed Description of the Pull Request / Additional comments

https://learn.microsoft.com/cpp/build/reference/vcxproj-files-and-wildcards#list-all-items-explicitly
Since wildcards where used for assets that doesn't change frequently I
have added `ReplaceWildcardsInProjectItems` for CPP projects. This will
make VS automatically expand wildcards.
2024-07-18 14:48:46 +01:00
Heiko
7808033436 [PTRun][DateTime]Setting for First week of year and First day of week (#33406)
## Summary of the Pull Request

This PR implements two new plugin settings:
- **First week of year**

![image](https://github.com/microsoft/PowerToys/assets/61519853/c866ffc2-2a21-438c-9a1a-5f4c7f68a22e)

- **First day of week**

![image](https://github.com/microsoft/PowerToys/assets/61519853/b2ec125b-d87c-40c5-8793-743a1ffae237)

## Detailed Description of the Pull Request / Additional comments

For both settings the users can decide to be in sync with the system
settings (default) or to use their own setting. The order of days for
the `first day of week` setting is based on the current system culture.

PT Run respects these settings for the relevant results:
- calendar week
- week of month
- number of day in week
2024-07-18 14:29:01 +01:00
Muhammad Danish
98cfeb0776 [ci]Case insensitively replace version prefix for winget's PackageVersion (#33810) 2024-07-18 11:39:51 +01:00
Davide Giacometti
a3e193e56e [Runner]Handle release tag with uppercase V during update (#33822)
## Summary of the Pull Request

The latest fix pushed with tag `V0.82.1` (uppercase V) isn't detected by
the PT internal updater.
Handle release tag with uppercase V during update.
2024-07-18 11:37:01 +01:00
Clint Rutkas
d668a659b5 [Build]Remove /Zm compiler limitation from KBM common (#33747)
## Summary of the Pull Request

This looks to have been added years ago for some reason. It used to be
2000 and was pushed to 200. If a user needed to do the /Zm flag,
shouldn't they pick what they want?
2024-07-18 10:57:25 +01:00
8LWXpg
fb8765b54d [PTRun][Docs] Add GitHubRepo and ProcessKiller third-party plugins (#33830)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Add [GitHubRepo](https://github.com/8LWXpg/PowerToysRun-GitHubRepo) and
[ProcessKiller](https://github.com/8LWXpg/PowerToysRun-ProcessKiller)

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2024-07-17 11:58:56 -07:00
Anthony Mason
b9f6ef6ee4 Add RDP plugin to thirdPartyRunPlugins.md (#33827)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Add RDP plugin  information to thirdPartyRunPlugins.md.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [x] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2024-07-17 11:33:28 -07:00
Ani
7457ff5202 [Settings]Fix crash on dashboard when KBM settings update (#33872)
## Summary of the Pull Request
Fixed crash of Settings when KBM settings update by any mechanism.

## Detailed Description of the Pull Request / Additional comments
To show updated KBM remappings on the dashboard, the Settings app
watches for changes to the KBM settings file and reloads the file on
change. Reloading the file can fail (most likely because the writing
process takes an exclusive lock and isn't yet done writing) and the
process crashes when this happens. This change guards against this.
2024-07-17 16:09:00 +01:00
Vaibhav Sharma
fb36e6ced9 [PTRun][UnitConverter]Accept kilometers per hour abbreviations (#33594)
## Summary of the Pull Request
This PR introduces abbreviations for the units - `kph` that gets
converted to `km/h` and `cph` that gets converted to `cm/h`.
2024-07-17 16:00:05 +01:00
octastylos-pseudodipteros
f1ca65ca78 [QuickAccent]Add Bulgarian (#33550)
## Summary of the Pull Request
Adds Bulgarian to Quick Accent

## Detailed Description of the Pull Request / Additional comments
Adds Cyrillic letter short I (й)
2024-07-17 15:44:42 +01:00
Heiko
b7c8bb201b [Settings]Adjust Action Keyword info bar padding on PowerToys Run plugins (#33693)
## Summary of the Pull Request

There is a layout bug that cause the info bar to overlap the space
between the settings cards.

### Before the fix

![image](https://github.com/microsoft/PowerToys/assets/61519853/3435e036-cc02-4663-ac94-4612afd6be9d)


### After the fix

![image](https://github.com/microsoft/PowerToys/assets/61519853/95bce0f8-c194-4300-80c0-294d237b5e8c)
2024-07-17 15:31:42 +01:00
Heiko
c1e8b70a64 [Settings]Align glyphs in the MWB shortcut settings section (#33586)
## Summary of the Pull Request

Add icon to `Easy Mouse` feature.
Fix icon of `Switch Between Machine Shortcut`.
2024-07-17 15:21:38 +01:00
Heiko
0ddff0fcf7 [MWB]Disable settings from old UI that we do not support (#33564)
## Summary of the Pull Request

This PR disables the check boxes for settings that we do not support in
the PowerToys implementation.

![image](https://github.com/microsoft/PowerToys/assets/61519853/da3dadf0-8ba4-4f0f-b491-9ca21b54c20a)

![image](https://github.com/microsoft/PowerToys/assets/61519853/8b561b9f-f4b5-4a5b-b61e-710d5b0f2b61)
2024-07-17 14:51:38 +01:00
Heiko
c87d8c37e1 [Settings]Fix MWB infobars to hide when the module is disabled (#33562)
## Summary of the Pull Request

This PR fixes two bugs for the info bars on the MWB settings page:
- The bars are shown if module is disabled.
- Some bars can be closed.
- Some bars are reacting to tab stop on closed state.
2024-07-17 14:42:22 +01:00
Heiko
c24000ec41 [Enterprise]Reorganize ADMX to make it easier to find some policies (#33529)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

1. The "Allow Experimentation" policy is moved into a new category
"General Settings".

![image](https://github.com/microsoft/PowerToys/assets/61519853/09c77ed5-3e50-470d-bec6-41da1cc363f3)

2. The order of utility policies is changed that the global policy shows
at the top on opening the edit dialog.

![image](https://github.com/microsoft/PowerToys/assets/61519853/34317791-6b2d-487c-b341-bdebdd81e58c)
The oder change does not take any effect in the HTML reports.

![image](https://github.com/microsoft/PowerToys/assets/61519853/0c84cabb-139d-4854-8f73-641d523ca555)

**These changes do not break configured Group Policies. But the changes
WILL BREAK EXISTING INTUNE POLICY CONFIGURATION SETS. **
2024-07-17 14:15:44 +01:00
ARCHISMAN DAS
2ca70e31c9 [Settings]Fix spacing in the update available card (#33198) 2024-07-17 11:32:42 +01:00
Jaime Bernardo
d27ac581ac [Deps]Set System.Text.Json version to 8.0.4 (#33867)
## Summary of the Pull Request

Sets the System.Text.Json version to 8.0.4
2024-07-16 16:17:53 +01:00
Clint Rutkas
5159c76976 [Deps]Upgrade Microsoft.Windows.Compatibility to 8.0.7 (#33851) 2024-07-16 11:57:51 +01:00
Jaime Bernardo
8d2ee4a8d2 [Docs]Update README for correct 0.82.1 links (#33849)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
We renamed the tag for lower-case v.
2024-07-15 17:57:19 +01:00
Clint Rutkas
5bcc6fbd86 Updated hashes for 82.1 (#33809)
updated hashes
2024-07-12 20:05:20 -07:00
162 changed files with 3808 additions and 1058 deletions

6
.github/CODEOWNERS vendored
View File

@@ -4,7 +4,7 @@
/.github/actions/spell-check/
# locking down pipeline folder
/.pipelines @crutkas @DHowett @ethanfangg
/.pipelines/ @crutkas @DHowett @ethanfangg
# locking down nuget config
nuget.config @crutkas @DHowett @ethanfangg
@@ -12,5 +12,5 @@ packages.config @crutkas @DHowett @ethanfangg
# locking down files that should not change
LICENSE @crutkas @DHowett @ethanfangg
SECURITY.MD @crutkas @DHowett @ethanfangg
CODE_OF_CONDUCT.MD @crutkas @DHowett @ethanfangg
SECURITY.md @crutkas @DHowett @ethanfangg
CODE_OF_CONDUCT.md @crutkas @DHowett @ethanfangg

View File

@@ -28,6 +28,7 @@ videoconference
# USERS
8LWXpg
Adoumie
Advaith
alekhyareddy
@@ -143,6 +144,7 @@ TBM
tilovell
Triet
waaverecords
Xpg
ycv
Yuniardi
yuyoyuppe

View File

@@ -200,6 +200,7 @@ CMINVOKECOMMANDINFO
CMINVOKECOMMANDINFOEX
CMock
CMONITORS
cmph
cmpgt
cne
CNF
@@ -220,7 +221,6 @@ comdlg
comexp
cominterop
commandline
COMMANDTITLE
commctrl
commdlg
compmgmt
@@ -244,6 +244,7 @@ copiedcolorrepresentation
cotaskmem
COULDNOT
countof
cph
CPower
cppblog
cppruntime
@@ -428,8 +429,8 @@ ENDSESSION
ENTERSIZEMOVE
ENU
EOAC
epu
EPO
epu
ERASEBKGND
EREOF
EResize
@@ -484,7 +485,6 @@ FILEFLAGSMASK
FILELOCKSMITH
FILELOCKSMITHCONTEXTMENU
FILELOCKSMITHEXT
FILELOCKSMITHLIB
FILELOCKSMITHLIBINTEROP
FILEMUSTEXIST
FILEOP
@@ -500,6 +500,7 @@ findfast
FIXEDFILEINFO
flac
flyouts
FMask
FOF
FOFX
FOLDERID
@@ -521,7 +522,6 @@ GCLP
gdi
gdiplus
GDISCALED
gdnbaselines
GEmoji
GETCLIENTAREAANIMATION
GETDESKWALLPAPER
@@ -541,7 +541,6 @@ gpedit
gpo
GPOCA
gpp
GPT
gpu
GSM
gtm
@@ -701,7 +700,6 @@ INSTALLSTARTMENUSHORTCUT
INSTALLSTATE
Inste
Intelli
interactable
Interlop
INTRESOURCE
INVALIDARG
@@ -730,7 +728,6 @@ IWeb
IWIC
iwr
IYUV
JArray
jfi
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
@@ -760,8 +757,8 @@ keyremaps
keyvault
KILLFOCUS
killrunner
kmph
Knownfolders
ksh
KSPROPERTY
Kybd
languagesjson
@@ -797,7 +794,6 @@ LOADFROMFILE
LOBYTE
LOCALDISPLAY
LOCALPACKAGE
localport
LOCALSYSTEM
LOCATIONCHANGE
LOGFONT
@@ -811,6 +807,7 @@ LOWORD
lparam
LPBITMAPINFOHEADER
LPCITEMIDLIST
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
LPCRECT
@@ -835,6 +832,7 @@ lptpm
LPTR
LPTSTR
LPW
lpwcx
lpwndpl
LReader
LRESULT
@@ -852,10 +850,10 @@ lwin
LZero
majortype
makecab
MAKELANGID
MAKEINTRESOURCE
MAKEINTRESOURCEA
MAKEINTRESOURCEW
MAKELANGID
makepri
manifestdependency
MAPPEDTOSAMEKEY
@@ -875,7 +873,6 @@ mdwn
MEDIASUBTYPE
mediatype
mef
MENUBREAK
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
@@ -1038,6 +1035,7 @@ NOSEARCH
NOSENDCHANGING
NOSIZE
NOTIFICATIONSDLL
NOTIFYICONDATA
NOTIFYICONDATAW
NOTIMPL
notlike
@@ -1074,7 +1072,6 @@ oldtheme
oleaut
OLECHAR
onebranch
OOBEPT
opencode
OPENFILENAME
opensource
@@ -1098,7 +1095,6 @@ OVERLAPPEDWINDOW
overlaywindow
Oversampling
OWNDC
OWNERDRAW
Packagemanager
PACL
PAINTSTRUCT
@@ -1112,7 +1108,6 @@ PARTIALCONFIRMATIONDIALOGTITLE
PATCOPY
pathcch
PATHMUSTEXIST
Pathto
PATINVERT
PATPAINT
PAUDIO
@@ -1161,6 +1156,7 @@ ploca
plocm
pluginsmodel
PMSIHANDLE
pnid
Pnp
Popups
POPUPWINDOW
@@ -1253,8 +1249,6 @@ QUERYENDSESSION
QUERYOPEN
QUEUESYNC
QUNS
qwertyuiopasdfghjklzxcvbnm
qwrtyuiopsghjklzxvnm
raf
RAII
RAlt
@@ -1271,7 +1265,6 @@ RECTDESTINATION
rectp
RECTSOURCE
recyclebin
redirectedfrom
Redist
redistributable
reencode
@@ -1353,8 +1346,6 @@ runas
rundll
rungameid
RUNLEVEL
runsettings
runspace
runtimeclass
runtimeobject
runtimepack
@@ -1375,6 +1366,7 @@ SCID
Scip
scipbe
Scode
screensaver
screenshots
scrollviewer
sddl
@@ -1587,7 +1579,6 @@ TDevice
telem
telephon
templatenamespace
testhost
testprocess
TEXCOORD
TEXTEXTRACTOR
@@ -1612,7 +1603,6 @@ tlb
tlbimp
TMPVAR
TNP
toggleswitch
Toolhelp
toolkitconverters
Toolset
@@ -1642,9 +1632,11 @@ TYPESHORTCUT
UAC
UAL
uap
UCallback
udit
uefi
uesc
UFlags
UHash
UIA
UIEx
@@ -1682,6 +1674,7 @@ USESHOWWINDOW
USESTDHANDLES
USRDLL
UType
uuidv
uwp
uxtheme
vabdq

View File

@@ -24,7 +24,7 @@ jobs:
$installerMachineX64Url = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysSetup.*x64' | Select -ExpandProperty browser_download_url
$installerUserArmUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysUserSetup.*arm64' | Select -ExpandProperty browser_download_url
$installerMachineArmUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysSetup.*arm64' | Select -ExpandProperty browser_download_url
$ver = $targetRelease.tag_name.Trim("v")
$ver = $targetRelease.tag_name -ireplace '^v'
# getting latest wingetcreate file
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe

View File

@@ -1,33 +0,0 @@
name: GitGudSimilarIssues comments
on:
issues:
types: [opened]
jobs:
getSimilarIssues:
runs-on: ubuntu-latest
outputs:
message: ${{ steps.getBody.outputs.message }}
steps:
- id: getBody
uses: craigloewen-msft/GitGudSimilarIssues@main
with:
issueTitle: ${{ github.event.issue.title }}
issueBody: ${{ github.event.issue.body }}
repo: ${{ github.repository }}
similarityTolerance: "0.75"
add-comment:
needs: getSimilarIssues
runs-on: ubuntu-latest
permissions:
issues: write
if: needs.getSimilarIssues.outputs.message != ''
steps:
- name: Add comment
run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
BODY: ${{ needs.getSimilarIssues.outputs.message }}

View File

@@ -246,7 +246,7 @@ steps:
inputs:
testResultsFormat: VSTest
testResultsFiles: '**/*.trx'
condition: always()
condition: ne(variables['BuildPlatform'],'arm64')
# Native dlls
- task: VSTest@2

View File

@@ -33,6 +33,7 @@
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
<VcpkgEnabled>false</VcpkgEnabled>
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath)</ExternalIncludePath>
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
<!-- This covers the case where a .dll exports a .lib, but doesn't have any ClCompile entries. -->

View File

@@ -85,7 +85,7 @@
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
<!-- Change this to bust the cache -->
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202407100737</MSBuildCacheCacheUniverse>
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202407200737</MSBuildCacheCacheUniverse>
<!--
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.

View File

@@ -17,7 +17,6 @@
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="coverlet.collector" Version="1.3.0" />
<PackageVersion Include="DotNetSeleniumExtras.WaitHelpers" Version="3.11.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />
@@ -28,19 +27,18 @@
<PackageVersion Include="Mages" Version="2.0.2" />
<PackageVersion Include="Markdig.Signed" Version="0.34.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.336902" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2365.46" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.1" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.7" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.4" />
@@ -52,8 +50,7 @@
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.2.0" />
<PackageVersion Include="MSTest.TestFramework" Version="3.2.0" />
<PackageVersion Include="MSTest" Version="3.5.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="NLog" Version="5.0.4" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
@@ -79,12 +76,13 @@
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="System.Reactive" Version="6.0.0-preview.9" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
<!-- Package System.Security.Cryptography.ProtectedData added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.50.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
@@ -98,4 +96,4 @@
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1318,17 +1318,16 @@ EXHIBIT A -Mozilla Public License.
- Mages 2.0.2
- Markdig.Signed 0.34.0
- Microsoft.CodeAnalysis.NetAnalyzers 8.0.0
- Microsoft.Data.Sqlite 8.0.0
- Microsoft.Data.Sqlite 8.0.7
- Microsoft.Extensions.DependencyInjection 8.0.0
- Microsoft.Extensions.Hosting 8.0.0
- Microsoft.Extensions.Hosting.WindowsServices 8.0.0
- Microsoft.Extensions.Logging 8.0.0
- Microsoft.Extensions.Logging.Abstractions 8.0.0
- Microsoft.NET.Test.Sdk 17.8.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2365.46
- Microsoft.Win32.SystemEvents 8.0.0
- Microsoft.Windows.Compatibility 8.0.1
- Microsoft.Windows.Compatibility 8.0.7
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.0.4
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
@@ -1338,8 +1337,7 @@ EXHIBIT A -Mozilla Public License.
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4
- Moq 4.18.4
- MSTest.TestAdapter 3.2.0
- MSTest.TestFramework 3.2.0
- MSTest 3.5.0
- NLog.Extensions.Logging 5.3.8
- NLog.Schema 5.2.8
- ReverseMarkdown 4.1.0
@@ -1359,11 +1357,12 @@ EXHIBIT A -Mozilla Public License.
- System.IO.Abstractions 17.2.3
- System.IO.Abstractions.TestingHelpers 17.2.3
- System.Management 8.0.0
- System.Reactive 6.0.0-preview.9
- System.Reactive 6.0.1
- System.Runtime.Caching 8.0.0
- System.Security.Cryptography.ProtectedData 8.0.0
- System.ServiceProcess.ServiceController 8.0.0
- System.Text.Encoding.CodePages 8.0.0
- System.Text.Json 8.0.4
- UnicodeInformation 2.6.0
- UnitsNet 5.50.0
- UTF.Unknown 2.5.1

View File

@@ -171,6 +171,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
src\.editorconfig = src\.editorconfig
.vsconfig = .vsconfig
Cpp.Build.props = Cpp.Build.props
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props

View File

@@ -8,8 +8,8 @@
| Architecture | Solution (Main) | Solution (Stable) | Installer (Main) |
|--------------|-----------------|-------------------|------------------|
| x64 | [![Build Status for Main](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=main&jobName=Build%20x64%20Release)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=main&jobName=Build%20x64%20Release) | [![Build Status for Stable](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=stable&jobName=Build%20x64%20Release)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=stable) | [![Build Status Installer pipeline](https://dev.azure.com/microsoft/Dart/_apis/build/status/PowerToys/PowerToys%20Signed%20YAML%20Release%20Build?branchName=main&jobName=Build&configuration=Build%20Release_x64)](https://dev.azure.com/microsoft/Dart/_build/latest?definitionId=76541&branchName=main) |
| ARM64 | [![Build Status for Main](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=main&jobName=Build%20arm64%20Release)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=main) | [![Build Status for Main](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=main&jobName=Build%20arm64%20Release)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=stable) | [![Build Status Installer pipeline](https://dev.azure.com/microsoft/Dart/_apis/build/status/PowerToys/PowerToys%20Signed%20YAML%20Release%20Build?branchName=main&jobName=Build&configuration=Build%20Release_arm64)](https://dev.azure.com/microsoft/Dart/_build/latest?definitionId=76541&branchName=main) |
| x64 | [![Build Status for Main](https://dev.azure.com/shine-oss/PowerToys/_apis/build/status%2FPowerToys%20CI?branchName=main&jobName=Build%20x64%20Release)](https://dev.azure.com/shine-oss/PowerToys/_build/latest?definitionId=3&branchName=main) | [![Build Status for Stable](https://dev.azure.com/shine-oss/PowerToys/_apis/build/status%2FPowerToys%20CI?branchName=stable&jobName=Build%20x64%20Release)](https://dev.azure.com/shine-oss/PowerToys/_build/latest?definitionId=3&branchName=stable) | [![Build Status Installer pipeline](https://dev.azure.com/microsoft/Dart/_apis/build/status/PowerToys/PowerToys%20Signed%20YAML%20Release%20Build?branchName=main&jobName=Build&configuration=Build%20Release_x64)](https://dev.azure.com/microsoft/Dart/_build/latest?definitionId=76541&branchName=main) |
| ARM64 | [![Build Status for Main](https://dev.azure.com/shine-oss/PowerToys/_apis/build/status%2FPowerToys%20CI?branchName=main&jobName=Build%20arm64%20Release)](https://dev.azure.com/shine-oss/PowerToys/_build/latest?definitionId=3&branchName=main) | [![Build Status for Stable](https://dev.azure.com/shine-oss/PowerToys/_apis/build/status%2FPowerToys%20CI?branchName=main&jobName=Build%20arm64%20Release)](https://dev.azure.com/shine-oss/PowerToys/_build/latest?definitionId=3&branchName=main) | [![Build Status Installer pipeline](https://dev.azure.com/microsoft/Dart/_apis/build/status/PowerToys/PowerToys%20Signed%20YAML%20Release%20Build?branchName=main&jobName=Build&configuration=Build%20Release_arm64)](https://dev.azure.com/microsoft/Dart/_build/latest?definitionId=76541&branchName=main) |
## About
@@ -43,17 +43,17 @@ Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and cl
<!-- 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.83%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.82%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.0/PowerToysUserSetup-0.82.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.0/PowerToysUserSetup-0.82.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.0/PowerToysSetup-0.82.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.0/PowerToysSetup-0.82.0-arm64.exe
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.1/PowerToysUserSetup-0.82.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.1/PowerToysUserSetup-0.82.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.1/PowerToysSetup-0.82.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.82.1/PowerToysSetup-0.82.1-arm64.exe
| Description | Filename | sha256 hash |
|----------------|----------|-------------|
| Per user - x64 | [PowerToysUserSetup-0.82.0-x64.exe][ptUserX64] | 295E2A4622C7E347D3E1BAEA6B36BECC328B566496678F1F87DE3F8A12A7F89A |
| Per user - ARM64 | [PowerToysUserSetup-0.82.0-arm64.exe][ptUserArm64] | 55D25D068C6148F0A15C7806B9F813224ABA9C461943F42BB2A8B0E22D28240C |
| Machine wide - x64 | [PowerToysSetup-0.82.0-x64.exe][ptMachineX64] | 01B59C00BB43C25BEFEF274755875717AB4DEAB57C0354AB96CF5B1DA4837C9A |
| Machine wide - ARM64 | [PowerToysSetup-0.82.0-arm64.exe][ptMachineArm64] | 1F642B50962516127793C4D3556BF4FC24B9738BAC2F362CAA3BFF8B0C3AF97F |
| Per user - x64 | [PowerToysUserSetup-0.82.1-x64.exe][ptUserX64] | B594C9A32125079186DCE776431E2DC77B896774D2AEE2ACF51BAB8791683485 |
| Per user - ARM64 | [PowerToysUserSetup-0.82.1-arm64.exe][ptUserArm64] | 41C1D9C0E8FA7EFFCE6F605C92C143AE933F8C999A2933A4D9D1115B16F14F67 |
| Machine wide - x64 | [PowerToysSetup-0.82.1-x64.exe][ptMachineX64] | B8FA7E7C8F88B69E070E234F561D32807634E2E9D782EDBB9DC35F3A454F2264 |
| Machine wide - ARM64 | [PowerToysSetup-0.82.1-arm64.exe][ptMachineArm64] | 58F22306F22CF9878C6DDE6AC128388DF4DFF78B76165E38A695490E55B3C8C4 |
This is our preferred method.
@@ -151,7 +151,7 @@ In this release, we focused on stability and improvements.
### Installer
- Fixed the remaining install failures when the folders the DSC module is to be installed in isn't accessible by the WiX installer for user scope installations.
- Fixed an issue causing ARM 64 uninstall process to not correctly finding powershell 7 to run uninstall scripts.
- Fixed an issue causing ARM64 uninstall process to not correctly find powershell 7 to run uninstall scripts.
### Peek

View File

@@ -71,12 +71,14 @@ The following formats are currently available:
- All available settings for the plugin are defined in the [`TimeDateSettings`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs) class of the plugin. The settings can be accessed everywhere in the plugin code via the static class instance `TimeDateSettings.Instance`.
- We have the following settings that the user can configure to change the behavior of the plugin:
| Key | Default value | Name | Description |
|--------------|-----------|------------|------------|
| `OnlyDateTimeNowGlobal` | `true` | Show only 'Time', 'Date', and 'Now' result for system time on global queries | Regardless of this setting, for global queries the first word of the query has to be a complete match. |
| `TimeWithSeconds` | `false` | Show time with seconds | This setting applies to the 'Time' and 'Now' result. |
| `DateWithWeekday` | `false` | Show date with weekday and name of month | This setting applies to the 'Date' and 'Now' result. |
| `HideNumberMessageOnGlobalQuery` | `false` | Hide 'Invalid number input' error message on global queries | |
| Key | Type | Default value | Name | Description |
|--------------|--------------|-----------|------------|------------|
| `CalendarFirstWeekRule` | Combo box | `-1` (Use system settings) | First week of the year | Configure the calendar rule for the first week of the year. |
| `FirstDayOfWeek` | Combo box | `-1` (Use system settings) | First day of the week | |
| `OnlyDateTimeNowGlobal` | Checkbox | `true` | Show only 'Time', 'Date', and 'Now' result for system time on global queries | Regardless of this setting, for global queries the first word of the query has to be a complete match. |
| `TimeWithSeconds` | Checkbox | `false` | Show time with seconds | This setting applies to the 'Time' and 'Now' result. |
| `DateWithWeekday` | Checkbox | `false` | Show date with weekday and name of month | This setting applies to the 'Date' and 'Now' result. |
| `HideNumberMessageOnGlobalQuery` | Checkbox | `false` | Hide 'Invalid number input' error message on global queries | |
## Classes
@@ -97,6 +99,7 @@ The following formats are currently available:
### [`TimeAndDateHelper.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs)
- The [`TimeAndDateHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs) class contains methods to format/convert date and time formats/strings.
- And it contains methods to return the `first week day` and `first week of the year rule` based on the current plugin settings.
### [`TimeDateSettings.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs)
- The [`TimeDateSettings`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs) class provides access to all optional plugin settings.
@@ -129,11 +132,6 @@ On global queries the high score returned by `FuzzySearch` has negative impacts
## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests)
We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests) that executes various test to ensure that the plugin works as expected.
### [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs)
- The [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs) class contains tests to validate that the time and date values are correctly formatted/calculated.
- That we can execute the tests at any time on any machine, we use a specified date/time value and set the thread culture always to `en-us` while executing the tests.
- Some tests contain checks that calculate the expected result at runtime instead of using an expected value written fix in the code. This is done to get valid results on every machine at any time.
### [`ImageTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs)
- The [`ImageTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs) class contains tests to validate that each result shows the expected and correct image.
- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
@@ -147,4 +145,13 @@ We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.
### [`StringParserTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs)
- The [`StringParserTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs) class contains tests to validate that the typed string gets converted correctly into a `DateTime` object.
- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
### [`TimeAndDateHelperTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeAndDateHelperTests.cs)
- The [`TimeAndDateHelperTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeAndDateHelperTests.cs) class contains tests to validate important methods form the `TimeAndDateHelper` class that are not used for string parsing.
### [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs)
- The [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs) class contains tests to validate that the time and date values are correctly formatted/calculated.
- That we can execute the tests at any time on any machine, we use a specified date/time value and set the thread culture always to `en-us` while executing the tests.
- Some tests use custom settings for the first day of week and the first week of year. (This is done in the tests for the affected results to validate them for different settings/cultures.)
- Some tests contain checks that calculate the expected result at runtime instead of using an expected value written fix in the code. This is done to get valid results on every machine at any time.

View File

@@ -1,21 +1,34 @@
---
last-update: 3-20-2022
last-update: 7-16-2024
---
# PowerToys Awake Changelog
## Builds
The build ID can be found in `Program.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
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 |
|:----------------------------------------------------------|:-----------------|
| [`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 |
### `DAISY023_04102024` (April 10, 2024)
>[!NOTE]
>See pull request: [Awake Update - `DAISY023_04102024`](https://github.com/microsoft/PowerToys/pull/32378)
- [#33630](https://github.com/microsoft/PowerToys/issues/33630) When in the UI and you select `0` as hours and `0` as minutes in `TIMED` awake mode, the UI becomes non-responsive whenever you try to get back to timed after it rolls back to `PASSIVE`.
- [#12714](https://github.com/microsoft/PowerToys/issues/12714) Adds the option to keep track of Awake state through tray tooltip.
- [#11996](https://github.com/microsoft/PowerToys/issues/11996) Adds custom icons support for mode changes in Awake.
- Removes the dependency on `System.Windows.Forms` and instead uses native Windows APIs to create the tray icon.
- Removes redundant/unused code that impacted application performance.
- Updates dependent packages to their latest versions (`Microsoft.Windows.CsWinRT` and `System.Reactive`).
### `ATRIOX_04132023` (April 13, 2023)
- Moves from using `Task.Run` to spin up threads to actually using a blocking queue that properly sets thread parameters on the same thread.

View File

@@ -36,6 +36,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [WebSearchShortcut](https://github.com/Daydreamer-riri/PowerToys-Run-WebSearchShortcut) | [Riri](https://github.com/Daydreamer-riri) | Select a specific search engine to perform searches. |
| [UnicodeInput](https://github.com/nathancartlidge/powertoys-run-unicode) | [nathancartlidge](https://github.com/nathancartlidge) | Copy Unicode characters to the clipboard |
| [PowerHexInspector](https://github.com/NaroZeol/PowerHexInspector) | [NaroZeol](https://github.com/NaroZeol) | Peek other forms of an input number |
| [GitHubRepo](https://github.com/8LWXpg/PowerToysRun-GitHubRepo) | [8LWXpg](https://github.com/8LWXpg) | Search and open GitHub repositories |
| [ProcessKiller](https://github.com/8LWXpg/PowerToysRun-ProcessKiller) | [8LWXpg](https://github.com/8LWXpg) | Search and kill processes |
## Extending software plugins
@@ -47,6 +49,7 @@ Below are community created plugins that target a website or software. They are
| [Edge Workspaces](https://github.com/quachpas/PowerToys-Run-EdgeWorkspaces) | [quachpas](https://github.com/quachpas) | Open Microsoft Edge workspaces|
| [Everything](https://github.com/lin-ycv/EverythingPowerToys) | [Yu Chieh (Victor) Lin](https://github.com/Lin-ycv) | Get search results from Everything |
| [GitKraken](https://github.com/davidegiacometti/PowerToys-Run-GitKraken) | [davidegiacometti](https://github.com/davidegiacometti) | Open GitKraken repositories |
| [RDP](https://github.com/anthony81799/PowerToysRun-RDP)) | [anthony81799](https://github.com/anthony81799) | Open Remote Desktop connections |
| [Visual Studio Recents](https://github.com/davidegiacometti/PowerToys-Run-VisualStudio) | [davidegiacometti](https://github.com/davidegiacometti) | Open Visual Studio recents |
| [WinGet](https://github.com/bostrot/PowerToysRunPluginWinget) | [bostrot](https://github.com/bostrot) | Search and install packages from WinGet |
| [Scoop](https://github.com/Quriz/PowerToysRunScoop) | [Quriz](https://github.com/Quriz) | Search and install packages from Scoop |

View File

@@ -76,4 +76,12 @@
<Error Condition="!Exists('..\wix.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\wix.props'))" />
</Target>
<!-- Prevents NU1503 -->
<Target Name="_IsProjectRestoreSupported" Returns="@(_ValidProjectsForRestore)">
<ItemGroup>
<_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" />
</ItemGroup>
</Target>
<Target Name="Restore" />
</Project>

View File

@@ -185,4 +185,12 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\..\src\common\FilePreviewCommon\Assets\Monaco\monacoSRC" PreprocessorVariable="var.MonacoSRCHarvestPath" OutputFile="MonacoSRC.wxs" ComponentGroupName="MonacoSRCHeatGenerated" DirectoryRefId="MonacoPreviewHandlerMonacoSRCFolder" AutogenerateGuids="false" GenerateGuidsNow="true" ToolPath="$(WixToolPath)" RunAsSeparateProcess="true" SuppressFragments="false" SuppressRegistry="false" SuppressRootDirectory="true"/>
</Target>
<!-- Prevents NU1503 -->
<Target Name="_IsProjectRestoreSupported" Returns="@(_ValidProjectsForRestore)">
<ItemGroup>
<_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" />
</ItemGroup>
</Target>
<Target Name="Restore" />
</Project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.MSBuildCache.AzurePipelines" version="0.1.271-preview" />
<package id="Microsoft.MSBuildCache.Local" version="0.1.271-preview" />
<package id="Microsoft.MSBuildCache.SharedCompilation" version="0.1.271-preview" />
<package id="Microsoft.MSBuildCache.AzurePipelines" version="0.1.283-preview" />
<package id="Microsoft.MSBuildCache.Local" version="0.1.283-preview" />
<package id="Microsoft.MSBuildCache.SharedCompilation" version="0.1.283-preview" />
</packages>

View File

@@ -9,7 +9,7 @@
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
// `lang` is the language of the file
// `wrap` if the editor is wrapping or not
var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
var lang = "[[PT_LANG]]";
var wrap = ([[PT_WRAP]] == 1) ? true : false;
@@ -19,11 +19,29 @@
var stickyScroll = ([[PT_STICKY_SCROLL]] == 1) ? true : false;
var fontSize = [[PT_FONT_SIZE]];
var contextMenu = ([[PT_CONTEXTMENU]] == 1) ? true : false;
var editor;
// Code taken from https://stackoverflow.com/a/30106551/14774889
var code = decodeURIComponent(atob(base64code).split('').map(function(c) {
var code = decodeURIComponent(atob(base64code).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
function runToggleTextWrapCommand() {
if (wrap) {
editor.updateOptions({ wordWrap: 'off' })
} else {
editor.updateOptions({ wordWrap: 'on' })
}
wrap = !wrap;
}
function runCopyCommand() {
editor.focus();
document.execCommand('copy');
}
</script>
<!-- Set browser to Edge-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@@ -33,32 +51,33 @@
<title>Previewer for developer Files</title>
<style>
/* Fits content to window size */
html, body{
padding:0;
html, body {
padding: 0;
}
#container,.monaco-editor {
position:fixed;
height:100%;
left:0;
top:0;
right:0;
bottom:0;
#container, .monaco-editor {
position: fixed;
height: 100%;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.overflowingContentWidgets{
.overflowingContentWidgets {
/*Hides alert box */
display:none!important
}
display: none !important
}
</style>
</head>
<body oncontextmenu="onContextMenu()">
<body>
<!-- Container for the editor -->
<div id="container"></div>
<!-- Script -->
<script src="http://[[PT_URL]]/monacoSRC/min/vs/loader.js"></script>
<script src="http://[[PT_URL]]/monacoSpecialLanguages.js" type="module"></script>
<script type="module">
var editor;
<script type="module">
import { registerAdditionalLanguages } from 'http://[[PT_URL]]/monacoSpecialLanguages.js';
import { customTokenColors } from 'http://[[PT_URL]]/customTokenColors.js';
require.config({ paths: { vs: 'http://[[PT_URL]]/monacoSRC/min/vs' } });
@@ -80,8 +99,9 @@
language: lang, // Sets language of the code
readOnly: true, // Sets to readonly
theme: 'theme', // Sets editor theme
minimap: {enabled: false}, // Disables minimap
minimap: { enabled: false }, // Disables minimap
lineNumbersMinChars: '3', // Width of the line numbers
contextmenu: contextMenu,
scrollbar: {
// Deactivate shadows
shadows: false,
@@ -90,7 +110,7 @@
vertical: 'auto',
horizontal: 'auto',
},
stickyScroll: {enabled: stickyScroll},
stickyScroll: { enabled: stickyScroll },
fontSize: fontSize,
wordWrap: (wrap ? 'on' : 'off') // Word wraps
});
@@ -117,12 +137,7 @@
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: function (ed) {
if (wrap) {
editor.updateOptions({ wordWrap: 'off' })
} else {
editor.updateOptions({ wordWrap: 'on' })
}
wrap = !wrap;
runToggleTextWrapCommand();
}
});
@@ -151,4 +166,4 @@
}
</script>
</body>
</html>
</html>

View File

@@ -176,4 +176,40 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbClipboardSharingEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbFileTransferEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbFileTransferEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbUseOriginalUserInterfaceValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbUseOriginalUserInterfaceValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbDisallowBlockingScreensaverValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbDisallowBlockingScreensaverValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbSameSubnetOnlyValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbSameSubnetOnlyValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbValidateRemoteIpValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbValidateRemoteIpValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredMwbDisableUserDefinedIpMappingRulesValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbDisableUserDefinedIpMappingRulesValue());
}
winrt::hstring GPOWrapper::GetConfiguredMwbPolicyDefinedIpMappingRules()
{
// Assuming powertoys_gpo::getConfiguredMwbPolicyDefinedIpMappingRules() returns a std::wstring
std::wstring rules = powertoys_gpo::getConfiguredMwbPolicyDefinedIpMappingRules();
// Convert std::wstring to winrt::hstring
return to_hstring(rules.c_str());
}
}

View File

@@ -50,6 +50,14 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
static GpoRuleConfigured GetConfiguredMwbDisallowBlockingScreensaverValue();
static GpoRuleConfigured GetConfiguredMwbSameSubnetOnlyValue();
static GpoRuleConfigured GetConfiguredMwbValidateRemoteIpValue();
static GpoRuleConfigured GetConfiguredMwbDisableUserDefinedIpMappingRulesValue();
static winrt::hstring GPOWrapper::GetConfiguredMwbPolicyDefinedIpMappingRules();
};
}

View File

@@ -54,6 +54,14 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
static GpoRuleConfigured GetConfiguredMwbDisallowBlockingScreensaverValue();
static GpoRuleConfigured GetConfiguredMwbSameSubnetOnlyValue();
static GpoRuleConfigured GetConfiguredMwbValidateRemoteIpValue();
static GpoRuleConfigured GetConfiguredMwbDisableUserDefinedIpMappingRulesValue();
static String GetConfiguredMwbPolicyDefinedIpMappingRules();
}
}
}

View File

@@ -58,6 +58,15 @@ namespace UnitTestsVersionHelper
Assert::AreEqual(25ull, sut->minor);
Assert::AreEqual(1ull, sut->revision);
}
TEST_METHOD (stringConstructorShouldProperlyInitializationVersionNumbersWithUppercaseV)
{
auto sut = VersionHelper::fromString(L"V2.25.1");
Assert::IsTrue(sut.has_value());
Assert::AreEqual(2ull, sut->major);
Assert::AreEqual(25ull, sut->minor);
Assert::AreEqual(1ull, sut->revision);
}
TEST_METHOD (emptyStringNotAccepted)
{
auto sut = VersionHelper::fromString("");

View File

@@ -47,9 +47,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,6 +2,7 @@
#include <Windows.h>
#include <optional>
#include <vector>
namespace powertoys_gpo {
enum gpo_rule_configured_t {
@@ -71,11 +72,25 @@ namespace powertoys_gpo {
const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation";
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS = L"PowerLauncherAllPluginsEnabledState";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS = L"AllowPowerToysAdvancedPasteOnlineAIModels";
const std::wstring POLICY_MWB_CLIPBOARD_SHARING_ENABLED = L"MwbClipboardSharingEnabled";
const std::wstring POLICY_MWB_FILE_TRANSFER_ENABLED = L"MwbFileTransferEnabled";
const std::wstring POLICY_MWB_USE_ORIGINAL_USER_INTERFACE = L"MwbUseOriginalUserInterface";
const std::wstring POLICY_MWB_DISALLOW_BLOCKING_SCREENSAVER = L"MwbDisallowBlockingScreensaver";
const std::wstring POLICY_MWB_SAME_SUBNET_ONLY = L"MwbSameSubnetOnly";
const std::wstring POLICY_MWB_VALIDATE_REMOTE_IP = L"MwbValidateRemoteIp";
const std::wstring POLICY_MWB_DISABLE_USER_DEFINED_IP_MAPPING_RULES = L"MwbDisableUserDefinedIpMappingRules";
const std::wstring POLICY_MWB_POLICY_DEFINED_IP_MAPPING_RULES = L"MwbPolicyDefinedIpMappingRules";
inline std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name)
inline std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name, const bool is_multi_line_text = false)
{
// Set value type
DWORD reg_value_type = REG_SZ;
DWORD reg_flags = RRF_RT_REG_SZ;
if (is_multi_line_text)
{
reg_value_type = REG_MULTI_SZ;
reg_flags = RRF_RT_REG_MULTI_SZ;
}
DWORD string_buffer_capacity;
// Request required buffer capacity / string length
@@ -97,8 +112,26 @@ namespace powertoys_gpo {
return std::nullopt;
}
// Convert buffer to std::wstring, delete buffer and return REG_SZ value
std::wstring string_value = temp_buffer;
// Convert buffer to std::wstring
std::wstring string_value = L"";
if (reg_value_type == REG_MULTI_SZ)
{
// If it is REG_MULTI_SZ handle this way
wchar_t* currentString = temp_buffer;
while (*currentString != L'\0')
{
// If first entry then assign the string, else add to the string
string_value = (string_value == L"") ? currentString : (string_value + L"\r\n" + currentString);
currentString += wcslen(currentString) + 1; // Move to the next string
}
}
else
{
// If it is REG_SZ handle this way
string_value = temp_buffer;
}
// delete buffer, return string value
delete temp_buffer;
return string_value;
}
@@ -475,4 +508,59 @@ namespace powertoys_gpo {
{
return getUtilityEnabledValue(POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS);
}
inline gpo_rule_configured_t getConfiguredMwbClipboardSharingEnabledValue()
{
return getUtilityEnabledValue(POLICY_MWB_CLIPBOARD_SHARING_ENABLED);
}
inline gpo_rule_configured_t getConfiguredMwbFileTransferEnabledValue()
{
return getUtilityEnabledValue(POLICY_MWB_FILE_TRANSFER_ENABLED);
}
inline gpo_rule_configured_t getConfiguredMwbUseOriginalUserInterfaceValue()
{
return getUtilityEnabledValue(POLICY_MWB_USE_ORIGINAL_USER_INTERFACE);
}
inline gpo_rule_configured_t getConfiguredMwbDisallowBlockingScreensaverValue()
{
return getUtilityEnabledValue(POLICY_MWB_DISALLOW_BLOCKING_SCREENSAVER);
}
inline gpo_rule_configured_t getConfiguredMwbSameSubnetOnlyValue()
{
return getUtilityEnabledValue(POLICY_MWB_SAME_SUBNET_ONLY);
}
inline gpo_rule_configured_t getConfiguredMwbValidateRemoteIpValue()
{
return getUtilityEnabledValue(POLICY_MWB_VALIDATE_REMOTE_IP);
}
inline gpo_rule_configured_t getConfiguredMwbDisableUserDefinedIpMappingRulesValue()
{
return getUtilityEnabledValue(POLICY_MWB_DISABLE_USER_DEFINED_IP_MAPPING_RULES);
}
inline std::wstring getConfiguredMwbPolicyDefinedIpMappingRules()
{
// Important: HKLM has priority over HKCU
auto mapping_rules = readRegistryStringValue(HKEY_LOCAL_MACHINE, POLICIES_PATH, POLICY_MWB_POLICY_DEFINED_IP_MAPPING_RULES, true);
if (!mapping_rules.has_value())
{
mapping_rules = readRegistryStringValue(HKEY_CURRENT_USER, POLICIES_PATH, POLICY_MWB_POLICY_DEFINED_IP_MAPPING_RULES, true);
}
// return value
if (mapping_rules.has_value())
{
return mapping_rules.value();
}
else
{
return std::wstring ();
}
}
}

View File

@@ -18,7 +18,8 @@ struct Constants;
template<>
struct Constants<char>
{
static inline const char* V = "v";
static inline const char* LOWER_V = "v";
static inline const char* UPPER_V = "V";
static inline const char* DOT = ".";
static inline const char SPACE = ' ';
};
@@ -26,7 +27,8 @@ struct Constants<char>
template<>
struct Constants<wchar_t>
{
static inline const wchar_t* V = L"v";
static inline const wchar_t* LOWER_V = L"v";
static inline const wchar_t* UPPER_V = L"V";
static inline const wchar_t* DOT = L".";
static inline const wchar_t SPACE = L' ';
};
@@ -36,7 +38,8 @@ std::optional<VersionHelper> fromString(std::basic_string_view<CharT> str)
{
try
{
str = left_trim<CharT>(trim<CharT>(str), Constants<CharT>::V);
str = left_trim<CharT>(trim<CharT>(str), Constants<CharT>::LOWER_V);
str = left_trim<CharT>(trim<CharT>(str), Constants<CharT>::UPPER_V);
std::basic_string<CharT> spacedStr{ str };
replace_chars<CharT>(spacedStr, Constants<CharT>::DOT, Constants<CharT>::SPACE);

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.10" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.11" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyNamespaces>
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
</policyNamespaces>
<resources minRequiredRevision="1.10"/><!-- Last changed with PowerToys v0.81.1 -->
<resources minRequiredRevision="1.11"/><!-- Last changed with PowerToys v0.83.0 -->
<supportedOn>
<definitions>
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
@@ -19,6 +19,7 @@
<definition name="SUPPORTED_POWERTOYS_0_78_0" displayName="$(string.SUPPORTED_POWERTOYS_0_78_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_81_0" displayName="$(string.SUPPORTED_POWERTOYS_0_81_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
<definition name="SUPPORTED_POWERTOYS_0_83_0" displayName="$(string.SUPPORTED_POWERTOYS_0_83_0)"/>
</definitions>
</supportedOn>
<categories>
@@ -32,10 +33,17 @@
<category name="AdvancedPaste" displayName="$(string.AdvancedPaste)">
<parentCategory ref="PowerToys" />
</category>
<category name="MouseWithoutBorders" displayName="$(string.MouseWithoutBorders)">
<parentCategory ref="PowerToys" />
</category>
<category name="GeneralSettings" displayName="$(string.GeneralSettings)">
<parentCategory ref="PowerToys" />
</category>
</categories>
<policies>
<policy name="ConfigureGlobalUtilityEnabledState" class="Both" displayName="$(string.ConfigureGlobalUtilityEnabledState)" explainText="$(string.ConfigureGlobalUtilityEnabledStateDescription)" key="Software\Policies\PowerToys" valueName="ConfigureGlobalUtilityEnabledState">
<!--The name (id) of the policy is different to sort it as first policy in edit dialog. The order is sorted alphabetically based on the "name" property.-->
<policy name="ConfigureAllUtilityGlobalEnabledState" class="Both" displayName="$(string.ConfigureAllUtilityGlobalEnabledState)" explainText="$(string.ConfigureAllUtilityGlobalEnabledStateDescription)" key="Software\Policies\PowerToys" valueName="ConfigureGlobalUtilityEnabledState">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_75_0" />
<enabledValue>
@@ -467,7 +475,7 @@
</disabledValue>
</policy>
<policy name="AllowExperimentation" class="Both" displayName="$(string.AllowExperimentation)" explainText="$(string.AllowExperimentationDescription)" key="Software\Policies\PowerToys" valueName="AllowExperimentation">
<parentCategory ref="PowerToys" />
<parentCategory ref="GeneralSettings" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_68_0" />
<enabledValue>
<decimal value="1" />
@@ -503,5 +511,83 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbClipboardSharingEnabled" class="Both" displayName="$(string.MwbClipboardSharingEnabled)" explainText="$(string.MwbClipboardSharingEnabledDescription)" key="Software\Policies\PowerToys" valueName="MwbClipboardSharingEnabled">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbFileTransferEnabled" class="Both" displayName="$(string.MwbFileTransferEnabled)" explainText="$(string.MwbFileTransferEnabledDescription)" key="Software\Policies\PowerToys" valueName="MwbFileTransferEnabled">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbUseOriginalUserInterface" class="Both" displayName="$(string.MwbUseOriginalUserInterface)" explainText="$(string.MwbUseOriginalUserInterfaceDescription)" key="Software\Policies\PowerToys" valueName="MwbUseOriginalUserInterface">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbDisallowBlockingScreensaver" class="Both" displayName="$(string.MwbDisallowBlockingScreensaver)" explainText="$(string.MwbDisallowBlockingScreensaverDescription)" key="Software\Policies\PowerToys" valueName="MwbDisallowBlockingScreensaver">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbSameSubnetOnly" class="Both" displayName="$(string.MwbSameSubnetOnly)" explainText="$(string.MwbSameSubnetOnlyDescription)" key="Software\Policies\PowerToys" valueName="MwbSameSubnetOnly">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbValidateRemoteIp" class="Both" displayName="$(string.MwbValidateRemoteIp)" explainText="$(string.MwbValidateRemoteIpDescription)" key="Software\Policies\PowerToys" valueName="MwbValidateRemoteIp">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbDisableUserDefinedIpMappingRules" class="Both" displayName="$(string.MwbDisableUserDefinedIpMappingRules)" explainText="$(string.MwbDisableUserDefinedIpMappingRulesDescription)" key="Software\Policies\PowerToys" valueName="MwbDisableUserDefinedIpMappingRules">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="MwbPolicyDefinedIpMappingRules" class="Both" displayName="$(string.MwbPolicyDefinedIpMappingRules)" explainText="$(string.MwbPolicyDefinedIpMappingRulesDescription)" presentation="$(presentation.MwbPolicyDefinedIpMappingRules)" key="Software\Policies\PowerToys">
<parentCategory ref="MouseWithoutBorders" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_83_0" />
<elements>
<!--Max length means here max length per line. We support 65 characters per line. (A string containing the hostname, a space and an IPv6 Address is max. 55 characters long.)-->
<multiText id="MwbPolicyDefinedIpMappingsList" valueName="MwbPolicyDefinedIpMappingRules" maxLength="65" required="true"/>
</elements>
</policy>
</policies>
</policyDefinitions>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.10" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.11" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@@ -10,6 +10,8 @@
<string id="InstallerUpdates">Installer and Updates</string>
<string id="PowerToysRun">PowerToys Run</string>
<string id="AdvancedPaste">Advanced Paste</string>
<string id="MouseWithoutBorders">Mouse Without Borders</string>
<string id="GeneralSettings">General settings</string>
<string id="SUPPORTED_POWERTOYS_0_64_0">PowerToys version 0.64.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_68_0">PowerToys version 0.68.0 or later</string>
@@ -22,8 +24,9 @@
<string id="SUPPORTED_POWERTOYS_0_78_0">PowerToys version 0.78.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_81_0">PowerToys version 0.81.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
<string id="SUPPORTED_POWERTOYS_0_83_0">PowerToys version 0.83.0 or later</string>
<string id="ConfigureGlobalUtilityEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
If you enable this setting, all utilities will be always enabled and the user won't be able to disable it.
@@ -121,13 +124,72 @@ You can set the enabled state for all plugins not configured by this policy usin
Note: Changes require a restart of PowerToys Run.
</string>
<string id="AllowPowerToysAdvancedPasteOnlineAIModelsDescription">This policy configures the enabled disable state for using Advanced Paste online AI models.
<string id="AllowPowerToysAdvancedPasteOnlineAIModelsDescription">This policy allows you to disable Advanced Paste online AI models.
If you enable or don't configure this policy, the user takes control over the enabled state of the Enable paste with AI Advanced Paste setting.
If you disable this policy, the user won't be able to enable Enable paste with AI Advanced Paste setting and use Advanced Paste AI prompt nor set up the Open AI key in PowerToys Settings.
</string>
<string id="ConfigureGlobalUtilityEnabledState">Configure global utility enabled state</string>
<string id="MwbClipboardSharingEnabledDescription">This policy configures if the user can share the clipboard between machines.
If you enable or don't configure this policy, the user takes control over the clipboard sharing setting.
If you disable this policy, the user won't be able to enable the clipboard sharing setting.
</string>
<string id="MwbFileTransferEnabledDescription">This policy configures if the user can transfer files between machines.
If you enable or don't configure this policy, the user takes control over the file sharing setting.
If you disable this policy, the user won't be able to enable the file sharing Settings.
Note: The file sharing feature depends on the clipboard sharing feature. Disabling clipboard sharing automatically disables file sharing too.
</string>
<string id="MwbUseOriginalUserInterfaceDescription">This policy configures if the user can use the old Mouse Without Borders user interface.
If you enable or don't configure this policy, the user takes control over the setting and can enable or disable the old user interface.
If you disable this policy, the user won't be able to enable the old user interface.
</string>
<string id="MwbDisallowBlockingScreensaverDescription">This policy configures if the user is allowed to disable the screensaver on the remote machines.
If you enable this policy, the user won't be able to enable the "block screensaver" screensaver setting and the screensaver is not blocked.
If you disable or don't configure this policy, the user takes control over the setting and can block the screensaver.
</string>
<string id="MwbSameSubnetOnlyDescription">This policy configures if connections are only allowed in the same subnet.
If you enable this policy, the setting is enabled and only connections in the same subnet are allowed.
If you disable this policy, the setting is disabled and all connections are allowed.
If you don't configure this policy, the user takes control over the setting and can enable or disable it.
</string>
<string id="MwbValidateRemoteIpDescription">This policy configures if reverse DNS lookup is used to validate the remote machine IP Address.
If you enable this policy, the setting is enabled and the IP Address is validated.
If you disable this policy, the setting is disabled and the IP Address is not validated.
If you don't configure this policy, the user takes control over the setting and can enable or disable it.
</string>
<string id="MwbDisableUserDefinedIpMappingRulesDescription">This policy configures if the user can define IP Address mapping rules.
If you enable this policy, the setting is disabled and the user can't define rules or use existing ones.
If you disable or don't configure this policy, the user takes control over the setting.
Note: Enabling this policy does not prevent policy defined mapping rules from working.
</string>
<string id="MwbPolicyDefinedIpMappingRulesDescription">This policy allows you to define IP Address mapping rules.
If you enable this policy, you can define IP Address mapping rules that the user can't change or disable.
Please enter one mapping per line in the format: "hostname IP"
If you disable or don't configure this policy, no predefined rules are applied.
</string>
<string id="ConfigureAllUtilityGlobalEnabledState">Configure global utility enabled state</string>
<string id="ConfigureEnabledUtilityAdvancedPaste">Advanced Paste: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAlwaysOnTop">Always On Top: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAwake">Awake: Configure enabled state</string>
@@ -173,13 +235,24 @@ If you disable this policy, the user won't be able to enable Enable paste with A
<string id="PowerToysRunIndividualPluginEnabledState">Configure enabled state for individual plugins</string>
<string id="ConfigureEnabledUtilityFileExplorerQOIPreview">QOI file preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerQOIThumbnails">QOI file thumbnail: Configure enabled state</string>
<string id="AllowPowerToysAdvancedPasteOnlineAIModels">Advanced Paste: Allow using online AI models</string>
<string id="AllowPowerToysAdvancedPasteOnlineAIModels">Allow using online AI models</string>
<string id="MwbClipboardSharingEnabled">Clipboard sharing enabled</string>
<string id="MwbFileTransferEnabled">File transfer enabled</string>
<string id="MwbUseOriginalUserInterface">Original user interface is available</string>
<string id="MwbDisallowBlockingScreensaver">Disallow blocking screensaver on other machines</string>
<string id="MwbSameSubnetOnly">Connect only in same subnet</string>
<string id="MwbValidateRemoteIp">Validate remote machine IP Address</string>
<string id="MwbDisableUserDefinedIpMappingRules">Disable user defined IP Address mapping rules</string>
<string id="MwbPolicyDefinedIpMappingRules">Predefined IP Address mapping rules</string>
</stringTable>
<presentationTable>
<presentation id="PowerToysRunIndividualPluginEnabledState">
<listBox refId="PowerToysRunIndividualPluginEnabledList">List of managed plugins:</listBox>
</presentation>
<presentation id="MwbPolicyDefinedIpMappingRules">
<multiTextBox refId="MwbPolicyDefinedIpMappingsList">List of IP Address mappings:</multiTextBox>
</presentation>
</presentationTable>
</resources>

View File

@@ -62,7 +62,7 @@
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
@@ -74,7 +74,8 @@
<!-- HACK: To align Microsoft.Bcl.AsyncInterfaces.dll version with PowerToys.Settings.csproj. -->
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -24,6 +24,14 @@ namespace AdvancedPaste.Helpers
private static readonly char[] CsvDelimArry = [',', ';', '\t'];
private static readonly Regex CsvSepIdentifierRegex = new Regex(@"^sep=(.)$", RegexOptions.IgnoreCase);
// CSV: Split on every occurrence of the delimiter except if it is enclosed by " and ignore two " as escaped "
private static readonly string CsvDelimSepRegexStr = @"(?=(?:[^""]*""[^""]*"")*(?![^""]*""))";
// CSV: Regex to remove/replace quotation marks
private static readonly Regex CsvRemoveSingleQuotationMarksRegex = new Regex(@"^""(?!"")|(?<!"")""$|^""""$");
private static readonly Regex CsvRemoveStartAndEndQuotationMarksRegex = new Regex(@"^""(?=(""{2})+)|(?<=(""{2})+)""$");
private static readonly Regex CsvReplaceDoubleQuotationMarksRegex = new Regex(@"""{2}");
internal static string ToJsonFromXmlOrCsv(DataPackageView clipboardData)
{
Logger.LogTrace();
@@ -131,7 +139,7 @@ namespace AdvancedPaste.Helpers
{
if (string.IsNullOrEmpty(jsonText))
{
var csv = new List<string[]>();
var csv = new List<IEnumerable<string>>();
string[] lines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
@@ -146,10 +154,12 @@ namespace AdvancedPaste.Helpers
continue;
}
// A CSV line is valid, if the delimiter occurs more or equal times in every line compared to the first data line. (More because sometimes the delimiter occurs in a data string.)
if (line.Count(x => x == delim) >= delimCount)
// A CSV line is valid, if the delimiter occurs equal times in every line compared to the first data line
// and if every line contains no or an even count of quotation marks.
if (Regex.Count(line, delim + CsvDelimSepRegexStr) == delimCount && int.IsEvenInteger(line.Count(x => x == '"')))
{
csv.Add(line.Split(delim));
string[] dataCells = Regex.Split(line, delim + CsvDelimSepRegexStr, RegexOptions.IgnoreCase);
csv.Add(dataCells.Select(x => ReplaceQuotationMarksInCsvData(x)));
}
else
{
@@ -205,7 +215,7 @@ namespace AdvancedPaste.Helpers
// We get the count from the second line, as the first one only contains the character definition and not a CSV data line.
char delimChar = matchChar.Groups[1].Value.Trim()[0];
delimiter = delimChar;
delimiterCount = csvLines[1].Count(x => x == delimChar);
delimiterCount = Regex.Count(csvLines[1], delimChar + CsvDelimSepRegexStr, RegexOptions.IgnoreCase);
}
}
@@ -214,19 +224,19 @@ namespace AdvancedPaste.Helpers
// Try to select the correct delimiter based on the first two CSV lines from a list of predefined delimiters.
foreach (char c in CsvDelimArry)
{
int cntFirstLine = csvLines[0].Count(x => x == c);
int cntFirstLine = Regex.Count(csvLines[0], c + CsvDelimSepRegexStr, RegexOptions.IgnoreCase);
int cntNextLine = 0; // Default to 0 that the 'second line' check is always true.
// Additional count if we have more than one line
if (csvLines.Length >= 2)
{
cntNextLine = csvLines[1].Count(x => x == c);
cntNextLine = Regex.Count(csvLines[1], c + CsvDelimSepRegexStr, RegexOptions.IgnoreCase);
}
// The delimiter is found if the count is bigger as from the last selected delimiter
// and if the next csv line does not exist or has the same number or more occurrences of the delimiter.
// and if the next csv line does not exist or has the same number of occurrences of the delimiter.
// (We check the next line to prevent false positives.)
if (cntFirstLine > delimiterCount && (cntNextLine == 0 || cntNextLine >= cntFirstLine))
if (cntFirstLine > delimiterCount && (cntNextLine == 0 || cntNextLine == cntFirstLine))
{
delimiter = c;
delimiterCount = cntFirstLine;
@@ -240,5 +250,26 @@ namespace AdvancedPaste.Helpers
throw new FormatException("Invalid CSV format: Failed to detect the delimiter.");
}
}
/// <summary>
/// Remove and replace quotation marks used as control sequences. (Enclosing quotation marks and escaping quotation marks.)
/// </summary>
/// <param name="str">CSV cell data to manipulate.</param>
/// <returns>Manipulated string.</returns>
private static string ReplaceQuotationMarksInCsvData(string str)
{
// Remove first and last single quotation mark (enclosing quotation marks) and remove quotation marks of an empty data set ("").
str = CsvRemoveSingleQuotationMarksRegex.Replace(str, string.Empty);
// Remove first quotation mark if followed by pairs of quotation marks
// and remove last quotation mark if precede by pairs of quotation marks.
// (Removes enclosing quotation marks around the cell data for data like /"""abc"""/.)
str = CsvRemoveStartAndEndQuotationMarksRegex.Replace(str, string.Empty);
// Replace pairs of two quotation marks with a single quotation mark. (Escaped quotation marks.)
str = CsvReplaceDoubleQuotationMarksRegex.Replace(str, "\"");
return str;
}
}
}

View File

@@ -76,6 +76,8 @@
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -68,6 +68,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="System.IO.Abstractions" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h FileLocksmithContextMenu.base.rc FileLocksmithContextMenu.rc" />
</Target>
@@ -98,7 +99,30 @@ MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv</Com
<None Include="Resources.resx" />
</ItemGroup>
<ItemGroup>
<None Include="Assets\FileLocksmith\**" CopyToOutputDirectory="PreserveNewest" />
<None Include="Assets\FileLocksmith\FileLocksmith.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\LargeTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\SmallTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\SplashScreen.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Square150x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Square44x44Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\storelogo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Wide310x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
@@ -118,11 +142,14 @@ MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv</Com
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -37,10 +37,38 @@
<None Include="Resources.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\FileLocksmith.ico">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\LargeTile.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\SmallTile.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\SplashScreen.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Square44x44Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Square150x150Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\storelogo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Wide310x150Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithContextMenu.base.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
@@ -78,7 +79,18 @@
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -51,4 +51,7 @@
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>

View File

@@ -25,6 +25,8 @@ namespace FileLocksmithUI
Logger.InitializeLogger("\\File Locksmith\\FileLocksmithUI\\Logs");
this.InitializeComponent();
UnhandledException += App_UnhandledException;
}
/// <summary>
@@ -55,6 +57,11 @@ namespace FileLocksmithUI
_window.Activate();
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
Logger.LogError("Unhandled exception", e.Exception);
}
private Window _window;
}
}

View File

@@ -141,27 +141,16 @@
IsTextSelectionEnabled="True"
Text="{x:Bind user}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Vertical">
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Header>
<TextBlock>
<Run x:Uid="Files" />
<Run Text="(" /><Run Text="{x:Bind files, Converter={StaticResource fileCountConverter}}" /><Run Text=")" />
</TextBlock>
</tkcontrols:SettingsCard.Header>
<ItemsRepeater ItemsSource="{x:Bind files}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTipService.ToolTip="{Binding}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Button Click="ShowProcessFiles_Click">
<TextBlock x:Uid="ShowProcessFiles" />
</Button>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
@@ -200,5 +189,13 @@
Text="{x:Bind ViewModel.PathsToString, Mode=OneWay}"
TextWrapping="Wrap" />
</ContentDialog>
<ContentDialog x:Name="ProcessFilesListDialog" x:Uid="ProcessFilesListDialog">
<ScrollViewer Padding="15" HorizontalScrollBarVisibility="Auto">
<TextBlock
x:Name="ProcessFilesListDialogTextBlock"
x:Uid="ProcessFilesListDialogTextBlock"
IsTextSelectionEnabled="True" />
</ScrollViewer>
</ContentDialog>
</Grid>
</Page>

View File

@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using FileLocksmith.Interop;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using PowerToys.FileLocksmithUI.ViewModels;
@@ -19,9 +21,17 @@ namespace PowerToys.FileLocksmithUI.Views
DataContext = ViewModel;
}
private async void ShowSelectedPathsButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
private async void ShowSelectedPathsButton_Click(object sender, RoutedEventArgs e)
{
await SelectedFilesListDialog.ShowAsync();
}
private async void ShowProcessFiles_Click(object sender, RoutedEventArgs e)
{
var processResult = (ProcessResult)((FrameworkElement)sender).DataContext;
ProcessFilesListDialogTextBlock.Text = string.Join(Environment.NewLine, processResult.files);
await ProcessFilesListDialog.ShowAsync();
}
}
}

View File

@@ -130,6 +130,13 @@
<data name="Files.Text" xml:space="preserve">
<value>Files</value>
</data>
<data name="ProcessFilesListDialog.Title" xml:space="preserve">
<value>Files</value>
</data>
<data name="ProcessFilesListDialog.CloseButtonText" xml:space="preserve">
<value>Close</value>
<comment>As in, close a dialog prompt.</comment>
</data>
<data name="PathsTooltipDescription.Text" xml:space="preserve">
<value>Click to see the entire list of paths.</value>
<comment>Paths as in file paths that were selected for the utility to check.</comment>
@@ -164,4 +171,8 @@
<value>Administrator: File Locksmith</value>
<comment>Title of the window when running as administrator.</comment>
</data>
<data name="ShowProcessFiles.Text" xml:space="preserve">
<value>Show files</value>
<comment>Show files for the selected process</comment>
</data>
</root>

View File

@@ -16,10 +16,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
<PackageReference Include="System.Diagnostics.EventLog">

View File

@@ -76,6 +76,8 @@
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -19,9 +19,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.CodeDom">
<!-- This package is a dependency of System.Management, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->

View File

@@ -12,8 +12,10 @@ using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
@@ -259,6 +261,11 @@ namespace MouseWithoutBorders.Class
{
get
{
if (GPOWrapper.GetConfiguredMwbClipboardSharingEnabledValue() == GpoRuleConfigured.Disabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.ShareClipboard;
@@ -267,6 +274,11 @@ namespace MouseWithoutBorders.Class
set
{
if (ShareClipboardIsGpoConfigured)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.ShareClipboard = value;
@@ -274,10 +286,19 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool ShareClipboardIsGpoConfigured => GPOWrapper.GetConfiguredMwbClipboardSharingEnabledValue() == GpoRuleConfigured.Disabled;
internal bool TransferFile
{
get
{
if (GPOWrapper.GetConfiguredMwbFileTransferEnabledValue() == GpoRuleConfigured.Disabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.TransferFile;
@@ -286,10 +307,19 @@ namespace MouseWithoutBorders.Class
set
{
if (TransferFileIsGpoConfigured)
{
return;
}
_properties.TransferFile = value;
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool TransferFileIsGpoConfigured => GPOWrapper.GetConfiguredMwbFileTransferEnabledValue() == GpoRuleConfigured.Disabled;
internal bool MatrixOneRow
{
get
@@ -448,6 +478,8 @@ namespace MouseWithoutBorders.Class
}
}
// Note(@htcfreek): Settings UI CheckBox is disabled in frmMatrix.cs > FrmMatrix_Load()
// Note(@htcfreek): If this settings gets implemented in the future we need a Group Policy for it!
internal bool DisableCAD
{
get
@@ -456,6 +488,8 @@ namespace MouseWithoutBorders.Class
}
}
// Note(@htcfreek): Settings UI CheckBox is disabled in frmMatrix.cs > FrmMatrix_Load()
// Note(@htcfreek): If this settings gets implemented in the future we need a Group Policy for it!
internal bool HideLogonLogo
{
get
@@ -487,6 +521,11 @@ namespace MouseWithoutBorders.Class
{
get
{
if (GPOWrapper.GetConfiguredMwbDisallowBlockingScreensaverValue() == GpoRuleConfigured.Enabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.BlockScreenSaverOnOtherMachines;
@@ -495,6 +534,11 @@ namespace MouseWithoutBorders.Class
set
{
if (BlockScreenSaverIsGpoConfigured)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.BlockScreenSaverOnOtherMachines = value;
@@ -502,6 +546,10 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool BlockScreenSaverIsGpoConfigured => GPOWrapper.GetConfiguredMwbDisallowBlockingScreensaverValue() == GpoRuleConfigured.Enabled;
internal bool MoveMouseRelatively
{
get
@@ -791,6 +839,15 @@ namespace MouseWithoutBorders.Class
{
get
{
if (GPOWrapper.GetConfiguredMwbValidateRemoteIpValue() == GpoRuleConfigured.Enabled)
{
return true;
}
else if (GPOWrapper.GetConfiguredMwbValidateRemoteIpValue() == GpoRuleConfigured.Disabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.ValidateRemoteMachineIP;
@@ -799,6 +856,11 @@ namespace MouseWithoutBorders.Class
set
{
if (ReverseLookupIsGpoConfigured)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.ValidateRemoteMachineIP = value;
@@ -806,10 +868,23 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool ReverseLookupIsGpoConfigured => GPOWrapper.GetConfiguredMwbValidateRemoteIpValue() == GpoRuleConfigured.Enabled || GPOWrapper.GetConfiguredMwbValidateRemoteIpValue() == GpoRuleConfigured.Disabled;
internal bool SameSubNetOnly
{
get
{
if (GPOWrapper.GetConfiguredMwbSameSubnetOnlyValue() == GpoRuleConfigured.Enabled)
{
return true;
}
else if (GPOWrapper.GetConfiguredMwbSameSubnetOnlyValue() == GpoRuleConfigured.Disabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.SameSubnetOnly;
@@ -818,6 +893,11 @@ namespace MouseWithoutBorders.Class
set
{
if (SameSubNetOnlyIsGpoConfigured)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.SameSubnetOnly = value;
@@ -825,10 +905,19 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool SameSubNetOnlyIsGpoConfigured => GPOWrapper.GetConfiguredMwbSameSubnetOnlyValue() == GpoRuleConfigured.Enabled || GPOWrapper.GetConfiguredMwbSameSubnetOnlyValue() == GpoRuleConfigured.Disabled;
internal string Name2IP
{
get
{
if (GPOWrapper.GetConfiguredMwbDisableUserDefinedIpMappingRulesValue() == GpoRuleConfigured.Enabled)
{
return string.Empty;
}
lock (_loadingSettingsLock)
{
return _properties.Name2IP.Value;
@@ -837,6 +926,11 @@ namespace MouseWithoutBorders.Class
set
{
if (Name2IpIsGpoConfigured)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.Name2IP.Value = value;
@@ -844,6 +938,18 @@ namespace MouseWithoutBorders.Class
}
}
[CmdConfigureIgnore]
[JsonIgnore]
internal bool Name2IpIsGpoConfigured => GPOWrapper.GetConfiguredMwbDisableUserDefinedIpMappingRulesValue() == GpoRuleConfigured.Enabled;
[CmdConfigureIgnore]
[JsonIgnore]
internal string Name2IpPolicyList => GPOWrapper.GetConfiguredMwbPolicyDefinedIpMappingRules();
[CmdConfigureIgnore]
[JsonIgnore]
internal bool Name2IpPolicyListIsGpoConfigured => !string.IsNullOrWhiteSpace(Name2IpPolicyList);
internal bool FirstCtrlShiftS
{
get
@@ -950,6 +1056,11 @@ namespace MouseWithoutBorders.Class
{
get
{
if (GPOWrapper.GetConfiguredMwbUseOriginalUserInterfaceValue() == GpoRuleConfigured.Disabled)
{
return false;
}
lock (_loadingSettingsLock)
{
return _properties.ShowOriginalUI;
@@ -958,6 +1069,11 @@ namespace MouseWithoutBorders.Class
set
{
if (GPOWrapper.GetConfiguredMwbUseOriginalUserInterfaceValue() == GpoRuleConfigured.Disabled)
{
return;
}
lock (_loadingSettingsLock)
{
_properties.ShowOriginalUI = value;
@@ -989,6 +1105,7 @@ namespace MouseWithoutBorders.Class
}
}
// Note(@htcfreek): Settings UI CheckBox is disabled in frmMatrix.cs > FrmMatrix_Load()
internal bool SendErrorLogV2
{
get

View File

@@ -887,7 +887,8 @@ namespace MouseWithoutBorders.Class
if (!string.IsNullOrEmpty(Setting.Values.Name2IP))
{
string[] name2ip = Setting.Values.Name2IP.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
string combinedName2ipList = Setting.Values.Name2IpPolicyList + Separator + Setting.Values.Name2IP;
string[] name2ip = combinedName2ipList.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
string[] nameNip;
if (name2ip != null)

View File

@@ -1,6 +1,7 @@
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using Windows.UI.Notifications;
namespace MouseWithoutBorders
{
@@ -88,6 +89,8 @@ namespace MouseWithoutBorders
this.linkLabelReConfigure = new System.Windows.Forms.LinkLabel();
this.tabControlSetting = new System.Windows.Forms.TabControl();
this.tabPageAdvancedSettings = new System.Windows.Forms.TabPage();
this.groupBoxName2IPPolicyList = new System.Windows.Forms.GroupBox();
this.textBoxMachineName2IPPolicyList = new System.Windows.Forms.TextBox();
this.pictureBoxMouseWithoutBorders = new System.Windows.Forms.PictureBox();
this.groupBoxDNS = new System.Windows.Forms.GroupBox();
this.textBoxMachineName2IP = new System.Windows.Forms.TextBox();
@@ -103,6 +106,7 @@ namespace MouseWithoutBorders
this.groupBoxMachineMatrix.SuspendLayout();
this.tabControlSetting.SuspendLayout();
this.tabPageAdvancedSettings.SuspendLayout();
this.groupBoxName2IPPolicyList.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBoxMouseWithoutBorders)).BeginInit();
this.groupBoxDNS.SuspendLayout();
this.SuspendLayout();
@@ -977,6 +981,7 @@ namespace MouseWithoutBorders
// tabPageAdvancedSettings
//
this.tabPageAdvancedSettings.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(246)))), ((int)(((byte)(245)))), ((int)(((byte)(242)))));
this.tabPageAdvancedSettings.Controls.Add(this.groupBoxName2IPPolicyList);
this.tabPageAdvancedSettings.Controls.Add(this.pictureBoxMouseWithoutBorders);
this.tabPageAdvancedSettings.Controls.Add(this.groupBoxDNS);
this.tabPageAdvancedSettings.Controls.Add(this.textBoxDNS);
@@ -986,6 +991,33 @@ namespace MouseWithoutBorders
this.tabPageAdvancedSettings.Size = new System.Drawing.Size(563, 362);
this.tabPageAdvancedSettings.TabIndex = 2;
this.tabPageAdvancedSettings.Text = "IP Mappings";
//
// groupBoxName2IPPolicyList
//
this.groupBoxName2IPPolicyList.Controls.Add(this.textBoxMachineName2IPPolicyList);
this.groupBoxName2IPPolicyList.Dock = System.Windows.Forms.DockStyle.Top;
this.groupBoxName2IPPolicyList.Location = new System.Drawing.Point(3, 241);
this.groupBoxName2IPPolicyList.Name = "groupBoxName2IPPolicyList";
this.groupBoxName2IPPolicyList.Size = new System.Drawing.Size(357, 150);
this.groupBoxName2IPPolicyList.TabIndex = 1;
this.groupBoxName2IPPolicyList.TabStop = false;
this.groupBoxName2IPPolicyList.Text = " Policy defined machine name to IP address mappings [Managed]";
this.groupBoxName2IPPolicyList.ForeColor = Color.DimGray;
this.groupBoxName2IPPolicyList.Visible = false;
//
// textBoxMachineName2IPPolicyList
//
this.textBoxMachineName2IPPolicyList.Dock = System.Windows.Forms.DockStyle.Fill;
this.textBoxMachineName2IPPolicyList.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.textBoxMachineName2IPPolicyList.Location = new System.Drawing.Point(3, 172); // 3,172
this.textBoxMachineName2IPPolicyList.MaxLength = 1024;
this.textBoxMachineName2IPPolicyList.Multiline = true;
this.textBoxMachineName2IPPolicyList.Name = "textBoxMachineName2IPPolicyList";
this.textBoxMachineName2IPPolicyList.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBoxMachineName2IPPolicyList.Size = new System.Drawing.Size(351, 131);
this.textBoxMachineName2IPPolicyList.TabIndex = 1;
this.textBoxMachineName2IPPolicyList.ReadOnly = true;
this.textBoxMachineName2IPPolicyList.Visible = false;
//
// pictureBoxMouseWithoutBorders
//
@@ -1098,12 +1130,13 @@ namespace MouseWithoutBorders
this.tabControlSetting.ResumeLayout(false);
this.tabPageAdvancedSettings.ResumeLayout(false);
this.tabPageAdvancedSettings.PerformLayout();
this.groupBoxName2IPPolicyList.ResumeLayout(false);
this.groupBoxName2IPPolicyList.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBoxMouseWithoutBorders)).EndInit();
this.groupBoxDNS.ResumeLayout(false);
this.groupBoxDNS.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
@@ -1140,6 +1173,8 @@ namespace MouseWithoutBorders
private GroupBox groupBoxDNS;
private TextBox textBoxDNS;
private TextBox textBoxMachineName2IP;
private GroupBox groupBoxName2IPPolicyList;
private TextBox textBoxMachineName2IPPolicyList;
private PictureBox pictureBoxMouseWithoutBorders;
private GroupBox groupBoxOtherOptions;
private CheckBox checkBoxDrawMouse;

View File

@@ -797,6 +797,14 @@ namespace MouseWithoutBorders
checkBoxHideLogo.Enabled = false;
}
// Note(@htcfreek): Disable checkboxes of settings that we don't support in the PowerToys implementation
checkBoxDisableCAD.Enabled = false;
checkBoxDisableCAD.Text = checkBoxDisableCAD.Text + " [Unsupported!]";
checkBoxHideLogo.Enabled = false;
checkBoxHideLogo.Text = checkBoxHideLogo.Text + " [Unsupported!]";
checkBoxSendLog.Enabled = false;
checkBoxSendLog.Text = checkBoxSendLog.Text + " [Unsupported!]";
checkBoxShareClipboard.Checked = Setting.Values.ShareClipboard;
if (!Setting.Values.ShareClipboard)
@@ -845,6 +853,56 @@ namespace MouseWithoutBorders
comboBoxEasyMouse.Text = Setting.Values.HotKeyToggleEasyMouse == 0 ? "Disable" : new string(new char[] { (char)Setting.Values.HotKeyToggleEasyMouse });
#endif
// Apply policy configuration on UI elements
// Has to be the last action
if (Setting.Values.ShareClipboardIsGpoConfigured)
{
checkBoxShareClipboard.Enabled = false;
checkBoxShareClipboard.Text += " [Managed]";
// transfer file setting depends on clipboard sharing
checkBoxTransferFile.Enabled = false;
}
if (Setting.Values.TransferFileIsGpoConfigured)
{
checkBoxTransferFile.Enabled = false;
checkBoxTransferFile.Text += " [Managed]";
}
if (Setting.Values.BlockScreenSaverIsGpoConfigured)
{
checkBoxBlockScreenSaver.Enabled = false;
checkBoxBlockScreenSaver.Text += " [Managed]";
}
if (Setting.Values.SameSubNetOnlyIsGpoConfigured)
{
checkBoxSameSubNet.Enabled = false;
checkBoxSameSubNet.Text += " [Managed]";
}
if (Setting.Values.ReverseLookupIsGpoConfigured)
{
checkBoxReverseLookup.Enabled = false;
checkBoxReverseLookup.Text += " [Managed]";
}
if (Setting.Values.Name2IpIsGpoConfigured)
{
textBoxMachineName2IP.Enabled = false;
groupBoxDNS.ForeColor = Color.DimGray;
groupBoxDNS.Text += " [Managed]";
}
if (Setting.Values.Name2IpPolicyListIsGpoConfigured)
{
pictureBoxMouseWithoutBorders.Visible = false;
groupBoxName2IPPolicyList.Visible = true;
textBoxMachineName2IPPolicyList.Visible = true;
textBoxMachineName2IPPolicyList.Text = Setting.Values.Name2IpPolicyList;
}
}
private void RadioButton_CheckedChanged(object sender, EventArgs e)

View File

@@ -82,6 +82,8 @@
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="System.Data.SqlClient" /> <!-- It's a dependency of Microsoft.Windows.Compatibility. We're adding it here to force it to the version specified in Directory.Packages.props -->
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{2833C9C6-AB32-4048-A5C7-A70898337B57}</ProjectGuid>
@@ -67,11 +67,14 @@
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -95,7 +95,55 @@
<ItemGroup>
<None Include="packages.config" />
<None Include="PropertySheet.props" />
<CopyFileToFolders Include="Assets\ShortcutGuide\**">
<CopyFileToFolders Include="Assets\ShortcutGuide\0.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\1.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\2.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\3.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\4.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\5.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\6.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\7.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\8.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\9.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\no_active_window.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\overlay.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="Assets\ShortcutGuide\overlay_portrait.svg">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\ShortcutGuide</DestinationFolders>
</CopyFileToFolders>

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -10,7 +10,7 @@
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWindowsForms>true</UseWindowsForms>
<UseWindowsForms>False</UseWindowsForms>
<!--Per documentation: https://learn.microsoft.com/dotnet/core/compatibility/windows-forms/5.0/automatically-infer-winexe-output-type#outputtype-set-to-winexe-for-wpf-and-winforms-apps -->
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<AssemblyName>PowerToys.Awake</AssemblyName>
@@ -35,6 +35,7 @@
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
@@ -59,6 +60,12 @@
<ItemGroup>
<None Remove="Assets\Awake\Awake.ico" />
<None Remove="Assets\Awake\disabled.ico" />
<None Remove="Assets\Awake\expirable.ico" />
<None Remove="Assets\Awake\indefinite.ico" />
<None Remove="Assets\Awake\normal.ico" />
<None Remove="Assets\Awake\scheduled.ico" />
<None Remove="Assets\Awake\timed.ico" />
</ItemGroup>
<ItemGroup>
@@ -90,6 +97,24 @@
<Content Include="Assets\Awake\Awake.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\disabled.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\expirable.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\indefinite.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\normal.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\scheduled.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Awake\timed.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View File

@@ -8,7 +8,15 @@ namespace Awake.Core
{
internal const string AppName = "Awake";
internal const string FullAppName = "PowerToys " + AppName;
internal const string TrayWindowId = "WindowsForms10.Window.0.app.0.";
internal const string TrayWindowId = "Awake.MessageWindow";
internal const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
// PowerToys Awake build code name. Used for exact logging
// that does not map to PowerToys broad version schema to pinpoint
// internal issues easier.
// 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 = "DAISY023_04102024";
}
}

View File

@@ -12,7 +12,6 @@ namespace Awake.Core
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source)
{
ArgumentNullException.ThrowIfNull(target);
ArgumentNullException.ThrowIfNull(source);
foreach (var element in source)
@@ -20,5 +19,17 @@ namespace Awake.Core
target.Add(element);
}
}
public static string ToHumanReadableString(this TimeSpan timeSpan)
{
// Get days, hours, minutes, and seconds from the TimeSpan
int days = timeSpan.Days;
int hours = timeSpan.Hours;
int minutes = timeSpan.Minutes;
int seconds = timeSpan.Seconds;
// Format the string based on the presence of days, hours, minutes, and seconds
return $"{days:D2}{Properties.Resources.AWAKE_LABEL_DAYS} {hours:D2}{Properties.Resources.AWAKE_LABEL_HOURS} {minutes:D2}{Properties.Resources.AWAKE_LABEL_MINUTES} {seconds:D2}{Properties.Resources.AWAKE_LABEL_SECONDS}";
}
}
}

View File

@@ -5,7 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reactive.Linq;
@@ -30,25 +30,35 @@ namespace Awake.Core
/// </summary>
public class Manager
{
private static readonly CompositeFormat AwakeMinutes = System.Text.CompositeFormat.Parse(Properties.Resources.AWAKE_MINUTES);
private static readonly CompositeFormat AwakeHours = System.Text.CompositeFormat.Parse(Properties.Resources.AWAKE_HOURS);
private static bool _isUsingPowerToysConfig;
private static BlockingCollection<ExecutionState> _stateQueue;
internal static bool IsUsingPowerToysConfig { get => _isUsingPowerToysConfig; set => _isUsingPowerToysConfig = value; }
private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES);
private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS);
private static readonly BlockingCollection<ExecutionState> _stateQueue;
// Core icons used for the tray
private static readonly Icon _timedIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/timed.ico"));
private static readonly Icon _expirableIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico"));
private static readonly Icon _indefiniteIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/indefinite.ico"));
private static readonly Icon _disabledIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/disabled.ico"));
private static CancellationTokenSource _tokenSource;
private static SettingsUtils? _moduleSettings;
private static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
internal static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
static Manager()
{
_tokenSource = new CancellationTokenSource();
_stateQueue = new BlockingCollection<ExecutionState>();
_stateQueue = [];
ModuleSettings = new SettingsUtils();
}
public static void StartMonitor()
internal static void StartMonitor()
{
Thread monitorThread = new(() =>
{
@@ -70,7 +80,7 @@ namespace Awake.Core
Bridge.SetConsoleCtrlHandler(handler, addHandler);
}
public static void AllocateConsole()
internal static void AllocateConsole()
{
Bridge.AllocConsole();
@@ -103,17 +113,12 @@ namespace Awake.Core
private static ExecutionState ComputeAwakeState(bool keepDisplayOn)
{
if (keepDisplayOn)
{
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS;
}
else
{
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS;
}
return keepDisplayOn
? ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS
: ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS;
}
public static void CancelExistingThread()
internal static void CancelExistingThread()
{
Logger.LogInfo($"Attempting to ensure that the thread is properly cleaned up...");
@@ -128,100 +133,200 @@ namespace Awake.Core
Logger.LogInfo("Instantiating of new token source and thread token completed.");
}
public static void SetIndefiniteKeepAwake(bool keepDisplayOn = false)
internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent());
CancelExistingThread();
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update);
if (IsUsingPowerToysConfig)
{
try
{
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.INDEFINITE ||
currentSettings.Properties.KeepDisplayOn != keepDisplayOn;
if (settingsChanged)
{
currentSettings.Properties.Mode = AwakeMode.INDEFINITE;
currentSettings.Properties.KeepDisplayOn = keepDisplayOn;
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}");
}
}
}
public static void SetNoKeepAwake()
internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent());
Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {keepDisplayOn}.");
CancelExistingThread();
}
public static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeExpirableKeepAwakeEvent());
CancelExistingThread();
if (expireAt > DateTime.Now && expireAt != null)
if (expireAt > DateTimeOffset.Now)
{
Logger.LogInfo($"Starting expirable log for {expireAt}");
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
Observable.Timer(expireAt).Subscribe(
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION} - {expireAt}]", _expirableIcon, TrayIconAction.Update);
Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe(
_ =>
{
Logger.LogInfo($"Completed expirable keep-awake.");
CancelExistingThread();
SetPassiveKeepAwakeMode(Constants.AppName);
if (IsUsingPowerToysConfig)
{
SetPassiveKeepAwake();
}
else
{
Logger.LogInfo("Exiting after expirable keep awake.");
CompleteExit(Environment.ExitCode);
}
},
_tokenSource.Token);
}
else
{
// The target date is not in the future.
Logger.LogError("The specified target date and time is not in the future.");
Logger.LogError($"Current time: {DateTime.Now}\tTarget time: {expireAt}");
Logger.LogError($"Current time: {DateTimeOffset.Now}\tTarget time: {expireAt}");
}
if (IsUsingPowerToysConfig)
{
try
{
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE ||
currentSettings.Properties.ExpirationDateTime != expireAt ||
currentSettings.Properties.KeepDisplayOn != keepDisplayOn;
if (settingsChanged)
{
currentSettings.Properties.Mode = AwakeMode.EXPIRABLE;
currentSettings.Properties.KeepDisplayOn = keepDisplayOn;
currentSettings.Properties.ExpirationDateTime = expireAt;
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}");
}
}
}
public static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true)
internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true)
{
Logger.LogInfo($"Timed keep-awake. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}.");
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent());
CancelExistingThread();
Logger.LogInfo($"Timed keep awake started for {seconds} seconds.");
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe(
_ =>
{
Logger.LogInfo($"Completed timed thread.");
CancelExistingThread();
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update);
SetPassiveKeepAwakeMode(Constants.AppName);
},
_tokenSource.Token);
var timerObservable = Observable.Timer(TimeSpan.FromSeconds(seconds));
var intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable);
var combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1);
combinedObservable.Subscribe(
elapsedSeconds =>
{
var timeRemaining = seconds - (uint)elapsedSeconds;
if (timeRemaining >= 0)
{
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}", _timedIcon, TrayIconAction.Update);
}
},
() =>
{
Console.WriteLine("Completed timed thread.");
CancelExistingThread();
if (IsUsingPowerToysConfig)
{
// If we're using PowerToys settings, we need to make sure that
// we just switch over the Passive Keep-Awake.
SetPassiveKeepAwake();
}
else
{
Logger.LogInfo("Exiting after timed keep-awake.");
CompleteExit(Environment.ExitCode);
}
},
_tokenSource.Token);
if (IsUsingPowerToysConfig)
{
try
{
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
var timeSpan = TimeSpan.FromSeconds(seconds);
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED ||
currentSettings.Properties.IntervalHours != (uint)timeSpan.Hours ||
currentSettings.Properties.IntervalMinutes != (uint)timeSpan.Minutes;
if (settingsChanged)
{
currentSettings.Properties.Mode = AwakeMode.TIMED;
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to handle timed keep awake command: {ex.Message}");
}
}
}
internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
/// <summary>
/// Performs a clean exit from Awake.
/// </summary>
/// <param name="exitCode">Exit code to exit with.</param>
internal static void CompleteExit(int exitCode)
{
SetNoKeepAwake();
SetPassiveKeepAwake(updateSettings: false);
IntPtr windowHandle = GetHiddenWindow();
if (windowHandle != IntPtr.Zero)
if (TrayHelper.HiddenWindowHandle != IntPtr.Zero)
{
Bridge.SendMessage(windowHandle, Native.Constants.WM_CLOSE, 0, 0);
// Delete the icon.
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, string.Empty, null, TrayIconAction.Delete);
// Close the message window that we used for the tray.
Bridge.SendMessage(TrayHelper.HiddenWindowHandle, Native.Constants.WM_CLOSE, 0, 0);
Bridge.DestroyWindow(TrayHelper.HiddenWindowHandle);
}
if (force)
{
Bridge.PostQuitMessage(exitCode);
}
try
{
exitSignal?.Set();
Bridge.DestroyWindow(windowHandle);
}
catch (Exception ex)
{
Logger.LogError($"Exit signal error ${ex}");
}
Bridge.PostQuitMessage(exitCode);
Environment.Exit(exitCode);
}
public static string GetOperatingSystemBuild()
/// <summary>
/// Gets the operating system for logging purposes.
/// </summary>
/// <returns>Returns the string representing the current OS build.</returns>
internal static string GetOperatingSystemBuild()
{
try
{
@@ -245,83 +350,71 @@ namespace Awake.Core
}
}
[SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")]
internal static IEnumerable<IntPtr> EnumerateWindowsForProcess(int processId)
/// <summary>
/// Generates the default system tray options in situations where no custom options are provided.
/// </summary>
/// <returns>Returns a dictionary of default Awake timed interval options.</returns>
internal static Dictionary<string, int> GetDefaultTrayOptions()
{
var handles = new List<IntPtr>();
var hCurrentWnd = IntPtr.Zero;
do
{
hCurrentWnd = Bridge.FindWindowEx(IntPtr.Zero, hCurrentWnd, null as string, null);
Bridge.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId);
if (targetProcessId == processId)
{
handles.Add(hCurrentWnd);
}
}
while (hCurrentWnd != IntPtr.Zero);
return handles;
}
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")]
internal static IntPtr GetHiddenWindow()
{
IEnumerable<IntPtr> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
string targetClass = $"{Constants.TrayWindowId}{domain}";
foreach (var handle in windowHandles)
{
StringBuilder className = new(256);
int classQueryResult = Bridge.GetClassName(handle, className, className.Capacity);
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
{
return handle;
}
}
return IntPtr.Zero;
}
public static Dictionary<string, int> GetDefaultTrayOptions()
{
Dictionary<string, int> optionsList = new Dictionary<string, int>
Dictionary<string, int> optionsList = new()
{
{ string.Format(CultureInfo.InvariantCulture, AwakeMinutes, 30), 1800 },
{ Resources.AWAKE_1_HOUR, 3600 },
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 1), 3600 },
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 2), 7200 },
};
return optionsList;
}
public static void SetPassiveKeepAwakeMode(string moduleName)
/// <summary>
/// Resets the computer to standard power settings.
/// </summary>
/// <param name="updateSettings">In certain cases, such as exits, we want to make sure that settings are not reset for the passive mode but rather retained based on previous execution. Default is to save settings, but otherwise it can be overridden.</param>
internal static void SetPassiveKeepAwake(bool updateSettings = true)
{
AwakeSettings currentSettings;
Logger.LogInfo($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
try
{
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed to reset Awake mode GetSettings: {ex.Message}";
Logger.LogError(errorString);
currentSettings = new AwakeSettings();
}
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent());
currentSettings.Properties.Mode = AwakeMode.PASSIVE;
CancelExistingThread();
try
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update);
if (IsUsingPowerToysConfig && updateSettings)
{
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
try
{
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
if (currentSettings.Properties.Mode != AwakeMode.PASSIVE)
{
currentSettings.Properties.Mode = AwakeMode.PASSIVE;
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to reset Awake mode: {ex.Message}");
}
}
catch (Exception ex)
}
/// <summary>
/// Sets the display settings.
/// </summary>
internal static void SetDisplay()
{
if (IsUsingPowerToysConfig)
{
string? errorString = $"Failed to reset Awake mode SaveSettings: {ex.Message}";
Logger.LogError(errorString);
try
{
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
}
catch (Exception ex)
{
Logger.LogError($"Failed to handle display setting command: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Awake.Core.Models
{
internal struct Msg
{
public IntPtr HWnd;
public uint Message;
public IntPtr WParam;
public IntPtr LParam;
public uint Time;
public Point Pt;
}
}

View File

@@ -0,0 +1,21 @@
// 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.Runtime.InteropServices;
namespace Awake.Core.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct MenuInfo
{
public uint CbSize; // Size of the structure, in bytes
public uint FMask; // Specifies which members of the structure are valid
public uint DwStyle; // Style of the menu
public uint CyMax; // Maximum height of the menu, in pixels
public IntPtr HbrBack; // Handle to the brush used for the menu's background
public uint DwContextHelpID; // Context help ID
public IntPtr DwMenuData; // Pointer to the menu's user data
}
}

View File

@@ -0,0 +1,22 @@
// 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.Runtime.InteropServices;
namespace Awake.Core.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct NotifyIconData
{
public int CbSize;
public IntPtr HWnd;
public int UId;
public int UFlags;
public int UCallbackMessage;
public IntPtr HIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string SzTip;
}
}

View File

@@ -0,0 +1,15 @@
// 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.Runtime.InteropServices;
namespace Awake.Core.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int X;
public int Y;
}
}

View File

@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
namespace Awake.Core.Models
{
internal sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
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).
{
lock (queue)
{
queue.Enqueue(Tuple.Create(d, state));
Monitor.Pulse(queue);
}
}
public void BeginMessageLoop()
{
while (true)
{
Tuple<SendOrPostCallback, object> work;
lock (queue)
{
while (queue.Count == 0)
{
Monitor.Wait(queue);
}
work = queue.Dequeue();
}
if (work == null)
{
break;
}
work.Item1(work.Item2);
}
}
public void EndMessageLoop()
{
lock (queue)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
queue.Enqueue(null); // Signal the end of the message loop
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
Monitor.Pulse(queue);
}
}
}
}

View File

@@ -6,11 +6,11 @@ namespace Awake.Core.Models
{
internal enum TrayCommands : uint
{
TC_DISPLAY_SETTING = Native.Constants.WM_USER + 1,
TC_MODE_PASSIVE = Native.Constants.WM_USER + 2,
TC_MODE_INDEFINITE = Native.Constants.WM_USER + 3,
TC_MODE_EXPIRABLE = Native.Constants.WM_USER + 4,
TC_EXIT = Native.Constants.WM_USER + 100,
TC_TIME = Native.Constants.WM_USER + 101,
TC_DISPLAY_SETTING = Native.Constants.WM_USER + 0x2,
TC_MODE_PASSIVE = Native.Constants.WM_USER + 0x3,
TC_MODE_INDEFINITE = Native.Constants.WM_USER + 0x4,
TC_MODE_EXPIRABLE = Native.Constants.WM_USER + 0x5,
TC_EXIT = Native.Constants.WM_USER + 0x64,
TC_TIME = Native.Constants.WM_USER + 0x65,
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Awake.Core.Models
{
internal enum TrayIconAction
{
Add,
Update,
Delete,
}
}

View File

@@ -0,0 +1,26 @@
// 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.Runtime.InteropServices;
namespace Awake.Core.Models
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WndClassEx
{
public uint CbSize;
public uint Style;
public IntPtr LpfnWndProc;
public int CbClsExtra;
public int CbWndExtra;
public IntPtr HInstance;
public IntPtr HIcon;
public IntPtr HCursor;
public IntPtr HbrBackground;
public string LpszMenuName;
public string LpszClassName;
public IntPtr HIconSm;
}
}

View File

@@ -5,14 +5,14 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Awake.Core.Models;
namespace Awake.Core.Native
{
internal sealed class Bridge
{
internal delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = true)]
internal delegate int WndProcDelegate(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam);
[DllImport("Powrprof.dll", SetLastError = true)]
internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities);
@@ -30,9 +30,6 @@ namespace Awake.Core.Native
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern uint GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPWStr)] string filename,
@@ -50,25 +47,13 @@ namespace Awake.Core.Native
internal static extern bool InsertMenu(IntPtr hMenu, uint uPosition, uint uFlags, uint uIDNewItem, string lpNewItem);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string? className, string? windowTitle);
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hWnd, IntPtr lptpm);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, nuint wParam, nint lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll")]
@@ -76,5 +61,46 @@ namespace Awake.Core.Native
[DllImport("user32.dll")]
internal static extern void PostQuitMessage(int nExitCode);
[DllImport("shell32.dll")]
internal static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData pnid);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool TranslateMessage(ref Msg lpMsg);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr DispatchMessage(ref Msg lpMsg);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr RegisterClassEx(ref WndClassEx lpwcx);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int DefWindowProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll")]
internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
[DllImport("user32.dll")]
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetMenuInfo(IntPtr hMenu, ref MenuInfo lpcmi);
[DllImport("user32.dll")]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
}
}

View File

@@ -7,26 +7,46 @@ namespace Awake.Core.Native
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 API convention.")]
internal sealed class Constants
{
internal const uint WM_COMMAND = 0x111;
internal const uint WM_USER = 0x400;
internal const uint WM_GETTEXT = 0x000D;
// Window Messages
internal const uint WM_COMMAND = 0x0111;
internal const uint WM_USER = 0x0400U;
internal const uint WM_CLOSE = 0x0010;
internal const int WM_DESTROY = 0x0002;
internal const int WM_LBUTTONDOWN = 0x0201;
internal const int WM_RBUTTONDOWN = 0x0204;
// Popup menu constants.
// Menu Flags
internal const uint MF_BYPOSITION = 1024;
internal const uint MF_STRING = 0;
internal const uint MF_MENUBREAK = 0x00000040;
internal const uint MF_SEPARATOR = 0x00000800;
internal const uint MF_POPUP = 0x00000010;
internal const uint MF_UNCHECKED = 0x00000000;
internal const uint MF_CHECKED = 0x00000008;
internal const uint MF_OWNERDRAW = 0x00000100;
internal const uint MF_ENABLED = 0x00000000;
internal const uint MF_DISABLED = 0x00000002;
// Standard Handles
internal const int STD_OUTPUT_HANDLE = -11;
// Generic Access Rights
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint GENERIC_READ = 0x80000000;
// Notification Icons
internal const int NIF_ICON = 0x00000002;
internal const int NIF_MESSAGE = 0x00000001;
internal const int NIF_TIP = 0x00000004;
internal const int NIM_ADD = 0x00000000;
internal const int NIM_DELETE = 0x00000002;
internal const int NIM_MODIFY = 0x00000001;
// Track Popup Menu Flags
internal const uint TPM_LEFT_ALIGN = 0x0000;
internal const uint TPM_BOTTOMALIGN = 0x0020;
internal const uint TPM_LEFT_BUTTON = 0x0000;
// Menu Item Info Flags
internal const uint MNS_AUTO_DISMISS = 0x10000000;
internal const uint MIM_STYLE = 0x00000010;
}
}

View File

@@ -4,12 +4,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Awake.Core.Models;
using Awake.Core.Native;
using Awake.Properties;
@@ -27,147 +27,384 @@ namespace Awake.Core
/// </remarks>
internal static class TrayHelper
{
private static NotifyIconData _notifyIconData;
private static IntPtr _trayMenu;
private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; }
private static NotifyIcon TrayIcon { get; set; }
private static IntPtr _hiddenWindowHandle;
internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; }
static TrayHelper()
{
TrayIcon = new NotifyIcon();
TrayMenu = IntPtr.Zero;
HiddenWindowHandle = IntPtr.Zero;
}
public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal, ContextMenuStrip? contextMenu = null)
public static void InitializeTray(string text, Icon icon)
{
Task.Factory.StartNew(
(tray) =>
{
try
{
Logger.LogInfo("Setting up the tray.");
if (tray != null)
{
((NotifyIcon)tray).Text = text;
((NotifyIcon)tray).Icon = icon;
((NotifyIcon)tray).ContextMenuStrip = contextMenu;
((NotifyIcon)tray).Visible = true;
((NotifyIcon)tray).MouseClick += TrayClickHandler;
Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
Application.Run();
Logger.LogInfo("Tray setup complete.");
}
}
catch (Exception ex)
{
Logger.LogError($"An error occurred initializing the tray. {ex.Message}");
Logger.LogError($"{ex.StackTrace}");
}
},
TrayIcon);
CreateHiddenWindow(icon, text);
}
/// <summary>
/// Function used to construct the context menu in the tray natively.
/// </summary>
/// <remarks>
/// We need to use the Windows API here instead of the common control exposed
/// by NotifyIcon because the one that is built into the Windows Forms stack
/// hasn't been updated in a while and is looking like Office XP. That introduces
/// scalability and coloring changes on any OS past Windows XP.
/// </remarks>
/// <param name="sender">The sender that triggers the handler.</param>
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
private static void TrayClickHandler(object? sender, MouseEventArgs e)
private static void ShowContextMenu(IntPtr hWnd)
{
IntPtr windowHandle = Manager.GetHiddenWindow();
if (windowHandle != IntPtr.Zero)
if (TrayMenu != IntPtr.Zero)
{
Bridge.SetForegroundWindow(windowHandle);
Bridge.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, 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.");
}
}
internal static void SetTray(string text, AwakeSettings settings, bool startedFromPowerToys)
private static void CreateHiddenWindow(Icon icon, string text)
{
IntPtr hWnd = IntPtr.Zero;
// Start the message loop asynchronously
Task.Run(() =>
{
RunOnMainThread(() =>
{
WndClassEx wcex = new()
{
CbSize = (uint)Marshal.SizeOf(typeof(WndClassEx)),
Style = 0,
LpfnWndProc = Marshal.GetFunctionPointerForDelegate<Bridge.WndProcDelegate>(WndProc),
CbClsExtra = 0,
CbWndExtra = 0,
HInstance = Marshal.GetHINSTANCE(typeof(Program).Module),
HIcon = IntPtr.Zero,
HCursor = IntPtr.Zero,
HbrBackground = IntPtr.Zero,
LpszMenuName = string.Empty,
LpszClassName = Constants.TrayWindowId,
HIconSm = IntPtr.Zero,
};
Bridge.RegisterClassEx(ref wcex);
hWnd = Bridge.CreateWindowEx(
0,
Constants.TrayWindowId,
text,
0x00CF0000 | 0x00000001 | 0x00000008, // WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MINIMIZEBOX
0,
0,
0,
0,
unchecked(-3),
IntPtr.Zero,
Marshal.GetHINSTANCE(typeof(Program).Module),
IntPtr.Zero);
if (hWnd == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, "Failed to add tray icon. Error code: " + errorCode);
}
// Keep this as a reference because we will need it when we update
// the tray icon in the future.
HiddenWindowHandle = hWnd;
Bridge.ShowWindow(hWnd, 0); // SW_HIDE
Bridge.UpdateWindow(hWnd);
SetShellIcon(hWnd, text, icon);
RunMessageLoop();
});
});
}
internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add)
{
int message = Native.Constants.NIM_ADD;
switch (action)
{
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)
{
_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 (action == TrayIconAction.Delete)
{
_notifyIconData = default;
}
}
private static void RunMessageLoop()
{
while (Bridge.GetMessage(out Msg msg, IntPtr.Zero, 0, 0))
{
Bridge.TranslateMessage(ref msg);
Bridge.DispatchMessage(ref msg);
}
}
private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
{
switch (message)
{
case Native.Constants.WM_USER:
if (lParam == (IntPtr)Native.Constants.WM_LBUTTONDOWN || lParam == (IntPtr)Native.Constants.WM_RBUTTONDOWN)
{
// Show the context menu associated with the tray icon
ShowContextMenu(hWnd);
}
break;
case Native.Constants.WM_DESTROY:
// Clean up resources when the window is destroyed
Bridge.PostQuitMessage(0);
break;
case Native.Constants.WM_COMMAND:
int trayCommandsSize = Enum.GetNames(typeof(TrayCommands)).Length;
long targetCommandIndex = wParam.ToInt64() & 0xFFFF;
switch (targetCommandIndex)
{
case (uint)TrayCommands.TC_EXIT:
{
Manager.CompleteExit(Environment.ExitCode);
break;
}
case (uint)TrayCommands.TC_DISPLAY_SETTING:
{
Manager.SetDisplay();
break;
}
case (uint)TrayCommands.TC_MODE_INDEFINITE:
{
AwakeSettings settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName);
Manager.SetIndefiniteKeepAwake(keepDisplayOn: settings.Properties.KeepDisplayOn);
break;
}
case (uint)TrayCommands.TC_MODE_PASSIVE:
{
Manager.SetPassiveKeepAwake();
break;
}
default:
{
if (targetCommandIndex >= trayCommandsSize)
{
AwakeSettings settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName);
if (settings.Properties.CustomTrayTimes.Count == 0)
{
settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions());
}
int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME;
uint targetTime = (uint)settings.Properties.CustomTrayTimes.ElementAt(index).Value;
Manager.SetTimedKeepAwake(targetTime, keepDisplayOn: settings.Properties.KeepDisplayOn);
}
break;
}
}
break;
default:
// Let the default window procedure handle other messages
return Bridge.DefWindowProc(hWnd, message, wParam, lParam);
}
return Bridge.DefWindowProc(hWnd, message, wParam, lParam);
}
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
{
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();
}
internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys)
{
SetTray(
text,
settings.Properties.KeepDisplayOn,
settings.Properties.Mode,
settings.Properties.CustomTrayTimes,
startedFromPowerToys);
}
public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts, bool startedFromPowerToys)
public static void SetTray(bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts, bool startedFromPowerToys)
{
if (TrayMenu != IntPtr.Zero)
{
var destructionStatus = Bridge.DestroyMenu(TrayMenu);
if (destructionStatus != true)
{
Logger.LogError("Failed to destroy menu.");
}
}
ClearExistingTrayMenu();
CreateNewTrayMenu(startedFromPowerToys, keepDisplayOn, mode);
InsertAwakeModeMenuItems(mode);
EnsureDefaultTrayTimeShortcuts(trayTimeShortcuts);
CreateAwakeTimeSubMenu(trayTimeShortcuts, mode == AwakeMode.TIMED);
}
private static void ClearExistingTrayMenu()
{
if (TrayMenu != IntPtr.Zero && !Bridge.DestroyMenu(TrayMenu))
{
int errorCode = Marshal.GetLastWin32Error();
Logger.LogError($"Failed to destroy menu: {errorCode}");
}
}
private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode)
{
TrayMenu = Bridge.CreatePopupMenu();
if (TrayMenu != IntPtr.Zero)
if (TrayMenu == IntPtr.Zero)
{
if (!startedFromPowerToys)
{
// If Awake is started from PowerToys, the correct way to exit it is disabling it from Settings.
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_EXIT, Resources.AWAKE_EXIT);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
}
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (keepDisplayOn ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, Resources.AWAKE_KEEP_SCREEN_ON);
return;
}
// In case there are no tray shortcuts defined for the application default to a
// reasonable initial set.
if (!startedFromPowerToys)
{
InsertMenuItem(0, TrayCommands.TC_EXIT, Resources.AWAKE_EXIT);
}
InsertMenuItem(0, TrayCommands.TC_DISPLAY_SETTING, Resources.AWAKE_KEEP_SCREEN_ON, keepDisplayOn, mode == AwakeMode.PASSIVE);
if (!startedFromPowerToys)
{
InsertSeparator(1);
}
}
private static void InsertMenuItem(int position, TrayCommands command, string text, bool checkedState = false, bool disabled = false)
{
uint state = Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING;
state |= checkedState ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED;
state |= disabled ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED;
Bridge.InsertMenu(TrayMenu, (uint)position, state, (uint)command, text);
}
private static void InsertSeparator(int position)
{
Bridge.InsertMenu(TrayMenu, (uint)position, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
}
private static void EnsureDefaultTrayTimeShortcuts(Dictionary<string, int> trayTimeShortcuts)
{
if (trayTimeShortcuts.Count == 0)
{
trayTimeShortcuts.AddRange(Manager.GetDefaultTrayOptions());
}
}
private static void CreateAwakeTimeSubMenu(Dictionary<string, int> trayTimeShortcuts, bool isChecked = false)
{
var awakeTimeMenu = Bridge.CreatePopupMenu();
for (int i = 0; i < trayTimeShortcuts.Count; i++)
{
Bridge.InsertMenu(awakeTimeMenu, (uint)i, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key);
}
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, Resources.AWAKE_OFF);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.INDEFINITE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, Resources.AWAKE_KEEP_INDEFINITELY);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (mode == AwakeMode.TIMED ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | Native.Constants.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, Resources.AWAKE_KEEP_UNTIL_EXPIRATION);
TrayIcon.Text = text;
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (isChecked == true ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL);
}
private sealed class CheckButtonToolStripMenuItemAccessibleObject : ToolStripItem.ToolStripItemAccessibleObject
private static void InsertAwakeModeMenuItems(AwakeMode mode)
{
private readonly CheckButtonToolStripMenuItem _menuItem;
InsertSeparator(0);
public CheckButtonToolStripMenuItemAccessibleObject(CheckButtonToolStripMenuItem menuItem)
: base(menuItem)
{
_menuItem = menuItem;
}
public override AccessibleRole Role => AccessibleRole.CheckButton;
public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? Resources.AWAKE_CHECKED : Resources.AWAKE_UNCHECKED);
}
private sealed class CheckButtonToolStripMenuItem : ToolStripMenuItem
{
protected override AccessibleObject CreateAccessibilityInstance()
{
return new CheckButtonToolStripMenuItemAccessibleObject(this);
}
InsertMenuItem(0, TrayCommands.TC_MODE_PASSIVE, Resources.AWAKE_OFF, mode == AwakeMode.PASSIVE);
InsertMenuItem(0, TrayCommands.TC_MODE_INDEFINITE, Resources.AWAKE_KEEP_INDEFINITELY, mode == AwakeMode.INDEFINITE);
InsertMenuItem(0, TrayCommands.TC_MODE_EXPIRABLE, Resources.AWAKE_KEEP_UNTIL_EXPIRATION, mode == AwakeMode.EXPIRABLE, true);
}
}
}

View File

@@ -1,172 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Windows.Forms;
using Awake.Core.Models;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Awake.Core
{
public class TrayMessageFilter : IMessageFilter
{
private static SettingsUtils? _moduleSettings;
private static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
private static ManualResetEvent? _exitSignal;
public TrayMessageFilter(ManualResetEvent? exitSignal)
{
_exitSignal = exitSignal;
ModuleSettings = new SettingsUtils();
}
public bool PreFilterMessage(ref Message m)
{
var trayCommandsSize = Enum.GetNames(typeof(TrayCommands)).Length;
switch (m.Msg)
{
case (int)Native.Constants.WM_COMMAND:
var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF;
switch (targetCommandIndex)
{
case (long)TrayCommands.TC_EXIT:
ExitCommandHandler(_exitSignal);
break;
case (long)TrayCommands.TC_DISPLAY_SETTING:
DisplaySettingCommandHandler(Constants.AppName);
break;
case (long)TrayCommands.TC_MODE_INDEFINITE:
IndefiniteKeepAwakeCommandHandler(Constants.AppName);
break;
case (long)TrayCommands.TC_MODE_PASSIVE:
PassiveKeepAwakeCommandHandler(Constants.AppName);
break;
case var _ when targetCommandIndex >= trayCommandsSize:
// Format for the timer block:
// TrayCommands.TC_TIME + ZERO_BASED_INDEX_IN_SETTINGS
AwakeSettings settings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName);
if (settings.Properties.CustomTrayTimes.Count == 0)
{
settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions());
}
int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME;
var targetTime = settings.Properties.CustomTrayTimes.ElementAt(index).Value;
TimedKeepAwakeCommandHandler(Constants.AppName, targetTime);
break;
}
break;
}
return false;
}
private static void ExitCommandHandler(ManualResetEvent? exitSignal)
{
Manager.CompleteExit(0, exitSignal, true);
}
private static void DisplaySettingCommandHandler(string moduleName)
{
AwakeSettings currentSettings;
try
{
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed GetSettings: {ex.Message}";
Logger.LogError(errorString);
currentSettings = new AwakeSettings();
}
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
try
{
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed SaveSettings: {ex.Message}";
Logger.LogError(errorString);
}
}
private static void TimedKeepAwakeCommandHandler(string moduleName, int seconds)
{
TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
AwakeSettings currentSettings;
try
{
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed GetSettings: {ex.Message}";
Logger.LogError(errorString);
currentSettings = new AwakeSettings();
}
currentSettings.Properties.Mode = AwakeMode.TIMED;
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
try
{
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed SaveSettings: {ex.Message}";
Logger.LogError(errorString);
}
}
private static void PassiveKeepAwakeCommandHandler(string moduleName)
{
Manager.SetPassiveKeepAwakeMode(moduleName);
}
private static void IndefiniteKeepAwakeCommandHandler(string moduleName)
{
AwakeSettings currentSettings;
try
{
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed GetSettings: {ex.Message}";
Logger.LogError(errorString);
currentSettings = new AwakeSettings();
}
currentSettings.Properties.Mode = AwakeMode.INDEFINITE;
try
{
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
catch (Exception ex)
{
string? errorString = $"Failed SaveSettings: {ex.Message}";
Logger.LogError(errorString);
}
}
}
}

View File

@@ -18,6 +18,7 @@ using System.Threading.Tasks;
using Awake.Core;
using Awake.Core.Models;
using Awake.Core.Native;
using Awake.Properties;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -25,14 +26,6 @@ namespace Awake
{
internal sealed class Program
{
// PowerToys Awake build code name. Used for exact logging
// that does not map to PowerToys broad version schema to pinpoint
// internal issues easier.
// 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.
private static readonly string BuildId = "ATRIOX_04132023";
private static Mutex? _mutex;
private static FileSystemWatcher? _watcher;
private static SettingsUtils? _settingsUtils;
@@ -46,12 +39,13 @@ namespace Awake
private static SystemPowerCapabilities _powerCapabilities;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
internal static readonly string[] AliasesConfigOption = new[] { "--use-pt-config", "-c" };
internal static readonly string[] AliasesDisplayOption = new[] { "--display-on", "-d" };
internal static readonly string[] AliasesTimeOption = new[] { "--time-limit", "-t" };
internal static readonly string[] AliasesPidOption = new[] { "--pid", "-p" };
internal static readonly string[] AliasesExpireAtOption = new[] { "--expire-at", "-e" };
internal static readonly string[] AliasesConfigOption = ["--use-pt-config", "-c"];
internal static readonly string[] AliasesDisplayOption = ["--display-on", "-d"];
internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"];
internal static readonly string[] AliasesPidOption = ["--pid", "-p"];
internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"];
private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico"));
private static int Main(string[] args)
{
@@ -60,20 +54,22 @@ namespace Awake
Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs"));
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, _exitSignal, true);
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)
{
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true);
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1);
}
Logger.LogInfo($"Launching {Core.Constants.AppName}...");
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
Logger.LogInfo($"Build: {BuildId}");
Logger.LogInfo($"Build: {Core.Constants.BuildId}");
Logger.LogInfo($"OS: {Environment.OSVersion}");
Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}");
@@ -90,85 +86,71 @@ namespace Awake
Logger.LogInfo("Parsing parameters...");
Option<bool> configOption = new(
aliases: AliasesConfigOption,
getDefaultValue: () => false,
description: $"Specifies whether {Core.Constants.AppName} will be using the PowerToys configuration file for managing the state.")
var configOption = new Option<bool>(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> displayOption = new(
aliases: AliasesDisplayOption,
getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.")
var displayOption = new Option<bool>(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<uint> timeOption = new(
aliases: AliasesTimeOption,
getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.")
var timeOption = new Option<uint>(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
{
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
Option<int> pidOption = new(
aliases: AliasesPidOption,
getDefaultValue: () => 0,
description: $"Bind the execution of {Core.Constants.AppName} to another process. When the process ends, the system will resume managing the current sleep and display state.")
var pidOption = new Option<int>(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<string> expireAtOption = new(
aliases: AliasesExpireAtOption,
getDefaultValue: () => string.Empty,
description: $"Determines the end date/time when {Core.Constants.AppName} will back off and let the system manage the current sleep and display state.")
var expireAtOption = new Option<string>(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
RootCommand? rootCommand = new()
{
RootCommand? rootCommand =
[
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption,
};
];
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(
HandleCommandLineArguments,
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption);
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption);
return rootCommand.InvokeAsync(args).Result;
}
private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception exception)
{
Logger.LogError(exception.ToString());
Logger.LogError(exception.StackTrace);
}
}
private static bool ExitHandler(ControlType ctrlType)
{
Logger.LogInfo($"Exited through handler with control type: {ctrlType}");
Exit("Exiting from the internal termination handler.", Environment.ExitCode, _exitSignal);
Exit(Resources.AWAKE_EXIT_MESSAGE, Environment.ExitCode);
return false;
}
private static void Exit(string message, int exitCode, ManualResetEvent exitSignal, bool force = false)
private static void Exit(string message, int exitCode)
{
Logger.LogInfo(message);
Manager.CompleteExit(exitCode, exitSignal, force);
Manager.CompleteExit(exitCode);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt)
@@ -197,31 +179,32 @@ namespace Awake
// Start the monitor thread that will be used to track the current state.
Manager.StartMonitor();
TrayHelper.InitializeTray(Core.Constants.FullAppName, _defaultAwakeIcon);
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent());
new Thread(() =>
{
WaitHandle.WaitAny([eventHandle]);
Exit(Resources.AWAKE_EXIT_SIGNAL_MESSAGE, 0);
}).Start();
if (usePtConfig)
{
// Configuration file is used, therefore we disregard any other command-line parameter
// and instead watch for changes in the file.
Manager.IsUsingPowerToysConfig = true;
try
{
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent());
new Thread(() =>
{
if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1)
{
Exit("Received a signal to end the process. Making sure we quit...", 0, _exitSignal, true);
}
}).Start();
TrayHelper.InitializeTray(Core.Constants.FullAppName, new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")), _exitSignal);
string? settingsPath = _settingsUtils!.GetSettingsFilePath(Core.Constants.AppName);
Logger.LogInfo($"Reading configuration file: {settingsPath}");
if (!File.Exists(settingsPath))
{
string? errorString = $"The settings file does not exist. Scaffolding default configuration...";
Logger.LogError("The settings file does not exist. Scaffolding default configuration...");
AwakeSettings scaffoldSettings = new AwakeSettings();
AwakeSettings scaffoldSettings = new();
_settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), Core.Constants.AppName);
}
@@ -229,8 +212,7 @@ namespace Awake
}
catch (Exception ex)
{
string? errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}";
Logger.LogError(errorString);
Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}");
}
}
else
@@ -241,24 +223,13 @@ namespace Awake
{
try
{
DateTime expirationDateTime = DateTime.Parse(expireAt, CultureInfo.CurrentCulture);
if (expirationDateTime > DateTime.Now)
{
// We want to have a dedicated expirable keep-awake logic instead of
// converting the target date to seconds and then passing to SetupTimedKeepAwake
// because that way we're accounting for the user potentially changing their clock
// while Awake is running.
Logger.LogInfo($"Operating in thread ID {Environment.CurrentManagedThreadId}.");
SetupExpirableKeepAwake(expirationDateTime, displayOn);
}
else
{
Logger.LogInfo($"Target date is not in the future, therefore there is nothing to wait for.");
}
DateTimeOffset expirationDateTime = DateTimeOffset.Parse(expireAt, CultureInfo.CurrentCulture);
Logger.LogInfo($"Operating in thread ID {Environment.CurrentManagedThreadId}.");
Manager.SetExpirableKeepAwake(expirationDateTime, displayOn);
}
catch (Exception ex)
{
Logger.LogError($"Could not parse date string {expireAt} into a viable date.");
Logger.LogError($"Could not parse date string {expireAt} into a DateTimeOffset object.");
Logger.LogError(ex.Message);
}
}
@@ -268,11 +239,11 @@ namespace Awake
if (mode == AwakeMode.INDEFINITE)
{
SetupIndefiniteKeepAwake(displayOn);
Manager.SetIndefiniteKeepAwake(displayOn);
}
else
{
SetupTimedKeepAwake(timeLimit, displayOn);
Manager.SetTimedKeepAwake(timeLimit, displayOn);
}
}
}
@@ -282,44 +253,43 @@ namespace Awake
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}.");
Exit("Terminating from process binding hook.", 0, _exitSignal, true);
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
_exitSignal.WaitOne();
}
private static void ScaffoldConfiguration(string settingsPath)
{
try
{
var directory = Path.GetDirectoryName(settingsPath)!;
var fileName = Path.GetFileName(settingsPath);
_watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(settingsPath)!,
Path = directory,
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
Filter = Path.GetFileName(settingsPath),
Filter = fileName,
};
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
var mergedObservable = Observable.Merge(
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h,
h => _watcher.Changed -= h);
h => _watcher.Changed -= h),
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Created += h,
h => _watcher.Created -= h));
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
cre => _watcher.Created += cre,
cre => _watcher.Created -= cre);
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? mergedObservable = Observable.Merge(changedObservable, createdObservable);
mergedObservable.Throttle(TimeSpan.FromMilliseconds(25))
mergedObservable
.Throttle(TimeSpan.FromMilliseconds(25))
.SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange);
TrayHelper.SetTray(Core.Constants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
var settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
TrayHelper.SetTray(settings, _startedFromPowerToys);
// Initially the file might not be updated, so we need to start processing
// settings right away.
ProcessSettings();
}
catch (Exception ex)
@@ -328,99 +298,64 @@ namespace Awake
}
}
private static void SetupIndefiniteKeepAwake(bool displayOn)
{
Manager.SetIndefiniteKeepAwake(displayOn);
}
private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent)
{
Logger.LogInfo("Detected a settings file change. Updating configuration...");
Logger.LogInfo("Resetting keep-awake to normal state due to settings change.");
ProcessSettings();
try
{
Logger.LogInfo("Detected a settings file change. Updating configuration...");
ProcessSettings();
}
catch (Exception e)
{
Logger.LogError($"Could not handle Awake configuration change. Error: {e.Message}");
}
}
private static void ProcessSettings()
{
try
{
AwakeSettings settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName);
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}");
if (settings != null)
switch (settings.Properties.Mode)
{
Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
case AwakeMode.PASSIVE:
Manager.SetPassiveKeepAwake();
break;
switch (settings.Properties.Mode)
{
case AwakeMode.PASSIVE:
{
SetupNoKeepAwake();
break;
}
case AwakeMode.INDEFINITE:
Manager.SetIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
break;
case AwakeMode.INDEFINITE:
{
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
break;
}
case AwakeMode.TIMED:
uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60);
Manager.SetTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
break;
case AwakeMode.TIMED:
{
uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60);
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
case AwakeMode.EXPIRABLE:
// When we are loading from the settings file, let's make sure that we never
// get users in a state where the expirable keep-awake is in the past.
if (settings.Properties.ExpirationDateTime <= DateTimeOffset.Now)
{
settings.Properties.ExpirationDateTime = DateTimeOffset.Now.AddMinutes(5);
_settingsUtils.SaveSettings(JsonSerializer.Serialize(settings), Core.Constants.AppName);
}
break;
}
Manager.SetExpirableKeepAwake(settings.Properties.ExpirationDateTime, settings.Properties.KeepDisplayOn);
break;
case AwakeMode.EXPIRABLE:
{
SetupExpirableKeepAwake(settings.Properties.ExpirationDateTime, settings.Properties.KeepDisplayOn);
break;
}
default:
{
string? errorMessage = "Unknown mode of operation. Check config file.";
Logger.LogError(errorMessage);
break;
}
}
TrayHelper.SetTray(Core.Constants.FullAppName, settings, _startedFromPowerToys);
}
else
{
string? errorMessage = "Settings are null.";
Logger.LogError(errorMessage);
default:
Logger.LogError("Unknown mode of operation. Check config file.");
break;
}
TrayHelper.SetTray(settings, _startedFromPowerToys);
}
catch (Exception ex)
{
string? errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
Logger.LogError(errorMessage);
Logger.LogError($"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}");
}
}
private static void SetupNoKeepAwake()
{
Logger.LogInfo($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
Manager.SetNoKeepAwake();
}
private static void SetupExpirableKeepAwake(DateTimeOffset expireAt, bool displayOn)
{
Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {displayOn}.");
Manager.SetExpirableKeepAwake(expireAt, displayOn);
}
private static void SetupTimedKeepAwake(uint time, bool displayOn)
{
Logger.LogInfo($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
Manager.SetTimedKeepAwake(time, displayOn);
}
}
}

View File

@@ -60,24 +60,6 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 1 hour.
/// </summary>
internal static string AWAKE_1_HOUR {
get {
return ResourceManager.GetString("AWAKE_1_HOUR", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 minute.
/// </summary>
internal static string AWAKE_1_MINUTE {
get {
return ResourceManager.GetString("AWAKE_1_MINUTE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checked.
/// </summary>
@@ -87,6 +69,51 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Specifies whether Awake will be using the PowerToys configuration file for managing the state..
/// </summary>
internal static string AWAKE_CMD_HELP_CONFIG_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_HELP_CONFIG_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Determines whether the display should be kept awake..
/// </summary>
internal static string AWAKE_CMD_HELP_DISPLAY_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_HELP_DISPLAY_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Determines the end date and time when Awake will back off and let the system manage the current sleep and display state..
/// </summary>
internal static string AWAKE_CMD_HELP_EXPIRE_AT_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_HELP_EXPIRE_AT_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bind the execution of Awake to another process. When the process ends, the system will resume managing the current sleep and display state..
/// </summary>
internal static string AWAKE_CMD_HELP_PID_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_HELP_PID_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Determines the interval (in seconds) during which the computer is kept awake..
/// </summary>
internal static string AWAKE_CMD_HELP_TIME_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_HELP_TIME_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exit.
/// </summary>
@@ -96,6 +123,33 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Terminating from process binding hook..
/// </summary>
internal static string AWAKE_EXIT_BINDING_HOOK_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_BINDING_HOOK_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exiting from the internal termination handler..
/// </summary>
internal static string AWAKE_EXIT_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Received a signal to end the process. Making sure we quit....
/// </summary>
internal static string AWAKE_EXIT_SIGNAL_MESSAGE {
get {
return ResourceManager.GetString("AWAKE_EXIT_SIGNAL_MESSAGE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} hours.
/// </summary>
@@ -141,6 +195,42 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to d.
/// </summary>
internal static string AWAKE_LABEL_DAYS {
get {
return ResourceManager.GetString("AWAKE_LABEL_DAYS", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to h.
/// </summary>
internal static string AWAKE_LABEL_HOURS {
get {
return ResourceManager.GetString("AWAKE_LABEL_HOURS", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to m.
/// </summary>
internal static string AWAKE_LABEL_MINUTES {
get {
return ResourceManager.GetString("AWAKE_LABEL_MINUTES", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to s.
/// </summary>
internal static string AWAKE_LABEL_SECONDS {
get {
return ResourceManager.GetString("AWAKE_LABEL_SECONDS", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} minutes.
/// </summary>
@@ -159,6 +249,42 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Expiring.
/// </summary>
internal static string AWAKE_TRAY_TEXT_EXPIRATION {
get {
return ResourceManager.GetString("AWAKE_TRAY_TEXT_EXPIRATION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Indefinite.
/// </summary>
internal static string AWAKE_TRAY_TEXT_INDEFINITE {
get {
return ResourceManager.GetString("AWAKE_TRAY_TEXT_INDEFINITE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Passive.
/// </summary>
internal static string AWAKE_TRAY_TEXT_OFF {
get {
return ResourceManager.GetString("AWAKE_TRAY_TEXT_OFF", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Interval.
/// </summary>
internal static string AWAKE_TRAY_TEXT_TIMED {
get {
return ResourceManager.GetString("AWAKE_TRAY_TEXT_TIMED", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unchecked.
/// </summary>

View File

@@ -123,9 +123,6 @@
<data name="AWAKE_EXIT" xml:space="preserve">
<value>Exit</value>
</data>
<data name="AWAKE_1_HOUR" xml:space="preserve">
<value>1 hour</value>
</data>
<data name="AWAKE_HOURS" xml:space="preserve">
<value>{0} hours</value>
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
@@ -145,9 +142,6 @@
<value>Keep awake until expiration date and time</value>
<comment>Keep the system awake until expiration date and time</comment>
</data>
<data name="AWAKE_1_MINUTE" xml:space="preserve">
<value>1 minute</value>
</data>
<data name="AWAKE_MINUTES" xml:space="preserve">
<value>{0} minutes</value>
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
@@ -159,4 +153,56 @@
<data name="AWAKE_UNCHECKED" xml:space="preserve">
<value>Unchecked</value>
</data>
<data name="AWAKE_CMD_HELP_CONFIG_OPTION" xml:space="preserve">
<value>Specifies whether Awake will be using the PowerToys configuration file for managing the state.</value>
</data>
<data name="AWAKE_CMD_HELP_DISPLAY_OPTION" xml:space="preserve">
<value>Determines whether the display should be kept awake.</value>
</data>
<data name="AWAKE_CMD_HELP_EXPIRE_AT_OPTION" xml:space="preserve">
<value>Determines the end date and time when Awake will back off and let the system manage the current sleep and display state.</value>
</data>
<data name="AWAKE_CMD_HELP_PID_OPTION" xml:space="preserve">
<value>Bind the execution of Awake to another process. When the process ends, the system will resume managing the current sleep and display state.</value>
</data>
<data name="AWAKE_CMD_HELP_TIME_OPTION" xml:space="preserve">
<value>Determines the interval (in seconds) during which the computer is kept awake.</value>
</data>
<data name="AWAKE_EXIT_BINDING_HOOK_MESSAGE" xml:space="preserve">
<value>Terminating from process binding hook.</value>
</data>
<data name="AWAKE_EXIT_MESSAGE" xml:space="preserve">
<value>Exiting from the internal termination handler.</value>
</data>
<data name="AWAKE_EXIT_SIGNAL_MESSAGE" xml:space="preserve">
<value>Received a signal to end the process. Making sure we quit...</value>
</data>
<data name="AWAKE_TRAY_TEXT_EXPIRATION" xml:space="preserve">
<value>Expiring</value>
</data>
<data name="AWAKE_TRAY_TEXT_INDEFINITE" xml:space="preserve">
<value>Indefinite</value>
</data>
<data name="AWAKE_TRAY_TEXT_OFF" xml:space="preserve">
<value>Passive</value>
</data>
<data name="AWAKE_TRAY_TEXT_TIMED" xml:space="preserve">
<value>Interval</value>
</data>
<data name="AWAKE_LABEL_DAYS" xml:space="preserve">
<value>d</value>
<comment>Used to display number of days in the system tray tooltip.</comment>
</data>
<data name="AWAKE_LABEL_HOURS" xml:space="preserve">
<value>h</value>
<comment>Used to display number of hours in the system tray tooltip.</comment>
</data>
<data name="AWAKE_LABEL_MINUTES" xml:space="preserve">
<value>m</value>
<comment>Used to display number of minutes in the system tray tooltip.</comment>
</data>
<data name="AWAKE_LABEL_SECONDS" xml:space="preserve">
<value>s</value>
<comment>Used to display number of seconds in the system tray tooltip.</comment>
</data>
</root>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@@ -48,6 +48,7 @@
AutomationProperties.Name="{x:Static p:Resources.Color_History}"
ItemsSource="{Binding ColorsHistory}"
KeyboardNavigation.DirectionalNavigation="Contained"
MouseWheel="HistoryColors_OnMouseWheelScroll"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectedIndex="{Binding SelectedColorIndex}"
SelectionMode="Extended"

View File

@@ -5,6 +5,7 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ColorPicker.Helpers;
using ColorPicker.ViewModels;
@@ -42,6 +43,60 @@ namespace ColorPicker.Views
};
}
/// <summary>
/// Handles the mouse wheel scroll event on the HistoryColors ListView.
/// Scrolls the ListView horizontally based on the direction of the mouse wheel scroll.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The mouse wheel event data.</param>
private void HistoryColors_OnMouseWheelScroll(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scrollViewer = FindVisualChild<ScrollViewer>(HistoryColors);
if (scrollViewer != null)
{
if (e.Delta > 0)
{
scrollViewer.LineLeft();
}
else
{
scrollViewer.LineRight();
}
e.Handled = true;
}
}
/// <summary>
/// Finds a visual child of a specified type within a given dependency object.
/// </summary>
/// <typeparam name="T">The type of the child element to find.</typeparam>
/// <param name="obj">The parent dependency object.</param>
/// <returns>The first child element of the specified type, or null if no such element is found.</returns>
private static T FindVisualChild<T>(DependencyObject obj)
where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T tChild)
{
return tChild;
}
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
/*
private void HistoryColors_ItemClick(object sender, ItemClickEventArgs e)
{

View File

@@ -19,9 +19,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="Microsoft.Win32.SystemEvents">
<!-- This package is a dependency of System.Drawing.Common, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->

View File

@@ -21,9 +21,7 @@
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>

View File

@@ -21,9 +21,7 @@
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>

View File

@@ -15,9 +15,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
<PackageReference Include="System.CodeDom">

View File

@@ -99,7 +99,30 @@ MakeAppx.exe pack /d . /p $(OutDir)ImageResizerContextMenuPackage.msix /nv</Comm
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="Assets\ImageResizer\**" CopyToOutputDirectory="PreserveNewest" />
<None Include="Assets\ImageResizer\ImageResizer.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\LargeTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\SmallTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\SplashScreen.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\Square150x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\Square44x44Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\storelogo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\ImageResizer\Wide310x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{18b3db45-4ffe-4d01-97d6-5223feee1853}</ProjectGuid>
@@ -30,7 +31,7 @@
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup >
<PropertyGroup>
<TargetName>PowerToys.$(ProjectName)</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
@@ -75,6 +76,18 @@
<ClCompile Include="Settings.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -42,4 +42,7 @@
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>

View File

@@ -51,10 +51,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.CodeDom">
<!-- This package is a dependency of System.Management, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->

View File

@@ -27,7 +27,7 @@
<ClCompile>
<PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;..\..\..\;..\..\..\common\telemetry;..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>%(AdditionalOptions) /Zm200</AdditionalOptions>
<AdditionalOptions>%(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Lib>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

View File

@@ -6,9 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>

View File

@@ -179,6 +179,22 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter
}
}
/// <summary>
/// Converts spelling "kph" to "km/h"
/// </summary>
public static void KPHHandler(ref string[] split)
{
split[1] = split[1].Replace("cph", "cm/h", System.StringComparison.CurrentCultureIgnoreCase);
split[1] = split[1].Replace("kph", "km/h", System.StringComparison.CurrentCultureIgnoreCase);
split[1] = split[1].Replace("kmph", "km/h", System.StringComparison.CurrentCultureIgnoreCase);
split[1] = split[1].Replace("cmph", "cm/h", System.StringComparison.CurrentCultureIgnoreCase);
split[3] = split[3].Replace("cph", "cm/h", System.StringComparison.CurrentCultureIgnoreCase);
split[3] = split[3].Replace("kph", "km/h", System.StringComparison.CurrentCultureIgnoreCase);
split[3] = split[3].Replace("kmph", "km/h", System.StringComparison.CurrentCultureIgnoreCase);
split[3] = split[3].Replace("cmph", "cm/h", System.StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Converts spelling "metre" to "meter", also for centimetre and other variants
/// </summary>
@@ -239,6 +255,7 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter
InputInterpreter.DegreePrefixer(ref split);
InputInterpreter.MetreToMeter(ref split);
InputInterpreter.FeetToFt(ref split);
InputInterpreter.KPHHandler(ref split);
InputInterpreter.GallonHandler(ref split, CultureInfo.CurrentCulture);
if (!double.TryParse(split[0], out double value))
{

View File

@@ -7,9 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Community.PowerToys.Run.Plugin.ValueGenerator
{
internal sealed class GeneratorData
{
public string Keyword { get; set; }
public string Description { get; set; }
public string Example { get; set; }
}
}

View File

@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Community.PowerToys.Run.Plugin.ValueGenerator.Properties;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Helper
{
/// <summary>
/// Helper class to easier work with queries
/// </summary>
internal static class QueryHelper
{
/// <summary>
/// a list of all value generator descriptions
/// </summary>
private static readonly string GeneratorDescriptionUuid = Resources.generator_description_uuid;
private static readonly string GeneratorDescriptionUuidv1 = Resources.generator_description_uuidv1;
private static readonly string GeneratorDescriptionUuidv3 = Resources.generator_description_uuidv3;
private static readonly string GeneratorDescriptionUuidv4 = Resources.generator_description_uuidv4;
private static readonly string GeneratorDescriptionUuidv5 = Resources.generator_description_uuidv5;
private static readonly string GeneratorDescriptionHash = Resources.generator_description_hash;
private static readonly string GeneratorDescriptionBase64 = Resources.generator_description_base64;
private static readonly string GeneratorDescriptionBase64d = Resources.generator_description_base64d;
private static readonly string GeneratorDescriptionUrl = Resources.generator_description_url;
private static readonly string GeneratorDescriptionUrld = Resources.generator_description_urld;
private static readonly string GeneratorDescriptionEscData = Resources.generator_description_esc_data;
private static readonly string GeneratorDescriptionUescData = Resources.generator_description_uesc_data;
private static readonly string GeneratorDescriptionEscHex = Resources.generator_description_esc_hex;
private static readonly string GeneratorDescriptionUescHex = Resources.generator_description_uesc_hex;
private static readonly string GeneratorDescriptionYourInput = Resources.generator_description_your_input;
private static readonly string GeneratorExample = Resources.generator_example;
private static readonly string Or = Resources.or;
private static string GetStringFormat(string value, string arg)
{
return string.Format(CultureInfo.CurrentCulture, value, arg);
}
private static string GetStringFormat(string value)
{
return string.Format(CultureInfo.CurrentCulture, value);
}
internal static string GetResultTitle(GeneratorData generatorData)
{
return $"{generatorData.Keyword} - {generatorData.Description}";
}
internal static string GetResultSubtitle(GeneratorData generatorData)
{
return GetStringFormat(GeneratorExample, generatorData.Example);
}
/// <summary>
/// A list that contain all of the value generators and its descriptions
/// </summary>
internal static readonly List<GeneratorData> GeneratorDataList =
[
new()
{
Keyword = "uuid",
Description = GetStringFormat(GeneratorDescriptionUuid),
Example = $"uuid {GetStringFormat(Or)} guid",
},
new()
{
Keyword = "uuidv1",
Description = GetStringFormat(GeneratorDescriptionUuidv1),
Example = $"uuidv1 {GetStringFormat(Or)} uuid1",
},
new()
{
Keyword = "uuidv3",
Description = GetStringFormat(GeneratorDescriptionUuidv3),
Example = $"uuidv3 ns:<DNS, URL, OID, {GetStringFormat(Or)} X500> <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "uuidv4",
Description = GetStringFormat(GeneratorDescriptionUuidv4),
Example = $"uuidv4 {GetStringFormat(Or)} uuid4",
},
new()
{
Keyword = "uuidv5",
Description = GetStringFormat(GeneratorDescriptionUuidv5),
Example = $"uuidv5 ns:<DNS, URL, OID, {GetStringFormat(Or)} X500> <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "md5",
Description = GetStringFormat(GeneratorDescriptionHash, "MD5"),
Example = $"md5 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "sha1",
Description = GetStringFormat(GeneratorDescriptionHash, "SHA1"),
Example = $"sha1 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "sha256",
Description = GetStringFormat(GeneratorDescriptionHash, "SHA256"),
Example = $"sha256 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "sha384",
Description = GetStringFormat(GeneratorDescriptionHash, "SHA384"),
Example = $"sha384 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "sha512",
Description = GetStringFormat(GeneratorDescriptionHash, "SHA512"),
Example = $"sha512 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "base64",
Description = GetStringFormat(GeneratorDescriptionBase64),
Example = $"base64 <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "base64d",
Description = GetStringFormat(GeneratorDescriptionBase64d),
Example = $"base64d <{GetStringFormat(GeneratorDescriptionYourInput)}>",
},
new()
{
Keyword = "url",
Description = GetStringFormat(GeneratorDescriptionUrl),
Example = "url https://bing.com/?q=My Test query",
},
new()
{
Keyword = "urld",
Description = GetStringFormat(GeneratorDescriptionUrld),
Example = "urld https://bing.com/?q=My+Test+query",
},
new()
{
Keyword = "esc:data",
Description = GetStringFormat(GeneratorDescriptionEscData),
Example = "esc:data C:\\Program Files\\PowerToys\\PowerToys.exe",
},
new()
{
Keyword = "uesc:data",
Description = GetStringFormat(GeneratorDescriptionUescData),
Example = "uesc:data C%3A%5CProgram%20Files%5CPowerToys%5CPowerToys.exe",
},
new()
{
Keyword = "esc:hex",
Description = GetStringFormat(GeneratorDescriptionEscHex),
Example = "esc:hex z",
},
new()
{
Keyword = "uesc:hex",
Description = GetStringFormat(GeneratorDescriptionUescHex),
Example = "uesc:hex %7A",
},
];
}
}

View File

@@ -64,7 +64,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
algorithmName = HashAlgorithmName.SHA512;
break;
default:
throw new ArgumentException("Unknown SHA variant. Supported variants: SHA1, SHA256, SHA384, SHA512");
throw new FormatException("Unknown SHA variant. Supported variants: SHA1, SHA256, SHA384, SHA512");
}
if (content == string.Empty)
@@ -93,7 +93,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
if (!int.TryParse(versionQuery, null, out version))
{
throw new ArgumentException("Could not determine requested GUID version");
throw new FormatException("Could not determine requested GUID version. Supported versions are 1, 3, 4 and 5");
}
}
@@ -103,7 +103,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
if (sParameters.Length != 2)
{
throw new ArgumentException("GUID versions 3 and 5 require 2 parameters - a namespace GUID and a name");
throw new ArgumentException($"GUID version {version} require 2 parameters - a namespace GUID and a name.\nExample: uuidv{version} ns:<DNS, URL, OID, or X500> <your input>");
}
string namespaceParameter = sParameters[0];

View File

@@ -7,8 +7,10 @@ using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using Community.PowerToys.Run.Plugin.ValueGenerator.Helper;
using Community.PowerToys.Run.Plugin.ValueGenerator.Properties;
using ManagedCommon;
using Wox.Infrastructure;
using Wox.Plugin;
using Wox.Plugin.Logger;
@@ -59,6 +61,19 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
}
}
private static string GetIcoPath(bool isWarning = false)
{
var imageName = isWarning ? "Warning" : "ValueGenerator";
if (_isLightTheme)
{
return $"Images/{imageName}.light.png";
}
else
{
return $"Images/{imageName}.dark.png";
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
@@ -90,6 +105,11 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
ArgumentNullException.ThrowIfNull(query);
var results = new List<Result>();
if (string.IsNullOrWhiteSpace(query?.Search) && !string.IsNullOrEmpty(query?.ActionKeyword))
{
return GetSuggestionResults(query, results);
}
try
{
IComputeRequest computeRequest = _inputParser.ParseInput(query);
@@ -110,7 +130,33 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
}
catch (FormatException e)
{
Log.Debug(GetTranslatedPluginTitle() + ": " + e.Message, GetType());
Log.Debug(GetTranslatedPluginTitle() + ": " + e.Message, GetType());
if (!string.IsNullOrEmpty(query.ActionKeyword))
{
return GetSuggestionFuzzyResults(query, results);
}
}
return results;
}
private List<Result> GetSuggestionResults(Query query, List<Result> results)
{
foreach (var generatorData in QueryHelper.GeneratorDataList)
{
results.Add(new Result
{
Title = QueryHelper.GetResultTitle(generatorData),
SubTitle = QueryHelper.GetResultSubtitle(generatorData),
IcoPath = GetIcoPath(),
ToolTipData = new ToolTipData(QueryHelper.GetResultTitle(generatorData), QueryHelper.GetResultSubtitle(generatorData)),
QueryTextDisplay = generatorData.Keyword + " ",
Action = c =>
{
_context.API.ChangeQuery($"{query.ActionKeyword} {generatorData.Keyword} ", true);
return false;
},
});
}
return results;
@@ -124,7 +170,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
{
ContextData = request.Result,
Title = request.ResultToString(),
IcoPath = _isLightTheme ? "Images/ValueGenerator.light.png" : "Images/ValueGenerator.dark.png",
IcoPath = GetIcoPath(),
Score = 300,
SubTitle = request.Description,
Action = c =>
@@ -150,13 +196,42 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
};
}
private List<Result> GetSuggestionFuzzyResults(Query query, List<Result> results)
{
foreach (var generatorData in QueryHelper.GeneratorDataList)
{
var matchScore = StringMatcher.FuzzySearch(query.Search.Trim(), generatorData.Keyword).Score;
if (matchScore > 0)
{
results.Add(new Result
{
Title = QueryHelper.GetResultTitle(generatorData),
SubTitle = QueryHelper.GetResultSubtitle(generatorData),
IcoPath = GetIcoPath(),
Score = matchScore,
ToolTipData = new ToolTipData(QueryHelper.GetResultTitle(generatorData), QueryHelper.GetResultSubtitle(generatorData)),
QueryTextDisplay = generatorData.Keyword + " ",
Action = c =>
{
_context.API.ChangeQuery($"{query.ActionKeyword} {generatorData.Keyword} ", true);
return false;
},
});
}
}
return results;
}
private Result GetErrorResult(string errorMessage)
{
return new Result
{
Title = Resources.error_title,
SubTitle = errorMessage,
IcoPath = _isLightTheme ? "Images/Warning.light.png" : "Images/Warning.dark.png",
ToolTipData = new ToolTipData(Resources.error_title, errorMessage),
IcoPath = GetIcoPath(isWarning: true),
Action = _ => { return true; },
};
}

View File

@@ -87,6 +87,159 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Encode a string with Base64.
/// </summary>
public static string generator_description_base64 {
get {
return ResourceManager.GetString("generator_description_base64", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Decode a string with Base64.
/// </summary>
public static string generator_description_base64d {
get {
return ResourceManager.GetString("generator_description_base64d", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Escape a data string.
/// </summary>
public static string generator_description_esc_data {
get {
return ResourceManager.GetString("generator_description_esc_data", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Escape a single hex character.
/// </summary>
public static string generator_description_esc_hex {
get {
return ResourceManager.GetString("generator_description_esc_hex", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hash a string with {0}.
/// </summary>
public static string generator_description_hash {
get {
return ResourceManager.GetString("generator_description_hash", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unescape a data string.
/// </summary>
public static string generator_description_uesc_data {
get {
return ResourceManager.GetString("generator_description_uesc_data", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unescape a single hex character.
/// </summary>
public static string generator_description_uesc_hex {
get {
return ResourceManager.GetString("generator_description_uesc_hex", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Encode a URL.
/// </summary>
public static string generator_description_url {
get {
return ResourceManager.GetString("generator_description_url", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Decode a URL.
/// </summary>
public static string generator_description_urld {
get {
return ResourceManager.GetString("generator_description_urld", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate a random UUID.
/// </summary>
public static string generator_description_uuid {
get {
return ResourceManager.GetString("generator_description_uuid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate a version 1: Time based UUID.
/// </summary>
public static string generator_description_uuidv1 {
get {
return ResourceManager.GetString("generator_description_uuidv1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate a version 3 (MD5): Namespace and name based UUID.
/// </summary>
public static string generator_description_uuidv3 {
get {
return ResourceManager.GetString("generator_description_uuidv3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate a version 4: Randomly generated UUID.
/// </summary>
public static string generator_description_uuidv4 {
get {
return ResourceManager.GetString("generator_description_uuidv4", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate a version 5 (SHA1): Namespace and name based UUID.
/// </summary>
public static string generator_description_uuidv5 {
get {
return ResourceManager.GetString("generator_description_uuidv5", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to your input.
/// </summary>
public static string generator_description_your_input {
get {
return ResourceManager.GetString("generator_description_your_input", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Example: {0}.
/// </summary>
public static string generator_example {
get {
return ResourceManager.GetString("generator_example", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to or.
/// </summary>
public static string or {
get {
return ResourceManager.GetString("or", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Calculates hashes and generate values.
/// </summary>

View File

@@ -126,6 +126,60 @@
<data name="error_title" xml:space="preserve">
<value>Value Generator Error</value>
</data>
<data name="generator_description_base64" xml:space="preserve">
<value>Encode a string with Base64</value>
</data>
<data name="generator_description_base64d" xml:space="preserve">
<value>Decode a string with Base64</value>
</data>
<data name="generator_description_esc_data" xml:space="preserve">
<value>Escape a data string</value>
</data>
<data name="generator_description_esc_hex" xml:space="preserve">
<value>Escape a single hex character</value>
</data>
<data name="generator_description_hash" xml:space="preserve">
<value>Hash a string with {0}</value>
</data>
<data name="generator_description_uesc_data" xml:space="preserve">
<value>Unescape a data string</value>
</data>
<data name="generator_description_uesc_hex" xml:space="preserve">
<value>Unescape a single hex character</value>
</data>
<data name="generator_description_url" xml:space="preserve">
<value>Encode a URL</value>
</data>
<data name="generator_description_urld" xml:space="preserve">
<value>Decode a URL</value>
</data>
<data name="generator_description_uuid" xml:space="preserve">
<value>Generate a random UUID</value>
</data>
<data name="generator_description_uuidv1" xml:space="preserve">
<value>Generate a version 1: Time based UUID</value>
</data>
<data name="generator_description_uuidv3" xml:space="preserve">
<value>Generate a version 3 (MD5): Namespace and name based UUID</value>
</data>
<data name="generator_description_uuidv4" xml:space="preserve">
<value>Generate a version 4: Randomly generated UUID</value>
</data>
<data name="generator_description_uuidv5" xml:space="preserve">
<value>Generate a version 5 (SHA1): Namespace and name based UUID</value>
</data>
<data name="generator_description_your_input" xml:space="preserve">
<value>your input</value>
<comment>Usage example: "md5 &lt;your input&gt;"</comment>
</data>
<data name="generator_example" xml:space="preserve">
<value>Example: {0}</value>
<comment>The arg following is the usage example</comment>
</data>
<data name="or" xml:space="preserve">
<value>or</value>
<comment>Used to indicate alternatives or options available</comment>
</data>
<data name="plugin_description" xml:space="preserve">
<value>Calculates hashes and generate values</value>
</data>

View File

@@ -9,9 +9,7 @@
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
</ItemGroup>

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