Compare commits

...

11 Commits

Author SHA1 Message Date
Niels Laute
0b646dfd61 Merge branch 'microsoft:main' into mikehall-ms/wrap-investigate 2025-11-13 17:02:36 +01:00
Mike Hall
6ef03d4a2d update coordinate mapping 2025-11-13 15:04:09 +00:00
Niels Laute
483e773299 [CursorWRap] Revert the shortcut removal (#43537)
See title
2025-11-13 22:37:13 +08:00
Niels Laute
dd4c7ba57e [Advanced Paste] Localization (#43536)
More strings to loc, and re-ordering a few settings.
2025-11-13 22:36:35 +08:00
dependabot[bot]
29534601be Build(deps): Bump actions/setup-dotnet from 4 to 5 (#41693)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet)
from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-dotnet/releases">actions/setup-dotnet's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<h3>Breaking Changes</h3>
<ul>
<li>Upgrade to Node.js 24 and modernize async usage by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/654">actions/setup-dotnet#654</a></li>
</ul>
<p>Make sure your runner is updated to this version or newer to use this
release. v2.327.1 <a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<h3>Dependency Updates</h3>
<ul>
<li>Upgrade <code>@​action/cache</code> from 4.0.2 to 4.0.3 by <a
href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a>
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/622">actions/setup-dotnet#622</a></li>
<li>Upgrade husky from 8.0.3 to 9.1.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/591">actions/setup-dotnet#591</a></li>
<li>Upgrade <code>@​actions/glob</code> from 0.4.0 to 0.5.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/594">actions/setup-dotnet#594</a></li>
<li>Upgrade eslint-config-prettier from 9.1.0 to 10.1.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/639">actions/setup-dotnet#639</a></li>
<li>Upgrade undici from 5.28.5 to 5.29.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/641">actions/setup-dotnet#641</a></li>
<li>Upgrade form-data to bring in fix for critical vulnerability by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-dotnet/pull/652">actions/setup-dotnet#652</a></li>
<li>Upgrade actions/checkout from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/662">actions/setup-dotnet#662</a></li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Remove Support for older .NET Versions and Update installers scripts
by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-dotnet/pull/647">actions/setup-dotnet#647</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/647">actions/setup-dotnet#647</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/654">actions/setup-dotnet#654</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-dotnet/compare/v4...v5.0.0">https://github.com/actions/setup-dotnet/compare/v4...v5.0.0</a></p>
<h2>v4.3.1</h2>
<h2>What's Changed</h2>
<ul>
<li><code>v4</code> - Remove <code>azureedge.net</code> fallback logic
and update install scripts by <a
href="https://github.com/zaataylor"><code>@​zaataylor</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/572">actions/setup-dotnet#572</a>
As outlined in<a
href="https://devblogs.microsoft.com/dotnet/critical-dotnet-install-links-are-changing/#call-to-action">
Critical .NET Install Links Are Changing</a>, remove the storage account
fallback logic added for v4 in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/566">actions/setup-dotnet#566</a>
and update the install scripts accordingly.
<strong>Related issue</strong>: <a
href="https://redirect.github.com/dotnet/install-scripts/issues/559">dotnet/install-scripts#559</a></li>
<li>upgrade <code>@​actions/cache</code> to 4.0.2 by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/615">actions/setup-dotnet#615</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-dotnet/compare/v4...v4.3.1">https://github.com/actions/setup-dotnet/compare/v4...v4.3.1</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>README update - add permissions section by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/587">actions/setup-dotnet#587</a></li>
<li>Configure Dependabot settings by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/585">actions/setup-dotnet#585</a></li>
<li>Upgrade <strong>cache</strong> from 3.2.4 to 4.0.0 by <a
href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a>
in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/586">actions/setup-dotnet#586</a></li>
<li>Upgrade <strong>actions/publish-immutable-action</strong> from 0.0.3
to 0.0.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/590">actions/setup-dotnet#590</a></li>
<li>Upgrade <strong><code>@​actions/http-client</code></strong> from
2.2.1 to 2.2.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/592">actions/setup-dotnet#592</a></li>
<li>Upgrade <strong>undici</strong> from 5.28.4 to 5.28.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/596">actions/setup-dotnet#596</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/benwells"><code>@​benwells</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/587">actions/setup-dotnet#587</a></li>
<li><a
href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-dotnet/pull/586">actions/setup-dotnet#586</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d4c94342e5"><code>d4c9434</code></a>
Update to Node.js 24 and modernize async usage (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/654">#654</a>)</li>
<li><a
href="5c125af7da"><code>5c125af</code></a>
Bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/662">#662</a>)</li>
<li><a
href="87c6e11776"><code>87c6e11</code></a>
Bumps form-data (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/652">#652</a>)</li>
<li><a
href="06a5327ecf"><code>06a5327</code></a>
Bump undici from 5.28.5 to 5.29.0 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/641">#641</a>)</li>
<li><a
href="e8e5b8203e"><code>e8e5b82</code></a>
Bump eslint-config-prettier from 9.1.0 to 10.1.5 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/639">#639</a>)</li>
<li><a
href="bf4cd79173"><code>bf4cd79</code></a>
Bump <code>@​actions/glob</code> from 0.4.0 to 0.5.0 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/594">#594</a>)</li>
<li><a
href="4ddad1c881"><code>4ddad1c</code></a>
Bump husky from 8.0.3 to 9.1.7 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/591">#591</a>)</li>
<li><a
href="0f55b457d2"><code>0f55b45</code></a>
removes end-of-line dotnet versions (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/647">#647</a>)</li>
<li><a
href="267870a9c4"><code>267870a</code></a>
upgrade actions/cache to 4.0.3 (<a
href="https://redirect.github.com/actions/setup-dotnet/issues/622">#622</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/setup-dotnet/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-dotnet&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 06:08:12 -06:00
Guilherme
74225b01ce [CmdPal] Enhance ToggleSetting's Label and Description layout (#43472)
<!-- 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
This PR changes the positioning of Label and Description from
ToggleSetting, making it more similar to
`CheckBoxWithDescriptionControl` used in Settings.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #39283 
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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
I initially tried solving this by using a Regex on the card JSON to
replace all `Input.Toggle` elements with the new `ColumnSet` structure.
However, since `ToggleSetting.cs` already serves as a wrapper for
creating an Input.Toggle, I decided it was more effective to update the
declaration inside the `ToDictionary()` method directly.

### This is the result:

<img width="797" height="531" alt="Screenshot 2025-11-11 120426"
src="https://github.com/user-attachments/assets/2ae5d15d-699e-45ef-ab52-afd653d82111"
/>


<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-11-13 05:47:05 -06:00
Kai Tao
2f001e8150 Advanced paste: Tweak Foundry Local Displayed Model and start server if server is turned on when using AP (#43529)
<!-- 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. Foundry local model name should not prefixed by fl://
2. If foundry service is shutdown, we should not just fail it, we should
start it then call FL to make availability better.

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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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
Verified locally:
1. Manually disable foundry local service, then run AP with foundry
local, it can return result instead of direct failure.
2. 
<img width="659" height="294" alt="image"
src="https://github.com/user-attachments/assets/113da451-7131-4ce7-ae82-0ccf772ad8aa"
/>
<img width="988" height="192" alt="image"
src="https://github.com/user-attachments/assets/aa3650ba-668a-40c4-ad8a-303e09000dd4"
/>
![Uploading image.png…]()
2025-11-13 17:28:23 +08:00
Shawn Yuan
a983a773f3 Fix advanced paste migration issue (#43524)
<!-- 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
This pull request refactors how legacy AI enablement settings are
migrated and simplifies the handling of legacy properties in the
settings UI. The main improvements are in the migration logic and the
removal of legacy extension data handling.


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

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
2025-11-13 16:55:09 +08:00
leileizhang
02eb55e3b9 Refine AdvancedPaste Terms UI and add default system prompt for Advanced AI (#43526)
<!-- 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. when no terms and Privacy links don't show the infobar
<img width="349" height="127" alt="image"
src="https://github.com/user-attachments/assets/b7eeeabf-365f-45f6-adb4-56335c14e8ad"
/>
<img width="410" height="217" alt="image"
src="https://github.com/user-attachments/assets/15e053c4-738d-4bb4-9544-24bdf8a5a584"
/>


2. add add default system prompt for Advanced AI

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

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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
2025-11-13 16:51:57 +08:00
Dave Rayment
afaf904016 [General] Update PR template with multiple issues note (#43442)
Added a comment noting that multiple issues should be placed on separate
lines, as per GitHub recommendations.

(When on the same line, issue references after the first are expanded
from their shortcut form, but are _not_ auto-closed once the PR is
merged.)

<!-- 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 the 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
2025-11-13 09:18:31 +01:00
Jiří Polášek
4425e3ad28 CmdPal: Change captitalization of "Last position" item (#43518)
## Summary of the Pull Request

This PR updates the capitalization of the drop-down item from "Last
Position" to "Last position" to align with the guidelines.

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

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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
2025-11-13 09:17:41 +01:00
21 changed files with 327 additions and 286 deletions

View File

@@ -5,6 +5,7 @@
## PR Checklist
- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) -->
- [ ] **Communication:** I've discussed this with core contributors already. If the 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

View File

@@ -40,7 +40,7 @@ jobs:
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: '9.0.x'

View File

@@ -205,4 +205,12 @@ internal sealed class FoundryClient
return false;
}
}
public async Task EnsureRunning()
{
if (!_foundryManager.IsServiceRunning)
{
await _foundryManager.StartServiceAsync();
}
}
}

View File

@@ -13,7 +13,7 @@ namespace LanguageModelProvider;
public sealed class FoundryLocalModelProvider : ILanguageModelProvider
{
private IEnumerable<ModelDetails>? _downloadedModels;
private FoundryClient? _foundryManager;
private FoundryClient? _foundryClient;
private string? _serviceUrl;
public static FoundryLocalModelProvider Instance { get; } = new();
@@ -22,13 +22,11 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
public string ProviderDescription => "The model will run locally via Foundry Local";
public string UrlPrefix => "fl://";
public IChatClient? GetIChatClient(string url)
public IChatClient? GetIChatClient(string modelId)
{
try
{
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {url}");
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}");
InitializeAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
@@ -37,26 +35,22 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
return null;
}
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryManager == null)
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryClient == null)
{
Logger.LogError("[FoundryLocal] Service URL or manager is null");
return null;
}
// Extract model ID from URL (format: fl://modelname)
var modelId = url.Replace(UrlPrefix, string.Empty).Trim('/');
if (string.IsNullOrWhiteSpace(modelId))
{
Logger.LogError("[FoundryLocal] Model ID is empty after extraction");
return null;
}
Logger.LogInfo($"[FoundryLocal] Extracted model ID: {modelId}");
// Ensure the model is loaded before returning chat client
try
{
var isLoaded = _foundryManager.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
var isLoaded = _foundryClient.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
if (!isLoaded)
{
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
@@ -72,7 +66,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
}
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
var baseUri = _foundryManager.GetServiceUri();
var baseUri = _foundryClient.GetServiceUri();
if (baseUri == null)
{
Logger.LogError("[FoundryLocal] Service URI is null");
@@ -133,24 +127,25 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
private async Task InitializeAsync(CancellationToken cancelationToken = default)
{
if (_foundryManager != null && _downloadedModels != null && _downloadedModels.Any())
if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any())
{
await _foundryClient.EnsureRunning().ConfigureAwait(false);
return;
}
Logger.LogInfo("[FoundryLocal] Initializing provider");
_foundryManager ??= await FoundryClient.CreateAsync();
_foundryClient ??= await FoundryClient.CreateAsync();
if (_foundryManager == null)
if (_foundryClient == null)
{
Logger.LogError("[FoundryLocal] Failed to create Foundry client");
return;
}
_serviceUrl ??= await _foundryManager.GetServiceUrl();
_serviceUrl ??= await _foundryClient.GetServiceUrl();
Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
var cachedModels = await _foundryManager.ListCachedModels();
var cachedModels = await _foundryClient.ListCachedModels();
Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models");
List<ModelDetails> downloadedModels = [];
@@ -162,7 +157,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
{
Id = $"fl-{model.Name}",
Name = model.Name,
Url = $"{UrlPrefix}{model.Name}",
Url = $"fl://{model.Name}",
Description = $"{model.Name} running locally with Foundry Local",
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
SupportedOnQualcomm = true,
@@ -178,7 +173,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
{
Logger.LogInfo("[FoundryLocal] Checking availability");
await InitializeAsync();
var available = _foundryManager != null;
var available = _foundryClient != null;
Logger.LogInfo($"[FoundryLocal] Available: {available}");
return available;
}

View File

@@ -10,13 +10,11 @@ public interface ILanguageModelProvider
{
string Name { get; }
string UrlPrefix { get; }
string ProviderDescription { get; }
Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default);
IChatClient? GetIChatClient(string url);
IChatClient? GetIChatClient(string modelId);
string GetIChatClientString(string url);
}

View File

@@ -1,106 +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.Collections.Concurrent;
using Microsoft.Extensions.AI;
namespace LanguageModelProvider;
public sealed class LanguageModelService
{
private readonly ConcurrentDictionary<string, ILanguageModelProvider> _providersByPrefix;
public LanguageModelService(IEnumerable<ILanguageModelProvider> providers)
{
ArgumentNullException.ThrowIfNull(providers);
_providersByPrefix = new ConcurrentDictionary<string, ILanguageModelProvider>(StringComparer.OrdinalIgnoreCase);
foreach (var provider in providers)
{
if (!string.IsNullOrWhiteSpace(provider.UrlPrefix))
{
_providersByPrefix[provider.UrlPrefix] = provider;
}
}
}
public static LanguageModelService CreateDefault()
{
return new LanguageModelService(new[]
{
FoundryLocalModelProvider.Instance,
});
}
public IReadOnlyCollection<ILanguageModelProvider> Providers => _providersByPrefix.Values.ToArray();
public bool RegisterProvider(ILanguageModelProvider provider)
{
ArgumentNullException.ThrowIfNull(provider);
if (string.IsNullOrWhiteSpace(provider.UrlPrefix))
{
throw new ArgumentException("Provider must supply a URL prefix.", nameof(provider));
}
_providersByPrefix[provider.UrlPrefix] = provider;
return true;
}
public ILanguageModelProvider? GetProviderFor(string? modelReference)
{
if (string.IsNullOrWhiteSpace(modelReference))
{
return null;
}
foreach (var provider in _providersByPrefix.Values)
{
if (modelReference.StartsWith(provider.UrlPrefix, StringComparison.OrdinalIgnoreCase))
{
return provider;
}
}
return null;
}
public async Task<IReadOnlyList<ModelDetails>> GetModelsAsync(bool refresh = false, CancellationToken cancellationToken = default)
{
List<ModelDetails> models = [];
foreach (var provider in _providersByPrefix.Values)
{
cancellationToken.ThrowIfCancellationRequested();
var providerModels = await provider.GetModelsAsync(refresh, cancellationToken).ConfigureAwait(false);
models.AddRange(providerModels);
}
return models;
}
public IChatClient? GetClient(ModelDetails model)
{
if (model is null)
{
return null;
}
var reference = !string.IsNullOrWhiteSpace(model.Url) ? model.Url : model.Id;
return GetClient(reference);
}
public IChatClient? GetClient(string? modelReference)
{
if (string.IsNullOrWhiteSpace(modelReference))
{
return null;
}
var provider = GetProviderFor(modelReference);
return provider?.GetIChatClient(modelReference);
}
}

View File

@@ -264,7 +264,8 @@
<Button
Padding="0"
VerticalAlignment="Center"
Style="{StaticResource SubtleButtonStyle}">
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind ViewModel.HasLegalLinks, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<FontIcon FontSize="12" Glyph="&#xE946;" />
<Button.Flyout>
<Flyout>

View File

@@ -213,10 +213,11 @@ namespace AdvancedPaste.Settings
RemoveLegacyOpenAICredential();
}
bool shouldEnableAI = legacyCredential is not null;
bool enabledUpdated = false;
if (!properties.IsAIEnabled && legacyCredential is not null)
if (properties.IsAIEnabled != shouldEnableAI)
{
properties.IsAIEnabled = true;
properties.IsAIEnabled = shouldEnableAI;
enabledUpdated = true;
}

View File

@@ -23,7 +23,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new FoundryLocalPasteProvider(config));
private static readonly LanguageModelService LanguageModels = LanguageModelService.CreateDefault();
private static readonly FoundryLocalModelProvider _modelProvider = FoundryLocalModelProvider.Instance;
private readonly PasteAIConfig _config;
@@ -72,11 +72,11 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
throw new PasteActionException(
"No Foundry Local model selected",
new InvalidOperationException("Model identifier is required"),
aiServiceMessage: "Please select a model in the AI provider settings. Model identifier should be in the format 'fl://model-name'.");
aiServiceMessage: "Please select a model in the AI provider settings.");
}
cancellationToken.ThrowIfCancellationRequested();
var chatClient = LanguageModels.GetClient(modelReference);
var chatClient = _modelProvider.GetIChatClient(modelReference);
if (chatClient is null)
{
throw new PasteActionException(
@@ -85,9 +85,6 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
aiServiceMessage: "The model may not be downloaded or the Foundry Local service may not be running. Please check the model status in settings.");
}
// Extract actual model ID from the URL (format: fl://modelId)
var actualModelId = modelReference.Replace("fl://", string.Empty).Trim('/');
var userMessageContent = $"""
User instructions:
{prompt}
@@ -104,7 +101,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
new(ChatRole.User, userMessageContent),
};
var chatOptions = CreateChatOptions(_config?.SystemPrompt, actualModelId);
var chatOptions = CreateChatOptions(_config?.SystemPrompt, modelReference);
progress?.Report(0.1);

View File

@@ -29,6 +29,7 @@ public abstract class KernelServiceBase(
ICustomActionTransformService customActionTransformService) : IKernelService
{
private const string PromptParameterName = "prompt";
private const string DefaultSystemPrompt = "You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task. Call function when necessary to help user finish the transformation task. You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best. The user will put in a request to format their clipboard data and you will fulfill it. Do not output anything else besides the reformatted clipboard content.";
private readonly IKernelQueryCacheService _queryCacheService = queryCacheService;
private readonly IPromptModerationService _promptModerationService = promptModerationService;
@@ -144,7 +145,8 @@ public abstract class KernelServiceBase(
ChatHistory chatHistory = [];
chatHistory.AddSystemMessage(runtimeConfig.SystemPrompt);
var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
chatHistory.AddSystemMessage(systemPrompt);
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
chatHistory.AddUserMessage(prompt);

View File

@@ -77,6 +77,7 @@ namespace AdvancedPaste.ViewModels
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
[NotifyPropertyChangedFor(nameof(HasLegalLinks))]
private bool _isAllowedByGPO;
[ObservableProperty]
@@ -222,6 +223,8 @@ namespace AdvancedPaste.ViewModels
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
public bool HasLegalLinks => HasTermsLink || HasPrivacyLink;
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
@@ -367,6 +370,7 @@ namespace AdvancedPaste.ViewModels
OnPropertyChanged(nameof(PrivacyLinkUri));
OnPropertyChanged(nameof(HasTermsLink));
OnPropertyChanged(nameof(HasPrivacyLink));
OnPropertyChanged(nameof(HasLegalLinks));
}
private void RefreshPasteFormats()

View File

@@ -428,8 +428,58 @@ private:
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
// Helper method to check if there's a monitor adjacent in coordinate space (not grid)
bool HasAdjacentMonitorInCoordinateSpace(const RECT& currentMonitorRect, int direction)
{
// direction: 0=left, 1=right, 2=top, 3=bottom
const int tolerance = 50; // Allow small gaps
for (const auto& monitor : m_monitors)
{
bool isAdjacent = false;
switch (direction)
{
case 0: // Left - check if another monitor's right edge touches/overlaps our left edge
isAdjacent = (abs(monitor.rect.right - currentMonitorRect.left) <= tolerance) &&
(monitor.rect.bottom > currentMonitorRect.top + tolerance) &&
(monitor.rect.top < currentMonitorRect.bottom - tolerance);
break;
case 1: // Right - check if another monitor's left edge touches/overlaps our right edge
isAdjacent = (abs(monitor.rect.left - currentMonitorRect.right) <= tolerance) &&
(monitor.rect.bottom > currentMonitorRect.top + tolerance) &&
(monitor.rect.top < currentMonitorRect.bottom - tolerance);
break;
case 2: // Top - check if another monitor's bottom edge touches/overlaps our top edge
isAdjacent = (abs(monitor.rect.bottom - currentMonitorRect.top) <= tolerance) &&
(monitor.rect.right > currentMonitorRect.left + tolerance) &&
(monitor.rect.left < currentMonitorRect.right - tolerance);
break;
case 3: // Bottom - check if another monitor's top edge touches/overlaps our bottom edge
isAdjacent = (abs(monitor.rect.top - currentMonitorRect.bottom) <= tolerance) &&
(monitor.rect.right > currentMonitorRect.left + tolerance) &&
(monitor.rect.left < currentMonitorRect.right - tolerance);
break;
}
if (isAdjacent)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: Found adjacent monitor in coordinate space (direction {})", direction);
#endif
return true;
}
}
return false;
}
// *** COMPLETELY REWRITTEN CURSOR WRAPPING LOGIC ***
// Implements vertical scrolling to bottom/top of vertical stack as requested
// Only wraps when there's NO adjacent monitor in the coordinate space
POINT HandleMouseMove(const POINT& currentPos)
{
POINT newPos = currentPos;
@@ -468,12 +518,22 @@ private:
// *** VERTICAL WRAPPING LOGIC - CONFIRMED WORKING ***
// Move to bottom of vertical stack when hitting top edge
// Only wrap if there's NO adjacent monitor in the coordinate space
if (currentPos.y <= currentMonitorInfo.rcMonitor.top)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: TOP EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor above in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 2))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists above (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the bottom-most monitor in the vertical stack (same column)
HMONITOR bottomMonitor = nullptr;
@@ -526,6 +586,15 @@ private:
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: BOTTOM EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor below in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 3))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists below (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the top-most monitor in the vertical stack (same column)
HMONITOR topMonitor = nullptr;
@@ -575,13 +644,22 @@ private:
// *** FIXED HORIZONTAL WRAPPING LOGIC ***
// Move to opposite end of horizontal stack when hitting left/right edge
// Only handle horizontal wrapping if we haven't already wrapped vertically
// Only wrap if there's NO adjacent monitor in the coordinate space (let Windows handle natural transitions)
if (!wrapped && currentPos.x <= currentMonitorInfo.rcMonitor.left)
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: LEFT EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor to the left in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 0))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists to the left (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the right-most monitor in the horizontal stack (same row)
HMONITOR rightMonitor = nullptr;
@@ -634,6 +712,15 @@ private:
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: RIGHT EDGE DETECTED =======");
#endif
// Check if there's an adjacent monitor to the right in coordinate space
if (HasAdjacentMonitorInCoordinateSpace(currentMonitorInfo.rcMonitor, 1))
{
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: SKIPPING WRAP - Adjacent monitor exists to the right (Windows will handle)");
#endif
return currentPos; // Let Windows handle natural cursor movement
}
// Find the left-most monitor in the horizontal stack (same row)
HMONITOR leftMonitor = nullptr;
@@ -903,45 +990,104 @@ void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors)
}
else
{
// For more than 2 monitors, use the general algorithm
RECT totalBounds = monitors[0].rect;
for (const auto& monitor : monitors)
{
totalBounds.left = min(totalBounds.left, monitor.rect.left);
totalBounds.top = min(totalBounds.top, monitor.rect.top);
totalBounds.right = max(totalBounds.right, monitor.rect.right);
totalBounds.bottom = max(totalBounds.bottom, monitor.rect.bottom);
// For more than 2 monitors, use edge-based alignment algorithm
// This ensures monitors with aligned edges (e.g., top edges at same Y) are grouped in same row
// Helper lambda to check if two ranges overlap or are adjacent (with tolerance)
auto rangesOverlapOrTouch = [](int start1, int end1, int start2, int end2, int tolerance = 50) -> bool {
// Check if ranges overlap or are within tolerance distance
return (start1 <= end2 + tolerance) && (start2 <= end1 + tolerance);
};
// Sort monitors by horizontal position (left edge) for column assignment
std::vector<const MonitorInfo*> monitorsByX;
for (const auto& monitor : monitors) {
monitorsByX.push_back(&monitor);
}
std::sort(monitorsByX.begin(), monitorsByX.end(), [](const MonitorInfo* a, const MonitorInfo* b) {
return a->rect.left < b->rect.left;
});
// Sort monitors by vertical position (top edge) for row assignment
std::vector<const MonitorInfo*> monitorsByY;
for (const auto& monitor : monitors) {
monitorsByY.push_back(&monitor);
}
std::sort(monitorsByY.begin(), monitorsByY.end(), [](const MonitorInfo* a, const MonitorInfo* b) {
return a->rect.top < b->rect.top;
});
// Assign rows based on vertical overlap - monitors that overlap vertically should be in same row
std::map<const MonitorInfo*, int> monitorToRow;
int currentRow = 0;
for (size_t i = 0; i < monitorsByY.size(); i++) {
const auto* monitor = monitorsByY[i];
// Check if this monitor overlaps vertically with any monitor already assigned to current row
bool foundOverlap = false;
for (size_t j = 0; j < i; j++) {
const auto* other = monitorsByY[j];
if (monitorToRow[other] == currentRow) {
// Check vertical overlap
if (rangesOverlapOrTouch(monitor->rect.top, monitor->rect.bottom,
other->rect.top, other->rect.bottom)) {
monitorToRow[monitor] = currentRow;
foundOverlap = true;
break;
}
}
}
if (!foundOverlap) {
// Start new row if no overlap found and we have room
if (currentRow < 2 && i < monitorsByY.size() - 1) {
currentRow++;
}
monitorToRow[monitor] = currentRow;
}
}
int totalWidth = totalBounds.right - totalBounds.left;
int totalHeight = totalBounds.bottom - totalBounds.top;
int gridWidth = max(1, totalWidth / 3);
int gridHeight = max(1, totalHeight / 3);
// Assign columns based on horizontal position (left-to-right order)
// Monitors are already sorted by X coordinate (left edge)
std::map<const MonitorInfo*, int> monitorToCol;
// Place monitors in the 3x3 grid based on their center points
// For horizontal arrangement, distribute monitors evenly across columns
if (monitorsByX.size() == 1) {
// Single monitor - place in middle column
monitorToCol[monitorsByX[0]] = 1;
}
else if (monitorsByX.size() == 2) {
// Two monitors - place at opposite ends for wrapping
monitorToCol[monitorsByX[0]] = 0; // Leftmost monitor
monitorToCol[monitorsByX[1]] = 2; // Rightmost monitor
}
else {
// Three or more monitors - distribute across grid
for (size_t i = 0; i < monitorsByX.size() && i < 3; i++) {
monitorToCol[monitorsByX[i]] = static_cast<int>(i);
}
// If more than 3 monitors, place extras in rightmost column
for (size_t i = 3; i < monitorsByX.size(); i++) {
monitorToCol[monitorsByX[i]] = 2;
}
}
// Place monitors in grid using the computed row/column assignments
for (const auto& monitor : monitors)
{
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
// Calculate center point of monitor
int centerX = (monitor.rect.left + monitor.rect.right) / 2;
int centerY = (monitor.rect.top + monitor.rect.bottom) / 2;
// Map to grid position
int col = (centerX - totalBounds.left) / gridWidth;
int row = (centerY - totalBounds.top) / gridHeight;
// Ensure we stay within bounds
col = max(0, min(2, col));
row = max(0, min(2, row));
int row = monitorToRow[&monitor];
int col = monitorToCol[&monitor];
grid[row][col] = hMonitor;
monitorToPosition[hMonitor] = {row, col, true};
positionToMonitor[{row, col}] = hMonitor;
#ifdef _DEBUG
Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}], center=({}, {})",
monitor.monitorId, row, col, centerX, centerY);
Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}] (left={}, top={}, right={}, bottom={})",
monitor.monitorId, row, col,
monitor.rect.left, monitor.rect.top, monitor.rect.right, monitor.rect.bottom);
#endif
}
}

View File

@@ -429,7 +429,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<value>More</value>
</data>
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
<value>Last Position</value>
<value>Last position</value>
<comment>Reopen the window where it was last closed</comment>
</data>
<data name="TrayMenu_Settings" xml:space="preserve">

View File

@@ -15,6 +15,7 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
[JsonSerializable(typeof(List<Choice>))]
[JsonSerializable(typeof(List<ChoiceSetSetting>))]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSerializable(typeof(List<Dictionary<string, object>>))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
internal sealed partial class JsonSerializationContext : JsonSerializerContext
{

View File

@@ -26,15 +26,69 @@ public sealed class ToggleSetting : Setting<bool>
public override Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
var items = new List<Dictionary<string, object>>();
if (!string.IsNullOrEmpty(Label))
{
{ "type", "Input.Toggle" },
{ "title", Label },
{ "id", Key },
{ "label", Description },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Label },
{ "wrap", true },
});
}
if (!(string.IsNullOrEmpty(Description) || string.Equals(Description, Label, StringComparison.OrdinalIgnoreCase)))
{
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Description },
{ "isSubtle", true },
{ "size", "Small" },
{ "spacing", "Small" },
{ "wrap", true },
});
}
return new()
{
{ "type", "ColumnSet" },
{
"columns", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Column" },
{ "width", "20px" },
{
"items", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Input.Toggle" },
{ "title", " " },
{ "id", Key },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
},
}
},
{ "verticalContentAlignment", "Center" },
},
new()
{
{ "type", "Column" },
{ "width", "stretch" },
{ "items", items },
{ "verticalContentAlignment", "Center" },
},
}
},
{ "spacing", "Medium" },
};
}

View File

@@ -34,52 +34,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool IsAIEnabled { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData
{
get => _extensionData;
set
{
_extensionData = value;
if (_extensionData != null && _extensionData.TryGetValue("IsOpenAIEnabled", out var legacyElement) && legacyElement.ValueKind == JsonValueKind.Object && legacyElement.TryGetProperty("value", out var valueElement))
{
IsAIEnabled = valueElement.ValueKind switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => IsAIEnabled,
};
_extensionData.Remove("IsOpenAIEnabled");
}
if (_extensionData != null && _extensionData.TryGetValue("IsAdvancedAIEnabled", out var legacyAdvancedElement))
{
bool? legacyValue = legacyAdvancedElement.ValueKind switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Object when legacyAdvancedElement.TryGetProperty("value", out var advancedValueElement) => advancedValueElement.ValueKind switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => null,
},
_ => null,
};
if (legacyValue.HasValue)
{
LegacyAdvancedAIEnabled = legacyValue.Value;
}
_extensionData.Remove("IsAdvancedAIEnabled");
}
}
}
private Dictionary<string, JsonElement> _extensionData;
private bool? _legacyAdvancedAIEnabled;
[JsonPropertyName("IsAdvancedAIEnabled")]

View File

@@ -168,6 +168,9 @@
</InfoBar>
</tkcontrols:SettingsExpander.ItemsHeader>
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
ContentAlignment="Left"
@@ -177,9 +180,6 @@
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" ContentAlignment="Left">
<CheckBox x:Uid="AdvancedPaste_CloseAfterLosingFocus" IsChecked="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
<CheckBox x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="AdvancedPasteShowCustomPreviewSettingsCard" ContentAlignment="Left">
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_ShowCustomPreviewSettingsCard" IsChecked="{x:Bind ViewModel.ShowCustomPreview, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
@@ -429,12 +429,11 @@
<!-- Paste AI provider dialog -->
<ContentDialog
x:Name="PasteAIProviderConfigurationDialog"
x:Uid="AdvancedPaste_EndpointDialog"
Title="Paste with AI provider configuration"
Closed="PasteAIProviderConfigurationDialog_Closed"
PrimaryButtonClick="PasteAIProviderConfigurationDialog_PrimaryButtonClick"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
PrimaryButtonText="Save"
SecondaryButtonText="Cancel">
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
<ContentDialog.Resources>
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>

View File

@@ -27,7 +27,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable
{
private readonly ObservableCollection<ModelDetails> _foundryCachedModels = new();
private readonly ObservableCollection<FoundryDownloadableModel> _foundryDownloadableModels = new();
private CancellationTokenSource _foundryModelLoadCts;
private bool _suppressFoundrySelectionChanged;
private bool _isFoundryLocalAvailable;
@@ -57,7 +56,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
if (FoundryLocalPicker is not null)
{
FoundryLocalPicker.CachedModels = _foundryCachedModels;
FoundryLocalPicker.DownloadableModels = _foundryDownloadableModels;
FoundryLocalPicker.SelectionChanged += FoundryLocalPicker_SelectionChanged;
FoundryLocalPicker.LoadRequested += FoundryLocalPicker_LoadRequested;
}
@@ -469,7 +467,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
var cachedModels = cachedModelsEnumerable?.ToList() ?? new List<ModelDetails>();
UpdateFoundryCollections(cachedModels, []);
UpdateFoundryCollections(cachedModels);
ShowFoundryAvailableState();
RestoreFoundrySelection(cachedModels);
}
@@ -538,7 +536,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
UpdateFoundrySaveButtonState();
}
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels, IReadOnlyCollection<ModelDetails> catalogModels)
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels)
{
_foundryCachedModels.Clear();
@@ -547,20 +545,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
_foundryCachedModels.Add(model);
}
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => NormalizeFoundryModelReference(m.Url ?? m.Name)), StringComparer.OrdinalIgnoreCase);
_foundryDownloadableModels.Clear();
foreach (var model in catalogModels.OrderBy(m => m.Name, StringComparer.OrdinalIgnoreCase))
{
var reference = NormalizeFoundryModelReference(model.Url ?? model.Name);
if (cachedReferences.Contains(reference))
{
continue;
}
_foundryDownloadableModels.Add(new FoundryDownloadableModel(model));
}
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => m.Name), StringComparer.OrdinalIgnoreCase);
}
private void RestoreFoundrySelection(IReadOnlyCollection<ModelDetails> cachedModels)
@@ -576,9 +561,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
if (!string.IsNullOrWhiteSpace(currentModelReference))
{
var normalizedReference = NormalizeFoundryModelReference(currentModelReference);
matchingModel = cachedModels.FirstOrDefault(model =>
string.Equals(NormalizeFoundryModelReference(model.Url ?? model.Name), normalizedReference, StringComparison.OrdinalIgnoreCase));
string.Equals(model.Name, currentModelReference, StringComparison.OrdinalIgnoreCase));
}
if (FoundryLocalPicker is null)
@@ -608,7 +592,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
if (ViewModel?.PasteAIProviderDraft is not null)
{
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(matchingModel.Url ?? matchingModel.Name);
ViewModel.PasteAIProviderDraft.ModelName = matchingModel.Name;
}
if (FoundryLocalPicker is not null)
@@ -620,19 +604,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
UpdateFoundrySaveButtonState();
}
private static string NormalizeFoundryModelReference(string modelReference)
{
if (string.IsNullOrWhiteSpace(modelReference))
{
return string.Empty;
}
var prefix = FoundryLocalModelProvider.Instance.UrlPrefix;
return modelReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? modelReference
: $"{prefix}{modelReference}";
}
private void UpdateFoundrySaveButtonState()
{
if (PasteAIProviderConfigurationDialog is null)
@@ -656,7 +627,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
return;
}
if (!_isFoundryLocalAvailable || _foundryDownloadableModels.Any(model => model.IsDownloading))
if (!_isFoundryLocalAvailable)
{
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
return;
@@ -677,7 +648,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
if (ViewModel?.PasteAIProviderDraft is not null)
{
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(selectedModel.Url ?? selectedModel.Name);
ViewModel.PasteAIProviderDraft.ModelName = selectedModel.Name;
}
if (FoundryLocalPicker is not null)

View File

@@ -275,23 +275,31 @@
<controls:SettingsGroup x:Uid="MouseUtils_CursorWrap" AutomationProperties.AutomationId="MouseUtils_CursorWrapTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsExpander
<tkcontrols:SettingsCard
Name="MouseUtilsEnableCursorWrap"
x:Uid="MouseUtils_Enable_CursorWrap"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CursorWrap.png}"
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
IsExpanded="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_AutoActivate" IsChecked="{x:Bind ViewModel.CursorWrapAutoActivate, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<tkcontrols:SettingsExpander
Name="MouseUtilsCursorWrapSettingsExpander"
x:Uid="MouseUtils_CursorWrap_ActivationShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.CursorWrapActivationShortcut, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard ContentAlignment="Left">
<CheckBox x:Uid="MouseUtils_AutoActivate" IsChecked="{x:Bind ViewModel.CursorWrapAutoActivate, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs" AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">

View File

@@ -648,10 +648,10 @@ Please review the placeholder content that represents the final terms and usage
<value>Use built-in functions to handle complex tasks. Token consumption may increase.</value>
</data>
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve">
<value>Show what's currently on your Clipboard and access your Clipboard history</value>
<value>Access Clipboard History</value>
</data>
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve">
<value>Clipboard History shows a list of previously copied items.</value>
<value>View and select previously copied items</value>
</data>
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
<value>Actions</value>
@@ -2685,10 +2685,7 @@ From there, simply click on one of the supported files in the File Explorer and
<value>Wrap the mouse cursor between monitor edges</value>
</data>
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
<value>Activation shortcut</value>
</data>
<data name="MouseUtils_CursorWrap_ActivationShortcut_Description.Text" xml:space="preserve">
<value>Hotkey to toggle cursor wrapping on/off</value>
<value>Activation and behavior</value>
</data>
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
<value>Set shortcut</value>
@@ -4601,7 +4598,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Automatically close the window after it loses focus</value>
<comment>Advanced Paste is a product name, do not loc</comment>
</data>
<data name="AdvancedPaste_EnableClipboardPreview.Content" xml:space="preserve">
<data name="AdvancedPaste_EnableClipboardPreview.Header" xml:space="preserve">
<value>Show clipboard preview</value>
<comment>Enables display of clipboard contents preview in the Advanced Paste window</comment>
</data>
@@ -5747,4 +5744,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
<value>System prompt</value>
</data>
<data name="AdvancedPaste_EndpointDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="AdvancedPaste_EndpointDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="AdvancedPaste_EnableClipboardPreview.Description" xml:space="preserve">
<value>Display a preview of the current clipboard content</value>
</data>
</root>

View File

@@ -233,10 +233,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
RemoveLegacyOpenAICredential();
}
bool shouldEnableAI = legacyCredential is not null;
bool enabledChanged = false;
if (!properties.IsAIEnabled && legacyCredential is not null)
if (properties.IsAIEnabled != shouldEnableAI)
{
properties.IsAIEnabled = true;
properties.IsAIEnabled = shouldEnableAI;
enabledChanged = true;
}