Compare commits

...

10 Commits

Author SHA1 Message Date
Leilei Zhang
75d6ad74d1 add w10 entryback 2025-08-25 17:36:20 +08:00
Mike Griese
ef4e619350 CmdPal: Fix winget in release builds (#41183)
Seems as though that doing `.ToArray` on the `IIterable<>` things we get
back from the winget API were not trim-safe. I'm not entirely sure why.

But we're totally okay just manually iterating over these things. That
works like a charm.

Closes #41172

As a drive-by, I wrapped the line from #41025 in a try-catch. If that
doesn't fix it, hopefully it at least gives us more logging.
2025-08-25 14:56:15 +08:00
Mike Griese
7f3349b3f5 CmdPal: fix missing builtin icons (#41298)
It would seem that the way we absorb the icons for built-in extension
into our package relies on the _extension_ package including WASDK. I
don't fully understand why.

This PR adds a common `.props` file we can use for all extensions, to
make sure they include it.

regressed in #41261
Closes #41279
2025-08-25 09:50:32 +08:00
leileizhang
9c285856bf [UI tests] Add accessibility IDs to FancyZones to fix part UI tests (#41316)
<!-- 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
Not sure why some text changed — for example, this button was ‘Launch
layout editor’, but in the UI it now shows as ‘Open layout editor’, so
it can’t be found :
<img width="1159" height="87" alt="image"
src="https://github.com/user-attachments/assets/d407a8fc-2876-4a85-9637-14d5923493d2"
/>

But using accessibility IDs is always more reliable.



<!-- 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
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-08-23 11:20:18 +08:00
Tibère B.
d90575b8da feat(MouseWithoutBorders): Prevent Easy Mouse from moving to another machine when an application is running in fullscreen mode. (#39854)
<!-- 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 adds a new feature to Easy Mouse, it is now possible to toggle a
setting that will prevent Easy Mouse to switch away from the host
machine when the foreground application is running in full screen mode,
requiring the user to first alt tab out of the application before
performing the switch, this also comes with a way to allow the switch on
specific apps.


![image](https://github.com/user-attachments/assets/e45bbfa7-89c9-4051-8f1a-f2ac2648a6ca)

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

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

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

This PR changes the way Easy Mouse checks wherever it should move to
another machine, after checking that the corresponding setting is
enabled and that we are trying to move away from the host machine, it
will run a test using native WinAPI methods to get the foreground window
and check if it is running in full screen.

If it is, it will then check the name of the executable against a list
of ignored app configured by the user, if the executable is found in
that list, the switch will be allowed despite the application running in
full screen.

These new settings were moved along with the original Easy Mouse toggle
to a new "Easy Mouse" setting group to avoid cluttering the Keyboard
shortcuts group.

This feature will only work when used from the controller machine, as I
didn't find a way to easily check for running application on a remote
machine that didn't involved touching the sockets, I felt like such a
change would be out of scope for this issue.

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

I had a hard time writing tests and didn't achieve anything meaningful
enough to be included, I may require some guidance on how to properly
write tests for this project.

I tested my changes by running my modified version of
MouseWithoutBorders on my machines, which I did for a few days now, It
allowed me to catch a few bugs, but it has been running smoothly
otherwise.

My changes didn't seemed to have caused any automated tests to fail.

It may require some additional testing for setups including more than
two machines.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kai Tao (from Dev Box) <kaitao@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
2025-08-22 20:42:36 +08:00
Jessica Dene Earley-Cha
da36d410e3 move Automation Notification functionality to UIHelper, implement UIHelper in ListPage and SettingsWindow (#41016)
## Summary of the Pull Request
Fixed #41014 and it overlapped with #40761, so I made a UIHelper
patterning off of WinUI Gallery's
[UIHelpder](0576fb508a/WinUIGallery/Helpers/UIHelper.cs (L63))

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

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



https://github.com/user-attachments/assets/011ee8c1-baaf-47fc-b8f8-ee489b01702b
2025-08-22 14:23:14 +08:00
Davide Giacometti
a50d548a07 [QuickAccent] Persist characters usage between runs (#37577)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

Persist characters usage between PowerToys/QuickAccent runs.

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

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

- Persist the dictionaries used to determine the characters usage in a
JSON file
`%LOCALAPPDATA%\Microsoft\PowerToys\QuickAccent\UsageInfo.json`

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

Manually tested:

- JSON is saved when PowerToys is closed and the **Sort characters by
usage frequency** is on
- JSON is deleted when QuickAccent is called and **Sort characters by
usage frequency** is off
- JSON is read when QuickAccent is started and characters order is
applied from the previous run

---------

Co-authored-by: Gleb Khmyznikov <gleb.khmyznikov@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-22 14:22:15 +08:00
Dave Rayment
8c4a3a6944 [QuickAccent] Update Topmost logic to attempt to fix hybrid graphics issues (#41044)
An attempt to fix a Quick Accent issue affecting laptops with 'Optimus'
hybrid graphics modes, where the utility locks the machine into discrete
graphics mode permanently.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR changes the Topmost behaviour for Quick Accent from always true
to only being true when the selection window is displayed.

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

- [x] Closes: #34849 (NB: requires testing on laptop with hybrid
graphics)
- [ ] **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

Topmost was set to `true` for the main application window on start,
which persisted for the lifetime of the application. This PR change
removes that, and instead dynamically toggles Topmost for the selection
window as it is activated/deactivated. The assumption is that the
FluentWindow-derived window is retaining graphics resources and
presenting as an active GPU consumer because of the main application
window's Topmost status, even if the selection window is hidden.

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

I've confirmed with manual tests that the existing QuickAccent
functionality appears to function identically with this change. However,
I do not own a laptop with a hybrid graphics capability, so am unable to
test whether this fixes the underlying problem. I will keep my fingers
crossed though 🤞😊

---------

Co-authored-by: Gleb Khmyznikov <gleb.khmyznikov@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-22 14:18:16 +08:00
Copilot
efc68bc0c9 Fix Spanish localization for Awake module name and all product name instances (#41252)
The Awake module name was being incorrectly translated to "Activo" in
Spanish localization, while it should remain as "Awake" (similar to how
"Text Extractor" remains untranslated).

**Issue:**
In the Spanish version of PowerToys Settings, the Awake module was
appearing as "Activo" instead of "Awake". This is inconsistent with
other module names like "Text Extractor" that remain in English.

**Root Cause:**
The localization system was translating strings that had generic
comments like "Product name: Navigation view item name for Awake".
Strings without comments or with specific "do not localize" comments are
preserved in their original language.

**Solution:**
Updated the resource file comments for all Awake-related strings to
include explicit localization prevention instructions:

1. Changed `Shell_Awake.Content` comment from "Product name: Navigation
view item name for Awake" to "Awake is a product name, do not localize"
2. Added "Awake is a product name, do not localize" comment to
`Awake.ModuleTitle` which previously had no comment
3. Added "Awake is a product name, do not localize" comment to OOBE (Out
of Box Experience) strings:
   - `Oobe_Awake.Description`
   - `Oobe_Awake_HowToUse.Text`
   - `Oobe_Awake_TipsAndTricks.Text`
4. Added "Awake is a product name, do not localize" comment to
`Awake_ModeSettingsCard.Description`

These changes follow the same pattern used by other PowerToys modules
(PowerRename, PowerToys Run, Shortcut Guide, etc.) to prevent
translation of product names across all user-facing contexts including
settings, navigation, and onboarding flows.

**Files Changed:**
- `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`

Fixes #41199.

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-08-22 13:57:22 +08:00
Davide Giacometti
bf74bc43d4 [Always On Top] Wait cursor fix (#41091)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

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

This PR resolves an issue where the wait cursor was incorrectly
displayed when the mouse hovered over the Always On Top window border.

_0.92.1_

![before](https://github.com/user-attachments/assets/40640734-7b49-4e50-9415-f005c8689ea9)

_PR_

![after](https://github.com/user-attachments/assets/95c8bf51-7ded-44ae-934a-53c4adf8d9e6)

- [x] Closes: #17923
- [ ] **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-08-22 13:55:18 +08:00
47 changed files with 717 additions and 159 deletions

View File

@@ -168,6 +168,7 @@ callbackptr
calpwstr
Cangjie
CANRENAME
Canvascustomlayout
CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
@@ -574,6 +575,7 @@ GPOCA
gpp
gpu
gradians
Gridcustomlayout
GSM
gtm
guiddata

View File

@@ -88,12 +88,9 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
FileLocksmithRuntimeRegistration::EnsureRegistered();
#endif
}
m_enabled = true;
}
@@ -101,13 +98,10 @@ public:
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered (Win10)");
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered (Win10)");
#endif
}
m_enabled = false;
}

View File

@@ -13,6 +13,7 @@ using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -1560,5 +1561,98 @@ namespace MouseWithoutBorders
}
}
}
private static bool DisableEasyMouseWhenForegroundWindowIsFullscreenSetting()
{
return Setting.Values.DisableEasyMouseWhenForegroundWindowIsFullscreen;
}
private static bool IsAppIgnoredByEasyMouseFullscreenCheck(IntPtr foregroundWindowHandle)
{
if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out var processId) == 0)
{
Logger.LogDebug($"GetWindowThreadProcessId failed with error : {Marshal.GetLastWin32Error()}");
return false;
}
var processHandle = NativeMethods.OpenProcess(0x1000, false, processId);
if (processHandle == IntPtr.Zero)
{
return false;
}
uint maxPath = 260;
var nameBuffer = new char[maxPath];
if (!NativeMethods.QueryFullProcessImageName(
processHandle, NativeMethods.QUERY_FULL_PROCESS_NAME_FLAGS.DEFAULT, nameBuffer, ref maxPath))
{
Logger.LogDebug($"QueryFullProcessImageName failed with error : {Marshal.GetLastWin32Error()}");
NativeMethods.CloseHandle(processHandle);
return false;
}
NativeMethods.CloseHandle(processHandle);
var name = new string(nameBuffer, 0, (int)maxPath);
var excludedApps = Setting.Values.EasyMouseFullscreenSwitchBlockExcludedApps;
return excludedApps.Contains(Path.GetFileNameWithoutExtension(name), StringComparer.OrdinalIgnoreCase)
|| excludedApps.Contains(Path.GetFileName(name), StringComparer.OrdinalIgnoreCase);
}
internal static bool IsEasyMouseBlockedByFullscreenWindow()
{
var shellHandle = NativeMethods.GetShellWindow();
var desktopHandle = NativeMethods.GetDesktopWindow();
var foregroundHandle = NativeMethods.GetForegroundWindow();
// If the foreground window is either the desktop or the Windows shell, we are not in fullscreen mode.
if (foregroundHandle.Equals(shellHandle) || foregroundHandle.Equals(desktopHandle))
{
return false;
}
if (NativeMethods.SHQueryUserNotificationState(out var userNotificationState) != 0)
{
Logger.LogDebug($"SHQueryUserNotificationState failed with error : {Marshal.GetLastWin32Error()}");
return false;
}
switch (userNotificationState)
{
// An application running in full screen mode, check if the foreground window is
// listed as ignored in the settings.
case NativeMethods.USER_NOTIFICATION_STATE.BUSY:
case NativeMethods.USER_NOTIFICATION_STATE.RUNNING_D3D_FULL_SCREEN:
case NativeMethods.USER_NOTIFICATION_STATE.PRESENTATION_MODE:
return !IsAppIgnoredByEasyMouseFullscreenCheck(foregroundHandle);
// No full screen app running.
case NativeMethods.USER_NOTIFICATION_STATE.NOT_PRESENT:
case NativeMethods.USER_NOTIFICATION_STATE.ACCEPTS_NOTIFICATIONS:
case NativeMethods.USER_NOTIFICATION_STATE.QUIET_TIME:
// Cannot determine
case NativeMethods.USER_NOTIFICATION_STATE.APP:
default:
return false;
}
}
/// <summary>
/// Check if a machine switch triggered by EasyMouse would be allowed to proceed due to other settings.
/// </summary>
/// <returns>A boolean that tells us if the switch isn't blocked by any other settings</returns>
internal static bool IsEasyMouseSwitchAllowed()
{
// Never prevent a switch if we are not moving out of the host machine.
if (!DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() || DesMachineID != MachineID)
{
return true;
}
// Check if the switch is blocked by a full-screen window running in the foreground
return !IsEasyMouseBlockedByFullscreenWindow();
}
}
}

View File

@@ -122,9 +122,16 @@ namespace MouseWithoutBorders.Class
[DllImport("user32.dll", SetLastError = false)]
internal static extern IntPtr GetDesktopWindow();
[LibraryImport("user32.dll")]
internal static partial IntPtr GetShellWindow();
[DllImport("user32.dll")]
internal static extern IntPtr GetWindowDC(IntPtr hWnd);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int DrawText(IntPtr hDC, string lpString, int nCount, ref RECT lpRect, uint uFormat);
@@ -291,6 +298,17 @@ namespace MouseWithoutBorders.Class
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[LibraryImport("kernel32.dll",
EntryPoint = "QueryFullProcessImageNameW",
SetLastError = true,
StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool QueryFullProcessImageName(
IntPtr hProcess, QUERY_FULL_PROCESS_NAME_FLAGS dwFlags, [Out] char[] lpExeName, ref uint lpdwSize);
[LibraryImport("shell32.dll", SetLastError = true)]
internal static partial int SHQueryUserNotificationState(out USER_NOTIFICATION_STATE state);
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
@@ -333,11 +351,11 @@ namespace MouseWithoutBorders.Class
[DllImport("ntdll.dll")]
internal static extern int NtQueryInformationProcess(
IntPtr hProcess,
int processInformationClass /* 0 */,
ref PROCESS_BASIC_INFORMATION processBasicInformation,
uint processInformationLength,
out uint returnLength);
IntPtr hProcess,
int processInformationClass /* 0 */,
ref PROCESS_BASIC_INFORMATION processBasicInformation,
uint processInformationLength,
out uint returnLength);
#endif
#if USE_GetSecurityDescriptorSacl
@@ -632,14 +650,14 @@ namespace MouseWithoutBorders.Class
{
internal int LowPart;
internal int HighPart;
}// end struct
} // end struct
[StructLayout(LayoutKind.Sequential)]
internal struct LUID_AND_ATTRIBUTES
{
internal LUID Luid;
internal int Attributes;
}// end struct
} // end struct
[StructLayout(LayoutKind.Sequential)]
internal struct TOKEN_PRIVILEGES
@@ -670,23 +688,23 @@ namespace MouseWithoutBorders.Class
internal const int TOKEN_ADJUST_SESSIONID = 0x0100;
internal const int TOKEN_ALL_ACCESS_P = STANDARD_RIGHTS_REQUIRED |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT;
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT;
internal const int TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID;
internal const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
internal const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT;
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT;
internal const int TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE;
@@ -940,6 +958,30 @@ namespace MouseWithoutBorders.Class
NameDnsDomain = 12,
}
internal enum MONITOR_FROM_WINDOW_FLAGS : uint
{
DEFAULT_TO_NULL = 0x00000000,
DEFAULT_TO_PRIMARY = 0x00000001,
DEFAULT_TO_NEAREST = 0x00000002,
}
internal enum QUERY_FULL_PROCESS_NAME_FLAGS : uint
{
DEFAULT = 0x00000000,
PROCESS_NAME_NATIVE = 0x00000001,
}
internal enum USER_NOTIFICATION_STATE
{
NOT_PRESENT = 1,
BUSY = 2,
RUNNING_D3D_FULL_SCREEN = 3,
PRESENTATION_MODE = 4,
ACCEPTS_NOTIFICATIONS = 5,
QUIET_TIME = 6,
APP = 7,
}
[DllImport("secur32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool GetUserNameEx(int nameFormat, StringBuilder userName, ref uint userNameSize);

View File

@@ -414,6 +414,44 @@ namespace MouseWithoutBorders.Class
}
}
internal bool DisableEasyMouseWhenForegroundWindowIsFullscreen
{
get
{
lock (_loadingSettingsLock)
{
return _properties.DisableEasyMouseWhenForegroundWindowIsFullscreen;
}
}
set
{
lock (_loadingSettingsLock)
{
_properties.DisableEasyMouseWhenForegroundWindowIsFullscreen = value;
}
}
}
internal HashSet<string> EasyMouseFullscreenSwitchBlockExcludedApps
{
get
{
lock (_loadingSettingsLock)
{
return _properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value;
}
}
set
{
lock (_loadingSettingsLock)
{
_properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value = value;
}
}
}
internal string Enc(string st, bool dec, DataProtectionScope protectionScope)
{
if (st == null || st.Length < 1)

View File

@@ -66,13 +66,17 @@ internal static class Event
try
{
Common.PaintCount = 0;
bool switchByMouseEnabled = IsSwitchingByMouseEnabled();
if (switchByMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == Common.WM_MOUSEMOVE)
// Check if easy mouse setting is enabled.
bool isEasyMouseEnabled = IsSwitchingByMouseEnabled();
if (isEasyMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == Common.WM_MOUSEMOVE)
{
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
if (!p.IsEmpty)
// Check if easy mouse switches are disabled when an application is running in fullscreen mode,
// if they are, check that there is no application running in fullscreen mode before switching.
if (!p.IsEmpty && Common.IsEasyMouseSwitchAllowed())
{
Common.HasSwitchedMachineSinceLastCopy = true;
@@ -165,7 +169,8 @@ internal static class Event
string newDesMachineName = MachineStuff.NameFromID(newDesMachineID);
if (!Common.IsConnectedTo(newDesMachineID))
{// Connection lost, cancel switching
{
// Connection lost, cancel switching
Logger.LogDebug("No active connection found for " + newDesMachineName);
// ShowToolTip("No active connection found for [" + newDesMachineName + "]!", 500);

View File

@@ -246,9 +246,17 @@ LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexce
case WM_ERASEBKGND:
return TRUE;
// prevent from beeping if the border was clicked
// Prevent from beeping if the border was clicked
case WM_SETCURSOR:
{
HCURSOR hCursor = LoadCursorW(nullptr, IDC_ARROW);
if (hCursor)
{
SetCursor(hCursor);
}
return TRUE;
}
default:
{

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -181,15 +182,15 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
if (more is not null)
{
MoreCommands = more
.Select(item =>
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorViewModel() as IContextItemViewModel;
return new SeparatorViewModel();
}
})
.ToList();
@@ -237,8 +238,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
FastInitializeProperties();
return true;
}
catch (Exception)
catch (Exception ex)
{
Logger.LogError("error fast initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -257,9 +259,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
SlowInitializeProperties();
return true;
}
catch (Exception)
catch (Exception ex)
{
Initialized |= InitializedState.Error;
Logger.LogError("error slow initializing CommandItemViewModel", ex);
}
return false;
@@ -272,8 +275,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
InitializeProperties();
return true;
}
catch (Exception)
catch (Exception ex)
{
Logger.LogError("error initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -342,15 +346,15 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
if (more is not null)
{
var newContextMenu = more
.Select(item =>
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
return new SeparatorViewModel() as IContextItemViewModel;
return new SeparatorViewModel();
}
})
.ToList();

View File

@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
@@ -162,11 +163,11 @@ public sealed partial class ListPage : Page,
if (listViewPeer is not null && li is not null)
{
var notificationText = li.Title;
listViewPeer.RaiseNotificationEvent(
Microsoft.UI.Xaml.Automation.Peers.AutomationNotificationKind.Other,
Microsoft.UI.Xaml.Automation.Peers.AutomationNotificationProcessing.MostRecent,
notificationText,
"CommandPaletteSelectedItemChanged");
UIHelper.AnnounceActionForAccessibility(
ItemsList,
notificationText,
"CommandPaletteSelectedItemChanged");
}
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
namespace Microsoft.CmdPal.UI.Helpers;
public static partial class UIHelper
{
static UIHelper()
{
}
public static void AnnounceActionForAccessibility(UIElement ue, string announcement, string activityID)
{
if (FrameworkElementAutomationPeer.FromElement(ue) is AutomationPeer peer)
{
peer.RaiseNotificationEvent(
AutomationNotificationKind.ActionCompleted,
AutomationNotificationProcessing.ImportantMostRecent,
announcement,
activityID);
}
}
}

View File

@@ -23,6 +23,12 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
<!-- <PropertyGroup>
<EnableCmdPalAOT>true</EnableCmdPalAOT>
<CIBuild>true</CIBuild>
</PropertyGroup> -->
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
<SelfContained>true</SelfContained>

View File

@@ -25,17 +25,11 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TO DO: Replace this with WinUI TitleBar once that ships. -->
<Button
x:Name="PaneToggleBtn"
Width="48"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Click="PaneToggleBtn_Click"
Style="{StaticResource PaneToggleButtonStyle}" />
<StackPanel
x:Name="AppTitleBar"
Grid.Row="0"
Height="48"
Margin="16,0,0,0"
Orientation="Horizontal">
<Image
Width="16"

View File

@@ -10,6 +10,7 @@ using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls;
using WinUIEx;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
@@ -22,6 +23,9 @@ public sealed partial class SettingsWindow : WindowEx,
{
public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
// Gets or sets optional action invoked after NavigationView is loaded.
public Action NavigationViewLoaded { get; set; } = () => { };
public SettingsWindow()
{
this.InitializeComponent();
@@ -35,10 +39,33 @@ public sealed partial class SettingsWindow : WindowEx,
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
}
// Handles NavigationView loaded event.
// Sets up initial navigation and accessibility notifications.
private void NavView_Loaded(object sender, RoutedEventArgs e)
{
// Delay necessary to ensure NavigationView visual state can match navigation
Task.Delay(500).ContinueWith(_ => this.NavigationViewLoaded?.Invoke(), TaskScheduler.FromCurrentSynchronizationContext());
NavView.SelectedItem = NavView.MenuItems[0];
Navigate("General");
if (sender is NavigationView navigationView)
{
// Register for pane open/close changes to announce to screen readers
navigationView.RegisterPropertyChangedCallback(NavigationView.IsPaneOpenProperty, AnnounceNavigationPaneStateChanged);
}
}
// Announces navigation pane open/close state to screen readers for accessibility.
private void AnnounceNavigationPaneStateChanged(DependencyObject sender, DependencyProperty dp)
{
if (sender is NavigationView navigationView)
{
UIHelper.AnnounceActionForAccessibility(
ue: (UIElement)sender,
(sender as NavigationView)?.IsPaneOpen == true ? RS_.GetString("NavigationPaneOpened") : RS_.GetString("NavigationPaneClosed"),
"NavigationViewPaneIsOpenChangeNotificationId");
}
}
private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
@@ -109,24 +136,15 @@ public sealed partial class SettingsWindow : WindowEx,
WeakReferenceMessenger.Default.UnregisterAll(this);
}
private void PaneToggleBtn_Click(object sender, RoutedEventArgs e)
{
NavView.IsPaneOpen = !NavView.IsPaneOpen;
}
private void NavView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
{
PaneToggleBtn.Visibility = Visibility.Visible;
NavView.IsPaneToggleButtonVisible = false;
AppTitleBar.Margin = new Thickness(48, 0, 0, 0);
}
else
{
PaneToggleBtn.Visibility = Visibility.Collapsed;
NavView.IsPaneToggleButtonVisible = true;
AppTitleBar.Margin = new Thickness(16, 0, 0, 0);
}
}

View File

@@ -435,4 +435,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="StatusMessagesButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Show status messages</value>
</data>
<data name="NavigationPaneClosed" xml:space="preserve">
<value>Navigation pane closed</value>
</data>
<data name="NavigationPageOpened" xml:space="preserve">
<value>Navigation page opened</value>
</data>
</root>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!--
Common external dependencies for all CmdPal extensions:
- Microsoft.WindowsAppSDK
- Microsoft.Web.WebView2
- Microsoft.CommandPalette.Extensions.Toolkit (via project reference)
We need the WASDK reference because without it, our image assets won't get
placed into our final package correctly.
-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Web.WebView2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Apps</RootNamespace>
<Nullable>enable</Nullable>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>Microsoft.CmdPal.Ext.Apps.pri</ProjectPriFileName>
</PropertyGroup>
<ItemGroup>
@@ -20,8 +24,8 @@
<PackageReference Include="WyHash" />
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Calc</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
@@ -17,7 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\CalculatorEngineCommon\CalculatorEngineCommon.vcxproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<CsWinRTInputs Include="..\..\..\..\..\$(Platform)\$(Configuration)\CalculatorEngineCommon.winmd" />

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.ClipboardHistory</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
@@ -10,10 +11,10 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" />
<!-- WASDK, WebView2, CmdPal Toolkit references now included via Common.ExtDependencies.props -->
</ItemGroup>
<!-- String resources -->

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Indexer</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
@@ -17,8 +18,7 @@
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Registry</RootNamespace>
@@ -16,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -2,6 +2,8 @@
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<Nullable>enable</Nullable>
<RootNamespace>Microsoft.CmdPal.Ext.Shell</RootNamespace>

View File

@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<Nullable>enable</Nullable>
<RootNamespace>Microsoft.CmdPal.Ext.System</RootNamespace>
@@ -8,9 +10,9 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<!-- WASDK, WebView2, CmdPal Toolkit references now included via Common.ExtDependencies.props -->
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.TimeDate</RootNamespace>
@@ -21,7 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WebSearch</RootNamespace>
<Nullable>enable</Nullable>
@@ -11,7 +12,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WinGet</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
@@ -39,7 +40,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
</ItemGroup>

View File

@@ -32,7 +32,16 @@ public partial class InstallPackageListItem : ListItem
{
_package = package;
var version = _package.DefaultInstallVersion ?? _package.InstalledVersion;
PackageVersionInfo? version = null;
try
{
version = _package.DefaultInstallVersion ?? _package.InstalledVersion;
}
catch (Exception e)
{
Logger.LogError("Could not get package version", e);
}
var versionTagText = "Unknown";
if (version is not null)
{
@@ -57,20 +66,27 @@ public partial class InstallPackageListItem : ListItem
}
catch (COMException ex)
{
Logger.LogWarning($"{ex.ErrorCode}");
Logger.LogWarning($"GetCatalogPackageMetadata error {ex.ErrorCode}");
}
if (metadata is not null)
{
if (metadata.Tags.Where(t => t.Equals(WinGetExtensionPage.ExtensionsTag, StringComparison.OrdinalIgnoreCase)).Any())
for (var i = 0; i < metadata.Tags.Count; i++)
{
if (_installCommand is not null)
if (metadata.Tags[i].Equals(WinGetExtensionPage.ExtensionsTag, StringComparison.OrdinalIgnoreCase))
{
_installCommand.SkipDependencies = true;
if (_installCommand is not null)
{
_installCommand.SkipDependencies = true;
}
break;
}
}
var description = string.IsNullOrEmpty(metadata.Description) ? metadata.ShortDescription : metadata.Description;
var description = string.IsNullOrEmpty(metadata.Description) ?
metadata.ShortDescription :
metadata.Description;
var detailsBody = $"""
{description}
@@ -116,9 +132,11 @@ public partial class InstallPackageListItem : ListItem
// These can be l o n g
{ Properties.Resources.winget_release_notes, (metadata.ReleaseNotes, string.Empty) },
};
var docs = metadata.Documentations.ToArray();
foreach (var item in docs)
var docs = metadata.Documentations;
var count = docs.Count;
for (var i = 0; i < count; i++)
{
var item = docs[i];
simpleData.Add(item.DocumentLabel, (string.Empty, item.DocumentUrl));
}
@@ -141,7 +159,7 @@ public partial class InstallPackageListItem : ListItem
}
}
if (metadata.Tags.Any())
if (metadata.Tags.Count > 0)
{
DetailsElement pair = new()
{
@@ -169,6 +187,7 @@ public partial class InstallPackageListItem : ListItem
{
// Handle other exceptions
ExtensionHost.LogMessage($"[WinGet] UpdatedInstalledStatus throw exception: {ex.Message}");
Logger.LogError($"[WinGet] UpdatedInstalledStatus throw exception", ex);
return;
}
@@ -233,10 +252,10 @@ public partial class InstallPackageListItem : ListItem
Stopwatch s = new();
Logger.LogDebug($"Starting RefreshPackageCatalogAsync");
s.Start();
var refs = WinGetStatics.AvailableCatalogs.ToArray();
foreach (var catalog in refs)
var refs = WinGetStatics.AvailableCatalogs;
for (var i = 0; i < refs.Count; i++)
{
var catalog = refs[i];
var operation = catalog.RefreshPackageCatalogAsync();
operation.Wait();
}

View File

@@ -32,7 +32,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
private CancellationTokenSource? _cancellationTokenSource;
private Task<IEnumerable<CatalogPackage>>? _currentSearchTask;
private IEnumerable<CatalogPackage>? _results;
private List<CatalogPackage>? _results;
public static string ExtensionsTag => "windows-commandpalette-extension";
@@ -48,7 +48,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
public override IListItem[] GetItems()
{
IListItem[] items = [];
lock (_resultsLock)
{
// emptySearchForTag ===
@@ -61,12 +60,28 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
{
IsLoading = true;
DoUpdateSearchText(string.Empty);
return items;
return [];
}
if (_results is not null && _results.Any())
if (_results is not null && _results.Count != 0)
{
ListItem[] results = _results.Select(PackageToListItem).ToArray();
var count = _results.Count;
var results = new ListItem[count];
var next = 0;
for (var i = 0; i < count; i++)
{
try
{
var li = PackageToListItem(_results[i]);
results[next] = li;
next++;
}
catch (Exception ex)
{
Logger.LogError("error converting result to listitem", ex);
}
}
IsLoading = false;
return results;
}
@@ -82,7 +97,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
IsLoading = false;
return items;
return [];
}
private static ListItem PackageToListItem(CatalogPackage p) => new InstallPackageListItem(p);
@@ -108,7 +123,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = _cancellationTokenSource.Token;
var cancellationToken = _cancellationTokenSource.Token;
IsLoading = true;
@@ -139,7 +154,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
{
try
{
IEnumerable<CatalogPackage> results = await searchTask;
var results = await searchTask;
// Ensure this is still the latest task
if (_currentSearchTask == searchTask)
@@ -156,7 +171,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
catch (Exception ex)
{
// Handle other exceptions
Logger.LogError(ex.Message);
Logger.LogError("Unexpected error while processing results", ex);
}
}
@@ -165,10 +180,10 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
Logger.LogDebug($"Completed search for '{query}'");
lock (_resultsLock)
{
this._results = results;
this._results = results.ToList();
}
RaiseItemsChanged(this._results.Count());
RaiseItemsChanged();
}
private async Task<IEnumerable<CatalogPackage>> DoSearchAsync(string query, CancellationToken ct)
@@ -190,12 +205,12 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
HashSet<CatalogPackage> results = new(new PackageIdCompare());
// Default selector: this is the way to do a `winget search <query>`
PackageMatchFilter selector = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
var selector = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
selector.Field = Microsoft.Management.Deployment.PackageMatchField.CatalogDefault;
selector.Value = query;
selector.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
FindPackagesOptions opts = WinGetStatics.WinGetFactory.CreateFindPackagesOptions();
var opts = WinGetStatics.WinGetFactory.CreateFindPackagesOptions();
opts.Selectors.Add(selector);
// testing
@@ -204,7 +219,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
// Selectors is "OR", Filters is "AND"
if (HasTag)
{
PackageMatchFilter tagFilter = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
var tagFilter = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
tagFilter.Field = Microsoft.Management.Deployment.PackageMatchField.Tag;
tagFilter.Value = _tag;
tagFilter.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
@@ -215,11 +230,11 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
// Clean up here, then...
ct.ThrowIfCancellationRequested();
Lazy<Task<PackageCatalog>> catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog;
var catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog;
// Both these catalogs should have been instantiated by the
// WinGetStatics static ctor when we were created.
PackageCatalog catalog = await catalogTask.Value;
var catalog = await catalogTask.Value;
if (catalog is null)
{
@@ -235,8 +250,8 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
// BODGY, re: microsoft/winget-cli#5151
// FindPackagesAsync isn't actually async.
Task<FindPackagesResult> internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct);
FindPackagesResult searchResults = await internalSearchTask;
var internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct);
var searchResults = await internalSearchTask;
// TODO more error handling like this:
if (searchResults.Status != FindPackagesResultStatus.Ok)
@@ -247,13 +262,17 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
}
Logger.LogDebug($" got results for ({query})", memberName: nameof(DoSearchAsync));
foreach (Management.Deployment.MatchResult? match in searchResults.Matches.ToArray())
// FYI Using .ToArray or any other kind of enumerable loop
// on arrays returned by the winget API are NOT trim safe
var count = searchResults.Matches.Count;
for (var i = 0; i < count; i++)
{
var match = searchResults.Matches[i];
ct.ThrowIfCancellationRequested();
// Print the packages
CatalogPackage package = match.CatalogPackage;
var package = match.CatalogPackage;
results.Add(package);
}

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<Nullable>enable</Nullable>
@@ -14,8 +15,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<!-- WASDK, WebView2, CmdPal Toolkit references now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WindowsServices</RootNamespace>
@@ -15,10 +16,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ServiceProcess.ServiceController" />
<!-- WASDK, WebView2, CmdPal Toolkit references now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WindowsSettings</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
@@ -18,10 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ServiceProcess.ServiceController" />
<!-- WASDK, WebView2, CmdPal Toolkit references now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<Import Project="..\..\Microsoft.CmdPal.UI\CmdPal.pre.props" />
<PropertyGroup>
@@ -16,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -283,8 +283,7 @@ namespace UITests_FancyZones
// Set Hotkey
this.AttachFancyZonesEditor();
var layout = "Grid custom layout";
Session.Find<Element>(layout).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
Session.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard)).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
const string key = "0";
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
Assert.IsNotNull(hotkeyComboBox);
@@ -299,7 +298,7 @@ namespace UITests_FancyZones
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num0);
Task.Delay(3000).Wait();
this.AttachFancyZonesEditor();
var element = this.Find<Element>(layout);
var element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
Assert.IsTrue(element.Selected, $"{element.Selected} Grid custom layout is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
@@ -307,7 +306,7 @@ namespace UITests_FancyZones
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num1);
Task.Delay(3000).Wait();
this.AttachFancyZonesEditor();
element = this.Find<Element>("Grid-9");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.Grid9LayoutCard));
Assert.IsTrue(element.Selected, $"{element.Selected} Grid-9 is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
@@ -315,7 +314,7 @@ namespace UITests_FancyZones
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num2);
Task.Delay(3000).Wait();
this.AttachFancyZonesEditor();
element = this.Find<Element>("Canvas custom layout");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.CanvasCustomLayoutCard));
Assert.IsTrue(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
@@ -424,21 +423,21 @@ namespace UITests_FancyZones
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num0);
this.AttachFancyZonesEditor();
var element = this.Find<Element>("Grid custom layout");
var element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
Assert.IsFalse(element.Selected, $"{element.Selected} Grid custom layout is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num1);
this.AttachFancyZonesEditor();
element = this.Find<Element>("Grid-9");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.Grid9LayoutCard));
Assert.IsFalse(element.Selected, $"{element.Selected} Grid-9 is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num2);
this.AttachFancyZonesEditor();
element = this.Find<Element>("Canvas custom layout");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.CanvasCustomLayoutCard));
Assert.IsFalse(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
this.CloseFancyZonesEditor();
this.AttachPowertoySetting();
@@ -453,7 +452,7 @@ namespace UITests_FancyZones
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
var element = this.Find<Element>("Grid custom layout");
var element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
element.Click();
this.CloseFancyZonesEditor();
this.ExitScopeExe();
@@ -463,8 +462,8 @@ namespace UITests_FancyZones
this.RestartScopeExe();
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
element = this.Find<Element>("Grid custom layout");
Assert.IsTrue(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
Assert.IsTrue(element.Selected, $"{element.Selected} Grid custom layout is not visible");
this.CloseFancyZonesEditor();
// close the virtual desktop
@@ -483,7 +482,7 @@ namespace UITests_FancyZones
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
var element = this.Find<Element>("Grid custom layout");
var element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
element.Click();
this.CloseFancyZonesEditor();
this.ExitScopeExe();
@@ -493,7 +492,7 @@ namespace UITests_FancyZones
this.RestartScopeExe();
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
element = this.Find<Element>("Grid-9");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.Grid9LayoutCard));
element.Click();
this.CloseFancyZonesEditor();
this.ExitScopeExe();
@@ -502,7 +501,7 @@ namespace UITests_FancyZones
this.RestartScopeExe();
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
element = this.Find<Element>("Grid custom layout");
element = this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard));
Assert.IsTrue(element.Selected, $"{element.Selected} Grid custom layout is not visible");
this.CloseFancyZonesEditor();
this.ExitScopeExe();
@@ -523,8 +522,8 @@ namespace UITests_FancyZones
this.OpenFancyZonesPanel();
this.AttachFancyZonesEditor();
this.Find<Element>("Grid custom layout").Click();
this.Find<Element>("Grid custom layout").Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard)).Click();
this.Find<Element>(By.AccessibilityId(AccessibilityId.GridCustomLayoutCard)).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
Session.Find<Button>(By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
Session.SendKeySequence(Key.Tab, Key.Enter);
@@ -619,7 +618,7 @@ namespace UITests_FancyZones
private void AttachFancyZonesEditor()
{
Task.Delay(4000).Wait();
this.Find<Button>("Launch layout editor").Click();
this.Find<Button>(By.AccessibilityId(AccessibilityId.LaunchLayoutEditorButton)).Click();
Task.Delay(3000).Wait();
this.Session.Attach(PowerToysModule.FancyZone);

View File

@@ -41,9 +41,13 @@ namespace Microsoft.FancyZonesEditor.UnitTests.Utils
public const string MainWindow = "MainWindow1";
public const string Monitors = "Monitors";
public const string NewLayoutButton = "NewLayoutButton";
public const string LaunchLayoutEditorButton = "LaunchLayoutEditorButton";
// layout card
public const string EditLayoutButton = "EditLayoutButton";
public const string GridCustomLayoutCard = "GridcustomlayoutCard";
public const string Grid9LayoutCard = "Grid-9Card";
public const string CanvasCustomLayoutCard = "CanvascustomlayoutCard";
// edit layout window: common for template and custom layouts
public const string DialogTitle = "EditLayoutDialogTitle";

View File

@@ -71,12 +71,22 @@ namespace FancyZonesEditor.Models
{
_name = value;
FirePropertyChanged(nameof(Name));
FirePropertyChanged(nameof(AutomationId));
}
}
}
private string _name;
// AutomationId - used for UI automation testing
public virtual string AutomationId
{
get
{
return _name?.Replace(" ", string.Empty) + "Card";
}
}
public LayoutType Type { get; set; }
#pragma warning disable CA1720 // Identifier contains type name (Not worth the effort to change this now.)

View File

@@ -145,6 +145,7 @@
<Setter Property="Background" Value="{DynamicResource LayoutItemBackgroundBrush}" />
<Setter Property="IsSelected" Value="{Binding IsApplied, Mode=OneWay}" />
<Setter Property="AutomationProperties.Name" Value="{Binding Name}" />
<Setter Property="AutomationProperties.AutomationId" Value="{Binding AutomationId}" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="Local" />
<!--<Setter Property="IsHoldingEnabled" Value="True" />-->
<Setter Property="CornerRadius" Value="4" />

View File

@@ -112,12 +112,9 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::EnsureRegistered();
ImageResizerRuntimeRegistration::EnsureRegistered();
#endif
}
Trace::EnableImageResizer(m_enabled);
}
@@ -127,13 +124,10 @@ public:
{
m_enabled = false;
Trace::EnableImageResizer(m_enabled);
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::Unregister();
Logger::info(L"ImageResizer context menu unregistered (Win10)");
ImageResizerRuntimeRegistration::Unregister();
Logger::info(L"ImageResizer context menu unregistered (Win10)");
#endif
}
}
// Returns if the powertoys is enabled

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace PowerAccent.Core.Models
{
public class UsageInfoData
{
public Dictionary<string, uint> CharacterUsageCounters { get; set; } = [];
public Dictionary<string, long> CharacterUsageTimestamp { get; set; } = [];
}
}

View File

@@ -117,8 +117,8 @@ public partial class PowerAccent : IDisposable
if (_settingService.SortByUsageFrequency)
{
characters = characters.OrderByDescending(character => _usageInfo.GetUsageFrequency(character))
.ThenByDescending(character => _usageInfo.GetLastUsageTimestamp(character)).
ToArray<string>();
.ThenByDescending(character => _usageInfo.GetLastUsageTimestamp(character))
.ToArray<string>();
}
else if (!_usageInfo.Empty())
{
@@ -337,6 +337,14 @@ public partial class PowerAccent : IDisposable
return _settingService.Position;
}
public void SaveUsageInfo()
{
if (_settingService.SortByUsageFrequency)
{
_usageInfo.Save();
}
}
public void Dispose()
{
_keyboardListener.UnInitHook();

View File

@@ -3,12 +3,14 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using PowerAccent.Core.Models;
using PowerAccent.Core.Services;
namespace PowerAccent.Core.SerializationContext;
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(SettingsService))]
[JsonSerializable(typeof(UsageInfoData))]
public partial class SourceGenerationContext : JsonSerializerContext
{
}

View File

@@ -2,12 +2,28 @@
// 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.IO;
using System.Text.Json;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using PowerAccent.Core.Models;
using PowerAccent.Core.SerializationContext;
namespace PowerAccent.Core.Tools
{
public class CharactersUsageInfo
{
private Dictionary<string, uint> _characterUsageCounters = new Dictionary<string, uint>();
private Dictionary<string, long> _characterUsageTimestamp = new Dictionary<string, long>();
private readonly string _filePath;
private readonly Dictionary<string, uint> _characterUsageCounters;
private readonly Dictionary<string, long> _characterUsageTimestamp;
public CharactersUsageInfo()
{
_filePath = new SettingsUtils().GetSettingsFilePath(PowerAccentSettings.ModuleName, "UsageInfo.json");
var data = GetUsageInfoData();
_characterUsageCounters = data.CharacterUsageCounters;
_characterUsageTimestamp = data.CharacterUsageTimestamp;
}
public bool Empty()
{
@@ -18,19 +34,18 @@ namespace PowerAccent.Core.Tools
{
_characterUsageCounters.Clear();
_characterUsageTimestamp.Clear();
Delete();
}
public uint GetUsageFrequency(string character)
{
_characterUsageCounters.TryGetValue(character, out uint frequency);
return frequency;
}
public long GetLastUsageTimestamp(string character)
{
_characterUsageTimestamp.TryGetValue(character, out long timestamp);
return timestamp;
}
@@ -47,5 +62,58 @@ namespace PowerAccent.Core.Tools
_characterUsageTimestamp[character] = DateTimeOffset.Now.ToUnixTimeSeconds();
}
public void Save()
{
var data = new UsageInfoData
{
CharacterUsageCounters = _characterUsageCounters,
CharacterUsageTimestamp = _characterUsageTimestamp,
};
try
{
var json = JsonSerializer.Serialize(data, SourceGenerationContext.Default.UsageInfoData);
File.WriteAllText(_filePath, json);
}
catch (Exception ex)
{
Logger.LogError("Failed to save usage file", ex);
}
}
public void Delete()
{
try
{
if (File.Exists(_filePath))
{
File.Delete(_filePath);
}
}
catch (Exception ex)
{
Logger.LogError("Failed to delete usage file", ex);
}
}
private UsageInfoData GetUsageInfoData()
{
if (!File.Exists(_filePath))
{
return new UsageInfoData();
}
try
{
var json = File.ReadAllText(_filePath);
return JsonSerializer.Deserialize(json, SourceGenerationContext.Default.UsageInfoData) ?? new UsageInfoData();
}
catch (Exception ex)
{
Logger.LogError("Failed to read usage file", ex);
return new UsageInfoData();
}
}
}
}

View File

@@ -44,7 +44,6 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);
Application.Current.MainWindow.ShowActivated = false;
Application.Current.MainWindow.Topmost = true;
}
protected override void OnSourceInitialized(EventArgs e)
@@ -64,6 +63,9 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
private void PowerAccent_OnChangeDisplay(bool isActive, string[] chars)
{
// Topmost is conditionally set here to address hybrid graphics issues on laptops.
this.Topmost = isActive;
CharacterNameVisibility = _powerAccent.ShowUnicodeDescription ? Visibility.Visible : Visibility.Collapsed;
if (isActive)
@@ -96,6 +98,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
protected override void OnClosed(EventArgs e)
{
_powerAccent.SaveUsageInfo();
_powerAccent.Dispose();
base.OnClosed(e);
}

View File

@@ -202,12 +202,9 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::EnsureRegistered();
PowerRenameRuntimeRegistration::EnsureRegistered();
#endif
}
}
// Disable the powertoy
@@ -215,13 +212,10 @@ public:
{
m_enabled = false;
Logger::info(L"PowerRename disabled");
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::Unregister();
Logger::info(L"PowerRename context menu unregistered (Win10)");
PowerRenameRuntimeRegistration::Unregister();
Logger::info(L"PowerRename context menu unregistered (Win10)");
#endif
}
}
// Returns if the powertoy is enabled

View File

@@ -92,6 +92,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public IntProperty EasyMouse { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool DisableEasyMouseWhenForegroundWindowIsFullscreen { get; set; }
// Apps that are to be excluded when using DisableEasyMouseWhenForegroundWindowIsFullscreen
// meaning that it is possible to switch screen when these apps are running in fullscreen.
[CmdConfigureIgnore]
public GenericProperty<HashSet<string>> EasyMouseFullscreenSwitchBlockExcludedApps { get; set; }
[CmdConfigureIgnore]
public IntProperty MachineID { get; set; }
@@ -173,6 +181,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ShowOriginalUI = false;
UseService = false;
DisableEasyMouseWhenForegroundWindowIsFullscreen = true;
EasyMouseFullscreenSwitchBlockExcludedApps = new GenericProperty<HashSet<string>>(new HashSet<string>(StringComparer.OrdinalIgnoreCase));
HotKeySwitchMachine = new IntProperty(0x70); // VK.F1
ToggleEasyMouseShortcut = DefaultHotKeyToggleEasyMouse;
LockMachineShortcut = DefaultHotKeyLockMachine;

View File

@@ -34,6 +34,7 @@
<tkcontrols:SettingsCard
x:Uid="FancyZones_LaunchEditorButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
AutomationProperties.AutomationId="LaunchLayoutEditorButton"
Command="{x:Bind ViewModel.LaunchEditorEventHandler}"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsClickEnabled="True" />

View File

@@ -267,7 +267,8 @@
<ToggleSwitch x:Uid="MouseWithoutBorders_ShowClipboardAndNetworkStatusMessages_ToggleSwitch" IsOn="{x:Bind ViewModel.ShowClipboardAndNetworkStatusMessages, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseWithoutBorders_KeyboardShortcuts_Group" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<controls:SettingsGroup x:Uid="MouseWithoutBorders_EasyMouseSettings_Group" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="MouseWithoutBorders_EasyMouseOption" HeaderIcon="{ui:FontIcon Glyph=&#xE962;}">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.EasyMouseOptionIndex, Mode=TwoWay}">
<ComboBoxItem x:Uid="MouseWithoutBorders_EasyMouseOption_Disabled" />
@@ -276,7 +277,46 @@
<ComboBoxItem x:Uid="MouseWithoutBorders_EasyMouseOption_Shift" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Uid="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen"
HeaderIcon="{ui:FontIcon Glyph=&#xE962;}"
IsEnabled="{x:Bind ViewModel.EasyMouseEnabled, Mode=OneWay}">
<ToggleSwitch x:Uid="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_ToggleSwitch" IsOn="{x:Bind ViewModel.DisableEasyMouseWhenForegroundWindowIsFullscreen, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="MouseWithoutBorders_CanOnlyStopEasyMouseIfMovingFromHostMachine"
IsClosable="False"
IsOpen="True"
Severity="Informational" />
<tkcontrols:SettingsExpander
x:Uid="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_Expander"
HeaderIcon="{ui:FontIcon Glyph=&#xECE4;}"
IsEnabled="{x:Bind ViewModel.IsEasyMouseBlockingOnFullscreenEnabled, Mode=OneWay}"
IsExpanded="False">
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard
x:Uid="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_ExcludedApps"
HorizontalContentAlignment="Stretch"
ContentAlignment="Vertical">
<TextBox
x:Uid="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_TextBoxControl"
MinWidth="240"
MinHeight="160"
AcceptsReturn="True"
IsSpellCheckEnabled="False"
ScrollViewer.IsVerticalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
Text="{x:Bind ViewModel.EasyMouseFullscreenSwitchBlockExcludedApps, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseWithoutBorders_KeyboardShortcuts_Group" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="MouseWithoutBorders_ToggleEasyMouseShortcut" HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"

View File

@@ -398,6 +398,9 @@
<data name="MouseWithoutBorders_AdvancedSettings_Group.Header" xml:space="preserve">
<value>Advanced Settings</value>
</data>
<data name="MouseWithoutBorders_EasyMouseSettings_Group.Header" xml:space="preserve">
<value>Easy Mouse</value>
</data>
<data name="MouseWithoutBorders_EasyMouseOption.Header" xml:space="preserve">
<value>Easy Mouse: move between machines by moving the mouse pointer to the screen edges.</value>
</data>
@@ -419,6 +422,27 @@
<value>Shift</value>
<comment>This is the Shift keyboard key</comment>
</data>
<data name="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen.Header" xml:space="preserve">
<value>Disable Easy Mouse when an application is running in full screen.</value>
</data>
<data name="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen.Description" xml:space="preserve">
<value>Prevent Easy Mouse from moving to another machine when an application is in full-screen mode.</value>
</data>
<data name="MouseWithoutBorders_CanOnlyStopEasyMouseIfMovingFromHostMachine.Title" xml:space="preserve">
<value>Disabling Easy Mouse in full-screen mode only affects the host PC. It wont stop the mouse from moving away from remote machines.</value>
</data>
<data name="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_TextBoxControl.PlaceholderText" xml:space="preserve">
<value>msedge.exe
firefox.exe
opera.exe</value>
<comment>Allow easy mouse when chrome is in fullscreen mode.</comment>
</data>
<data name="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_Expander.Header" xml:space="preserve">
<value>Ignored fullscreen applications</value>
</data>
<data name="MouseWithoutBorders_DisableEasyMouseWhenForegroundWindowIsFullscreen_Expander.Description" xml:space="preserve">
<value>Allow Easy Mouse to move between machines even if one of these applications is running in full screen, separate each executable with a new line.</value>
</data>
<data name="MouseWithoutBorders_LockMachinesShortcut.Header" xml:space="preserve">
<value>Shortcut to lock all machines.</value>
</data>
@@ -485,7 +509,7 @@
</data>
<data name="Shell_Awake.Content" xml:space="preserve">
<value>Awake</value>
<comment>Product name: Navigation view item name for Awake</comment>
<comment>{Locked}</comment>
</data>
<data name="Shell_PowerLauncher.Content" xml:space="preserve">
<value>PowerToys Run</value>
@@ -2280,6 +2304,7 @@ From there, simply click on one of the supported files in the File Explorer and
</data>
<data name="Awake.ModuleTitle" xml:space="preserve">
<value>Awake</value>
<comment>Awake is a product name, do not localize</comment>
</data>
<data name="Awake.ModuleDescription" xml:space="preserve">
<value>A convenient way to keep your PC awake on-demand.</value>
@@ -2333,12 +2358,15 @@ From there, simply click on one of the supported files in the File Explorer and
</data>
<data name="Oobe_Awake.Description" xml:space="preserve">
<value>Awake is a Windows tool designed to keep your PC awake on-demand without having to manage its power settings. This behavior can be helpful when running time-consuming tasks while ensuring that your PC does not go to sleep or turn off its screens.</value>
<comment>Awake is a product name, do not localize</comment>
</data>
<data name="Oobe_Awake_HowToUse.Text" xml:space="preserve">
<value>Open **PowerToys Settings** and enable Awake</value>
<comment>Awake is a product name, do not localize</comment>
</data>
<data name="Oobe_Awake_TipsAndTricks.Text" xml:space="preserve">
<value>You can always change modes quickly by **right-clicking the Awake icon** in the system tray.</value>
<comment>Awake is a product name, do not localize</comment>
</data>
<data name="General_FailedToDownloadTheNewVersion.Title" xml:space="preserve">
<value>An error occurred trying to install this update:</value>
@@ -2433,6 +2461,7 @@ From there, simply click on one of the supported files in the File Explorer and
</data>
<data name="Awake_ModeSettingsCard.Description" xml:space="preserve">
<value>Manage the state of your device when Awake is active</value>
<comment>Awake is a product name, do not localize</comment>
</data>
<data name="ExcludedApps.Header" xml:space="preserve">
<value>Excluded apps</value>

View File

@@ -904,6 +904,43 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private string _easyMouseIgnoredFullscreenAppsString;
public string EasyMouseFullscreenSwitchBlockExcludedApps
{
// Convert the list of excluded apps retrieved from the settings
// to a single string that can be displayed in the bound textbox
get
{
if (_easyMouseIgnoredFullscreenAppsString == null)
{
var excludedApps = Settings.Properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value;
_easyMouseIgnoredFullscreenAppsString = excludedApps.Count == 0 ? string.Empty : string.Join('\r', excludedApps);
}
return _easyMouseIgnoredFullscreenAppsString;
}
set
{
if (EasyMouseFullscreenSwitchBlockExcludedApps == value)
{
return;
}
_easyMouseIgnoredFullscreenAppsString = value;
var ignoredAppsSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (value != string.Empty)
{
ignoredAppsSet.UnionWith(value.Split('\r', StringSplitOptions.RemoveEmptyEntries));
}
Settings.Properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value = ignoredAppsSet;
NotifyPropertyChanged();
}
}
public bool CardForName2IpSettingIsEnabled => _disableUserDefinedIpMappingRulesIsGPOConfigured == false;
public bool ShowPolicyConfiguredInfoForName2IPSetting => _disableUserDefinedIpMappingRulesIsGPOConfigured && IsEnabled;
@@ -1005,6 +1042,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public bool EasyMouseEnabled => (EasyMouseOption)EasyMouseOptionIndex != EasyMouseOption.Disable;
public bool IsEasyMouseBlockingOnFullscreenEnabled =>
EasyMouseEnabled && DisableEasyMouseWhenForegroundWindowIsFullscreen;
public bool DisableEasyMouseWhenForegroundWindowIsFullscreen
{
get
{
return Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen;
}
set
{
if (Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen == value)
{
return;
}
Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen = value;
NotifyPropertyChanged();
}
}
public HotkeySettings ToggleEasyMouseShortcut
{
get => Settings.Properties.ToggleEasyMouseShortcut;