Compare commits

...

35 Commits

Author SHA1 Message Date
Leilei Zhang
1074f9f812 test 2025-07-18 12:42:27 +08:00
Leilei Zhang
f478533db9 update reg 2025-07-18 11:40:39 +08:00
Leilei Zhang
1e0b6d5e38 fix merge error 2025-07-18 11:22:39 +08:00
Leilei Zhang
3bee31bfd7 Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/peekuitests 2025-07-18 10:33:01 +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
Leilei Zhang
a89d8476f8 use compare 2025-07-18 09:53:42 +08:00
Leilei Zhang
19ada53ce3 get image for testing 2025-07-17 15:58:16 +08:00
Leilei Zhang
0654edb18a update default program tests 2025-07-17 15:49:56 +08:00
Leilei Zhang
7d02867b60 add enter 2025-07-17 13:41:16 +08:00
Leilei Zhang
b05cf8bf9f update common function 2025-07-17 12:23:03 +08:00
Leilei Zhang
522f12a0b2 clean windows 2025-07-17 11:21:30 +08:00
Leilei Zhang
d12bbf0cbf close settings page before testing 2025-07-17 10:18:37 +08:00
Leilei Zhang
f067a925f6 add more tests 2025-07-16 20:41:35 +08: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
Leilei Zhang
37c690b216 update screenshot name 2025-07-16 17:49:08 +08:00
Leilei Zhang
f6ff5064a3 use all 2025-07-16 17:05:20 +08:00
Leilei Zhang
78884bb587 use wrong flag 2025-07-16 15:52:16 +08:00
Leilei Zhang
d2b81450e3 add retry 2025-07-16 14:59:18 +08:00
Leilei Zhang
425883508f update image pipeline 2025-07-16 14:09:25 +08:00
Leilei Zhang
41ff1f8cf7 add more image and add long time 2025-07-16 13:57:53 +08:00
Leilei Zhang
2ee40a99d5 exit command paletter 2025-07-16 12:52:30 +08: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
Leilei Zhang
943d79bb83 clean code 2025-07-16 10:58:43 +08:00
Leilei Zhang
e42a1b8753 add test run title 2025-07-16 10:22:53 +08:00
Leilei Zhang
ed424faea5 add png 2025-07-16 10:11:56 +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
Leilei Zhang
85db62e946 add more time 2025-07-16 00:09:18 +08:00
Leilei Zhang
96de5d7a87 add png 2025-07-15 23:00:54 +08:00
Leilei Zhang
8a1aff68c1 add pin and unpin 2025-07-15 21:17:22 +08:00
Leilei Zhang
f19c2e1a78 create image 2025-07-15 09:34:33 +08:00
Leilei Zhang
1dab45c87c check file 2025-07-14 21:44:43 +08:00
Leilei Zhang
0040112855 add delay 2025-07-14 19:48:00 +08:00
Leilei Zhang
6205e25eb5 add delay 2025-07-14 18:54:44 +08:00
Leilei Zhang
dbe3ac6077 add peek ui tests 2025-07-14 17:50:41 +08:00
255 changed files with 2302 additions and 918 deletions

View File

@@ -218,6 +218,7 @@ coclass
CODENAME
codereview
Codespaces
Coen
COINIT
colid
colorconv
@@ -1614,6 +1615,8 @@ svgz
SVSI
SWFO
SWP
SWPNOSIZE
SWPNOZORDER
SWRESTORE
symbolrequestprod
SYMCACHE

View File

@@ -113,6 +113,23 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- pwsh: |-
# Set Windows Error Reporting to not show UI to prevent test hangs
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f
Write-Host "Windows Error Reporting DontShowUI set to prevent dialog popups during UI tests"
displayName: Configure Windows Error Reporting for UI Tests
- pwsh: |-
# Verify the setting was actually applied
$result = reg query "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI 2>$null
if ($result -match "DontShowUI.*REG_DWORD.*0x1") {
Write-Host "✓ Verification successful: DontShowUI is set to 1"
} else {
Write-Host "✗ Verification failed: DontShowUI setting not found or incorrect"
Write-Host "Registry query result: $result"
}
displayName: Verify Windows Error Reporting Configuration
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- task: DownloadPipelineArtifact@2
inputs:
@@ -160,6 +177,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 +200,7 @@ jobs:
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
testAssemblyVer2: |
**\*${{ module }}*.dll
!**\obj\**

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" />

View File

@@ -1496,6 +1496,7 @@ 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

View File

@@ -736,6 +736,12 @@ 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}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}"
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2718,6 +2724,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 +2740,14 @@ 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
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Debug|ARM64.Build.0 = Debug|ARM64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Debug|x64.ActiveCfg = Debug|x64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Debug|x64.Build.0 = Debug|x64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|ARM64.ActiveCfg = Release|ARM64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|ARM64.Build.0 = Release|ARM64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|x64.ActiveCfg = Release|x64
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3010,7 +3032,10 @@ 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}
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -364,7 +364,7 @@ namespace Microsoft.PowerToys.UITest
/// Save UI Element to a PNG file.
/// </summary>
/// <param name="path">the full path</param>
internal void SaveToPngFile(string path)
public void SaveToPngFile(string path)
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method SaveToPngFile with parameter: path = {path}");
this.windowsElement.GetScreenshot().SaveAsFile(path);

View File

@@ -91,15 +91,12 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Exit a exe.
/// Exit a exe by Name.
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
public void ExitExe(string appPath)
/// <param name="processName">The path to the application executable.</param>
public void ExitExeByName(string processName)
{
// Exit Exe
string exeName = Path.GetFileNameWithoutExtension(appPath);
Process[] processes = Process.GetProcessesByName(exeName);
Process[] processes = Process.GetProcessesByName(processName);
foreach (Process process in processes)
{
try
@@ -114,6 +111,18 @@ namespace Microsoft.PowerToys.UITest
}
}
/// <summary>
/// Exit a exe.
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
public void ExitExe(string appPath)
{
// Exit Exe
string exeName = Path.GetFileNameWithoutExtension(appPath);
ExitExeByName(exeName);
}
/// <summary>
/// Starts a new exe and takes control of it.
/// </summary>
@@ -122,26 +131,34 @@ namespace Microsoft.PowerToys.UITest
public void StartExe(string appPath, string[]? args = null)
{
var opts = new AppiumOptions();
opts.AddAdditionalCapability("app", appPath);
if (args != null && args.Length > 0)
if (scope == PowerToysModule.PowerToysSettings)
{
// Build command line arguments string
string argsString = string.Join(" ", args.Select(arg =>
TryLaunchPowerToysSettings(opts);
}
else
{
opts.AddAdditionalCapability("app", appPath);
if (args != null && args.Length > 0)
{
// Quote arguments that contain spaces
if (arg.Contains(' '))
// Build command line arguments string
string argsString = string.Join(" ", args.Select(arg =>
{
return $"\"{arg}\"";
}
// Quote arguments that contain spaces
if (arg.Contains(' '))
{
return $"\"{arg}\"";
}
return arg;
}));
return arg;
}));
opts.AddAdditionalCapability("appArguments", argsString);
opts.AddAdditionalCapability("appArguments", argsString);
}
}
this.Driver = NewWindowsDriver(opts);
Driver = NewWindowsDriver(opts);
}
private void TryLaunchPowerToysSettings(AppiumOptions opts)
@@ -150,13 +167,13 @@ namespace Microsoft.PowerToys.UITest
var runnerProcessInfo = new ProcessStartInfo
{
FileName = locationPath + this.runnerPath,
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
this.ExitExe(runnerProcessInfo.FileName);
this.runner = Process.Start(runnerProcessInfo);
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
Thread.Sleep(5000);
if (root != null)
@@ -168,7 +185,7 @@ namespace Microsoft.PowerToys.UITest
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
var settingsWindow = ApiHelper.FindDesktopWindowHandler(
new[] { windowName, AdministratorPrefix + windowName });
[windowName, AdministratorPrefix + windowName]);
if (settingsWindow.Count > 0)
{
@@ -187,6 +204,9 @@ namespace Microsoft.PowerToys.UITest
}
}
}
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
}
/// <summary>

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

@@ -22,6 +22,8 @@ namespace Microsoft.PowerToys.UITest
public bool IsInPipeline { get; }
public string? ScreenshotDirectory { get; set; }
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
private readonly PowerToysModule scope;
@@ -29,7 +31,6 @@ namespace Microsoft.PowerToys.UITest
private readonly string[]? commandLineArgs;
private SessionHelper? sessionHelper;
private System.Threading.Timer? screenshotTimer;
private string? screenshotDirectory;
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
@@ -58,11 +59,11 @@ namespace Microsoft.PowerToys.UITest
CloseOtherApplications();
if (IsInPipeline)
{
screenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
Directory.CreateDirectory(screenshotDirectory);
ScreenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
Directory.CreateDirectory(ScreenshotDirectory);
// Take screenshot every 1 second
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, screenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, ScreenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
// Escape Popups before starting
System.Windows.Forms.SendKeys.SendWait("{ESC}");
@@ -415,9 +416,9 @@ namespace Microsoft.PowerToys.UITest
protected void AddScreenShotsToTestResultsDirectory()
{
if (screenshotDirectory != null)
if (ScreenshotDirectory != null)
{
foreach (string file in Directory.GetFiles(screenshotDirectory))
foreach (string file in Directory.GetFiles(ScreenshotDirectory))
{
this.TestContext.AddResultFile(file);
}
@@ -627,6 +628,23 @@ namespace Microsoft.PowerToys.UITest
Console.WriteLine($"Failed to change display resolution. Error code: {result}");
}
}
// Windows API for moving windows
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
private const uint SWPNOSIZE = 0x0001;
private const uint SWPNOZORDER = 0x0004;
public static void MoveWindow(Element window, int x, int y)
{
var windowHandle = IntPtr.Parse(window.GetAttribute("NativeWindowHandle") ?? "0", System.Globalization.CultureInfo.InvariantCulture);
if (windowHandle != IntPtr.Zero)
{
SetWindowPos(windowHandle, IntPtr.Zero, x, y, 0, 0, SWPNOSIZE | SWPNOZORDER);
Task.Delay(500).Wait();
}
}
}
}
}

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

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

@@ -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,6 +149,7 @@ public partial class CommandBarViewModel : ObservableObject,
if (command.HasMoreCommands)
{
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
return ContextKeybindingResult.KeepOpen;
}
else

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,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 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 CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{

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 CommandViewModel : ExtensionObjectViewModel
{

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();

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)

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

@@ -2,9 +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.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Services;
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IRootPageService
{
@@ -29,9 +27,11 @@ public interface IRootPageService
Task PostLoadRootPageAsync();
/// <summary>
/// Called when a top-level command is performed. The context is the
/// 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 OnPerformTopLevelCommand(object? context);
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);
@@ -158,10 +158,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
// Cancel any ongoing search
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Cancel();
}
_cancellationTokenSource?.Cancel();
lock (_listLock)
{

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

@@ -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,12 @@ 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 PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<IListItem> context)
{
Command = command;

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

@@ -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,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(
() =>
{
@@ -263,5 +263,5 @@ public interface IPageViewModelFactoryService
/// <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, CommandPaletteHost host);
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host);
}

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 Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public class PageViewModelFactory : IPageViewModelFactoryService
{
@@ -15,7 +15,7 @@ public class PageViewModelFactory : IPageViewModelFactoryService
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, CommandPaletteHost host)
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{

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,25 +1,22 @@
// 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.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
{
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
@@ -61,17 +58,22 @@ public partial class ShellViewModel : ObservableObject,
private IPage? _rootPage;
private IExtensionWrapper? _activeExtension;
private bool _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService, IPageViewModelFactoryService pageViewModelFactory)
public ShellViewModel(
TaskScheduler scheduler,
IRootPageService rootPageService,
IPageViewModelFactoryService pageViewModelFactory,
IAppHostService appHostService)
{
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_currentPage = new LoadingPageViewModel(null, _scheduler);
_appHostService = appHostService;
_currentPage = new LoadingPageViewModel(null, _scheduler, appHostService.GetDefaultHost());
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
@@ -173,11 +175,6 @@ public partial class ShellViewModel : ObservableObject,
}
}
public void PerformTopLevelCommand(PerformCommandMessage message)
{
_rootPageService.OnPerformTopLevelCommand(message.Context);
}
public void Receive(PerformCommandMessage message)
{
PerformCommand(message);
@@ -191,61 +188,12 @@ 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");
@@ -274,18 +222,18 @@ public partial class ShellViewModel : ObservableObject,
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)
@@ -298,13 +246,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
{
@@ -324,7 +272,7 @@ 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);
}
}
@@ -399,41 +347,9 @@ public partial class ShellViewModel : ObservableObject,
}
}
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));
}
@@ -442,20 +358,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;

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;

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
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;

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
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;

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
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;

View File

@@ -7,8 +7,9 @@ using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Templating;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Windows.Data.Json;

View File

@@ -2,7 +2,8 @@
// 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.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;

View File

@@ -3,7 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -37,7 +38,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
var root = model.RootContent;
if (root != null)
{
RootContent = ContentPageViewModel.ViewModelFromContent(root, PageContext);
RootContent = ViewModelFromContent(root, PageContext);
RootContent?.InitializeProperties();
UpdateProperty(nameof(RootContent));
UpdateProperty(nameof(Root));
@@ -48,6 +49,20 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
model.ItemsChanged += Model_ItemsChanged;
}
// Theoretically, we should unify this with the one in CommandPalettePageViewModelFactory
// and maybe just have a ContentViewModelFactory or something
public 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;
}
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
private void Model_ItemsChanged(object sender, IItemsChangedEventArgs args) => FetchContent();
@@ -78,7 +93,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
var root = model.RootContent;
if (root != null)
{
RootContent = ContentPageViewModel.ViewModelFromContent(root, PageContext);
RootContent = ViewModelFromContent(root, PageContext);
}
else
{
@@ -103,7 +118,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
foreach (var item in newItems)
{
var viewModel = ContentPageViewModel.ViewModelFromContent(item, PageContext);
var viewModel = ViewModelFromContent(item, PageContext);
if (viewModel != null)
{
viewModel.InitializeProperties();

View File

@@ -56,6 +56,7 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<ProjectReference Include="..\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj" />
<ProjectReference Include="..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Apps\Microsoft.CmdPal.Ext.Apps.csproj" />

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.
@@ -12,7 +12,6 @@ using Windows.Win32;
using Windows.Win32.System.Com;
using WinRT;
// [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]
namespace Microsoft.CmdPal.UI.ViewModels.Models;
public class ExtensionWrapper : IExtensionWrapper

View File

@@ -6,7 +6,8 @@ using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -10,7 +10,8 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;

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