Compare commits

...

51 Commits

Author SHA1 Message Date
Mike Griese
d5249a0ba7 put commands in the top level 2025-08-02 20:45:25 -05:00
Mike Griese
8dca07767c Revert "I don't think I actually want to try and support top-level status messages today"
This reverts commit 9ec1eae62e.
2025-08-02 20:44:45 -05:00
Mike Griese
9ec1eae62e I don't think I actually want to try and support top-level status messages today 2025-08-02 20:44:38 -05:00
Mike Griese
a5b09f7c34 minor nits 2025-07-27 20:38:04 -05:00
Mike Griese
81f5032eef eh, don't love the icons 2025-07-27 07:21:38 -05:00
Mike Griese
5f8dd34d02 more icons 2025-07-26 20:07:58 -05:00
Mike Griese
9380f83e82 just for fun 2025-07-26 17:31:09 -05:00
Mike Griese
1493b52f82 better 2025-07-26 14:45:38 -05:00
Mike Griese
b86bd28be5 ARGS IS ARGS 2025-07-26 14:29:35 -05:00
Mike Griese
dfcf309135 okay, but args should be args 2025-07-26 14:05:36 -05:00
Mike Griese
53d443f520 lets start parsing those args, shall we? 2025-07-26 13:22:09 -05:00
Mike Griese
c617055888 what if we were raycast 2025-07-26 07:21:35 -05:00
Mike Griese
8e72cfd9d0 MAIN: add metrics for GetItems 2025-07-21 06:24:06 -05:00
Mike Griese
3808659b76 more action types actually supported 2025-07-21 05:28:15 -05:00
Mike Griese
5127860759 contacts are a lost fuckin cause 2025-07-20 10:23:20 -05:00
Mike Griese
5145a5302e ContactPicker? 2025-07-20 06:17:31 -05:00
Mike Griese
57f86edd9e Have the button change its text when a value is picked
This is incredibly incredible
2025-07-20 05:54:19 -05:00
Mike Griese
6971ed8845 holy shit it worked (adding a file picker) 2025-07-19 16:48:26 -05:00
Mike Griese
d31fe2f390 incredible that this merge conflict worked 2025-07-19 15:54:46 -05:00
Mike Griese
000e443fec Merge remote-tracking branch 'origin/main' into dev/migrie/f/invokable-params-experiments 2025-07-19 10:49:52 -05:00
Mike Griese
dcbd90206e Merge remote-tracking branch 'origin/main' into dev/migrie/f/invokable-params-experiments 2025-07-19 10:22:26 -05:00
Mike Griese
caa2ae5a06 Start trying to wire up pickers 2025-07-19 06:25:51 -05:00
Yu Leng
ca473b488b [CmdPal][UI Tests] Add basic test cases for cmdpal (#40694)
<!-- 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. Create some basic cmdpal test cases.
2. Add ui tests support for cmdpal modules.


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

- [x] **Closes:** #40695
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **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

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-07-18 16:22:37 +08:00
leileizhang
d37105bf84 [UI Tests] Replace pixel-by-pixel image comparison with perceptual hash (pHash) for improved visual similarity detection (#40653)
<!-- 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 replaces the previous pixel-by-pixel image comparison logic with
a perceptual hash (pHash)-based comparison using the
CoenM.ImageSharp.ImageHash library.

**Removes the need for golden images from CI pipelines**
Since the comparison is perceptual rather than binary, we no longer need
to fetch pixel-perfect golden images from pipelines for validation.
Developers can now capture screenshots locally and still get meaningful,
robust comparisons.

### Why pHash?
Unlike direct pixel comparison (which fails on minor rendering
differences), pHash focuses on the overall structure and visual
perception of the image. This provides several benefits:

- Robust to minor differences: tolerates compression artifacts,
anti-aliasing, subtle rendering changes, and border padding.
- Resilient to resolution or format changes: works even if images are
scaled or compressed differently.
- Closer to human perception: more accurately reflects whether two
images "look" the same to a person.

<!-- 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-07-18 10:32:09 +08:00
Mike Griese
2cb58ae087 hey look this works a little better 2025-07-17 15:09:20 -05:00
Mike Griese
87f65e90d1 some spec ideas 2025-07-17 14:17:26 -05:00
Mike Griese
a285d32697 invoke actions for fun and profit 2025-07-17 14:17:06 -05:00
Michael Jolley
6642c805b7 Context menu cleanup (#40584)
Addressing items in #40583

- [x] When navigating the context menu with the up/down keys, separators
should not be selectable.
- [x] [For context items with a super long title, we need to trim those
with an ellipsis. Ideally, we'd show a tooltip for just those
items.](https://github.com/microsoft/PowerToys/issues/40313)
- [x] [Context menu search bar text size doesn't update after changing
system text size](https://github.com/microsoft/PowerToys/issues/39648)
- [x] Weird "kick out" on first context menu item
- [x] [Primary button doesn't work if the command has more items (fix
regression)](https://github.com/microsoft/PowerToys/issues/40624)

Example of long context menu item titles with tooltips: (@niels9001,
look okay?)


https://github.com/user-attachments/assets/fc0a4034-9c22-48ee-a3f0-44fcc2f294a6

closes #40624
2025-07-16 06:25:24 -05:00
leileizhang
d5b15026ae [UI Test Pipeline] Add platform and installMode to testRunTitle for better test result distinction in pipeline (#40628)
<!-- 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
Currently, all test runs appear under a generic name in the pipeline,
making it hard to identify which platform or install mode caused
failures. Adding this context improves debugging efficiency and report
clarity.

### Before:
<img width="288" height="217" alt="image"
src="https://github.com/user-attachments/assets/5086e26a-7c42-4317-8f04-3db3d1dc5f0e"
/>

### After:
<img width="254" height="183" alt="image"
src="https://github.com/user-attachments/assets/6e5961b6-aeb2-47d1-9a1a-ec0e9eac33b8"
/>

<!-- 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-07-16 12:48:36 +08:00
Michael Jolley
b552f2ac1e Standardizing built-in extension icon handling (#40606)
Just standardizing built-in extensions to use a `internal sealed class
Icons` for all their non-dynamic icons.

Looks like a LOT of changes, but it's icons all the way down.
2025-07-15 14:33:25 -05:00
Mike Griese
cc16b61eb7 Create a Microsoft.CmdPal.Core.ViewModels project (#40560)
_targets #40504_ 

Major refactoring for #40113

This moves a large swath of the codebase to a `.Core` project. "Core"
doesn't have any explicit dependencies on "extensions", settings or the
current `MainListPage`. It's just a filterable list of stuff. This
should let us make this component a bit more reusable.

This is half of a PR. As I did this, I noticed a particular bit of code
for TopLevelVViewModels and CommandPaletteHost that was _very rough_.
Solving it in this PR would make "move everything to a new project" much
harder to review. So I'm submitting two PRs simultaneously, so we can
see the changes separately, then merge together.
2025-07-15 12:21:44 -05:00
Mike Griese
53bb471449 CmdPal: Add a viewmodel factory for pages (#40504)
_targets #40482_
ref #40113

A smaller refactor, to be sure.

This just moves the instantiation of PageViewModel objects out of the
ShellViewModel, and into its own class.

The idea being that other page types could be added, just by extending
that factory (or implementing your own), and then also handling those
new VMs in your ShellPage.xaml.cs equivalent.
2025-07-15 09:33:28 -05:00
Yu Leng
0783763dd0 [AOT] Enable AOT for CmdPal (#40551)
<!-- 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

Base on https://github.com/microsoft/PowerToys/pull/40486 we can easily
use lab package. So all blocker has been resolved.

1. Replace CommunityToolkit.WinUI.UI.Controls.Markdown with
CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
2. Add default markdown style config to align some configuration with
the original one. (but still have some gap)
3. Add new configuration in pipeline to control the AOT enable/disable.

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

- [x] **Closes:** #38279
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **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

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-15 22:15:18 +08:00
Michael Jolley
19390e3198 Users can now pin/unpin apps from the top of AllApps extension (#40544)
Users can now use Ctrl+P to toggle pinning and unpinning applications
from the top of the All Apps extension.

This pinned status persists through restarts & reboots.


https://github.com/user-attachments/assets/86895a38-7312-438a-9409-b50a85979d12

Reference: #40543

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-15 08:59:32 -05:00
Jiří Polášek
5800b81638 Fix loading top-level commands (#40602)
## Summary of the Pull Request
Updates LoadTopLevelCommandsFromProvider so it returns list of top-level
commands for further instead of modifying TopLevelCommands collection
directly.

Reverts unintended change from cfa5f75 where
LoadTopLevelCommandsFromProvider updates the shared TopLevelCommands
collection directly from in a task, causing thread-safety issues and
bypassing synchronization lock on the collection.

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

- [ ] **Closes:** --
- [ ] **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-07-14 20:03:52 -05:00
Mike Griese
d96c29d22d Replace our nuget.org feed with a public azure artifacts feed (#40486)
_This madness has gone on too long, I say_

This replaces our default nuget.org feed with a public azure artifacts
feed in the shine-oss org. This is what literally everyone else does, I
don't know why we don't.

This should unblock _wait where'd that issue go_ since we can just add
the community toolkit labs feed as an upstream

This has the negative side effect that it did prompt me to log in to
azure artifacts with my MSA. I've cancelled like 5 prompts now, but it
seems to still be working on it?
2025-07-14 13:58:59 -05:00
Davide Giacometti
227c5d8147 [Peek] Terminate Preview Handlers Processes (#40116)
<!-- 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 is an attempt to release Preview Handlers instantiated by Peek and
close the related processe.

⚠️ Note that even if the PR improve the current behavior, the solution
doesn't work 100% of times.
I noticed that sometimes the process gets leaked also when Preview
Handler is used in Explorer 🤔

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

- [x] **Closes:** #40117
- [ ] **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

- Ported the same fix applied to CmdPal to ensure the process is
terminated gracefully instead of being killed:
https://github.com/microsoft/PowerToys/pull/39589
- Attempt to cleanup Preview Handlers and close the relative process

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Tested manually:
- Preview through some Excel and Word files
- Close Peek window
- Excel and Word processes are closed
2025-07-14 08:50:45 +08:00
Jiří Polášek
d258dcd61b CmdPal: Remove backdrop targets from existing backdrop controller before assigning a new one (#40540)
## Summary of the Pull Request
Also runs the code through UI thread. it will ensure correct access to
_acrylicController, and also because the method touches UI element.


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

- [x] **Closes:** #38659 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **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)
- [x] **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-07-12 14:38:01 -05:00
Jiří Polášek
1d464cc307 Use Empty content for empty Web search page (#40549)
## Summary of the Pull Request
Display full page message when the Web Search extension page is empty

<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/2d08d809-1127-44b3-9842-50969eb6bef7"
/>
<img width="786" height="473" alt="image"
src="https://github.com/user-attachments/assets/155374cc-3e13-4cc0-b7e5-b4fa2b371ba7"
/>

## PR Checklist

- [x] **Closes:** #38969 
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** yay
- [x] **Localization:** nope
- [x] **Dev docs:** none
- [x] **New binaries:** zilch
- [x] **Documentation updated:** no need

## Detailed Description of the Pull Request / Additional comments

--

## Validation Steps Performed

Tested with and without enabled history.
2025-07-11 21:11:13 -05:00
ruslanlap
3686d6ac19 Add PowerToysRun-Hotkeys plugin to third-party plugins documentation (#40530)
This PR adds the Hotkeys and RandomGen plugins to the third-party
plugins documentation in the General plugins section.

## Hotkeys Plugin
⌨️ Hotkeys for PowerToys Run - Create, manage, and trigger custom
keyboard shortcuts directly from PowerToys Run

![Hotkeys
Demo](https://github.com/ruslanlap/PowerToysRun-Hotkeys/blob/main/assets/demo-hotkeys.gif)

This plugin enables users to create and manage custom keyboard shortcuts
without leaving PowerToys Run, making it easier to automate repetitive
tasks and improve workflow efficiency.

### Features
- 🔑 Create custom keyboard shortcuts
- 🔄 Manage existing shortcuts
-  Trigger shortcuts directly from PowerToys Run
- 📋 Import/export shortcuts
- 🔍 Search through your shortcuts
- ⚙️ Configure shortcut behavior
- 🌐 Application-specific shortcuts
- 📝 Add descriptions to shortcuts

## RandomGen Plugin
🎲 RandomGen for PowerToys Run - Generate random data instantly with a
single keystroke

![RandomGen
Demo](https://github.com/ruslanlap/PowerToysRun-RandomGen/blob/master/assets/demo-randomgen-password.gif)

This plugin is particularly useful for designers who need random color
codes and placeholder content, as well as developers and testers who
need quick access to various types of random data.

### Features
- 🔐 Cryptographically Secure Passwords
- 📍 Secure PIN Codes
- 👤 Personal Data (names, emails, phone numbers)
- 🏢 Business Data
- 📅 Date Generation
- 🔢 Number Generation
- 🆔 Unique Identifiers (GUIDs/UUIDs)
- 🎨 Color Codes (especially useful for designers)
- 🌐 Web Data
- 💳 Payment Testing Data
- 📝 Lorem Ipsum placeholder text

## Links to plugins
- https://github.com/ruslanlap/PowerToysRun-Hotkeys
- https://github.com/ruslanlap/PowerToysRun-RandomGen
2025-07-11 12:18:17 -05:00
Jiří Polášek
a0495736f1 CmdPal: Hide Open URL fallback item when search query is empty or doesn't contain valid URL (#40514)
## Summary of the Pull Request
Clears and hides fallback item for URL when query changes and URL is no
longer a valid URL. Fixes the situation when the item remains visible in
the list with generic text and pointing the last valid URL it was
updated with.

## PR Checklist

- [x] **Closes:** #40512
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** no new strings
- [x] **Dev docs:** nothing to update
- [x] **New binaries:** none
- [x] **Documentation updated:** none

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Entered URL to search field. Observed the Open URL item is present.
Cleared URL. Observed that Open URL item is not present.
2025-07-11 12:08:31 -05:00
octastylos-pseudodipteros
22f565ca49 [QuickAccent] Add Vietnamese (#40164)
<!-- 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
Adds support for Vietnamese in QuickAccent
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #36491
- [x] **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
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** no need
- [x] **New binaries:** no need
- [x] **Documentation updated:** no need

<!-- 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
Vietnamese character set based of on
[Wikipedia](https://en.wikipedia.org/wiki/Vietnamese_alphabet)
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually tested
2025-07-11 11:08:42 -05:00
Jiří Polášek
e041556395 CmdPal: Handle Alt+Left Arrow even when search bar contains text (#40516)
Previously, the `KeyDown` handler for `Alt+Left Arrow` was not invoked
if the search bar contained text. By moving the logic to the
`PreviewKeyDown` handler, we ensure the shortcut is handled regardless
of the search bar’s content.

The handler was added to the `ShellPage`'s `PreviewKeyDown` handler,
rather than the one on `SearchBar`. This will allow the shortcut to be
available throughout the entire window and will ensure it works from
within the page content, e.g. from forms

<!-- 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

- [x] **Closes:** #40515 
- [ ] **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-07-11 11:08:17 -05:00
Mike Griese
b55c4eeed3 CmdPal: Remove ShellViewModel's dependency on MainListPage (#40482)
targets #40479

Kinda mental that the viewmodel had this hard dependency.

So instead I added a service for providing the root page for the app.
Theoretically, you could add a different `IRootPageService`, and give
out
a different root page.


ref #40113
2025-07-11 06:29:39 -05:00
Mike Griese
1045c4b830 well it does work for passing args 2025-07-10 12:44:08 -05:00
Mike Griese
b4f9e7b32e dummy activation 2025-07-10 10:27:56 -05:00
Mike Griese
fc866d3a6a create text boxes from param VMs 2025-07-10 09:51:34 -05:00
Mike Griese
a02da42722 make it build 2025-07-09 20:14:56 -05:00
Mike Griese
47d9563623 Merge remote-tracking branch 'origin/main' into dev/migrie/f/invokable-params-experiments 2025-07-09 16:05:13 -05:00
Mike Griese
7ab131c467 start passing around a viewmodel 2025-06-30 05:44:00 -05:00
Mike Griese
b8b6418e68 Add IInvokableWithParams, as a different type of command
if it's IInvokableWithParams requires IInvokable, it doesn't work
But this does
and I cannot tell you why
2025-06-24 09:08:10 -05:00
291 changed files with 4851 additions and 1100 deletions

View File

@@ -218,6 +218,7 @@ coclass
CODENAME
codereview
Codespaces
Coen
COINIT
colid
colorconv
@@ -1383,6 +1384,7 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
Rns
rop
ROUNDSMALL
ROWSETEXT

View File

@@ -38,6 +38,11 @@ parameters:
displayName: "Build Using Visual Studio Preview"
default: false
- name: enableAOT
type: boolean
displayName: "Enable AOT (Ahead-of-Time) Compilation for CmdPal"
default: true
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
variables:
@@ -95,7 +100,7 @@ extends:
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=${{ parameters.enableAOT }}
beforeBuildSteps:
# Sets versions for all PowerToy created DLLs
- pwsh: |-

View File

@@ -160,6 +160,7 @@ jobs:
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
testAssemblyVer2: |
**\*UITest*.dll
@@ -182,6 +183,7 @@ jobs:
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
testAssemblyVer2: |
**\*${{ module }}*.dll
!**\obj\**

View File

@@ -19,7 +19,7 @@ Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude *UITest*,MouseJum
# Temporarily exclude All UI-Test, Fuzzer-Test projects because of Appium.WebDriver dependencies
$depsJsonFullFileName = $_.FullName
if ($depsJsonFullFileName -like "*CmdPal*") {
if ($depsJsonFullFileName -like "*CmdPal*" -or $depsJsonFullFileName -like "*CommandPalette*") {
return
}

View File

@@ -9,6 +9,7 @@
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.17" />
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
@@ -21,6 +22,7 @@
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250703-build.2173" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />

View File

@@ -1496,7 +1496,9 @@ SOFTWARE.
- AdaptiveCards.Templating 2.0.5
- Appium.WebDriver 4.4.5
- Azure.AI.OpenAI 1.0.0-beta.17
- CoenM.ImageSharp.ImageHash 1.3.6
- CommunityToolkit.Common 8.4.0
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock 0.1.250703-build.2173
- CommunityToolkit.Mvvm 8.4.0
- CommunityToolkit.WinUI.Animations 8.2.250402
- CommunityToolkit.WinUI.Collections 8.2.250402
@@ -1579,4 +1581,3 @@ SOFTWARE.
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

View File

@@ -736,6 +736,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCsWin32", "src\commo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerRenameUITest", "src\modules\powerrename\PowerRenameUITest\PowerRenameUITest.csproj", "{9D3F3793-EFE3-4525-8782-238015DABA62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Actions", "src\modules\cmdpal\ext\MIcrosoft.CmdPal.Ext.Actions\Microsoft.CmdPal.Ext.Actions.csproj", "{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Core.ViewModels", "src\modules\cmdpal\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj", "{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UITests", "src\modules\cmdpal\Microsoft.CmdPal.UITests\Microsoft.CmdPal.UITests.csproj", "{840455DF-5634-51BB-D937-9D7D32F0B0C2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2718,6 +2726,14 @@ Global
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.Build.0 = Release|ARM64
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.ActiveCfg = Release|x64
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.Build.0 = Release|x64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.ActiveCfg = Debug|ARM64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.Build.0 = Debug|ARM64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.ActiveCfg = Debug|x64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.Build.0 = Debug|x64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.ActiveCfg = Release|ARM64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.Build.0 = Release|ARM64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.ActiveCfg = Release|x64
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.Build.0 = Release|x64
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.ActiveCfg = Debug|ARM64
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.Build.0 = Debug|ARM64
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|x64.ActiveCfg = Debug|x64
@@ -2726,6 +2742,22 @@ Global
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|ARM64.Build.0 = Release|ARM64
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.ActiveCfg = Release|x64
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.Build.0 = Release|x64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|ARM64.Build.0 = Debug|ARM64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|x64.ActiveCfg = Debug|x64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|x64.Build.0 = Debug|x64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|ARM64.ActiveCfg = Release|ARM64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|ARM64.Build.0 = Release|ARM64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|x64.ActiveCfg = Release|x64
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|x64.Build.0 = Release|x64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.Build.0 = Debug|ARM64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|x64.ActiveCfg = Debug|x64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|x64.Build.0 = Debug|x64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Release|ARM64.ActiveCfg = Release|ARM64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Release|ARM64.Build.0 = Release|ARM64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Release|x64.ActiveCfg = Release|x64
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3010,7 +3042,11 @@ Global
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{9D3F3793-EFE3-4525-8782-238015DABA62} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
{840455DF-5634-51BB-D937-9D7D32F0B0C2} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -47,6 +47,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Weather](https://github.com/ruslanlap/PowerToysRun-Weather) | [ruslanlap](https://github.com/ruslanlap) | Get real-time weather information directly from PowerToys Run. |
| [Pomodoro](https://github.com/ruslanlap/PowerToysRun-Pomodoro) | [ruslanlap](https://github.com/ruslanlap) | Manage Pomodoro productivity sessions directly from PowerToys Run. |
| [Definition](https://github.com/ruslanlap/PowerToysRun-Definition) | [ruslanlap](https://github.com/ruslanlap) | Lookup word definitions, phonetics, and synonyms directly in PowerToys Run. |
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
## Extending software plugins

View File

@@ -2,10 +2,10 @@
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<packageSource key="PowerToysPublicDependencies">
<package pattern="*" />
</packageSource>
</packageSourceMapping>

View File

@@ -7,6 +7,7 @@
<CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<WarningsNotAsErrors>IL2081;CsWinRT1028;$(WarningsNotAsErrors)</WarningsNotAsErrors>
<!-- Suppress CA1416 for Windows-specific APIs that are used in PowerToys which only runs on Windows 10.0.19041.0+ -->
<WarningsNotAsErrors>IL2081;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
</Project>

View File

@@ -32,6 +32,7 @@ namespace Microsoft.PowerToys.UITest
Runner,
Workspaces,
PowerRename,
CommandPalette,
}
/// <summary>
@@ -104,6 +105,7 @@ namespace Microsoft.PowerToys.UITest
[PowerToysModule.Runner] = new ModuleInfo("PowerToys.exe", "PowerToys"),
[PowerToysModule.Workspaces] = new ModuleInfo("PowerToys.WorkspacesEditor.exe", "Workspaces Editor"),
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
[PowerToysModule.CommandPalette] = new ModuleInfo("Microsoft.CmdPal.UI.exe", "PowerToys Command Palette", "WinUI3Apps\\CmdPal"),
};
}

View File

@@ -20,6 +20,7 @@
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
</ItemGroup>
</Project>

View File

@@ -6,7 +6,11 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using CoenM.ImageHash;
using CoenM.ImageHash.HashAlgorithms;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Microsoft.PowerToys.UITest
{
@@ -127,34 +131,75 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Test if two images are equal bit-by-bit
/// Test if two images are equal using ImageHash comparison
/// </summary>
/// <param name="baselineImage">baseline image</param>
/// <param name="testImage">test image</param>
/// <returns>true if are equal,otherwise false</returns>
private static bool AreEqual(Bitmap baselineImage, Bitmap testImage)
{
if (baselineImage.Width != testImage.Width || baselineImage.Height != testImage.Height)
try
{
return false;
// Define a threshold for similarity percentage
const int SimilarityThreshold = 95;
// Use CoenM.ImageHash for perceptual hash comparison
var hashAlgorithm = new AverageHash();
// Convert System.Drawing.Bitmap to SixLabors.ImageSharp.Image
using var baselineImageSharp = ConvertBitmapToImageSharp(baselineImage);
using var testImageSharp = ConvertBitmapToImageSharp(testImage);
// Calculate hashes for both images
var baselineHash = hashAlgorithm.Hash(baselineImageSharp);
var testHash = hashAlgorithm.Hash(testImageSharp);
// Compare hashes using CompareHash method
// Returns similarity percentage (0-100, where 100 is identical)
var similarity = CompareHash.Similarity(baselineHash, testHash);
// Consider images equal if similarity is very high
// Allow for minor rendering differences (threshold can be adjusted)
return similarity >= SimilarityThreshold; // 95% similarity threshold
}
// WinAppDriver sometimes adds a border to the screenshot (around 2 pix width), and it is not always consistent.
// So we exclude the border when comparing the images, and usually it is the edge of the windows, won't affect the comparison.
int excludeBorderWidth = 5, excludeBorderHeight = 5;
for (int x = excludeBorderWidth; x < baselineImage.Width - excludeBorderWidth; x++)
catch
{
for (int y = excludeBorderHeight; y < baselineImage.Height - excludeBorderHeight; y++)
// Fallback to pixel-by-pixel comparison if hash comparison fails
if (baselineImage.Width != testImage.Width || baselineImage.Height != testImage.Height)
{
if (!VisualHelper.PixIsSame(baselineImage.GetPixel(x, y), testImage.GetPixel(x, y)))
return false;
}
// WinAppDriver sometimes adds a border to the screenshot (around 2 pix width), and it is not always consistent.
// So we exclude the border when comparing the images, and usually it is the edge of the windows, won't affect the comparison.
int excludeBorderWidth = 5, excludeBorderHeight = 5;
for (int x = excludeBorderWidth; x < baselineImage.Width - excludeBorderWidth; x++)
{
for (int y = excludeBorderHeight; y < baselineImage.Height - excludeBorderHeight; y++)
{
return false;
if (!VisualHelper.PixIsSame(baselineImage.GetPixel(x, y), testImage.GetPixel(x, y)))
{
return false;
}
}
}
}
return true;
return true;
}
}
/// <summary>
/// Convert System.Drawing.Bitmap to SixLabors.ImageSharp.Image
/// </summary>
/// <param name="bitmap">The bitmap to convert</param>
/// <returns>ImageSharp Image</returns>
private static Image<Rgba32> ConvertBitmapToImageSharp(Bitmap bitmap)
{
using var memoryStream = new MemoryStream();
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
memoryStream.Position = 0;
return SixLabors.ImageSharp.Image.Load<Rgba32>(memoryStream);
}
}
}

View File

@@ -26,6 +26,11 @@
"input": "pushd .\\ExtensionTemplate\\ ; git archive -o ..\\Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip HEAD -- .\\TemplateCmdPalExtension\\ ; popd",
"name": "Update template project",
"description": "zips up the ExtensionTemplate into our assets. Run this in the cmdpal/ directory."
},
{
"input": "pwsh -c .\\clean-sdk.ps1",
"name": "Delete old extensions output",
"description": "Delete old extensions output directory.\r\nUse this after regenerating the interface, otherwise it will not pass fast up to date checks."
}
]
}

View File

@@ -19,13 +19,13 @@ public interface IExtensionService
Task SignalStopExtensionsAsync();
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
public void EnableExtension(string extensionUniqueId);
void EnableExtension(string extensionUniqueId);
public void DisableExtension(string extensionUniqueId);
void DisableExtension(string extensionUniqueId);
///// <summary>
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature

View File

@@ -0,0 +1,168 @@
// 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.ObjectModel;
using System.Diagnostics;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class AppExtensionHost : IExtensionHost
{
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private static ulong _hostingHwnd;
public static ObservableCollection<LogMessageViewModel> LogMessages { get; } = [];
public ulong HostingHwnd => _hostingHwnd;
public string LanguageOverride => string.Empty;
public ObservableCollection<StatusMessageViewModel> StatusMessages { get; } = [];
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
public void DebugLog(string message)
{
#if DEBUG
this.ProcessLogMessage(new LogMessage(message));
#endif
}
public IAsyncAction HideStatus(IStatusMessage? message)
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
_ = Task.Run(() =>
{
ProcessHideStatusMessage(message);
});
return Task.CompletedTask.AsAsyncAction();
}
public void Log(string message)
{
this.ProcessLogMessage(new LogMessage(message));
}
public IAsyncAction LogMessage(ILogMessage? message)
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
Logger.LogDebug(message.Message);
_ = Task.Run(() =>
{
ProcessLogMessage(message);
});
// We can't just make a LogMessageViewModel : ExtensionObjectViewModel
// because we don't necessarily know the page context. Butts.
return Task.CompletedTask.AsAsyncAction();
}
public void ProcessHideStatusMessage(IStatusMessage message)
{
Task.Factory.StartNew(
() =>
{
try
{
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (vm != null)
{
StatusMessages.Remove(vm);
}
}
catch
{
}
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public void ProcessLogMessage(ILogMessage message)
{
var vm = new LogMessageViewModel(message, _globalLogPageContext);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
() =>
{
LogMessages.Add(vm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public void ProcessStatusMessage(IStatusMessage message, StatusContext context)
{
// If this message is already in the list of messages, just bring it to the top
var oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (oldVm != null)
{
Task.Factory.StartNew(
() =>
{
StatusMessages.Remove(oldVm);
StatusMessages.Add(oldVm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
return;
}
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
() =>
{
StatusMessages.Add(vm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public IAsyncAction ShowStatus(IStatusMessage? message, StatusContext context)
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
Debug.WriteLine(message.Message);
_ = Task.Run(() =>
{
ProcessStatusMessage(message, context);
});
return Task.CompletedTask.AsAsyncAction();
}
public abstract string? GetExtensionDisplayName();
}
public interface IAppHostService
{
AppExtensionHost GetDefaultHost();
AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost);
}

View File

@@ -0,0 +1,162 @@
// 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 CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ArgumentItemViewModel : ExtensionObjectViewModel
{
public ExtensionObject<ICommandArgument> Model => _model;
private readonly ExtensionObject<ICommandArgument> _model = new(null);
public ParameterType Type { get; private set; }
public string Name { get; private set; } = string.Empty;
public bool Required { get; private set; }
private string ModelDisplayName { get; set; } = string.Empty;
public string DisplayName => string.IsNullOrEmpty(ModelDisplayName) ? Name : ModelDisplayName;
// TODO! This should be an ExtensionObject<object> since it's out-of-proc
public object? Value
{
get; set
{
field = value;
SafeSetValue(value);
}
}
public ArgumentItemViewModel(ExtensionObject<ICommandArgument> model, WeakReference<IPageContext> pageContext)
: base(pageContext)
{
_model = model;
}
public override void InitializeProperties()
{
var model = _model.Unsafe;
if (model == null)
{
return;
}
Type = model.Type;
Name = model.Name;
Required = model.Required;
Value = model.Value;
ModelDisplayName = model.DisplayName;
// Register for property changes
model.PropChanged += Model_PropChanged;
}
private void Model_PropChanged(object sender, IPropChangedEventArgs args)
{
try
{
FetchProperty(args.PropertyName);
}
catch (Exception ex)
{
ShowException(ex);
}
}
protected virtual void FetchProperty(string propertyName)
{
var model = this._model.Unsafe;
if (model == null)
{
return; // throw?
}
switch (propertyName)
{
case nameof(ICommandArgument.Type):
if (model.Type == Type)
{
return;
}
Type = model.Type;
break;
case nameof(ICommandArgument.Name):
if (model.Name == Name)
{
return;
}
Name = model.Name;
UpdateProperty(nameof(DisplayName));
break;
case nameof(ICommandArgument.Required):
if (model.Required == Required)
{
return;
}
Required = model.Required;
break;
case nameof(ICommandArgument.Value):
if (model.Value == Value)
{
return;
}
Value = model.Value;
break;
case nameof(ICommandArgument.DisplayName):
if (model.DisplayName == ModelDisplayName)
{
return;
}
ModelDisplayName = model.DisplayName;
break;
}
UpdateProperty(propertyName);
}
private void SafeSetValue(object? value)
{
_ = Task.Run(() => SafeSetValueSynchronous(value));
}
private void SafeSetValueSynchronous(object? value)
{
try
{
var model = _model.Unsafe;
if (model == null)
{
return;
}
model.Value = value;
}
catch
{
}
}
public void OpenPicker()
{
var model = _model.Unsafe;
if (model == null)
{
return;
}
WeakReferenceMessenger.Default.Send<RequestOpenPickerMessage>(new(_model));
}
}

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.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ArgumentsViewModel : INotifyPropertyChanged
{
public ObservableCollection<ArgumentItemViewModel> Arguments { get; } = new();
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation
// 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.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandBarViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
@@ -149,11 +149,12 @@ public partial class CommandBarViewModel : ObservableObject,
if (command.HasMoreCommands)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
return ContextKeybindingResult.KeepOpen;
}
else
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
return ContextKeybindingResult.Hide;
}
}

View File

@@ -1,13 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
{

View File

@@ -1,13 +1,14 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using System.Collections.ObjectModel;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{
@@ -62,6 +63,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
public bool HasParameters => Command.HasParameters;
public ObservableCollection<ArgumentItemViewModel> Parameters => Command.Parameters;
public List<IContextItemViewModel> AllCommands
{
get
@@ -181,14 +186,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
MoreCommands = more
.Select(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
return item is ICommandContextItem contextItem
? new CommandContextItemViewModel(contextItem, PageContext)
: new SeparatorContextItemViewModel() as IContextItemViewModel;
})
.ToList();
}
@@ -338,14 +338,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
var newContextMenu = more
.Select(item =>
{
if (item is CommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
return item is CommandContextItem contextItem
? new CommandContextItemViewModel(contextItem, PageContext)
: new SeparatorContextItemViewModel() as IContextItemViewModel;
})
.ToList();
lock (MoreCommands)

View File

@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using System.Collections.ObjectModel;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandViewModel : ExtensionObjectViewModel
{
@@ -29,6 +30,10 @@ public partial class CommandViewModel : ExtensionObjectViewModel
public IconInfoViewModel Icon { get; private set; }
public bool HasParameters { get; set; }
public ObservableCollection<ArgumentItemViewModel> Parameters { get; private set; } = new();
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
: base(pageContext)
{
@@ -51,6 +56,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Id = model.Id ?? string.Empty;
Name = model.Name ?? string.Empty;
IsFastInitialized = true;
}
@@ -80,6 +86,22 @@ public partial class CommandViewModel : ExtensionObjectViewModel
UpdateProperty(nameof(Icon));
}
// TODO! we can probably make this a slow initialization
if (model is IInvokableCommandWithParameters withParams)
{
HasParameters = true;
if (withParams.Parameters is ICommandArgument[] parameters)
{
foreach (var p in parameters)
{
var paramViewModel = new ArgumentItemViewModel(new(p), PageContext);
paramViewModel.InitializeProperties();
Parameters.Add(paramViewModel);
}
}
}
model.PropChanged += Model_PropChanged;
}

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)

View File

@@ -7,12 +7,12 @@ using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
@@ -47,7 +47,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, CommandPaletteHost host)
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
_model = new(model);
@@ -91,16 +91,12 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
});
}
public static ContentViewModel? ViewModelFromContent(IContent content, WeakReference<IPageContext> context)
public virtual ContentViewModel? ViewModelFromContent(IContent content, WeakReference<IPageContext> context)
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
_ => null,
};
return viewModel;
// The core ContentPageViewModel doesn't actually handle any content,
// so we just return null here.
// The real content is handled by the derived class CommandPaletteContentPageViewModel
return null;
}
public override void InitializeProperties()
@@ -115,15 +111,15 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
Commands = model.Commands
.ToList()
.Select(item =>
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is CommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel();
}
})
.ToList();
@@ -182,7 +178,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
}
else
{
return new SeparatorContextItemViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel();
}
})
.ToList();
@@ -254,7 +250,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (PrimaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand.Command.Model, PrimaryCommand.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand));
}
}
@@ -264,7 +260,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (SecondaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand));
}
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ContentViewModel(WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)

View File

@@ -5,13 +5,13 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Diagnostics.Utilities;
using Windows.System;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContextMenuViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>,
@@ -22,15 +22,8 @@ public partial class ContextMenuViewModel : ObservableObject,
get => field;
set
{
if (field != null)
{
field.PropertyChanged -= SelectedItemPropertyChanged;
}
field = value;
SetSelectedItem(value);
OnPropertyChanged(nameof(SelectedItem));
UpdateContextItems();
}
}
@@ -68,33 +61,6 @@ public partial class ContextMenuViewModel : ObservableObject,
OnPropertyChanged(nameof(FilterOnTop));
}
private void SetSelectedItem(ICommandBarContext? value)
{
if (value != null)
{
value.PropertyChanged += SelectedItemPropertyChanged;
}
else
{
if (SelectedItem != null)
{
SelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
}
UpdateContextItems();
}
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SelectedItem.HasMoreCommands):
UpdateContextItems();
break;
}
}
public void UpdateContextItems()
{
if (SelectedItem != null)
@@ -259,7 +225,7 @@ public partial class ContextMenuViewModel : ObservableObject,
}
else
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
UpdateContextItems();
return ContextKeybindingResult.Hide;
}

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsCommandsViewModel(
IDetailsElement _detailsElement,

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsLinkViewModel(
IDetailsElement _detailsElement,

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsTagsViewModel(
IDetailsElement _detailsElement,

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsViewModel(IDetails _details, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ExtensionObjectViewModel : ObservableObject
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public class GlobalLogPageContext : IPageContext
{

View File

@@ -8,7 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IContextItemViewModel
{

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IRootPageService
{
/// <summary>
/// Gets the root page of the command palette. Return any IPage implementation that
/// represents the root view of this instance of the command palette.
/// </summary>
Microsoft.CommandPalette.Extensions.IPage GetRootPage();
/// <summary>
/// Pre-loads any necessary data or state before the root page is loaded.
/// This will be awaited before the root page and the user can do anything,
/// so ideally it should be quick and not block the UI thread for long.
/// </summary>
Task PreLoadAsync();
/// <summary>
/// Do any loading work that can be done after the root page is loaded and
/// displayed to the user.
/// This is run asynchronously, on a background thread.
/// </summary>
Task PostLoadRootPageAsync();
/// <summary>
/// Called when a command is performed. The context is the
/// sender context for the invoked command. This is typically the IListItem
/// or ICommandContextItem that was used to invoke the command.
/// </summary>
void OnPerformCommand(object? context, bool topLevel, AppExtensionHost? currentHost);
void GoHome();
}

View File

@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Windows.Storage.Streams;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class IconDataViewModel : ObservableObject, IIconData
{

View File

@@ -1,12 +1,12 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class IconInfoViewModel : ObservableObject, IIconInfo
{

View File

@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
: CommandItemViewModel(new(model), context)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -6,13 +6,13 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ListViewModel : PageViewModel, IDisposable
{
@@ -72,7 +72,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public ListViewModel(IListPage model, TaskScheduler scheduler, CommandPaletteHost host)
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
_model = new(model);
@@ -132,7 +132,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
try
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
stopwatch.Start();
var newItems = _model.Unsafe!.GetItems();
var getItemsTime = stopwatch.ElapsedMilliseconds;
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
@@ -158,10 +162,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
// Cancel any ongoing search
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Cancel();
}
_cancellationTokenSource?.Cancel();
lock (_listLock)
{
@@ -172,6 +173,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
// TODO: Iterate over everything in Items, and prune items from the
// cache if we don't need them anymore
stopwatch.Stop();
var initializeTime = stopwatch.ElapsedMilliseconds - getItemsTime;
WeakReferenceMessenger.Default.Send<FetchItemsMetricsMessage>(new(
newViewModels.Count,
getItemsTime,
initializeTime
));
}
catch (Exception ex)
{
@@ -300,13 +308,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
if (item != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command.Model, item.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item));
}
else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
EmptyContent.PrimaryCommand.Command.Model,
EmptyContent.PrimaryCommand.Model));
EmptyContent.PrimaryCommand));
}
}
@@ -318,14 +325,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
if (item.SecondaryCommand != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand.Command.Model, item.Model));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand, item));
}
}
else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe != null)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
EmptyContent.SecondaryCommand.Command.Model,
EmptyContent.SecondaryCommand.Model));
EmptyContent.SecondaryCommand));
}
}
@@ -373,6 +379,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
TextToSuggest = item.TextToSuggest;
if (item.HasParameters)
{
WeakReferenceMessenger.Default.Send<UpdateParametersMessage>(new(item.Parameters));
}
else
{
WeakReferenceMessenger.Default.Send<UpdateParametersMessage>(new(null));
}
});
_lastSelectedItem = item;

View File

@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class LoadingPageViewModel : PageViewModel
{
public LoadingPageViewModel(IPage? model, TaskScheduler scheduler)
: base(model, scheduler, CommandPaletteHost.Instance)
public LoadingPageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
ModelIsLoading = true;
IsInitialized = false;

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class LogMessageViewModel : ExtensionObjectViewModel
{
@@ -13,8 +13,6 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public string Message { get; private set; } = string.Empty;
public string ExtensionPfn { get; set; } = string.Empty;
public LogMessageViewModel(ILogMessage message, IPageContext context)
: base(context)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to perform a list item's secondary command when the user presses ctrl+enter in the search box

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to perform a list item's command when the user presses enter in the search box

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record BeginInvokeMessage;

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ClearSearchMessage()
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to announce that a context menu should close

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record CmdPalInvokeResultMessage(Microsoft.CommandPalette.Extensions.CommandResultKind Kind);

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record DismissMessage()
{

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record FetchItemsMetricsMessage(int ItemCount, long GetItemsTime, long InitializeItemsTime)
{
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record FocusSearchBoxMessage()
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record GoBackMessage(bool WithAnimation = true, bool FocusSearch = true)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
// TODO! sticking these properties here feels like leaking the UI into the models
public record GoHomeMessage(bool WithAnimation = true, bool FocusSearch = true)

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HandleCommandResultMessage(ExtensionObject<ICommandResult> Result)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HideDetailsMessage()
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HotkeySummonMessage(string CommandId, IntPtr Hwnd)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record LaunchUriMessage(Uri Uri)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record NavigateBackMessage(bool FromBackspace = false)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate to the next command in the page when pressing the Down key in the SearchBox.

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate to the previous command in the page when pressing the Down key in the SearchBox.

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record NavigateToPageMessage(PageViewModel Page, bool WithAnimation)
{

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -6,7 +6,7 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to announce the context menu should open

View File

@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record OpenSettingsMessage()
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to do a command - navigate to a page or invoke it
@@ -18,21 +18,13 @@ public record PerformCommandMessage
public bool WithAnimation { get; set; } = true;
public CommandPaletteHost? ExtensionHost { get; private set; }
public PerformCommandMessage(ExtensionObject<ICommand> command)
{
Command = command;
Context = null;
}
public PerformCommandMessage(TopLevelViewModel topLevelCommand)
{
Command = topLevelCommand.CommandViewModel.Model;
Context = null;
ExtensionHost = topLevelCommand.ExtensionHost;
}
public ICommandArgument?[] Arguments { get; set; } = [];
// public PerformCommandMessage(ExtensionObject<ICommand> command)
// {
// Command = command;
// Context = null;
// }
public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<IListItem> context)
{
Command = command;
@@ -57,6 +49,16 @@ public record PerformCommandMessage
Context = contextCommand.Model.Unsafe;
}
public PerformCommandMessage(CommandItemViewModel item, CommandItemViewModel? context = null)
{
Command = item.Command.Model;
Context = context?.Model.Unsafe ?? item.Model.Unsafe;
if (item.Parameters != null && item.Parameters.Any())
{
Arguments = item.Parameters.Select(p => p.Model.Unsafe).ToArray();
}
}
public PerformCommandMessage(ConfirmResultViewModel vm)
{
Command = vm.PrimaryCommand.Model;

View File

@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Message which closes the application. Used by <see cref="QuitCommand"/> via <see cref="BuiltInsCommandProvider"/>.

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ReloadCommandsMessage()
{

View File

@@ -0,0 +1,12 @@
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record RequestOpenPickerMessage(ExtensionObject<ICommandArgument> Argument)
{
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record SettingsWindowClosedMessage
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowConfirmationMessage(Microsoft.CommandPalette.Extensions.IConfirmationArgs? Args)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowDetailsMessage(DetailsViewModel Details)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowToastMessage(string Message)
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowWindowMessage(IntPtr Hwnd)
{

View File

@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation
// 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 Windows.System;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record TryCommandKeybindingMessage(bool Ctrl, bool Alt, bool Shift, bool Win, VirtualKey Key)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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.ComponentModel;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to update the command bar at the bottom to reflect the commands for a list item

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record UpdateFallbackItemsMessage()
{

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record UpdateParametersMessage(IEnumerable<ArgumentItemViewModel>? Parameters)
{
}

View File

@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<!-- For MVVM Toolkit Partial Properties/AOT support -->
<LangVersion>preview</LangVersion>
<!-- Disable SA1313 for Primary Constructor fields conflict https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors -->
<NoWarn>SA1313;</NoWarn>
</PropertyGroup>
<PropertyGroup>
<CsWinRTAotOptimizerEnabled>true</CsWinRTAotOptimizerEnabled>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="WyHash" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<ProjectReference Include="..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<!-- Just mark it as AOT compatible. Do not publish with AOT now. We need fully test before we really publish it as AOT enabled-->
<!--<PropertyGroup>
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
--><!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> --><!--
<PublishAot>true</PublishAot>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>-->
</Project>

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Models;
namespace Microsoft.CmdPal.Core.ViewModels.Models;
public class ExtensionObject<T>(T? value) // where T : IInspectable
{

View File

@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false
}

View File

@@ -0,0 +1,19 @@
GetPhysicallyInstalledSystemMemory
GlobalMemoryStatusEx
GetSystemInfo
CoCreateInstance
SetForegroundWindow
IsIconic
RegisterHotKey
SetWindowLongPtr
CallWindowProc
ShowWindow
SetForegroundWindow
SetFocus
SetActiveWindow
MonitorFromWindow
GetMonitorInfo
SHCreateStreamOnFileEx
CoAllowSetForegroundWindow
SHCreateStreamOnFileEx
SHLoadIndirectString

View File

@@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation
// 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.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
@@ -43,7 +43,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public bool ShowSuggestion => !string.IsNullOrEmpty(TextToSuggest) && TextToSuggest != Filter;
[ObservableProperty]
public partial CommandPaletteHost ExtensionHost { get; private set; }
public partial AppExtensionHost ExtensionHost { get; private set; }
public bool HasStatusMessage => MostRecentStatusMessage != null;
@@ -69,7 +69,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public IconInfoViewModel Icon { get; protected set; }
public PageViewModel(IPage? model, TaskScheduler scheduler, CommandPaletteHost extensionHost)
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost)
: base((IPageContext?)null)
{
_pageModel = new(model);
@@ -220,7 +220,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
// Set the extensionHint to the Page Title (if we have one, and one not provided).
// extensionHint ??= _pageModel?.Unsafe?.Title;
extensionHint ??= ExtensionHost.Extension?.ExtensionDisplayName ?? Title;
extensionHint ??= ExtensionHost.GetExtensionDisplayName() ?? Title;
Task.Factory.StartNew(
() =>
{
@@ -249,7 +249,19 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public interface IPageContext
{
public void ShowException(Exception ex, string? extensionHint = null);
void ShowException(Exception ex, string? extensionHint = null);
public TaskScheduler Scheduler { get; }
TaskScheduler Scheduler { get; }
}
public interface IPageViewModelFactoryService
{
/// <summary>
/// Creates a new instance of the page view model for the given page type.
/// </summary>
/// <param name="page">The page for which to create the view model.</param>
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
/// <param name="host">The command palette host that will host the page (for status messages)</param>
/// <returns>A new instance of the page view model.</returns>
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host);
}

View File

@@ -0,0 +1,27 @@
// 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 Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class PageViewModelFactory : IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
public PageViewModelFactory(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
}

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ProgressViewModel : ExtensionObjectViewModel
{

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
{

View File

@@ -1,28 +1,24 @@
// Copyright (c) Microsoft Corporation
// 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;
using System.Runtime.Versioning;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
{
private readonly IServiceProvider _serviceProvider;
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
@@ -60,18 +56,24 @@ public partial class ShellViewModel : ObservableObject,
}
}
private MainListPage? _mainListPage;
private IPage? _rootPage;
private IExtensionWrapper? _activeExtension;
private bool _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(IServiceProvider serviceProvider, TaskScheduler scheduler)
public ShellViewModel(
TaskScheduler scheduler,
IRootPageService rootPageService,
IPageViewModelFactoryService pageViewModelFactory,
IAppHostService appHostService)
{
_serviceProvider = serviceProvider;
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_currentPage = new LoadingPageViewModel(null, _scheduler);
_rootPageService = rootPageService;
_appHostService = appHostService;
_currentPage = new LoadingPageViewModel(null, _scheduler, appHostService.GetDefaultHost());
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
@@ -80,24 +82,27 @@ public partial class ShellViewModel : ObservableObject,
[RelayCommand]
public async Task<bool> LoadAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>();
await tlcManager!.LoadBuiltinsAsync();
// First, do any loading that the root page service needs to do before we can
// display the root page. For example, this might include loading
// the built-in commands, or loading the settings.
await _rootPageService.PreLoadAsync();
IsLoaded = true;
// Built-ins have loaded. We can display our page at this point.
_mainListPage = new MainListPage(_serviceProvider);
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_mainListPage)));
// Now that the basics are set up, we can load the root page.
_rootPage = _rootPageService.GetRootPage();
// This sends a message to us to load the root page view model.
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage), new ExtensionObject<ICommandContextItem>(null)));
// Now that the root page is loaded, do any post-load work that the root page service needs to do.
// This runs asynchronously, on a background thread.
// This might include starting extensions, for example.
// Note: We don't await this, so that we can return immediately.
// This is important because we don't want to block the UI thread.
_ = Task.Run(async () =>
{
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
tlcManager.LoadExtensionsCommand.Execute(null);
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
{
// TODO: Handle failure case
}
await _rootPageService.PostLoadRootPageAsync();
});
return true;
@@ -170,19 +175,6 @@ public partial class ShellViewModel : ObservableObject,
}
}
public void PerformTopLevelCommand(PerformCommandMessage message)
{
if (_mainListPage == null)
{
return;
}
if (message.Context is IListItem listItem)
{
_mainListPage.UpdateHistory(listItem);
}
}
public void Receive(PerformCommandMessage message)
{
PerformCommand(message);
@@ -196,69 +188,21 @@ public partial class ShellViewModel : ObservableObject,
return;
}
if (!CurrentPage.IsNested)
{
// on the main page here
PerformTopLevelCommand(message);
}
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
IExtensionWrapper? extension = null;
_rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
try
{
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command,
// else just use the global one.
CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null)
{
if (messageHost != null)
{
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
}
else
{
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
}
}
SetActiveExtension(extension);
if (command is IPage page)
{
Logger.LogDebug($"Navigating to page");
var isMainPage = command is MainListPage;
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = GetViewModelForPage(page, !isMainPage, host);
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
@@ -267,31 +211,39 @@ public partial class ShellViewModel : ObservableObject,
// Kick off async loading of our ViewModel
LoadPageViewModel(pageViewModel);
_isNested = !isMainPage;
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
// See RootFrame_Navigated event handler.
}
else if (command is IInvokableCommandWithParameters commandWithParams)
{
Logger.LogDebug($"Invoking command with args");
// var args = ArgumentsViewModel.Arguments;
// var aa = args.Select(a => a.Model.Unsafe).ToArray() ?? [];
// message.Arguments = aa;
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
HandleInvokeCommandWithArgs(message, commandWithParams, host);
}
else if (command is IInvokableCommand invokable)
{
Logger.LogDebug($"Invoking command");
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
StartInvoke(message, invokable);
StartInvoke(message, invokable, host);
}
}
catch (Exception ex)
{
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
host?.Log(ex.Message);
}
}
private void StartInvoke(PerformCommandMessage message, IInvokableCommand invokable)
private void StartInvoke(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
@@ -304,13 +256,13 @@ public partial class ShellViewModel : ObservableObject,
{
_handleInvokeTask = Task.Run(() =>
{
SafeHandleInvokeCommandSynchronous(message, invokable);
SafeHandleInvokeCommandSynchronous(message, invokable, host);
});
}
}
}
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable)
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
{
try
{
@@ -330,7 +282,45 @@ public partial class ShellViewModel : ObservableObject,
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
host?.Log(ex.Message);
}
}
private void HandleInvokeCommandWithArgs(PerformCommandMessage message, IInvokableCommandWithParameters invokable, AppExtensionHost? host)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
else
{
_handleInvokeTask = Task.Run(() =>
{
try
{
// Call out to extension process.
// * May fail!
// * May never return!
var result = invokable.InvokeWithArgs(message.Context, message.Arguments);
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
_handleInvokeTask = null;
}
catch (Exception ex)
{
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
host?.Log(ex.Message);
}
});
}
}
}
@@ -405,54 +395,9 @@ public partial class ShellViewModel : ObservableObject,
}
}
private PageViewModel? GetViewModelForPage(IPage page, bool nested, CommandPaletteHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host)
{
IsNested = nested,
},
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
public void SetActiveExtension(IExtensionWrapper? extension)
{
if (extension != _activeExtension)
{
// There's not really a CoDisallowSetForegroundWindow, so we don't
// need to handle that
_activeExtension = extension;
var extensionWinRtObject = _activeExtension?.GetExtensionObject();
if (extensionWinRtObject != null)
{
try
{
unsafe
{
var winrtObj = (IWinRTObject)extensionWinRtObject;
var intPtr = winrtObj.NativeObject.ThisPtr;
var hr = Native.CoAllowSetForegroundWindow(intPtr);
if (hr != 0)
{
Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}");
}
}
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
}
}
}
public void GoHome(bool withAnimation = true, bool focusSearch = true)
{
SetActiveExtension(null);
_rootPageService.GoHome();
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(withAnimation, focusSearch));
}
@@ -461,20 +406,6 @@ public partial class ShellViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
// You may ask yourself, why aren't we using CsWin32 for this?
// The CsWin32 projected version includes some object marshalling, like so:
//
// HRESULT CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk,...)
//
// And if you do it like that, then the IForegroundTransfer interface isn't marshalled correctly
internal sealed class Native
{
[DllImport("OLE32.dll", ExactSpelling = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class StatusMessageViewModel : ExtensionObjectViewModel
{
@@ -15,14 +15,10 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public MessageState State { get; private set; } = MessageState.Info;
public string ExtensionPfn { get; set; } = string.Empty;
public ProgressViewModel? Progress { get; private set; }
public bool HasProgress => Progress != null;
// public bool IsIndeterminate => Progress != null && Progress.IsIndeterminate;
// public double ProgressValue => (Progress?.ProgressPercent ?? 0) / 100.0;
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context)
: base(context)
{

View File

@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{

View File

@@ -4,9 +4,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ToastViewModel : ObservableObject
{

View File

@@ -4,7 +4,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -38,7 +38,8 @@ public partial class AliasManager : ObservableObject
if (topLevelCommand != null)
{
WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(topLevelCommand));
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(topLevelCommand.GetPerformCommandMessage());
return true;
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandPaletteContentPageViewModel : ContentPageViewModel
{
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
}
public override ContentViewModel? ViewModelFromContent(IContent content, WeakReference<IPageContext> context)
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
_ => null,
};
return viewModel;
}
}

View File

@@ -1,35 +1,19 @@
// Copyright (c) Microsoft Corporation
// 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.ObjectModel;
using System.Diagnostics;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class CommandPaletteHost : IExtensionHost
public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHost
{
// Static singleton, so that we can access this from anywhere
// Post MVVM - this should probably be like, a dependency injection thing.
public static CommandPaletteHost Instance { get; } = new();
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private static ulong _hostingHwnd;
public ulong HostingHwnd => _hostingHwnd;
public string LanguageOverride => string.Empty;
public static ObservableCollection<LogMessageViewModel> LogMessages { get; } = [];
public ObservableCollection<StatusMessageViewModel> StatusMessages { get; } = [];
public IExtensionWrapper? Extension { get; }
private readonly ICommandProvider? _builtInProvider;
@@ -48,145 +32,8 @@ public sealed partial class CommandPaletteHost : IExtensionHost
_builtInProvider = builtInProvider;
}
public IAsyncAction ShowStatus(IStatusMessage? message, StatusContext context)
public override string? GetExtensionDisplayName()
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
Debug.WriteLine(message.Message);
_ = Task.Run(() =>
{
ProcessStatusMessage(message, context);
});
return Task.CompletedTask.AsAsyncAction();
}
public IAsyncAction HideStatus(IStatusMessage? message)
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
_ = Task.Run(() =>
{
ProcessHideStatusMessage(message);
});
return Task.CompletedTask.AsAsyncAction();
}
public IAsyncAction LogMessage(ILogMessage? message)
{
if (message == null)
{
return Task.CompletedTask.AsAsyncAction();
}
Logger.LogDebug(message.Message);
_ = Task.Run(() =>
{
ProcessLogMessage(message);
});
// We can't just make a LogMessageViewModel : ExtensionObjectViewModel
// because we don't necessarily know the page context. Butts.
return Task.CompletedTask.AsAsyncAction();
}
public void ProcessLogMessage(ILogMessage message)
{
var vm = new LogMessageViewModel(message, _globalLogPageContext);
vm.SafeInitializePropertiesSynchronous();
if (Extension != null)
{
vm.ExtensionPfn = Extension.PackageFamilyName;
}
Task.Factory.StartNew(
() =>
{
LogMessages.Add(vm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public void ProcessStatusMessage(IStatusMessage message, StatusContext context)
{
// If this message is already in the list of messages, just bring it to the top
var oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (oldVm != null)
{
Task.Factory.StartNew(
() =>
{
StatusMessages.Remove(oldVm);
StatusMessages.Add(oldVm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
return;
}
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
vm.SafeInitializePropertiesSynchronous();
if (Extension != null)
{
vm.ExtensionPfn = Extension.PackageFamilyName;
}
Task.Factory.StartNew(
() =>
{
StatusMessages.Add(vm);
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public void ProcessHideStatusMessage(IStatusMessage message)
{
Task.Factory.StartNew(
() =>
{
try
{
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (vm != null)
{
StatusMessages.Remove(vm);
}
}
catch
{
}
},
CancellationToken.None,
TaskCreationOptions.None,
_globalLogPageContext.Scheduler);
}
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
public void DebugLog(string message)
{
#if DEBUG
this.ProcessLogMessage(new LogMessage(message));
#endif
}
public void Log(string message)
{
this.ProcessLogMessage(new LogMessage(message));
return Extension?.ExtensionDisplayName;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public class CommandPalettePageViewModelFactory
: IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
public CommandPalettePageViewModelFactory(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
}

View File

@@ -4,7 +4,8 @@
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -3,10 +3,11 @@
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -23,6 +23,12 @@ public partial class BuiltInsCommandProvider : CommandProvider
[
new CommandItem(openSettings) { Subtitle = Properties.Resources.builtin_open_settings_subtitle },
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
new ListItem(new CommandWithParams() { Name = "Invoke with params 2" })
{
Title = "Do a thing with a string",
Subtitle = "This command requires more input",
Icon = new IconInfo("\uE961"),
}
];
public override IFallbackCommandItem[] FallbackCommands() =>
@@ -40,4 +46,46 @@ public partial class BuiltInsCommandProvider : CommandProvider
}
public override void InitializeWithHost(IExtensionHost host) => BuiltinsExtensionHost.Instance.Initialize(host);
internal sealed partial class CommandWithParams : InvokableCommand, IInvokableCommandWithParameters
{
public ICommandArgument[] Parameters => [new TextParam("Test")];
public ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args)
{
if (args.Length > 0)
{
var arg = args[0];
var msg = $"Arg {arg.Name} = {arg.Value}";
var toast = new ToastStatusMessage(new StatusMessage() { Message = msg, State = MessageState.Success });
toast.Show();
}
else
{
var toast = new ToastStatusMessage(new StatusMessage() { Message = "didn't work homes", State = MessageState.Error });
toast.Show();
}
return CommandResult.KeepOpen();
}
}
internal sealed partial class TextParam(string name, bool required = true) : BaseObservable, ICommandArgument
{
public string Name => name;
public bool Required => required;
public ParameterType Type => ParameterType.Text;
public object? Value { get; set; }
public string DisplayName => string.Empty;
public IIconInfo? Icon => null;
public void ShowPicker(ulong hostHwnd)
{
}
}
}

View File

@@ -2,15 +2,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CmdPal.Common;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Specialized;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -10,7 +11,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Commands;
public partial class LogMessagesPage : ListPage
{
private readonly List<IListItem> _listItems = new();
private readonly List<IListItem> _listItems = [];
public LogMessagesPage()
{
@@ -31,7 +32,6 @@ public partial class LogMessagesPage : ListPage
var li = new ListItem(new NoOpCommand())
{
Title = logMessageViewModel.Message,
Subtitle = logMessageViewModel.ExtensionPfn,
};
_listItems.Insert(0, li);
}

View File

@@ -6,8 +6,8 @@ using System.Collections.Immutable;
using System.Collections.Specialized;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -6,7 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;

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