Compare commits

...

17 Commits

Author SHA1 Message Date
Shawn Yuan
78ed5c7966 Add new pipeline file 2024-11-28 11:49:56 +08:00
Shuai Yuan
3dc491339a Upgrade Windows App SDK to the latest version (#36093)
Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
2024-11-27 13:57:20 -08:00
grzhan
271d64b8dc [PTRun][Docs] Add HttpStatusCodes to Third-Party plugins (#36055)
* [PTRun][Docs] Add HttpStatusCodes to Third-Party plugins

* adding grzhan to names.txt

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
2024-11-27 11:44:56 -08:00
Andrea Bartolucci
a90b646e72 [PTRun][WindowWalker]Added score to the results (#35691)
Added Score to the results returned by WindowWalker

Co-authored-by: Andrea Bartolucci <8514491+andbartol@users.noreply.github.com>
2024-11-27 17:30:25 +00:00
Christian Gaarden Gaardmark
084402a2bd [New+]Windows 10 support (#35832) 2024-11-27 14:22:05 +00:00
Ani
08fe8089b8 [Logs]Fixed name of logging method in managed logging (#35952)
* Fixed name of method in managed logging

* Added hardening
2024-11-26 20:38:41 +00:00
Michael Clayton
53212188b7 [Mouse Jump] Customisable appearance - borders, margins, colours, etc - final part (#35521)
* [MouseJump] move Mouse Jump settings into separate control (#27511)

* [MouseJump] added Mouse Jump style controls to Settings UI (#27511)

* [MouseJump] added Mouse Jump style controls to Settings UI (#27511)

* [MouseJump] removing unused MouseJumpUI code (#27511)

* [MouseJump] whitespace (#27511)

* [MouseJump] fix spellcheck (#27511)

* [MouseJump] enabled "Copy to custom style" (#27511)

* [MouseJump] fixing build (internal members -> public) (#27511)

* [MouseJump] remove unused "using"s (#27511)

* [MouseJump] use custom styles in preview image (#27511)

* [MouseJump] fixing failing test (#27511)

* [MouseJump] fixing failing test (#27511)

* [MouseJump] fixing failing test (#27511)

* [MouseJump] fixing failing test (#27511)

* [MouseJump] delinting to trigger a build (#27511)

* [MouseJump] updated settings preview image ("browser" header) (#27511)

* [MouseJump] upgrade default "custom" style settings in config (#27511)

* [MouseJump] fixed a glitch in settings upgrade (#27511)

* [MouseJump] fixed spell checker (#27511)

* [MouseJump] typo in resource strings (image -> images) (#27511)

* Remove unused include
2024-11-26 15:37:59 +00:00
Ishita Agarwal
7c9876582c Removed extra space from welcome page (#35778)
* Removed extra space from welcome page

* Added space between info bar and release notes
2024-11-25 21:13:44 -08:00
Dave Rayment
6cece12b85 [Peek]Add support for previewing .ahk files as plaintext (#35538) 2024-11-22 16:25:01 +00:00
Dave Rayment
863f7aa233 [Peek] Fix Monaco assets folder discovery (#35925) 2024-11-22 16:24:15 +00:00
Dave Rayment
b81478eb97 [Peek]Expand image format support for Image Previewer using local capabilities (#35622)
* Use BitmapDecoder to query compatible file extensions.

* Delete spellcheck exceptions for removed file types

* Remove unused usings.
2024-11-22 14:49:35 +00:00
Søren Kottal
40acdea356 Adds two new third party plugins for Run (#35453)
* Update thirdPartyRunPlugins.md

* Update expect.txt

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
2024-11-21 07:35:18 -08:00
Jaime Bernardo
f1322d22c3 [ci]Fix signing new Workspaces dll in Dart (#36036) 2024-11-21 08:29:57 -06:00
Laszlo Nemeth
6b3bbf7e8f [Workspaces] Implement PWA recognition, launch. (#35913)
* [Workspaces] PWA: first steps: implement PWA app searcher, add basic controls to the editor

* spell checker

* Snapshot tool: adding command line args for edge

* PWA: add icon handling, add launch of PWA

* Impllement Aumid getters and comparison to connect PWA windows and processes. Update LauncherUI, Launcher

* Minor fixes, simplifications

* Spell checker

* Removing manual PWA selection, spell checker

* Fix merge conflict

* Trying to convince spell checker, that "PEB" is a correct word.

* XAML format fix

* Extending snapshot tool by logs for better testablility

* spell checker fix

* extending logs

* extending logs

* Removing some logs, modifying search criteria for pwa helper process search

* extending PWA detection for the case the directory with the app-id is missing

* Fix issue when pwaAppId is null

* fix missing pwa-app-id handling in the editor. Removed unused property (code cleaning) and updating json parser in Launcher

* Code cleaning: Moving duplicate code to a common project

* Fix issue: adding new Guid as app id if it is empty

* Code cleanup: moving Pwa related code from snapshotUtils to PwaHelper

* Code cleaning

* Code cleanup: Move common Application model to Csharp Library

* code cleanup

* modifying package name

* Ading project reference to Common.UI

* Code cleaning, fixing references

---------

Co-authored-by: donlaci <donlaci@yahoo.com>
2024-11-20 20:15:18 +01:00
桜井 ホタル
f66450f6a8 [PTRun][Docs] Add Bilibili to Third-Party plugins (#35926)
* Update thirdPartyRunPlugins.md

* Update names.txt
2024-11-20 09:25:35 -08:00
Ethan Fang
d31934b7de [DATA_AND_PRIVACY.md] updating the list of diagnostic data events (#35922)
* updating the list of diagnostic data events

* updating the list of events
2024-11-20 09:25:21 -08:00
Sanskar Gupta
4f3d7009a8 Simplify language and structure for clarity and ease of use. (#35943)
1. Clearer Section Headings: Renamed sections like "Indicating Interest in Issues" to improve readability.
2. Conciseness and Flow: Shortened sentences and rephrased for directness.
3. Improved Organization: Streamlined instructions in sections like “Filing an Issue” and “Contributing Fixes/Features.”
4. Reduced Redundancy: Simplified repetitive language, especially in localization and contribution details.
5. Enhanced Call to Action: Adjusted "Help Wanted" and "Becoming a Collaborator" to guide users clearly on next steps.
2024-11-20 09:24:04 -08:00
113 changed files with 4077 additions and 1808 deletions

View File

@@ -71,6 +71,7 @@ Garside
Gershaft
Giordani
Gokce
grzhan
Guo
hanselman
Harmath
@@ -144,6 +145,7 @@ TBM
tilovell
Triet
waaverecords
Whuihuan
Xpg
ycv
Yuniardi
@@ -154,6 +156,8 @@ Zykova
# OTHERS
Bilibili
BVID
cmdow
Controlz
cortana

View File

@@ -69,11 +69,9 @@ AQS
ARandom
ARCHITEW
ARemapped
ari
ARPINSTALLLOCATION
ARPPRODUCTICON
ARRAYSIZE
arw
asf
AShortcut
ASingle
@@ -104,6 +102,7 @@ backtracer
bbwe
bck
BESTEFFORT
bezelled
bhid
BIF
bigbar
@@ -151,6 +150,7 @@ CALG
callbackptr
calpwstr
Cangjie
caniuse
CANRENAME
CAPTUREBLT
CAPTURECHANGED
@@ -259,7 +259,7 @@ CRH
critsec
Crossdevice
CRSEL
crw
crx
CSearch
CSettings
cso
@@ -301,8 +301,8 @@ DCOM
dcommon
dcomp
DComposition
dcr
dcs
DCR
DCs
ddd
DDEIf
DDevice
@@ -372,7 +372,6 @@ DRAWCLIPBOARD
DRAWFRAME
drawingcolor
dreamsofameaningfullife
drf
drivedetectionwarning
dshow
DSTINVERT
@@ -416,7 +415,6 @@ editkeyboardwindow
EDITSHORTCUTS
editshortcutswindow
EFile
eip
ekus
emmintrin
Emoji
@@ -590,7 +588,7 @@ Hiberboot
HIBYTE
hicon
HIDEWINDOW
hif
Hif
HIMAGELIST
himl
hinst
@@ -639,6 +637,7 @@ HWNDLAST
HWNDNEXT
HWNDPREV
hyjiacan
IApp
IBeam
ICapture
IClass
@@ -665,7 +664,6 @@ IGNOREUNKNOWN
IGraphics
iid
Iindex
iiq
IJson
Ijwhost
IKs
@@ -728,17 +726,17 @@ ISSEPARATOR
ITask
ith
ITHUMBNAIL
ITwoWayPipeMessageIPCManaged
IUI
IUnknown
IUse
IWbem
IWeb
IWIC
iwr
IYUV
jfi
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jif
jjw
jobject
jpe
@@ -747,7 +745,6 @@ Jsons
jsonval
junja
jxr
kdc
keybd
KEYBDDATA
KEYBDINPUT
@@ -872,7 +869,6 @@ MAXIMIZEBOX
MAXSHORTCUTSIZE
maxversiontested
MBR
mdc
MDICHILD
MDL
mdtext
@@ -880,7 +876,6 @@ mdtxt
mdwn
MEDIASUBTYPE
mediatype
mef
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
@@ -938,7 +933,6 @@ mpmc
MRM
MRT
mru
mrw
msc
mscorlib
msdata
@@ -1003,6 +997,7 @@ newitem
newpath
newplus
NEWPLUSCONTEXTMENU
NEWPLUSSHELLEXTENSIONWIN
newrow
newsgroups
NIF
@@ -1061,7 +1056,6 @@ NOZORDER
NPH
npmjs
NResize
nrw
nsunt
NTAPI
ntdll
@@ -1091,7 +1085,6 @@ opensource
openxmlformats
OPTIMIZEFORINVOKE
ORAW
ori
ORPHANEDDIALOGTITLE
ORSCANS
oss
@@ -1140,7 +1133,7 @@ pdo
pdto
pdtobj
pdw
Peb
peb
pef
PElems
Pels
@@ -1176,6 +1169,7 @@ pnid
Pnp
Popups
POPUPWINDOW
POSITIONITEM
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1270,7 +1264,6 @@ QUERYENDSESSION
QUERYOPEN
QUEUESYNC
QUNS
raf
RAII
RAlt
Rasterize
@@ -1374,8 +1367,6 @@ runtimes
ruuid
rvm
rwin
rwl
rwz
sacl
safeprojectname
SAMEKEYPREVIOUSLYMAPPED
@@ -1509,7 +1500,6 @@ Srch
SRCINVERT
SRCPAINT
SResize
srf
srme
srre
srw
@@ -1584,6 +1574,8 @@ SYSLIB
SYSMENU
SYSTEMAPPS
SYSTEMTIME
SYSTEMWOW
tailwindcss
tapp
TApplication
TApplied
@@ -1627,6 +1619,7 @@ tkconverters
TLayout
tlb
tlbimp
tlhelp
TMPVAR
TNP
Toolhelp

View File

@@ -181,6 +181,7 @@
"WinUI3Apps\\PowerToys.NewPlus.ShellExtension.dll",
"WinUI3Apps\\NewPlusPackage.msix",
"WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll",
"PowerAccent.Core.dll",
"PowerToys.PowerAccent.dll",
@@ -201,6 +202,7 @@
"PowerToys.WorkspacesLauncherUI.exe",
"PowerToys.WorkspacesLauncherUI.dll",
"PowerToys.WorkspacesModuleInterface.dll",
"PowerToys.WorkspacesCsharpLibrary.dll",
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
"WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll",

View File

View File

@@ -1,53 +1,53 @@
# PowerToys Contributor's Guide
Below is our guidance for how to report issues, propose new features, and submit contributions via Pull Requests (PRs). Our philosophy is heavily based around understanding the problem and scenarios first, this is why we follow this pattern before work has started.
Below is our guidance for reporting issues, proposing new features, and submitting contributions via Pull Requests (PRs). Our philosophy is to understand the problem and scenarios first, which is why we follow this pattern before work starts.
1. There is an issue
2. There has been a conversation
3. There is agreement on the problem, the fit for PowerToys, and the solution to the problem (implementation)
1. There is an issue.
2. There has been a conversation.
3. There is agreement on the problem, the fit for PowerToys, and the solution to the problem (implementation).
## Filing an issue
## Filing an Issue
Please follow this simple rule to help us eliminate any unnecessary wasted effort & frustration, and ensure an efficient and effective use of everyone's time - yours, ours, and other community members':
**Importance of Filing an Issue First**
> 👉 If you have a question, think you've discovered an issue, would like to propose a new feature, etc., then find/file an issue **BEFORE** starting work to fix/implement it.
Please follow this rule to help eliminate wasted effort and frustration, and to ensure an efficient and effective use of everyones time:
When requesting new features / enhancements, understanding problem and scenario around it is extremely important. Having additional evidence, data, tweets, blog posts, research, ... anything is extremely helpful. This information provides context to the scenario that may otherwise be lost.
> 👉 If you have a question, think you've discovered an issue, or would like to propose a new feature, please find/file an issue **BEFORE** starting work to fix/implement it.
* Don't know whether you're reporting an issue or requesting a feature? File an issue
* Have a question that you don't see answered in docs, videos, etc.? File an issue
* Want to know if we're planning on building a particular feature? File an issue
* Got a great idea for a new utility or feature? File an issue/request/idea
* Don't understand how to do something? File an issue/Community Guidance Request
* Found an existing issue that describes yours? Great - upvote and add additional commentary / info / repro-steps / etc.
When requesting new features or enhancements, providing additional evidence, data, tweets, blog posts, or research is extremely helpful. This information gives context to the scenario that may otherwise be lost.
A quick search before filing an issue also could be helpful. It is likely someone else has found the problem you're seeing, and someone may be working on or have already contributed a fix!
* Unsure whether its an issue or feature request? File an issue.
* Have a question that isn't answered in the docs, videos, etc.? File an issue.
* Want to know if were planning a particular feature? File an issue.
* Got a great idea for a new utility or feature? File an issue/request/idea.
* Dont understand how to do something? File an issue/Community Guidance Request.
* Found an existing issue that describes yours? Great! Upvote and add additional commentary, info, or repro steps.
### How to tell the PowerToys team this is an interesting thing to focus on
A quick search before filing an issue could be helpful. Its likely someone else has found the same problem, and they may even be working on or have already contributed a fix!
Upvote the original issue by clicking its [+😊] button and hitting 👍 (+1) icon or a different one. This way allows us to measure how impactful different issues are compared to others. The issue with comments like "+1", "me too", or similar is they actually make it harder to have a conversation and harder to quickly determine trending important requests.
### Indicating Interest in Issues
To let the team know which issues are important, upvote by clicking the [+😊] button and the 👍 icon on the original issue post. Avoid comments like "+1" or "me too" as they clutter the discussion and make it harder to prioritize requests.
---
## Contributing fixes / features
## Contributing Fixes/Features
Please comment on [our "Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769) to let us know you're interested in working on something before you start the work. Not only does this avoid multiple people unexpectedly working on the same thing at the same time but it enables us to make sure everyone is clear on what should be done to implement any new functionality. It's less work for everyone, in the long run, to establish this up front.
Please comment on our ["Would you like to contribute to PowerToys?"](https://github.com/microsoft/PowerToys/issues/28769) thread to let us know you're interested in working on something before you start. This helps avoid multiple people unexpectedly working on the same thing and ensures everyone is clear on what should be done. It's less work for everyone to establish this up front.
### Localization issues
### Localization Issues
Please file localization issues, so our internal localization team can identify and fix them. However we currently don't accept community Pull Requests fixing localization issues. Localization is handled by the internal Microsoft team only.
For localization issues, please file an issue to notify our internal localization team, as community PRs for localization aren't accepted. Localization is handled exclusively by the internal Microsoft team.
### To Spec or not to Spec
### To Spec or Not to Spec
A key point is for everyone to understand the approach that will be taken. We want to be sure if anyone does work, we will accept it in. Items that are larger in scope we'll want some type of spec to understand what is being planned and have a discussion. Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run.
A key point is for everyone to understand the approach that will be taken. We want to be sure that any work done will be accepted. Larger-scope items will require a spec to outline the approach and allow for discussion. Specs help collaborators consider different solutions, describe feature behavior, and plan for errors. Achieving agreement in a spec before writing code often results in simpler code and less wasted effort.
For such scenarios, once a team member has agreed with your approach, skip ahead to the section headed "Development" section below.
Team members will be happy to help review specs and guide them to completion.
Once a team member has agreed with your approach, proceed to the "Development" section below. Team members are happy to help review specs and guide them to completion.
### Help Wanted
Once the team has approved an issue/spec approach to solving, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/PowerToys/labels/Help%20Wanted).
Once the team has approved an issue/spec approach, development can proceed. If no developers are immediately available, the spec may be parked and labeled "Help Wanted," ready for a developer to get started. For development opportunities, visit [Issues labeled Help Wanted](https://github.com/microsoft/PowerToys/labels/Help%20Wanted).
---
@@ -55,18 +55,18 @@ Once the team has approved an issue/spec approach to solving, development can pr
Follow the [development guidelines](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md).
### Naming of features and functionality
### Naming Features and Functionality
Naming should be descriptive and straight forward. We want names to be clear about functionality and usefulness moving forward.
Names should be descriptive and straightforward, clearly reflecting functionality and usefulness.
### How can I become a collaborator on the PowerToys team
### Becoming a Collaborator on the PowerToys Team
Be a great community member. Just help out a lot and make useful additions, filing bugs/suggestions, help develop fixes and features, code reviews, and always, docs. Lets continue to make the PowerToys repository a great spot to learn and make a great set of utilities.
Be an active community member! Make helpful contributions by filing bugs, offering suggestions, developing fixes and features, conducting code reviews, and updating documentation.
When the time comes, Microsoft will reach out and help make you a formal team member. Just make sure they can reach out to you :)
When the time comes, Microsoft will reach out to you about becoming a formal team member. Just make sure they have a way to contact you. 😊
---
## Thank you
## Thank You
Thank you in advance for your contribution!
Thank you in advance for your contribution! We appreciate your help in making PowerToys a better tool for everyone.

View File

@@ -67,6 +67,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.TrayFlyoutModuleRunEvent</td>
<td>Logs when a utility from the tray flyout menu is run.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Uninstall_Success</td>
<td>Logs when PowerToys is successfully uninstalled (who would do such a thing!).</td>
</tr>
</table>
### OOBE (Out-of-box experience)
@@ -139,6 +143,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.AdvancedPasteInAppKeyboardShortcutEvent</td>
<td>Triggered when a keyboard shortcut is used within the Advanced Paste interface.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent</td>
<td>Triggered when Advanced Paste leverages the Semantic Kernel.</td>
</tr>
</table>
### Always on Top

View File

@@ -47,7 +47,7 @@
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.1.5" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.241114003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />

View File

@@ -1333,7 +1333,7 @@ EXHIBIT A -Mozilla Public License.
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.1.5
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
- Microsoft.WindowsAppSDK 1.6.240923002
- Microsoft.WindowsAppSDK 1.6.241114003
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4

View File

@@ -630,6 +630,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "src\common\Tele
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesCsharpLibrary", "src\modules\Workspaces\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj", "{89D0E199-B17A-418C-B2F8-7375B6708357}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension.win10", "src\modules\NewPlus\NewShellExtensionContextMenu.win10\NewPlus.ShellExtension.win10.vcxproj", "{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2246,6 +2250,30 @@ Global
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x64.Build.0 = Release|x64
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x86.ActiveCfg = Release|x64
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x86.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.Build.0 = Release|x64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.Build.0 = Debug|ARM64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.ActiveCfg = Debug|x64
@@ -2646,30 +2674,6 @@ Global
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.Build.0 = Release|x64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
@@ -2778,6 +2782,30 @@ Global
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.Build.0 = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.ActiveCfg = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.Build.0 = Release|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.ActiveCfg = Debug|ARM64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.Build.0 = Debug|ARM64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.ActiveCfg = Debug|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.Build.0 = Debug|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x86.ActiveCfg = Debug|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x86.Build.0 = Debug|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.ActiveCfg = Release|ARM64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.Build.0 = Release|ARM64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.ActiveCfg = Release|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.Build.0 = Release|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.ActiveCfg = Release|x64
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.Build.0 = Release|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.Build.0 = Debug|ARM64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.ActiveCfg = Debug|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.Build.0 = Debug|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x86.ActiveCfg = Debug|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x86.Build.0 = Debug|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.ActiveCfg = Release|ARM64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.Build.0 = Release|ARM64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.ActiveCfg = Release|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.Build.0 = Release|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.ActiveCfg = Release|x64
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2956,6 +2984,8 @@ Global
{B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{A663E672-B26D-4EC0-BEAB-FE2E424AC46F} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{8A08D663-4995-40E3-B42C-3F910625F284} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
@@ -2995,8 +3025,6 @@ Global
{8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
{CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
{923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
@@ -3009,6 +3037,8 @@ Global
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216}
{66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -39,6 +39,9 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [GitHubRepo](https://github.com/8LWXpg/PowerToysRun-GitHubRepo) | [8LWXpg](https://github.com/8LWXpg) | Search and open GitHub repositories |
| [ProcessKiller](https://github.com/8LWXpg/PowerToysRun-ProcessKiller) | [8LWXpg](https://github.com/8LWXpg) | Search and kill processes |
| [ChatGPT](https://github.com/ferraridavide/ChatGPTPowerToys) | [ferraridavide](https://github.com/ferraridavide) | Ask a question to ChatGPT |
| [CanIUse](https://github.com/skttl/ptrun-caniuse) | [skttl](https://github.com/skttl) | Look up browser feature support with caniuse.com |
| [TailwindCSS](https://github.com/skttl/ptrun-tailwindcss) | [skttl](https://github.com/skttl) | Search the documentation of TailwindCSS |
| [HttpStatusCodes](https://github.com/grzhan/HttpStatusCodePowerToys) | [grzhan](https://github.com/grzhan) | Search for http status codes |
## Extending software plugins
@@ -58,3 +61,4 @@ Below are community created plugins that target a website or software. They are
| [PowerSearch for 1Password](https://github.com/KairuDeibisu/PowerToysRunPlugin1Password) | [KairuDeibisu](https://github.com/KairuDeibisu) | An unofficial plugin for searching 1Password for usernames and passwords |
| [HackMD](https://github.com/8LWXpg/PowerToysRun-HackMD) | [8LWXpg](https://github.com/8LWXpg) | Open HackMD notes |
| [SSH](https://github.com/8LWXpg/PowerToysRun-SSH) | [8LWXpg](https://github.com/8LWXpg) | Connect to ssh clients |
| [Bilibili](https://github.com/Whuihuan/PowerToysRun-Bilibili) | [Whuihuan](https://github.com/Whuihuan) | Use AVID or BVID to parse and jump to Bilibili |

View File

@@ -18,6 +18,19 @@
<DirectoryRef Id="NewPlusAssetsInstallFolder" FileSource="$(var.NewPlusAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--NewPlusAssetsFiles_Component_Def-->
<!-- NewPlus Shell Extension for Win10 registration -->
<Component Id="NewPlus_ShellExtension_win10" Guid="D5456D4A-6EEC-4B85-944D-6A6A4A74FFA6" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{FF90D477-E32A-4BE8-8CC5-A502A97F5401}">
<RegistryValue Type="string" Value="NewPlus Shell Extension Win10" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.NewPlus.ShellExtension.win10.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\Directory\background\ShellEx\ContextMenuHandlers\NewPlusShellExtensionWin10">
<RegistryValue Type="string" Value="{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="NewPlusComponentGroup">
@@ -27,6 +40,7 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderNewPlusAssetsFolder" Directory="NewPlusAssetsInstallFolder" On="uninstall"/>
</Component>
<ComponentRef Id="NewPlus_ShellExtension_win10" />
</ComponentGroup>

View File

@@ -7,7 +7,7 @@ export async function registerAdditionalLanguages(monaco){
await languageDefinitions();
registerAdditionalLanguage("cppExt", [".ino", ".pde"], "cpp", monaco)
registerAdditionalLanguage("xmlExt", [".wsdl", ".csproj", ".vcxproj", ".vbproj", ".fsproj"], "xml", monaco)
registerAdditionalLanguage("txtExt", [".sln", ".log", ".vsconfig", ".env", ".srt"], "txt", monaco)
registerAdditionalLanguage("txtExt", [".sln", ".log", ".vsconfig", ".env", ".srt", ".ahk"], "txt", monaco)
registerAdditionalLanguage("razorExt", [".razor"], "razor", monaco)
registerAdditionalLanguage("vbExt", [".vbs"], "vb", monaco)
registerAdditionalLanguage("iniExt", [".inf", ".gitconfig", ".gitattributes", ".editorconfig"], "ini", monaco)

File diff suppressed because one or more lines are too long

View File

@@ -29,34 +29,28 @@ namespace Microsoft.PowerToys.FilePreviewCommon
new XmlFormatter(),
}.AsReadOnly();
private static string? _monacoDirectory;
private static readonly Lazy<string> _monacoDirectory = new(GetRuntimeMonacoDirectory);
public static string GetRuntimeMonacoDirectory()
/// <summary>
/// Gets the path of the Monaco assets folder.
/// </summary>
public static string MonacoDirectory => _monacoDirectory.Value;
private static string GetRuntimeMonacoDirectory()
{
string codeBase = Assembly.GetExecutingAssembly().Location;
string path = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(codeBase) ?? string.Empty, "Assets", "Monaco"));
if (Path.Exists(path))
{
return path;
}
else
{
// We're likely in WinUI3Apps directory and need to go back to the base directory.
return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(codeBase) ?? string.Empty, "..", "Assets", "Monaco"));
}
}
string exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty;
public static string MonacoDirectory
{
get
// If the executable is within "WinUI3Apps", correct the path first.
if (Path.GetFileName(exePath) == "WinUI3Apps")
{
if (string.IsNullOrEmpty(_monacoDirectory))
{
_monacoDirectory = GetRuntimeMonacoDirectory();
}
return _monacoDirectory;
exePath = Path.Combine(exePath, "..");
}
string monacoPath = Path.Combine(exePath, "Assets", "Monaco");
return Directory.Exists(monacoPath) ?
monacoPath :
throw new DirectoryNotFoundException($"Monaco assets directory not found at {monacoPath}");
}
public static JsonDocument GetLanguages()

View File

@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using PowerToys.Interop;
@@ -52,16 +53,18 @@ namespace ManagedCommon
Trace.AutoFlush = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogError(string message)
{
Log(message, Error);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogError(string message, Exception ex)
{
if (ex == null)
{
LogError(message);
Log(message, Error);
}
else
{
@@ -84,26 +87,31 @@ namespace ManagedCommon
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogWarning(string message)
{
Log(message, Warning);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogInfo(string message)
{
Log(message, Info);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogDebug(string message)
{
Log(message, Debug);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogTrace()
{
Log(string.Empty, TraceFlag);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Log(string message, string type)
{
Trace.WriteLine("[" + DateTime.Now.TimeOfDay + "] [" + type + "] " + GetCallerInfo());
@@ -116,13 +124,49 @@ namespace ManagedCommon
Trace.Unindent();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetCallerInfo()
{
StackTrace stackTrace = new();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType.Name;
return className + "::" + methodName?.Name;
var callerMethod = GetCallerMethod(stackTrace);
return $"{callerMethod?.DeclaringType?.Name}::{callerMethod.Name}";
}
private static MethodBase GetCallerMethod(StackTrace stackTrace)
{
const int topFrame = 3;
var topMethod = stackTrace.GetFrame(topFrame)?.GetMethod();
try
{
if (topMethod?.Name == nameof(IAsyncStateMachine.MoveNext) && typeof(IAsyncStateMachine).IsAssignableFrom(topMethod?.DeclaringType))
{
// Async method; return actual method as determined by heuristic:
// "Nearest method on stack to async state-machine's MoveNext() in same namespace but in a different type".
// There are tighter ways of determining the actual method, but this is good enough and probably faster.
for (int deepFrame = topFrame + 1; deepFrame < stackTrace.FrameCount; deepFrame++)
{
var deepMethod = stackTrace.GetFrame(deepFrame)?.GetMethod();
if (deepMethod?.DeclaringType != topMethod?.DeclaringType && deepMethod?.DeclaringType?.Namespace == topMethod?.DeclaringType?.Namespace)
{
return deepMethod;
}
}
}
}
catch (Exception)
{
// Ignore exceptions in Release. The code above won't throw, but if it does, we don't want to crash the app.
#if DEBUG
throw;
#endif
}
return topMethod;
}
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
@@ -141,7 +141,7 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -153,7 +153,7 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target>
</Project>

View File

@@ -4,5 +4,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240923002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.241114003" targetFramework="native" />
</packages>

View File

@@ -2,8 +2,9 @@
// 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.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJump.Common.Helpers;
using MouseJump.Common.Imaging;
@@ -16,7 +17,7 @@ namespace MouseJump.Common.UnitTests.Helpers;
public static class DrawingHelperTests
{
[TestClass]
public sealed class GetPreviewLayoutTests
public sealed class RenderPreviewTests
{
public sealed class TestCase
{
@@ -46,7 +47,7 @@ public static class DrawingHelperTests
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(0, 0, 500, 500),
@@ -62,7 +63,7 @@ public static class DrawingHelperTests
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(5120, 349, 1920, 1080),
@@ -79,7 +80,7 @@ public static class DrawingHelperTests
public void RunTestCases(TestCase data)
{
// load the fake desktop image
using var desktopImage = GetPreviewLayoutTests.LoadImageResource(data.DesktopImageFilename);
using var desktopImage = RenderPreviewTests.LoadImageResource(data.DesktopImageFilename);
// draw the preview image
var previewLayout = LayoutHelper.GetPreviewLayout(
@@ -90,28 +91,29 @@ public static class DrawingHelperTests
using var actual = DrawingHelper.RenderPreview(previewLayout, imageCopyService);
// load the expected image
var expected = GetPreviewLayoutTests.LoadImageResource(data.ExpectedImageFilename);
var expected = RenderPreviewTests.LoadImageResource(data.ExpectedImageFilename);
// compare the images
var screens = System.Windows.Forms.Screen.AllScreens;
AssertImagesEqual(expected, actual);
}
private static Bitmap LoadImageResource(string filename)
{
// assume embedded resources are in the same source folder as this
// class, and the namespace hierarchy matches the folder structure.
// that way we can build resource names from the current namespace
var resourcePrefix = typeof(DrawingHelperTests).Namespace;
var resourceName = $"{resourcePrefix}.{filename}";
var assembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
var resourceName = $"{typeof(DrawingHelperTests).Namespace}.{filename.Replace("/", ".")}";
var resourceNames = assembly.GetManifestResourceNames();
if (!resourceNames.Contains(resourceName))
{
var message = $"Embedded resource '{resourceName}' does not exist. " +
"Valid resource names are: \r\n" + string.Join("\r\n", resourceNames);
throw new InvalidOperationException(message);
var message = new StringBuilder();
message.AppendLine(CultureInfo.InvariantCulture, $"Embedded resource '{resourceName}' does not exist.");
message.AppendLine($"Known resources:");
foreach (var name in resourceNames)
{
message.AppendLine(name);
}
throw new InvalidOperationException(message.ToString());
}
var stream = assembly.GetManifestResourceStream(resourceName)
@@ -121,7 +123,7 @@ public static class DrawingHelperTests
}
/// <summary>
/// Naive / brute force image comparison - we can optimise this later :-)
/// Naive / brute force image comparison - we can optimize this later :-)
/// </summary>
private static void AssertImagesEqual(Bitmap expected, Bitmap actual)
{

View File

@@ -129,7 +129,7 @@ public static class LayoutHelperTests
public static IEnumerable<object[]> GetTestCases()
{
// happy path - single screen with 50% scaling,
// *has* a preview borders but *no* screenshot borders
// *has* a preview border but *no* screenshot borders
//
// +----------------+
// | |
@@ -160,7 +160,7 @@ public static class LayoutHelperTests
new(0, 0, 1024, 768),
};
var activatedLocation = new PointInfo(512, 384);
var previewLayout = new PreviewLayout(
var expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
@@ -183,7 +183,7 @@ public static class LayoutHelperTests
contentBounds: new(6, 6, 512, 384)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
// happy path - single screen with 50% scaling,
// *no* preview borders but *has* screenshot borders
@@ -217,7 +217,7 @@ public static class LayoutHelperTests
new(0, 0, 1024, 768),
};
activatedLocation = new PointInfo(512, 384);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
@@ -240,7 +240,59 @@ public static class LayoutHelperTests
contentBounds: new(6, 6, 500, 372)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
// rounding error check - single screen with 33% scaling,
// no borders, check to make sure form scales to exactly
// fill the canvas size with no rounding errors.
//
// in this test the preview width is 300 and the desktop is
// 900, so the scaling factor is 1/3, but this gets rounded
// to 0.3333333333333333333333333333, and 900 times this value
// is 299.99999999999999999999999997. if we don't scale correctly
// the resulting form width might only be 299 pixels instead of 300
//
// +----------------+
// | |
// | 0 |
// | |
// +----------------+
previewStyle = new PreviewStyle(
canvasSize: new(
width: 300,
height: 200
),
canvasStyle: BoxStyle.Empty,
screenStyle: BoxStyle.Empty);
screens = new List<RectangleInfo>
{
new(0, 0, 900, 200),
};
activatedLocation = new PointInfo(450, 100);
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 900, 200),
screens: screens,
activatedScreenIndex: 0,
formBounds: new(300, 66.5m, 300, 67),
previewStyle: previewStyle,
previewBounds: new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
screenshotBounds: new()
{
new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
// primary monitor not topmost / leftmost - if there are screens
// that are further left or higher up than the primary monitor
@@ -291,7 +343,7 @@ public static class LayoutHelperTests
new(0, 0, 5120, 1440),
};
activatedLocation = new(-960, 60);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(-1920, -480, 7040, 1920),
screens: screens,
activatedScreenIndex: 0,
@@ -321,7 +373,7 @@ public static class LayoutHelperTests
contentBounds: new(204, 60, 500, 132)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
}
[TestMethod]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

@@ -15,45 +15,49 @@ public static class SizeInfoTests
{
public sealed class TestCase
{
public TestCase(SizeInfo obj, SizeInfo bounds, SizeInfo expectedResult)
public TestCase(SizeInfo source, SizeInfo bounds, SizeInfo expectedResult, decimal scalingRatio)
{
this.Obj = obj;
this.Source = source;
this.Bounds = bounds;
this.ExpectedResult = expectedResult;
this.ScalingRatio = scalingRatio;
}
public SizeInfo Obj { get; }
public SizeInfo Source { get; }
public SizeInfo Bounds { get; }
public SizeInfo ExpectedResult { get; }
public decimal ScalingRatio { get; }
}
public static IEnumerable<object[]> GetTestCases()
{
// identity tests
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384)), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384), 1), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768), 1), };
// general tests
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536), 4), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768), 0.5m), };
// scale to fit width
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536), 4), };
// scale to fit height
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536), 4), };
}
[TestMethod]
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
public void RunTestCases(TestCase data)
{
var actual = data.Obj.ScaleToFit(data.Bounds);
var actual = data.Source.ScaleToFit(data.Bounds, out var scalingRatio);
var expected = data.ExpectedResult;
Assert.AreEqual(expected.Width, actual.Width);
Assert.AreEqual(expected.Height, actual.Height);
Assert.AreEqual(scalingRatio, data.ScalingRatio);
}
}

View File

@@ -0,0 +1,91 @@
// 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.Globalization;
namespace MouseJump.Common.Helpers;
public static class ConfigHelper
{
public static Color? ToUnnamedColor(Color? value)
{
if (!value.HasValue)
{
return null;
}
var color = value.Value;
return Color.FromArgb(color.A, color.R, color.G, color.B);
}
public static string? SerializeToConfigColorString(Color? value)
{
if (!value.HasValue)
{
return null;
}
var color = value.Value;
return color switch
{
Color { IsNamedColor: true } =>
$"{nameof(Color)}.{color.Name}",
Color { IsSystemColor: true } =>
$"{nameof(SystemColors)}.{color.Name}",
_ =>
$"#{color.R:X2}{color.G:X2}{color.B:X2}",
};
}
public static Color? DeserializeFromConfigColorString(string? value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
// e.g. "#AABBCC"
if (value.StartsWith('#'))
{
var culture = CultureInfo.InvariantCulture;
if ((value.Length == 7)
&& int.TryParse(value[1..3], NumberStyles.HexNumber, culture, out var r)
&& int.TryParse(value[3..5], NumberStyles.HexNumber, culture, out var g)
&& int.TryParse(value[5..7], NumberStyles.HexNumber, culture, out var b))
{
return Color.FromArgb(0xFF, r, g, b);
}
}
const StringComparison comparison = StringComparison.InvariantCulture;
// e.g. "Color.Red"
const string colorPrefix = $"{nameof(Color)}.";
if (value.StartsWith(colorPrefix, comparison))
{
var colorName = value[colorPrefix.Length..];
var property = typeof(Color).GetProperties()
.SingleOrDefault(property => property.Name == colorName);
if (property is not null)
{
return (Color?)property.GetValue(null, null);
}
}
// e.g. "SystemColors.Highlight"
const string systemColorPrefix = $"{nameof(SystemColors)}.";
if (value.StartsWith(systemColorPrefix, comparison))
{
var colorName = value[systemColorPrefix.Length..];
var property = typeof(SystemColors).GetProperties()
.SingleOrDefault(property => property.Name == colorName);
if (property is not null)
{
return (Color?)property.GetValue(null, null);
}
}
return null;
}
}

View File

@@ -102,8 +102,13 @@ public static class DrawingHelper
return;
}
if (borderStyle.Color is null)
{
return;
}
// draw the main box border
using var borderBrush = new SolidBrush(borderStyle.Color);
using var borderBrush = new SolidBrush(borderStyle.Color.Value);
var borderRegion = new Region(boxBounds.BorderBounds.ToRectangle());
borderRegion.Exclude(boxBounds.PaddingBounds.ToRectangle());
graphics.FillRegion(borderBrush, borderRegion);

View File

@@ -46,16 +46,13 @@ public static class LayoutHelper
.Shrink(previewStyle.CanvasStyle.BorderStyle)
.Shrink(previewStyle.CanvasStyle.PaddingStyle);
// scale the virtual screen to fit inside the content area
var screenScalingRatio = builder.VirtualScreen.Size
.ScaleToFitRatio(maxContentSize);
// work out the actual size of the "content area" by scaling the virtual screen
// to fit inside the maximum content area while maintaining its aspect ration.
// we'll also offset it to allow for any margins, borders and padding
var contentBounds = builder.VirtualScreen.Size
.Scale(screenScalingRatio)
.Floor()
.ScaleToFit(maxContentSize, out var scalingRatio)
.Round()
.Clamp(maxContentSize)
.PlaceAt(0, 0)
.Offset(previewStyle.CanvasStyle.MarginStyle.Left, previewStyle.CanvasStyle.MarginStyle.Top)
.Offset(previewStyle.CanvasStyle.BorderStyle.Left, previewStyle.CanvasStyle.BorderStyle.Top)
@@ -82,16 +79,16 @@ public static class LayoutHelper
screen => LayoutHelper.GetBoxBoundsFromOuterBounds(
screen
.Offset(builder.VirtualScreen.Location.ToSize().Invert())
.Scale(screenScalingRatio)
.Scale(scalingRatio)
.Offset(builder.PreviewBounds.ContentBounds.Location.ToSize())
.Truncate(),
.Round(),
previewStyle.ScreenStyle))
.ToList();
return builder.Build();
}
internal static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screens)
public static RectangleInfo GetCombinedScreenBounds(List<RectangleInfo> screens)
{
return screens.Skip(1).Aggregate(
seed: screens.First(),

View File

@@ -102,7 +102,7 @@ public static class MouseHelper
/// See https://github.com/microsoft/PowerToys/issues/24523
/// https://github.com/microsoft/PowerToys/pull/24527
/// </remarks>
internal static void SimulateMouseMovementEvent(PointInfo location)
private static void SimulateMouseMovementEvent(PointInfo location)
{
var inputs = new User32.INPUT[]
{

View File

@@ -10,49 +10,9 @@ namespace MouseJump.Common.Helpers;
public static class StyleHelper
{
/// <summary>
/// Default v2 preview style
/// Compact (legacy) preview style
/// </summary>
public static readonly PreviewStyle DefaultPreviewStyle = new(
canvasSize: new(
width: 1600,
height: 1200
),
canvasStyle: new(
marginStyle: MarginStyle.Empty,
borderStyle: new(
color: SystemColors.Highlight,
all: 6,
depth: 0
),
paddingStyle: new(
all: 4
),
backgroundStyle: new(
color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2),
color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0)
)
),
screenStyle: new(
marginStyle: new(
all: 4
),
borderStyle: new(
color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22),
all: 12,
depth: 4
),
paddingStyle: PaddingStyle.Empty,
backgroundStyle: new(
color1: Color.MidnightBlue,
color2: Color.MidnightBlue
)
)
);
/// <summary>
/// Legacy preview style
/// </summary>
public static readonly PreviewStyle LegacyPreviewStyle = new(
public static readonly PreviewStyle CompactPreviewStyle = new(
canvasSize: new(
width: 1600,
height: 1200
@@ -89,6 +49,46 @@ public static class StyleHelper
)
);
/// <summary>
/// Bezelled preview style
/// </summary>
public static readonly PreviewStyle BezelledPreviewStyle = new(
canvasSize: new(
width: 1600,
height: 1200
),
canvasStyle: new(
marginStyle: MarginStyle.Empty,
borderStyle: new(
color: SystemColors.Highlight,
all: 6,
depth: 0
),
paddingStyle: new(
all: 4
),
backgroundStyle: new(
color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2),
color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0)
)
),
screenStyle: new(
marginStyle: new(
all: 4
),
borderStyle: new(
color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22),
all: 12,
depth: 4
),
paddingStyle: PaddingStyle.Empty,
backgroundStyle: new(
color1: Color.MidnightBlue,
color2: Color.MidnightBlue
)
)
);
public static PreviewStyle WithCanvasSize(this PreviewStyle previewStyle, SizeInfo canvasSize)
{
ArgumentNullException.ThrowIfNull(previewStyle);

View File

@@ -2,6 +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 System.Drawing.Drawing2D;
using MouseJump.Common.Models.Drawing;
namespace MouseJump.Common.Imaging;
@@ -31,6 +33,11 @@ public sealed class StaticImageRegionCopyService : IImageRegionCopyService
RectangleInfo sourceBounds,
RectangleInfo targetBounds)
{
// prevent the background bleeding through into screen images
// (see https://github.com/mikeclayton/FancyMouse/issues/44)
targetGraphics.PixelOffsetMode = PixelOffsetMode.Half;
targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
targetGraphics.DrawImage(
image: this.SourceImage,
destRect: targetBounds.ToRectangle(),

View File

@@ -203,6 +203,15 @@ public sealed class RectangleInfo
public RectangleInfo Offset(decimal dx, decimal dy) =>
new(this.X + dx, this.Y + dy, this.Width, this.Height);
public RectangleInfo Round() =>
this.Round(0);
public RectangleInfo Round(int decimals) => new(
Math.Round(this.X, decimals),
Math.Round(this.Y, decimals),
Math.Round(this.Width, decimals),
Math.Round(this.Height, decimals));
/// <summary>
/// Returns a new <see cref="RectangleInfo"/> that is a scaled version of the current rectangle.
/// The dimensions of the new rectangle are calculated by multiplying the current rectangle's dimensions by the scaling factor.

View File

@@ -12,6 +12,8 @@ public sealed class ScreenInfo
{
public ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
{
// this.Handle is a HMONITOR that has been cast to an int because we don't want
// to expose the HMONITOR type outside the current assembly.
this.Handle = handle;
this.Primary = primary;
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));

View File

@@ -33,6 +33,20 @@ public sealed class SizeInfo
get;
}
public SizeInfo Clamp(SizeInfo max)
{
return new(
width: Math.Clamp(this.Width, 0, max.Width),
height: Math.Clamp(this.Height, 0, max.Height));
}
public SizeInfo Clamp(decimal maxWidth, decimal maxHeight)
{
return new(
width: Math.Clamp(this.Width, 0, maxWidth),
height: Math.Clamp(this.Height, 0, maxHeight));
}
public SizeInfo Enlarge(BorderStyle border) =>
new(
this.Width + border.Horizontal,
@@ -43,6 +57,17 @@ public sealed class SizeInfo
this.Width + padding.Horizontal,
this.Height + padding.Vertical);
/// <summary>
/// Rounds down the width and height of this size to the nearest whole number.
/// </summary>
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
public SizeInfo Floor()
{
return new SizeInfo(
Math.Floor(this.Width),
Math.Floor(this.Height));
}
/// <summary>
/// Calculates the intersection of this size with another size, resulting in a size that represents
/// the overlapping dimensions. Both sizes must be non-negative.
@@ -69,19 +94,6 @@ public sealed class SizeInfo
public SizeInfo Invert() =>
new(-this.Width, -this.Height);
public SizeInfo Scale(decimal scalingFactor) => new(
this.Width * scalingFactor,
this.Height * scalingFactor);
public SizeInfo Shrink(BorderStyle border) =>
new(this.Width - border.Horizontal, this.Height - border.Vertical);
public SizeInfo Shrink(MarginStyle margin) =>
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);
public SizeInfo Shrink(PaddingStyle padding) =>
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
/// <summary>
/// Creates a new <see cref="RectangleInfo"/> instance representing a rectangle with this size,
/// positioned at the specified coordinates.
@@ -92,32 +104,39 @@ public sealed class SizeInfo
public RectangleInfo PlaceAt(decimal x, decimal y) =>
new(x, y, this.Width, this.Height);
public SizeInfo Round() =>
this.Round(0);
public SizeInfo Round(int decimals) => new(
Math.Round(this.Width, decimals),
Math.Round(this.Height, decimals));
public SizeInfo Scale(decimal scalingFactor) => new(
this.Width * scalingFactor,
this.Height * scalingFactor);
/// <summary>
/// Scales this size to fit within the bounds of another size, while maintaining the aspect ratio.
/// </summary>
/// <param name="bounds">The size to fit this size into.</param>
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
public SizeInfo ScaleToFit(SizeInfo bounds)
public SizeInfo ScaleToFit(SizeInfo bounds, out decimal scalingRatio)
{
var widthRatio = bounds.Width / this.Width;
var heightRatio = bounds.Height / this.Height;
return widthRatio.CompareTo(heightRatio) switch
switch (widthRatio.CompareTo(heightRatio))
{
< 0 => new(bounds.Width, this.Height * widthRatio),
0 => bounds,
> 0 => new(this.Width * heightRatio, bounds.Height),
};
}
/// <summary>
/// Rounds down the width and height of this size to the nearest whole number.
/// </summary>
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
public SizeInfo Floor()
{
return new SizeInfo(
Math.Floor(this.Width),
Math.Floor(this.Height));
case < 0:
scalingRatio = widthRatio;
return new(bounds.Width, this.Height * widthRatio);
case 0:
// widthRatio and heightRatio are the same, so just pick one
scalingRatio = widthRatio;
return bounds;
case > 0:
scalingRatio = heightRatio;
return new(this.Width * heightRatio, bounds.Height);
}
}
/// <summary>
@@ -140,6 +159,15 @@ public sealed class SizeInfo
return scalingRatio;
}
public SizeInfo Shrink(BorderStyle border) =>
new(this.Width - border.Horizontal, this.Height - border.Vertical);
public SizeInfo Shrink(MarginStyle margin) =>
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);
public SizeInfo Shrink(PaddingStyle padding) =>
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
public Size ToSize() => new((int)this.Width, (int)this.Height);
public Point ToPoint() => new((int)this.Width, (int)this.Height);

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJump.Common.Models.Settings;
public enum PreviewType
{
Custom = 0,
Compact = 1,
Bezelled = 2,
}

View File

@@ -9,14 +9,14 @@ namespace MouseJump.Common.Models.Styles;
/// </summary>
public sealed class BorderStyle
{
public static readonly BorderStyle Empty = new(Color.Transparent, 0, 0);
public static readonly BorderStyle Empty = new(null, 0, 0);
public BorderStyle(Color color, decimal all, decimal depth)
public BorderStyle(Color? color, decimal all, decimal depth)
: this(color, all, all, all, all, depth)
{
}
public BorderStyle(Color color, decimal left, decimal top, decimal right, decimal bottom, decimal depth)
public BorderStyle(Color? color, decimal left, decimal top, decimal right, decimal bottom, decimal depth)
{
this.Color = color;
this.Left = left;
@@ -26,7 +26,7 @@ public sealed class BorderStyle
this.Depth = depth;
}
public Color Color
public Color? Color
{
get;
}

View File

@@ -10,6 +10,10 @@ using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using MouseJump.Common.Helpers;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Settings;
using MouseJump.Common.Models.Styles;
namespace MouseJumpUI.Helpers;
@@ -93,4 +97,65 @@ internal sealed class SettingsHelper
{
this.CurrentSettings = this.LoadSettings();
}
public static PreviewStyle GetActivePreviewStyle(MouseJumpSettings settings)
{
var previewType = Enum.TryParse<PreviewType>(settings.Properties.PreviewType, true, out var previewTypeResult)
? previewTypeResult
: PreviewType.Bezelled;
var canvasSize = new SizeInfo(
settings.Properties.ThumbnailSize.Width,
settings.Properties.ThumbnailSize.Height);
var properties = settings.Properties;
var previewStyle = previewType switch
{
PreviewType.Compact => StyleHelper.CompactPreviewStyle.WithCanvasSize(canvasSize),
PreviewType.Bezelled => StyleHelper.BezelledPreviewStyle.WithCanvasSize(canvasSize),
PreviewType.Custom => new PreviewStyle(
canvasSize: canvasSize,
canvasStyle: new(
marginStyle: new(0),
borderStyle: new(
color: ConfigHelper.DeserializeFromConfigColorString(
properties.BorderColor),
all: properties.BorderThickness,
depth: properties.Border3dDepth
),
paddingStyle: new(
all: properties.BorderPadding
),
backgroundStyle: new(
color1: ConfigHelper.DeserializeFromConfigColorString(
properties.BackgroundColor1),
color2: ConfigHelper.DeserializeFromConfigColorString(
properties.BackgroundColor2)
)
),
screenStyle: new(
marginStyle: new(
all: properties.ScreenMargin
),
borderStyle: new(
color: ConfigHelper.DeserializeFromConfigColorString(
properties.BezelColor),
all: properties.BezelThickness,
depth: properties.Bezel3dDepth
),
paddingStyle: new(0),
backgroundStyle: new(
color1: ConfigHelper.DeserializeFromConfigColorString(
properties.ScreenColor1),
color2: ConfigHelper.DeserializeFromConfigColorString(
properties.ScreenColor2)
)
)),
_ => throw new InvalidOperationException(
$"Unhandled {nameof(PreviewType)} '{previewType}'"),
};
return previewStyle;
}
}

View File

@@ -183,12 +183,9 @@ internal sealed partial class MainForm : Form
var appSettings = this.SettingsHelper.CurrentSettings ?? throw new InvalidOperationException();
var screens = ScreenHelper.GetAllScreens().Select(screen => screen.DisplayArea).ToList();
var activatedLocation = MouseHelper.GetCursorPosition();
this.PreviewLayout = LayoutHelper.GetPreviewLayout(
previewStyle: StyleHelper.LegacyPreviewStyle.WithCanvasSize(
new(
appSettings.Properties.ThumbnailSize.Width,
appSettings.Properties.ThumbnailSize.Height
)),
previewStyle: SettingsHelper.GetActivePreviewStyle(appSettings),
screens: screens,
activatedLocation: activatedLocation);

View File

@@ -1,60 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows.Forms;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Windows.Forms.Padding object with some extra utility methods.
/// </summary>
public sealed class PaddingInfo
{
public PaddingInfo(decimal all)
: this(all, all, all, all)
{
}
public PaddingInfo(decimal left, decimal top, decimal right, decimal bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
public decimal Left
{
get;
}
public decimal Top
{
get;
}
public decimal Right
{
get;
}
public decimal Bottom
{
get;
}
public decimal Horizontal => this.Left + this.Right;
public decimal Vertical => this.Top + this.Bottom;
public override string ToString()
{
return "{" +
$"{nameof(this.Left)}={this.Left}," +
$"{nameof(this.Top)}={this.Top}," +
$"{nameof(this.Right)}={this.Right}," +
$"{nameof(this.Bottom)}={this.Bottom}" +
"}";
}
}

View File

@@ -1,53 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Drawing;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Point object with some extra utility methods.
/// </summary>
public sealed class PointInfo
{
public PointInfo(decimal x, decimal y)
{
this.X = x;
this.Y = y;
}
public PointInfo(Point point)
: this(point.X, point.Y)
{
}
public decimal X
{
get;
}
public decimal Y
{
get;
}
public SizeInfo ToSize()
{
return new((int)this.X, (int)this.Y);
}
public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor);
public PointInfo Offset(PointInfo amount) => new(this.X + amount.X, this.Y + amount.Y);
public Point ToPoint() => new((int)this.X, (int)this.Y);
public override string ToString()
{
return "{" +
$"{nameof(this.X)}={this.X}," +
$"{nameof(this.Y)}={this.Y}" +
"}";
}
}

View File

@@ -1,131 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.
/// </summary>
public sealed class RectangleInfo
{
public RectangleInfo(decimal x, decimal y, decimal width, decimal height)
{
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
public RectangleInfo(Rectangle rectangle)
: this(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)
{
}
public RectangleInfo(Point location, SizeInfo size)
: this(location.X, location.Y, size.Width, size.Height)
{
}
public RectangleInfo(SizeInfo size)
: this(0, 0, size.Width, size.Height)
{
}
public decimal X
{
get;
}
public decimal Y
{
get;
}
public decimal Width
{
get;
}
public decimal Height
{
get;
}
public decimal Left => this.X;
public decimal Top => this.Y;
public decimal Right => this.X + this.Width;
public decimal Bottom => this.Y + this.Height;
public SizeInfo Size => new(this.Width, this.Height);
public PointInfo Location => new(this.X, this.Y);
public decimal Area => this.Width * this.Height;
/// <remarks>
/// Adapted from https://github.com/dotnet/runtime
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
/// </remarks>
public bool Contains(RectangleInfo rect) =>
(this.X <= rect.X) && (rect.X + rect.Width <= this.X + this.Width) &&
(this.Y <= rect.Y) && (rect.Y + rect.Height <= this.Y + this.Height);
public RectangleInfo Enlarge(PaddingInfo padding) => new(
this.X + padding.Left,
this.Y + padding.Top,
this.Width + padding.Horizontal,
this.Height + padding.Vertical);
public RectangleInfo Offset(SizeInfo amount) => this.Offset(amount.Width, amount.Height);
public RectangleInfo Offset(decimal dx, decimal dy) => new(this.X + dx, this.Y + dy, this.Width, this.Height);
public RectangleInfo Scale(decimal scalingFactor) => new(
this.X * scalingFactor,
this.Y * scalingFactor,
this.Width * scalingFactor,
this.Height * scalingFactor);
public RectangleInfo Center(PointInfo point) => new(
x: point.X - (this.Width / 2),
y: point.Y - (this.Height / 2),
width: this.Width,
height: this.Height);
public PointInfo Midpoint => new(
x: this.X + (this.Width / 2),
y: this.Y + (this.Height / 2));
public RectangleInfo Clamp(RectangleInfo outer)
{
if ((this.Width > outer.Width) || (this.Height > outer.Height))
{
throw new ArgumentException($"Value cannot be larger than {nameof(outer)}.");
}
return new(
x: Math.Clamp(this.X, outer.X, outer.Right - this.Width),
y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height),
width: this.Width,
height: this.Height);
}
public Rectangle ToRectangle() => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
public override string ToString()
{
return "{" +
$"{nameof(this.Left)}={this.Left}," +
$"{nameof(this.Top)}={this.Top}," +
$"{nameof(this.Width)}={this.Width}," +
$"{nameof(this.Height)}={this.Height}" +
"}";
}
}

View File

@@ -1,87 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Size object with some extra utility methods.
/// </summary>
public sealed class SizeInfo
{
public SizeInfo(decimal width, decimal height)
{
this.Width = width;
this.Height = height;
}
public SizeInfo(Size size)
: this(size.Width, size.Height)
{
}
public decimal Width
{
get;
}
public decimal Height
{
get;
}
public SizeInfo Negate() => new(-this.Width, -this.Height);
public SizeInfo Shrink(PaddingInfo padding) => new(this.Width - padding.Horizontal, this.Height - padding.Vertical);
public SizeInfo Intersect(SizeInfo size) => new(
Math.Min(this.Width, size.Width),
Math.Min(this.Height, size.Height));
public RectangleInfo PlaceAt(decimal x, decimal y) => new(x, y, this.Width, this.Height);
public SizeInfo ScaleToFit(SizeInfo bounds)
{
var widthRatio = bounds.Width / this.Width;
var heightRatio = bounds.Height / this.Height;
return widthRatio.CompareTo(heightRatio) switch
{
< 0 => new(bounds.Width, this.Height * widthRatio),
0 => bounds,
> 0 => new(this.Width * heightRatio, bounds.Height),
};
}
/// <summary>
/// Get the scaling ratio to scale obj by so that it fits inside the specified bounds
/// without distorting the aspect ratio.
/// </summary>
public decimal ScaleToFitRatio(SizeInfo bounds)
{
if (bounds.Width == 0 || bounds.Height == 0)
{
throw new ArgumentException($"{nameof(bounds.Width)} or {nameof(bounds.Height)} cannot be zero", nameof(bounds));
}
var widthRatio = bounds.Width / this.Width;
var heightRatio = bounds.Height / this.Height;
var scalingRatio = Math.Min(widthRatio, heightRatio);
return scalingRatio;
}
public Size ToSize() => new((int)this.Width, (int)this.Height);
public Point ToPoint() => new((int)this.Width, (int)this.Height);
public override string ToString()
{
return "{" +
$"{nameof(this.Width)}={this.Width}," +
$"{nameof(this.Height)}={this.Height}" +
"}";
}
}

View File

@@ -1,123 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Screen;
namespace MouseJumpUI.Models.Layout;
/// <summary>
/// Represents a collection of values needed for calculating the MainForm layout.
/// </summary>
public sealed class LayoutConfig
{
public LayoutConfig(
RectangleInfo virtualScreenBounds,
List<ScreenInfo> screens,
PointInfo activatedLocation,
int activatedScreenIndex,
int activatedScreenNumber,
SizeInfo maximumFormSize,
PaddingInfo formPadding,
PaddingInfo previewPadding)
{
// make sure the virtual screen entirely contains all of the individual screen bounds
ArgumentNullException.ThrowIfNull(virtualScreenBounds);
ArgumentNullException.ThrowIfNull(screens);
if (screens.Any(screen => !virtualScreenBounds.Contains(screen.Bounds)))
{
throw new ArgumentException($"'{nameof(virtualScreenBounds)}' must contain all of the screens in '{nameof(screens)}'", nameof(virtualScreenBounds));
}
this.VirtualScreenBounds = virtualScreenBounds;
this.Screens = new(screens.ToList());
this.ActivatedLocation = activatedLocation;
this.ActivatedScreenIndex = activatedScreenIndex;
this.ActivatedScreenNumber = activatedScreenNumber;
this.MaximumFormSize = maximumFormSize;
this.FormPadding = formPadding;
this.PreviewPadding = previewPadding;
}
/// <summary>
/// Gets the coordinates of the entire virtual screen.
/// </summary>
/// <remarks>
/// The Virtual Screen is the bounding rectangle of all the monitors.
/// https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen
/// </remarks>
public RectangleInfo VirtualScreenBounds
{
get;
}
/// <summary>
/// Gets a collection containing the individual screens connected to the system.
/// </summary>
public ReadOnlyCollection<ScreenInfo> Screens
{
get;
}
/// <summary>
/// Gets the point where the cursor was located when the form was activated.
/// </summary>
/// <summary>
/// The preview form will be centered on this location unless there are any
/// constraints such as being too close to edge of a screen, in which case
/// the form will be displayed centered as close as possible to this location.
/// </summary>
public PointInfo ActivatedLocation
{
get;
}
/// <summary>
/// Gets the index of the screen the cursor was on when the form was activated.
/// The value is an index into the ScreenBounds array and is 0-indexed as a result.
/// </summary>
public int ActivatedScreenIndex
{
get;
}
/// <summary>
/// Gets the screen number the cursor was on when the form was activated.
/// The value matches the screen numbering scheme in the "Display Settings" dialog
/// and is 1-indexed as a result.
/// </summary>
public int ActivatedScreenNumber
{
get;
}
/// <summary>
/// Gets the maximum size of the screen preview form.
/// </summary>
public SizeInfo MaximumFormSize
{
get;
}
/// <summary>
/// Gets the padding border around the screen preview form.
/// </summary>
public PaddingInfo FormPadding
{
get;
}
/// <summary>
/// Gets the padding border inside the screen preview image.
/// </summary>
public PaddingInfo PreviewPadding
{
get;
}
}

View File

@@ -1,113 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.Models.Layout;
public sealed class LayoutInfo
{
public sealed class Builder
{
public Builder()
{
this.ScreenBounds = new();
}
public LayoutConfig? LayoutConfig
{
get;
set;
}
public RectangleInfo? FormBounds
{
get;
set;
}
public RectangleInfo? PreviewBounds
{
get;
set;
}
public List<RectangleInfo> ScreenBounds
{
get;
set;
}
public RectangleInfo? ActivatedScreenBounds
{
get;
set;
}
public LayoutInfo Build()
{
return new LayoutInfo(
layoutConfig: this.LayoutConfig ?? throw new InvalidOperationException(),
formBounds: this.FormBounds ?? throw new InvalidOperationException(),
previewBounds: this.PreviewBounds ?? throw new InvalidOperationException(),
screenBounds: this.ScreenBounds ?? throw new InvalidOperationException(),
activatedScreenBounds: this.ActivatedScreenBounds ?? throw new InvalidOperationException());
}
}
public LayoutInfo(
LayoutConfig layoutConfig,
RectangleInfo formBounds,
RectangleInfo previewBounds,
IEnumerable<RectangleInfo> screenBounds,
RectangleInfo activatedScreenBounds)
{
this.LayoutConfig = layoutConfig ?? throw new ArgumentNullException(nameof(layoutConfig));
this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds));
this.PreviewBounds = previewBounds ?? throw new ArgumentNullException(nameof(previewBounds));
this.ScreenBounds = new(
(screenBounds ?? throw new ArgumentNullException(nameof(screenBounds)))
.ToList());
this.ActivatedScreenBounds = activatedScreenBounds ?? throw new ArgumentNullException(nameof(activatedScreenBounds));
}
/// <summary>
/// Gets the original LayoutConfig settings used to calculate coordinates.
/// </summary>
public LayoutConfig LayoutConfig
{
get;
}
/// <summary>
/// Gets the size and location of the preview form.
/// </summary>
public RectangleInfo FormBounds
{
get;
}
/// <summary>
/// Gets the size and location of the preview image.
/// </summary>
public RectangleInfo PreviewBounds
{
get;
}
public ReadOnlyCollection<RectangleInfo> ScreenBounds
{
get;
}
public RectangleInfo ActivatedScreenBounds
{
get;
}
}

View File

@@ -1,47 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.Models.Screen;
/// <summary>
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
/// take a dependency on WinForms just for screen info.
/// </summary>
public sealed class ScreenInfo
{
internal ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
{
this.Handle = handle;
this.Primary = primary;
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
this.WorkingArea = workingArea ?? throw new ArgumentNullException(nameof(workingArea));
}
public int Handle
{
get;
}
public bool Primary
{
get;
}
public RectangleInfo DisplayArea
{
get;
}
public RectangleInfo Bounds =>
this.DisplayArea;
public RectangleInfo WorkingArea
{
get;
}
}

View File

@@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;
@@ -97,29 +95,4 @@ internal static class Program
cancellationTokenSource.Cancel();
Application.Exit();
}
private static MouseJumpSettings ReadSettings()
{
var settingsUtils = new SettingsUtils();
var settingsPath = settingsUtils.GetSettingsFilePath(MouseJumpSettings.ModuleName);
if (!File.Exists(settingsPath))
{
var scaffoldSettings = new MouseJumpSettings();
settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), MouseJumpSettings.ModuleName);
}
var settings = new MouseJumpSettings();
try
{
settings = settingsUtils.GetSettings<MouseJumpSettings>(MouseJumpSettings.ModuleName);
}
catch (Exception ex)
{
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
Logger.LogInfo(errorMessage);
Logger.LogDebug(errorMessage);
}
return settings;
}
}

View File

@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h new.base.rc new.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{0db0f63a-d2f8-4da3-a650-2d0b8724218e}</ProjectGuid>
<RootNamespace>NewPlusShellExtensionWin10</RootNamespace>
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.NewPlus.ShellExtension.win10</TargetName>
<LinkIncremental />
<IgnoreImportLibrary />
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu</AdditionalIncludeDirectories>
<CompileAsWinRT>false</CompileAsWinRT>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu</AdditionalIncludeDirectories>
<CompileAsWinRT>false</CompileAsWinRT>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu_item.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\template_folder.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\template_item.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\trace.h" />
<ClInclude Include="dll_main.h" />
<ClInclude Include="Generated Files\resource.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.base.h" />
<ClInclude Include="shell_context_menu_win10.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\shell_context_sub_menu_item.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\template_folder.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\template_item.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\trace.cpp" />
<ClCompile Include="dll_main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="shell_context_menu_win10.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files\new.rc" />
<None Include="new.base.rc" />
</ItemGroup>
<ItemGroup>
<None Include="resources.resx">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="dll.def" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{4cea4fff-ccef-4b62-9e46-f33da2b9a0cc}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shell_context_menu_win10.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.base.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Generated Files\resource.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu_item.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\template_folder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\template_item.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dll_main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dll_main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shell_context_menu_win10.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\template_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\template_folder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\shell_context_sub_menu_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files\new.rc">
<Filter>Generated Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="dll.def">
<Filter>Source Files</Filter>
</None>
<None Include="new.base.rc">
<Filter>Resource Files</Filter>
</None>
<None Include="resources.resx">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
LIBRARY
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllGetActivationFactory PRIVATE

View File

@@ -0,0 +1,44 @@
#include "pch.h"
#include "shell_context_menu_win10.h"
#include "dll_main.h"
#include "trace.h"
#include <common/Telemetry/EtwTrace/EtwTrace.h>
HMODULE module_instance_handle = 0;
Shared::Trace::ETWTrace trace(L"NewPlusShellExtension_Win10");
BOOL APIENTRY DllMain(HMODULE module_handle, DWORD ul_reason_for_call, LPVOID reserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
module_instance_handle = module_handle;
Trace::RegisterProvider();
newplus::utilities::init_logger();
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
{
return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
}
STDAPI DllCanUnloadNow()
{
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(_In_ REFCLSID ref_class_id, _In_ REFIID ref_interface_id, _Outptr_ LPVOID FAR* object)
{
return Module<InProc>::GetModule().GetClassObject(ref_class_id, ref_interface_id, object);
}
CoCreatableClass(shell_context_menu_win10)

View File

@@ -0,0 +1,6 @@
#pragma once
#include <common/Telemetry/EtwTrace/EtwTrace.h>
extern HMODULE module_instance_handle;
extern Shared::Trace::ETWTrace trace;

View File

@@ -0,0 +1,54 @@
#include <windows.h>
#include "Generated Files/resource.h"
#include "../../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.

View File

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

View File

@@ -0,0 +1,3 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"

View File

@@ -0,0 +1,39 @@
// Precompiled header file.
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOHELP
#define NOCOMM
// Windows and STL
#include <Shobjidl.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <Windows.h>
#include <shlobj.h>
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
#include <atlbase.h>
#include <wrl.h>
#include <wrl/module.h>
#include <wrl/client.h>
#include <unknwn.h>
using namespace Microsoft::WRL;
// PowerToys project common
#include <ProjectTelemetry.h>
#include <common/utils/resources.h>
#include <common/logger/logger.h>
#include <common/logger/logger_settings.h>
#include <common/utils/logger_helper.h>
#include <common/Themes/theme_helpers.h>
// New project specific
#include "dll_main.h"
#include "template_folder.h"
#include "settings.h"
#include "new_utilities.h"

View File

@@ -0,0 +1,12 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys.New+"
#define INTERNAL_NAME "PowerToys.New+"
#define ORIGINAL_FILENAME "PowerToys.NewPlus.ShellExtension.win10.dll"
// Non-localizable
//////////////////////////////

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="context_menu_item_new" xml:space="preserve">
<value>New+</value>
<comment>The main context menu item that users click on. This should be localized to match New in Windows. e.g. Danish it would become Ny+</comment>
</data>
<data name="context_menu_item_open_templates" xml:space="preserve">
<value>Open templates</value>
<comment>The menu item in the context menu that enables user to open the folder that contains their templates.</comment>
</data>
<data name="default_template_sub_folder_name_where_templates_are_stored" xml:space="preserve">
<value>Templates</value>
<comment>Default subfolder name where templates are stored.</comment>
</data>
</root>

View File

@@ -0,0 +1,270 @@
#include "pch.h"
#include "shell_context_menu_win10.h"
#include "shell_context_sub_menu.h"
#include "shell_context_sub_menu_item.h"
#include "new_utilities.h"
#include "settings.h"
#include "trace.h"
#include "Generated Files/resource.h"
#include <common/Themes/icon_helpers.h>
#include <common/utils/winapi_error.h>
using namespace Microsoft::WRL;
using namespace newplus;
shell_context_menu_win10::~shell_context_menu_win10()
{
for (const auto& handle : bitmap_handles)
{
DeleteObject(handle);
}
}
#pragma region IShellExtInit
IFACEMETHODIMP shell_context_menu_win10::Initialize(PCIDLIST_ABSOLUTE, IDataObject*, HKEY)
{
return S_OK;
}
#pragma endregion
#pragma region IContextMenu
IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags)
{
if (!NewSettingsInstance().GetEnabled()
|| package::IsWin11OrGreater()
)
{
return E_FAIL;
}
if (menu_flags & CMF_DEFAULTONLY)
{
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
}
try
{
// Create the initial context popup menu containing the list of templates and open templates action
int menu_id = menu_first_cmd_id;
MENUITEMINFO newplus_main_context_menu_item;
HMENU sub_menu_of_templates = CreatePopupMenu();
int sub_menu_index = 0;
// Determine the New+ Template folder location
const std::filesystem::path template_folder_root = utilities::get_new_template_folder_location();
// Create the New+ Template folder location if it doesn't exist (very rare scenario)
utilities::create_folder_if_not_exist(template_folder_root);
// Scan the folder for any files and folders (the templates)
templates = new template_folder(template_folder_root);
templates->rescan_template_folder();
const auto number_of_templates = templates->list_of_templates.size();
// Create the New+ menu item and point to the initial context popup menu
static const std::wstring localized_context_menu_item =
GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_NEW, L"New+");
wchar_t newplus_menu_name[20] = { 0 };
wcscpy_s(newplus_menu_name, ARRAYSIZE(newplus_menu_name), localized_context_menu_item.c_str());
newplus_main_context_menu_item.cbSize = sizeof(MENUITEMINFOW);
newplus_main_context_menu_item.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU;
newplus_main_context_menu_item.wID = menu_id;
newplus_main_context_menu_item.fType = MFT_STRING;
newplus_main_context_menu_item.dwTypeData = (PWSTR)newplus_menu_name;
newplus_main_context_menu_item.hSubMenu = sub_menu_of_templates;
const auto newplus_icon_index = 0;
if (bitmap_handles.size() == 0)
{
const std::wstring icon_file = utilities::get_new_icon_resource_filepath(
module_instance_handle, ThemeHelpers::GetAppTheme())
.c_str();
HICON local_icon_handle = static_cast<HICON>(
LoadImage(NULL, icon_file.c_str(), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE));
if (local_icon_handle)
{
bitmap_handles.push_back(CreateBitmapFromIcon(local_icon_handle));
DestroyIcon(local_icon_handle);
}
}
if (bitmap_handles.size() > newplus_icon_index && bitmap_handles[newplus_icon_index])
{
newplus_main_context_menu_item.fMask |= MIIM_BITMAP;
newplus_main_context_menu_item.hbmpItem = bitmap_handles[newplus_icon_index];
}
menu_id++;
// Add template items to context menu
int index = 0;
for (; index < number_of_templates; index++)
{
const auto template_item = templates->get_template_item(index);
add_template_item_to_context_menu(sub_menu_of_templates, sub_menu_index, template_item, menu_id, index);
menu_id++;
sub_menu_index++;
}
// Add separator to context menu
add_separator_to_context_menu(sub_menu_of_templates, sub_menu_index);
sub_menu_index++;
// Add "Open templates" item to context menu
add_open_templates_to_context_menu(sub_menu_of_templates, sub_menu_index, template_folder_root, menu_id, index);
menu_id++;
if (!InsertMenuItem(menu_handle, menu_index, TRUE, &newplus_main_context_menu_item))
{
Logger::error(L"QueryContextMenu() failed. {}", get_last_error_or_default(GetLastError()));
return HRESULT_FROM_WIN32(GetLastError());
}
else
{
// Return the amount if entries inserted
const auto number_of_items_inserted = menu_id - menu_first_cmd_id;
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, number_of_items_inserted);
}
}
catch (const std::exception& ex)
{
Logger::error(ex.what());
}
return E_FAIL;
}
void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, const std::filesystem::path& template_folder_root, int menu_id, int index)
{
static const std::wstring localized_context_menu_item_open_templates =
GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_OPEN_TEMPLATES, L"Open templates");
wchar_t menu_name_open[256] = { 0 };
wcscpy_s(menu_name_open, ARRAYSIZE(menu_name_open), localized_context_menu_item_open_templates.c_str());
const auto open_folder_item = Make<template_folder_context_menu_item>(template_folder_root);
MENUITEMINFO newplus_menu_item_open_templates;
newplus_menu_item_open_templates.cbSize = sizeof(MENUITEMINFO);
newplus_menu_item_open_templates.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID;
newplus_menu_item_open_templates.wID = menu_id;
newplus_menu_item_open_templates.fType = MFT_STRING;
newplus_menu_item_open_templates.dwTypeData = (PWSTR)menu_name_open;
const auto open_templates_icon_index = index + 1;
if (bitmap_handles.size() <= open_templates_icon_index)
{
const std::wstring icon_file = utilities::get_open_templates_icon_resource_filepath(
module_instance_handle, ThemeHelpers::GetAppTheme())
.c_str();
HICON open_template_icon_handle = static_cast<HICON>(
LoadImage(NULL, icon_file.c_str(), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE));
if (open_template_icon_handle)
{
bitmap_handles.push_back(CreateBitmapFromIcon(open_template_icon_handle));
DestroyIcon(open_template_icon_handle);
}
}
if (bitmap_handles.size() > open_templates_icon_index && bitmap_handles[open_templates_icon_index])
{
newplus_menu_item_open_templates.fMask |= MIIM_BITMAP;
newplus_menu_item_open_templates.hbmpItem = bitmap_handles[open_templates_icon_index];
}
InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &newplus_menu_item_open_templates);
}
void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index)
{
MENUITEMINFO menu_item_separator;
menu_item_separator.cbSize = sizeof(MENUITEMINFO);
menu_item_separator.fMask = MIIM_FTYPE;
menu_item_separator.fType = MFT_SEPARATOR;
InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &menu_item_separator);
}
void shell_context_menu_win10::add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index)
{
wchar_t menu_name[256] = { 0 };
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(!utilities::get_newplus_setting_hide_extension(), !utilities::get_newplus_setting_hide_starting_digits()).c_str());
MENUITEMINFO newplus_menu_item_template;
newplus_menu_item_template.cbSize = sizeof(MENUITEMINFO);
newplus_menu_item_template.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_DATA;
newplus_menu_item_template.wID = menu_id;
newplus_menu_item_template.fType = MFT_STRING;
newplus_menu_item_template.dwTypeData = (PWSTR)menu_name;
const auto current_template_icon_index = index + 1;
if (bitmap_handles.size() <= current_template_icon_index)
{
HICON template_icon_handle = template_item->get_explorer_icon_handle();
if (template_icon_handle)
{
bitmap_handles.push_back(CreateBitmapFromIcon(template_icon_handle));
DestroyIcon(template_icon_handle);
}
}
if (bitmap_handles.size() > current_template_icon_index && bitmap_handles[current_template_icon_index])
{
newplus_menu_item_template.fMask |= MIIM_BITMAP;
newplus_menu_item_template.hbmpItem = bitmap_handles[current_template_icon_index];
}
InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &newplus_menu_item_template);
}
IFACEMETHODIMP shell_context_menu_win10::InvokeCommand(CMINVOKECOMMANDINFO* params)
{
if (!params)
{
return E_FAIL;
}
// Get selected menu item (a template or the "Open templates" item)
const auto selected_menu_item_index = LOWORD(params->lpVerb) - 1;
if (selected_menu_item_index < 0)
{
return E_FAIL;
}
const auto number_of_templates = templates->list_of_templates.size();
const bool is_template_item = selected_menu_item_index < number_of_templates;
// Save how many item templates we have so it can be sent later when we do something with New+.
// It will be sent when the user does something, similar to Windows 11 context menu.
newplus::utilities::set_saved_number_of_templates(static_cast<size_t>(number_of_templates));
if (is_template_item)
{
// It's a template menu item
const auto template_entry = templates->get_template_item(selected_menu_item_index);
return newplus::utilities::copy_template(template_entry, site_of_folder);
}
else
{
// It's the "Open templates" menu item
const std::filesystem::path template_folder_root = utilities::get_new_template_folder_location();
return newplus::utilities::open_template_folder(template_folder_root);
}
return E_FAIL;
}
IFACEMETHODIMP shell_context_menu_win10::GetCommandString(UINT_PTR, UINT, UINT*, CHAR*, UINT)
{
return E_NOTIMPL;
}
#pragma endregion
#pragma region IObjectWithSite
IFACEMETHODIMP shell_context_menu_win10::SetSite(_In_ IUnknown* site) noexcept
{
this->site_of_folder = site;
return S_OK;
}
IFACEMETHODIMP shell_context_menu_win10::GetSite(_In_ REFIID riid, _COM_Outptr_ void** returned_site) noexcept
{
return this->site_of_folder.CopyTo(riid, returned_site);
}
#pragma endregion

View File

@@ -0,0 +1,45 @@
#pragma once
#include "pch.h"
#include <template_folder.h>
using namespace Microsoft::WRL;
#define NEW_SHELL_EXTENSION_EXPLORER_COMMAND_WIN10_UUID "FF90D477-E32A-4BE8-8CC5-A502A97F5401"
// File Explorer context menu "New+" for Windows 10
class __declspec(uuid(NEW_SHELL_EXTENSION_EXPLORER_COMMAND_WIN10_UUID)) shell_context_menu_win10 :
public RuntimeClass<
RuntimeClassFlags<ClassicCom>,
IShellExtInit,
IContextMenu,
IObjectWithSite>
{
public:
~shell_context_menu_win10();
#pragma region IShellExtInit
IFACEMETHODIMP Initialize(_In_opt_ PCIDLIST_ABSOLUTE, _In_ IDataObject*, HKEY);
#pragma endregion
#pragma region IContextMenu
IFACEMETHODIMP QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags);
IFACEMETHODIMP InvokeCommand(CMINVOKECOMMANDINFO* pici);
IFACEMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, CHAR*, UINT);
#pragma endregion
#pragma region IObjectWithSite
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept;
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept;
#pragma endregion
protected:
void add_open_templates_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, const std::filesystem::path& template_folder_root, int menu_id, int index);
void add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index);
void add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index);
HINSTANCE instance_handle = 0;
ComPtr<IUnknown> site_of_folder;
newplus::template_folder* templates = nullptr;
std::vector<HBITMAP> bitmap_handles;
};

View File

@@ -128,6 +128,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<ClInclude Include="template_item.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="new_utilities.cpp" />
<ClCompile Include="shell_context_menu.cpp" />
<ClCompile Include="shell_context_sub_menu.cpp" />
<ClCompile Include="shell_context_sub_menu_item.cpp" />
@@ -227,7 +228,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

View File

@@ -31,6 +31,9 @@
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="new_utilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="template_folder.h">
@@ -123,9 +126,6 @@
<None Include="AppxManifest.xml">
<Filter>Source Files</Filter>
</None>
<None Include="Assets\NewPlus\New.ico">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SmallTile.png">
<Filter>Asset Files</Filter>
</None>
@@ -190,14 +190,8 @@
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Text Include="TemplateExamples\Example folder\Another example txt file.txt">
<Filter>Template Examples\Example folder</Filter>
</Text>
<Text Include="TemplateExamples\Example folder\Example txt file.txt">
<Filter>Template Examples\Example folder</Filter>
</Text>
<Text Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt">
<Filter>Template Examples</Filter>
</Text>
<CopyFileToFolders Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt" />
<CopyFileToFolders Include="TemplateExamples\Example folder\Example txt file.txt" />
<CopyFileToFolders Include="TemplateExamples\Example folder\Another example txt file.txt" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
#include "pch.h"
#include "new_utilities.h"
// HACK: Store number of templates when generating the menu entries to send later.
size_t saved_number_of_templates = -1;
size_t newplus::utilities::get_saved_number_of_templates()
{
return saved_number_of_templates;
}
void newplus::utilities::set_saved_number_of_templates(size_t templates)
{
saved_number_of_templates = templates;
}

View File

@@ -7,11 +7,17 @@
#include "constants.h"
#include "settings.h"
#include "template_item.h"
#include "trace.h"
#pragma comment(lib, "Shlwapi.lib")
using namespace newplus;
namespace newplus::utilities
{
size_t get_saved_number_of_templates();
void set_saved_number_of_templates(size_t templates);
inline std::wstring get_explorer_icon(std::filesystem::path path)
{
@@ -39,6 +45,33 @@ namespace newplus::utilities
return icon_resource;
}
inline HICON get_explorer_icon_handle(std::filesystem::path path)
{
SHFILEINFO shell_file_info = { 0 };
const std::wstring filepath = path.wstring();
DWORD_PTR result = SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICON);
if (shell_file_info.hIcon)
{
return shell_file_info.hIcon;
}
WCHAR icon_resource_specifier[MAX_PATH] = { 0 };
DWORD buffer_length = MAX_PATH;
const std::wstring extension = path.extension().wstring();
const HRESULT hr = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN,
ASSOCSTR_DEFAULTICON,
extension.c_str(),
NULL,
icon_resource_specifier,
&buffer_length);
const std::wstring icon_resource = icon_resource_specifier;
const auto icon_x = GetSystemMetrics(SM_CXSMICON);
const auto icon_y = GetSystemMetrics(SM_CYSMICON);
HICON hIcon = static_cast<HICON>(LoadImage(NULL, icon_resource.c_str(), IMAGE_ICON, icon_x, icon_y, LR_LOADFROMFILE));
return hIcon;
}
inline bool is_hidden(const std::filesystem::path path)
{
const std::filesystem::path::string_type name = path.filename();
@@ -180,4 +213,227 @@ namespace newplus::utilities
return path;
}
inline bool is_desktop_folder(const std::filesystem::path target_fullpath)
{
TCHAR desktopPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath)))
{
return StrCmpIW(target_fullpath.c_str(), desktopPath) == 0;
}
return false;
}
inline void explorer_enter_rename_mode(const std::filesystem::path target_fullpath_of_new_instance)
{
const std::filesystem::path path_without_new_file_or_dir = target_fullpath_of_new_instance.parent_path();
const std::filesystem::path new_file_or_dir_without_path = target_fullpath_of_new_instance.filename();
ComPtr<IShellWindows> shell_windows;
HRESULT hr;
if (FAILED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_PPV_ARGS(&shell_windows))))
{
return;
}
long window_handle;
ComPtr<IDispatch> shell_window;
const bool object_created_on_desktop = is_desktop_folder(path_without_new_file_or_dir.c_str());
if (object_created_on_desktop)
{
// Special handling for desktop folder
VARIANT empty_yet_needed_incl_init;
VariantInit(&empty_yet_needed_incl_init);
if (FAILED(shell_windows->FindWindowSW(&empty_yet_needed_incl_init, &empty_yet_needed_incl_init, SWC_DESKTOP, &window_handle, SWFO_NEEDDISPATCH, &shell_window)))
{
return;
}
}
else
{
long count_of_shell_windows = 0;
shell_windows->get_Count(&count_of_shell_windows);
for (long i = 0; i < count_of_shell_windows; ++i)
{
ComPtr<IWebBrowserApp> web_browser_app;
VARIANT v;
V_VT(&v) = VT_I4;
V_I4(&v) = i;
hr = shell_windows->Item(v, &shell_window);
if (SUCCEEDED(hr) && shell_window)
{
hr = shell_window.As(&web_browser_app);
if (SUCCEEDED(hr))
{
BSTR folder_view_location;
hr = web_browser_app->get_LocationURL(&folder_view_location);
if (SUCCEEDED(hr) && folder_view_location)
{
wchar_t path[MAX_PATH];
DWORD pathLength = ARRAYSIZE(path);
hr = PathCreateFromUrl(folder_view_location, path, &pathLength, 0);
SysFreeString(folder_view_location);
if (SUCCEEDED(hr) && StrCmpIW(path_without_new_file_or_dir.c_str(), path) == 0)
{
break;
}
}
}
}
shell_window = nullptr;
}
}
if (!shell_window)
{
return;
}
ComPtr<IServiceProvider> service_provider;
shell_window.As(&service_provider);
ComPtr<IShellBrowser> shell_browser;
service_provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shell_browser));
ComPtr<IShellView> shell_view;
shell_browser->QueryActiveShellView(&shell_view);
ComPtr<IFolderView> folder_view;
shell_view.As(&folder_view);
// Find the newly created object (file or folder)
// And put object into edit mode (SVSI_EDIT) and if desktop also reposition
int number_of_objects_in_view = 0;
bool done = false;
folder_view->ItemCount(SVGIO_ALLVIEW, &number_of_objects_in_view);
for (int i = 0; i < number_of_objects_in_view && !done; ++i)
{
std::wstring path_of_item(MAX_PATH, 0);
LPITEMIDLIST shell_item_ids;
folder_view->Item(i, &shell_item_ids);
SHGetPathFromIDList(shell_item_ids, &path_of_item[0]);
const std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename();
if (utilities::wstring_same_when_comparing_ignore_case(new_file_or_dir_without_path, current_filename))
{
const DWORD common_select_flags = SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED;
if (object_created_on_desktop)
{
// Newly created object is on the desktop -- reposition under mouse and enter rename mode
LPCITEMIDLIST shell_item_to_select_and_position[] = { shell_item_ids };
POINT mouse_position;
GetCursorPos(&mouse_position);
mouse_position.x -= GetSystemMetrics(SM_CXMENUSIZE);
mouse_position.x = max(mouse_position.x, 20);
mouse_position.y -= GetSystemMetrics(SM_CXMENUSIZE)/2;
mouse_position.y = max(mouse_position.y, 20);
POINT position[] = { mouse_position };
folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM);
}
else
{
// Enter rename mode
folder_view->SelectItem(i, common_select_flags);
}
done = true;
}
CoTaskMemFree(shell_item_ids);
}
}
inline HRESULT copy_template(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder)
{
HRESULT hr = S_OK;
try
{
Logger::info(L"Copying template");
if (newplus::utilities::get_saved_number_of_templates() >= 0)
{
// Log that context menu was shown and with how many items
trace.UpdateState(true);
Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates());
trace.Flush();
trace.UpdateState(false);
}
// Determine target path of where context menu was displayed
const auto target_path_name = utilities::get_path_from_unknown_site(site_of_folder);
// Determine initial filename
std::filesystem::path source_fullpath = template_entry->path;
std::filesystem::path target_fullpath = std::wstring(target_path_name);
// Only append name to target if source is not a directory
if (!utilities::is_directory(source_fullpath))
{
target_fullpath.append(template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits()));
}
// Copy file and determine final filename
std::filesystem::path target_final_fullpath = template_entry->copy_object_to(GetActiveWindow(), target_fullpath);
trace.UpdateState(true);
Trace::EventCopyTemplate(target_final_fullpath.extension().c_str());
trace.Flush();
trace.UpdateState(false);
// Refresh folder items
template_entry->refresh_target(target_final_fullpath);
// Enter rename mode
template_entry->enter_rename_mode(target_final_fullpath);
}
catch (const std::exception& ex)
{
Logger::error(ex.what());
hr = S_FALSE;
}
trace.UpdateState(true);
Trace::EventCopyTemplateResult(hr);
trace.Flush();
trace.UpdateState(false);
return hr;
}
inline HRESULT open_template_folder(const std::filesystem::path template_folder)
{
HRESULT hr = S_OK;
try
{
Logger::info(L"Open templates folder");
if (newplus::utilities::get_saved_number_of_templates() >= 0)
{
// Log that context menu was shown and with how many items
trace.UpdateState(true);
Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates());
trace.Flush();
trace.UpdateState(false);
}
const std::wstring verb_hardcoded_do_not_change = L"open";
ShellExecute(nullptr, verb_hardcoded_do_not_change.c_str(), template_folder.c_str(), NULL, NULL, SW_SHOWNORMAL);
trace.UpdateState(true);
Trace::EventOpenTemplates();
trace.Flush();
trace.UpdateState(false);
}
catch (const std::exception& ex)
{
Logger::error(ex.what());
hr = S_FALSE;
}
return hr;
}
}

View File

@@ -27,7 +27,6 @@ public:
#pragma region IObjectWithSite
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept;
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept;
#pragma endregion

View File

@@ -1,13 +1,13 @@
#include "pch.h"
#include "shell_context_sub_menu.h"
#include "trace.h"
#include "new_utilities.h"
using namespace Microsoft::WRL;
// // Sub context menu command enumerator
shell_context_sub_menu::shell_context_sub_menu(const ComPtr<IUnknown> site_of_folder)
{
trace.UpdateState(true);
this->site_of_folder = site_of_folder;
// Determine the New+ Template folder location
@@ -36,8 +36,9 @@ shell_context_sub_menu::shell_context_sub_menu(const ComPtr<IUnknown> site_of_fo
current_command = explorer_menu_item_commands.cbegin();
// Log that context menu was shown and with how many items
Trace::EventShowTemplateItems(number_of_templates);
// Save how many item templates we have so it can be sent later when we do something with New+.
// We don't send it here or it would send an event every time we open a context menu.
newplus::utilities::set_saved_number_of_templates(static_cast<size_t>(number_of_templates));
}
// IEnumExplorerCommand

View File

@@ -63,49 +63,7 @@ IFACEMETHODIMP shell_context_sub_menu_item::GetState(_In_opt_ IShellItemArray* s
IFACEMETHODIMP shell_context_sub_menu_item::Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept
{
HRESULT hr = S_OK;
try
{
trace.UpdateState(true);
// Determine target path of where context menu was displayed
const auto target_path_name = utilities::get_path_from_unknown_site(site_of_folder);
// Determine initial filename
std::filesystem::path source_fullpath = template_entry->path;
std::filesystem::path target_fullpath = std::wstring(target_path_name);
// Only append name to target if source is not a directory
if (!utilities::is_directory(target_fullpath))
{
target_fullpath.append(this->template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits()));
}
// Copy file and determine final filename
std::filesystem::path target_final_fullpath = this->template_entry->copy_object_to(GetActiveWindow(), target_fullpath);
Trace::EventCopyTemplate(target_final_fullpath.extension().c_str());
// Refresh folder items
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL);
// Enter rename mode
this->template_entry->enter_rename_mode(site_of_folder, target_final_fullpath);
Trace::EventCopyTemplateResult(S_OK);
}
catch (const std::exception& ex)
{
Trace::EventCopyTemplateResult(S_FALSE);
Logger::error(ex.what());
hr = S_FALSE;
}
trace.Flush();
trace.UpdateState(false);
return hr;
return newplus::utilities::copy_template(template_entry, site_of_folder);
}
IFACEMETHODIMP shell_context_sub_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
@@ -162,9 +120,5 @@ IFACEMETHODIMP template_folder_context_menu_item::GetIcon(_In_opt_ IShellItemArr
IFACEMETHODIMP template_folder_context_menu_item::Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept
{
Logger::info(L"Open templates folder");
const std::wstring verb_hardcoded_do_not_change = L"open";
ShellExecute(nullptr, verb_hardcoded_do_not_change.c_str(), shell_template_folder.c_str(), NULL, NULL, SW_SHOWNORMAL);
return S_OK;
return newplus::utilities::open_template_folder(shell_template_folder);
}

View File

@@ -11,6 +11,11 @@ template_folder::template_folder(const std::filesystem::path newplus_template_fo
this->template_folder_path = newplus_template_folder;
}
template_folder::~template_folder()
{
list_of_templates.clear();
}
void template_folder::init()
{
rescan_template_folder();

View File

@@ -13,6 +13,8 @@ namespace newplus
{
public:
template_folder(const std::filesystem::path newplus_template_folder);
~template_folder();
void rescan_template_folder();
std::filesystem::path template_folder_path;

View File

@@ -63,6 +63,11 @@ std::wstring template_item::get_explorer_icon() const
return utilities::get_explorer_icon(path);
}
HICON template_item::get_explorer_icon_handle() const
{
return utilities::get_explorer_icon_handle(path);
}
std::filesystem::path template_item::copy_object_to(const HWND window_handle, const std::filesystem::path destination) const
{
// SHFILEOPSTRUCT wants the from and to paths to be terminated with two NULLs,
@@ -86,6 +91,14 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co
if (!file_operation_params.hNameMappings)
{
// No file name collision on copy
if (utilities::is_directory(this->path))
{
// Append dir for consistency on directory naming inclusion for with and without collision
std::filesystem::path with_dir = destination;
with_dir /= this->path.filename();
return with_dir;
}
return destination;
}
@@ -104,44 +117,23 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co
return final_path;
}
void template_item::enter_rename_mode(const ComPtr<IUnknown> site, const std::filesystem::path target_fullpath) const
void template_item::refresh_target(const std::filesystem::path target_final_fullpath) const
{
std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, site, target_fullpath);
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL);
}
void template_item::enter_rename_mode(const std::filesystem::path target_fullpath) const
{
std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, target_fullpath);
thread_for_renaming_workaround.detach();
}
void template_item::rename_on_other_thread_workaround(const ComPtr<IUnknown> site, const std::filesystem::path target_fullpath)
void template_item::rename_on_other_thread_workaround(const std::filesystem::path target_fullpath)
{
// Have been unable to have Windows Explorer Shell enter rename mode from the main thread
// Sleep for a bit to only enter rename mode when icon has been drawn. Not strictly needed.
const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 350 };
// Sleep for a bit to only enter rename mode when icon has been drawn.
const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 50 };
std::this_thread::sleep_for(std::chrono::milliseconds(approx_wait_for_icon_redraw_not_needed));
const std::wstring filename = target_fullpath.filename();
ComPtr<IServiceProvider> service_provider;
site->QueryInterface(IID_PPV_ARGS(&service_provider));
ComPtr<IFolderView> folder_view;
service_provider->QueryService(__uuidof(IFolderView), IID_PPV_ARGS(&folder_view));
int count = 0;
folder_view->ItemCount(SVGIO_ALLVIEW, &count);
for (int i = 0; i < count; ++i)
{
std::wstring path_of_item(MAX_PATH, 0);
LPITEMIDLIST pidl;
folder_view->Item(i, &pidl);
SHGetPathFromIDList(pidl, &path_of_item[0]);
CoTaskMemFree(pidl);
std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename();
if (utilities::wstring_same_when_comparing_ignore_case(filename, current_filename))
{
folder_view->SelectItem(i, SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED);
break;
}
}
newplus::utilities::explorer_enter_rename_mode(target_fullpath);
}

View File

@@ -20,15 +20,19 @@ namespace newplus
std::wstring get_target_filename(const bool include_starting_digits) const;
std::wstring get_explorer_icon() const;
HICON get_explorer_icon_handle() const;
std::filesystem::path copy_object_to(const HWND window_handle, const std::filesystem::path destination) const;
void enter_rename_mode(const ComPtr<IUnknown> site, const std::filesystem::path target_folder) const;
void refresh_target(const std::filesystem::path target_final_fullpath) const;
void enter_rename_mode(const std::filesystem::path target_fullpath) const;
std::filesystem::path path;
private:
static void rename_on_other_thread_workaround(const ComPtr<IUnknown> site, const std::filesystem::path target_fullpath);
static void rename_on_other_thread_workaround(const std::filesystem::path target_fullpath);
std::wstring remove_starting_digits_from_filename(std::wstring filename) const;
};

View File

@@ -58,3 +58,12 @@ void Trace::EventCopyTemplateResult(_In_ const HRESULT hr) noexcept
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::EventOpenTemplates() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"NewPlus_EventOpenTemplates",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -12,4 +12,5 @@ public:
static void EventShowTemplateItems(_In_ const size_t number_of_templates) noexcept;
static void EventCopyTemplate(_In_ const std::wstring template_file_extension) noexcept;
static void EventCopyTemplateResult(_In_ const HRESULT hr) noexcept;
static void EventOpenTemplates() noexcept;
};

View File

@@ -0,0 +1,217 @@
// 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.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using Windows.Management.Deployment;
namespace WorkspacesCsharpLibrary.Models
{
public class BaseApplication : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public string PwaAppId { get; set; }
public string AppPath { get; set; }
private bool _isNotFound;
public string PackagedId { get; set; }
public string PackagedName { get; set; }
public string PackagedPublisherID { get; set; }
public string Aumid { get; set; }
[JsonIgnore]
public bool IsNotFound
{
get
{
return _isNotFound;
}
set
{
if (_isNotFound != value)
{
_isNotFound = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
}
}
}
private Icon _icon;
public Icon Icon
{
get
{
if (_icon == null)
{
try
{
if (IsPackagedApp)
{
Uri uri = GetAppLogoByPackageFamilyName();
var bitmap = new Bitmap(uri.LocalPath);
var iconHandle = bitmap.GetHicon();
_icon = Icon.FromHandle(iconHandle);
}
else if (IsEdge || IsChrome)
{
string iconFilename = PwaHelper.GetPwaIconFilename(PwaAppId);
if (!string.IsNullOrEmpty(iconFilename))
{
Bitmap bitmap;
if (iconFilename.EndsWith("ico", StringComparison.InvariantCultureIgnoreCase))
{
bitmap = new Bitmap(iconFilename);
}
else
{
bitmap = (Bitmap)Image.FromFile(iconFilename);
}
var iconHandle = bitmap.GetHicon();
_icon = Icon.FromHandle(iconHandle);
}
}
if (_icon == null)
{
_icon = Icon.ExtractAssociatedIcon(AppPath);
}
}
catch (Exception)
{
IsNotFound = true;
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
}
}
return _icon;
}
}
private BitmapImage _iconBitmapImage;
public BitmapImage IconBitmapImage
{
get
{
if (_iconBitmapImage == null)
{
try
{
Bitmap previewBitmap = new Bitmap(32, 32);
using (Graphics graphics = Graphics.FromImage(previewBitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
}
using (var memory = new MemoryStream())
{
previewBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
_iconBitmapImage = bitmapImage;
}
}
catch (Exception)
{
}
}
return _iconBitmapImage;
}
}
public bool IsEdge
{
get => AppPath.EndsWith("edge.exe", StringComparison.InvariantCultureIgnoreCase);
}
public bool IsChrome
{
get => AppPath.EndsWith("chrome.exe", StringComparison.InvariantCultureIgnoreCase);
}
public Uri GetAppLogoByPackageFamilyName()
{
var pkgManager = new PackageManager();
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
if (pkg == null)
{
return null;
}
return pkg.Logo;
}
private bool? _isPackagedApp;
public bool IsPackagedApp
{
get
{
if (_isPackagedApp == null)
{
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
{
_isPackagedApp = false;
}
else
{
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
Match match = packagedAppPathRegex.Match(appPath);
_isPackagedApp = match.Success;
if (match.Success)
{
PackagedName = match.Groups["APPID"].Value;
PackagedPublisherID = match.Groups["PublisherID"].Value;
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
Aumid = $"{PackagedId}!App";
}
}
}
return _isPackagedApp.Value;
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace WorkspacesCsharpLibrary
{
public class PwaApp
{
public required string Name { get; set; }
public required string IconFilename { get; set; }
public required string AppId { get; set; }
}
}

View File

@@ -0,0 +1,90 @@
// 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.IO;
using System.Linq;
namespace WorkspacesCsharpLibrary
{
public class PwaHelper
{
private const string ChromeBase = "Google\\Chrome\\User Data\\Default\\Web Applications";
private const string EdgeBase = "Microsoft\\Edge\\User Data\\Default\\Web Applications";
private const string ResourcesDir = "Manifest Resources";
private const string IconsDir = "Icons";
private const string PwaDirIdentifier = "_CRX_";
private static List<PwaApp> pwaApps = new List<PwaApp>();
public PwaHelper()
{
InitPwaData(EdgeBase);
InitPwaData(ChromeBase);
}
private void InitPwaData(string p_baseDir)
{
var baseFolderName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), p_baseDir);
if (Directory.Exists(baseFolderName))
{
foreach (string subDir in Directory.GetDirectories(baseFolderName))
{
string dirName = Path.GetFileName(subDir);
if (!dirName.StartsWith(PwaDirIdentifier, StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
string appId = dirName.Substring(PwaDirIdentifier.Length, dirName.Length - PwaDirIdentifier.Length).Trim('_');
foreach (string iconFile in Directory.GetFiles(subDir, "*.ico"))
{
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(iconFile);
pwaApps.Add(new PwaApp() { Name = filenameWithoutExtension, IconFilename = iconFile, AppId = appId });
break;
}
}
string resourcesDir = Path.Combine(baseFolderName, ResourcesDir);
if (Directory.Exists(resourcesDir))
{
foreach (string subDir in Directory.GetDirectories(resourcesDir))
{
string dirName = Path.GetFileName(subDir);
if (pwaApps.Any(app => app.AppId == dirName))
{
continue;
}
string iconsDir = Path.Combine(subDir, IconsDir);
if (Directory.Exists(iconsDir))
{
foreach (string iconFile in Directory.GetFiles(iconsDir, "*.png"))
{
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(iconFile);
pwaApps.Add(new PwaApp() { Name = filenameWithoutExtension, IconFilename = iconFile, AppId = dirName });
break;
}
}
}
}
}
}
public static string GetPwaIconFilename(string pwaAppId)
{
var candidates = pwaApps.Where(x => x.AppId == pwaAppId).ToList();
if (candidates.Count > 0)
{
return candidates.First().IconFilename;
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.WorkspacesCsharpLibrary</AssemblyTitle>
<AssemblyDescription>PowerToys Workspaces Csharp Library</AssemblyDescription>
<Description>PowerToys Workspaces Csharp Library</Description>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
<AssemblyName>PowerToys.WorkspacesCsharpLibrary</AssemblyName>
</PropertyGroup>
</Project>

View File

@@ -37,6 +37,8 @@ namespace WorkspacesEditor.Data
public string AppUserModelId { get; set; }
public string PwaAppId { get; set; }
public string CommandLineArguments { get; set; }
public bool IsElevated { get; set; }

View File

@@ -13,18 +13,17 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Windows.Management.Deployment;
using WorkspacesCsharpLibrary;
using WorkspacesCsharpLibrary.Models;
namespace WorkspacesEditor.Models
{
public class Application : INotifyPropertyChanged, IDisposable
public class Application : BaseApplication, IDisposable
{
private bool _isInitialized;
public event PropertyChangedEventHandler PropertyChanged;
public Application()
{
}
@@ -37,6 +36,7 @@ namespace WorkspacesEditor.Models
AppTitle = other.AppTitle;
PackageFullName = other.PackageFullName;
AppUserModelId = other.AppUserModelId;
PwaAppId = other.PwaAppId;
CommandLineArguments = other.CommandLineArguments;
IsElevated = other.IsElevated;
CanLaunchElevated = other.CanLaunchElevated;
@@ -100,8 +100,6 @@ namespace WorkspacesEditor.Models
public string AppName { get; set; }
public string AppPath { get; set; }
public string AppTitle { get; set; }
public string PackageFullName { get; set; }
@@ -187,26 +185,6 @@ namespace WorkspacesEditor.Models
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
private bool _isNotFound;
[JsonIgnore]
public bool IsNotFound
{
get
{
return _isNotFound;
}
set
{
if (_isNotFound != value)
{
_isNotFound = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
}
}
}
[JsonIgnore]
public bool IsHighlighted { get; set; }
@@ -222,100 +200,6 @@ namespace WorkspacesEditor.Models
}
}
[JsonIgnore]
private Icon _icon = null;
[JsonIgnore]
public Icon Icon
{
get
{
if (_icon == null)
{
try
{
if (IsPackagedApp)
{
Uri uri = GetAppLogoByPackageFamilyName();
var bitmap = new Bitmap(uri.LocalPath);
var iconHandle = bitmap.GetHicon();
_icon = Icon.FromHandle(iconHandle);
}
else
{
_icon = Icon.ExtractAssociatedIcon(AppPath);
}
}
catch (Exception)
{
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
IsNotFound = true;
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
}
}
return _icon;
}
}
public Uri GetAppLogoByPackageFamilyName()
{
var pkgManager = new PackageManager();
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
if (pkg == null)
{
return null;
}
return pkg.Logo;
}
private BitmapImage _iconBitmapImage;
public BitmapImage IconBitmapImage
{
get
{
if (_iconBitmapImage == null)
{
try
{
Bitmap previewBitmap = new Bitmap(32, 32);
using (Graphics graphics = Graphics.FromImage(previewBitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
}
using (var memory = new MemoryStream())
{
previewBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
_iconBitmapImage = bitmapImage;
}
}
catch (Exception e)
{
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
}
}
return _iconBitmapImage;
}
}
private WindowPosition _position;
public WindowPosition Position
@@ -367,56 +251,11 @@ namespace WorkspacesEditor.Models
}
}
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public void InitializationFinished()
{
_isInitialized = true;
}
private bool? _isPackagedApp;
public string PackagedId { get; set; }
public string PackagedName { get; set; }
public string PackagedPublisherID { get; set; }
public string Aumid { get; set; }
public bool IsPackagedApp
{
get
{
if (_isPackagedApp == null)
{
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
{
_isPackagedApp = false;
}
else
{
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
Match match = packagedAppPathRegex.Match(appPath);
_isPackagedApp = match.Success;
if (match.Success)
{
PackagedName = match.Groups["APPID"].Value;
PackagedPublisherID = match.Groups["PublisherID"].Value;
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
Aumid = $"{PackagedId}!App";
}
}
}
return _isPackagedApp.Value;
}
}
private bool _isExpanded;
public bool IsExpanded
@@ -454,11 +293,6 @@ namespace WorkspacesEditor.Models
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
internal void CommandLineTextChanged(string newCommandLineValue)
{
CommandLineArguments = newCommandLineValue;

View File

@@ -251,10 +251,11 @@ namespace WorkspacesEditor.Models
{
Models.Application newApp = new Models.Application()
{
Id = app.Id != null ? app.Id : $"{{{Guid.NewGuid().ToString()}}}",
Id = string.IsNullOrEmpty(app.Id) ? $"{{{Guid.NewGuid().ToString()}}}" : app.Id,
AppName = app.Application,
AppPath = app.ApplicationPath,
AppTitle = app.Title,
PwaAppId = string.IsNullOrEmpty(app.PwaAppId) ? string.Empty : app.PwaAppId,
PackageFullName = app.PackageFullName,
AppUserModelId = app.AppUserModelId,
Parent = this,

View File

@@ -75,16 +75,16 @@ namespace WorkspacesEditor.Utils
foreach (Application app in appsIncluded)
{
if (repeatCounter.TryGetValue(app.AppPath, out int value))
if (repeatCounter.TryGetValue(app.AppPath + app.AppTitle, out int value))
{
repeatCounter[app.AppPath] = ++value;
repeatCounter[app.AppPath + app.AppTitle] = ++value;
}
else
{
repeatCounter.Add(app.AppPath, 1);
repeatCounter.Add(app.AppPath + app.AppTitle, 1);
}
app.RepeatIndex = repeatCounter[app.AppPath];
app.RepeatIndex = repeatCounter[app.AppPath + app.AppTitle];
}
foreach (Application app in project.Applications.Where(x => !x.IsIncluded))

View File

@@ -103,6 +103,7 @@ namespace WorkspacesEditor.Utils
Title = app.AppTitle,
PackageFullName = app.PackageFullName,
AppUserModelId = app.AppUserModelId,
PwaAppId = app.PwaAppId,
CommandLineArguments = app.CommandLineArguments,
IsElevated = app.IsElevated,
CanLaunchElevated = app.CanLaunchElevated,

View File

@@ -14,10 +14,10 @@ using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using WorkspacesCsharpLibrary;
using WorkspacesEditor.Data;
using WorkspacesEditor.Models;
using WorkspacesEditor.Telemetry;
@@ -39,6 +39,7 @@ namespace WorkspacesEditor.ViewModels
private MainWindow _mainWindow;
private Timer lastUpdatedTimer;
private WorkspacesSettings settings;
private PwaHelper _pwaHelper;
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
@@ -147,6 +148,7 @@ namespace WorkspacesEditor.ViewModels
settings = Utils.Settings.ReadSettings();
_orderByIndex = (int)settings.Properties.SortBy;
_workspacesEditorIO = workspacesEditorIO;
_pwaHelper = new PwaHelper();
lastUpdatedTimer = new System.Timers.Timer();
lastUpdatedTimer.Interval = 1000;
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;

View File

@@ -65,7 +65,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ControlzEx" />
<PackageReference Include="ModernWpfUI" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
@@ -77,6 +76,7 @@
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -80,7 +80,7 @@
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{Binding IconBitmapImage}" />
Source="{Binding IconBitmapImage, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Width="20"

View File

@@ -18,6 +18,15 @@ using namespace Windows::Management::Deployment;
namespace AppLauncher
{
namespace NonLocalizable
{
const std::wstring EdgeFilename = L"msedge.exe";
const std::wstring EdgePwaFilename = L"msedge_proxy.exe";
const std::wstring ChromeFilename = L"chrome.exe";
const std::wstring ChromePwaFilename = L"chrome_proxy.exe";
const std::wstring PwaCommandLineAddition = L"--profile-directory=Default --app-id=";
}
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
{
std::wstring dir = std::filesystem::path(appPath).parent_path();
@@ -133,30 +142,50 @@ namespace AppLauncher
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
}
std::wstring appPathFinal;
std::wstring commandLineArgsFinal;
appPathFinal = app.path;
commandLineArgsFinal = app.commandLineArgs;
if (!launched && !app.pwaAppId.empty())
{
std::filesystem::path appPath(app.path);
if (appPath.filename() == NonLocalizable::EdgeFilename)
{
appPathFinal = appPath.parent_path() / NonLocalizable::EdgePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
}
if (appPath.filename() == NonLocalizable::ChromeFilename)
{
appPathFinal = appPath.parent_path() / NonLocalizable::ChromePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
}
}
if (!launched)
{
Logger::trace(L"Launching {} at {}", app.name, app.path);
Logger::trace(L"Launching {} at {}", app.name, appPathFinal);
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
DWORD dwAttrib = GetFileAttributesW(appPathFinal.c_str());
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
{
Logger::error(L"File not found at {}", app.path);
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
Logger::error(L"File not found at {}", appPathFinal);
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), L"File not found" });
return false;
}
auto res = LaunchApp(app.path, app.commandLineArgs, app.isElevated);
auto res = LaunchApp(appPathFinal, commandLineArgsFinal, app.isElevated);
if (res.isOk())
{
launched = true;
}
else
{
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), res.error() });
}
}
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), appPathFinal);
return launched;
}
}

View File

@@ -16,6 +16,8 @@ namespace WorkspacesLauncherUI.Data
public string AppUserModelId { get; set; }
public string PwaAppId { get; set; }
public string CommandLineArguments { get; set; }
public bool IsElevated { get; set; }

View File

@@ -3,77 +3,23 @@
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Windows.Management.Deployment;
using WorkspacesCsharpLibrary.Models;
using WorkspacesLauncherUI.Data;
namespace WorkspacesLauncherUI.Models
{
public class AppLaunching : INotifyPropertyChanged, IDisposable
public class AppLaunching : BaseApplication, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public ApplicationWrapper Application { get; set; }
public bool Loading => LaunchState == LaunchingState.Waiting || LaunchState == LaunchingState.Launched;
private Icon _icon;
public Icon Icon
{
get
{
if (_icon == null)
{
try
{
if (IsPackagedApp)
{
Uri uri = GetAppLogoByPackageFamilyName();
var bitmap = new Bitmap(uri.LocalPath);
var iconHandle = bitmap.GetHicon();
_icon = Icon.FromHandle(iconHandle);
}
else
{
_icon = Icon.ExtractAssociatedIcon(Application.ApplicationPath);
}
}
catch (Exception)
{
Logger.LogWarning($"Icon not found on app path: {Application.ApplicationPath}. Using default icon");
IsNotFound = true;
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
}
}
return _icon;
}
}
public string Name
{
get
{
return Application.Application;
}
}
public string Name { get; set; }
public LaunchingState LaunchState { get; set; }
@@ -96,128 +42,5 @@ namespace WorkspacesLauncherUI.Models
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
};
}
private bool _isNotFound;
[JsonIgnore]
public bool IsNotFound
{
get
{
return _isNotFound;
}
set
{
if (_isNotFound != value)
{
_isNotFound = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
}
}
}
public Uri GetAppLogoByPackageFamilyName()
{
var pkgManager = new PackageManager();
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
if (pkg == null)
{
return null;
}
return pkg.Logo;
}
private bool? _isPackagedApp;
public string PackagedId { get; set; }
public string PackagedName { get; set; }
public string PackagedPublisherID { get; set; }
public string Aumid { get; set; }
public bool IsPackagedApp
{
get
{
if (_isPackagedApp == null)
{
if (!Application.ApplicationPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
{
_isPackagedApp = false;
}
else
{
string appPath = Application.ApplicationPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
Match match = packagedAppPathRegex.Match(appPath);
_isPackagedApp = match.Success;
if (match.Success)
{
PackagedName = match.Groups["APPID"].Value;
PackagedPublisherID = match.Groups["PublisherID"].Value;
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
Aumid = $"{PackagedId}!App";
}
}
}
return _isPackagedApp.Value;
}
}
private BitmapImage _iconBitmapImage;
public BitmapImage IconBitmapImage
{
get
{
if (_iconBitmapImage == null)
{
try
{
Bitmap previewBitmap = new Bitmap(32, 32);
using (Graphics graphics = Graphics.FromImage(previewBitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
}
using (var memory = new MemoryStream())
{
previewBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
_iconBitmapImage = bitmapImage;
}
}
catch (Exception e)
{
Logger.LogError($"Exception while drawing icon for app with path: {Application.ApplicationPath}. Exception message: {e.Message}");
}
}
return _iconBitmapImage;
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

@@ -9,8 +9,10 @@ using System.ComponentModel;
using System.Diagnostics;
using ManagedCommon;
using WorkspacesCsharpLibrary;
using WorkspacesLauncherUI.Data;
using WorkspacesLauncherUI.Models;
using WorkspacesLauncherUI.Utils;
namespace WorkspacesLauncherUI.ViewModels
{
@@ -20,6 +22,7 @@ namespace WorkspacesLauncherUI.ViewModels
private StatusWindow _snapshotWindow;
private int launcherProcessID;
private PwaHelper _pwaHelper;
public event PropertyChangedEventHandler PropertyChanged;
@@ -30,6 +33,8 @@ namespace WorkspacesLauncherUI.ViewModels
public MainViewModel()
{
_pwaHelper = new PwaHelper();
// receive IPC Message
App.IPCMessageReceivedCallback = (string msg) =>
{
@@ -54,7 +59,11 @@ namespace WorkspacesLauncherUI.ViewModels
{
appLaunchingList.Add(new AppLaunching()
{
Application = app.Application,
Name = app.Application.Application,
AppPath = app.Application.ApplicationPath,
PackagedName = app.Application.PackageFullName,
Aumid = app.Application.AppUserModelId,
PwaAppId = app.Application.PwaAppId,
LaunchState = app.State,
});
}

View File

@@ -79,6 +79,7 @@
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -23,7 +23,7 @@ namespace WorkspacesData
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
return settingsFolderPath + L"\\temp-workspaces.json";
}
RECT WorkspacesProject::Application::Position::toRect() const noexcept
{
return RECT{ .left = x, .top = y, .right = x + width, .bottom = y + height };
@@ -79,6 +79,7 @@ namespace WorkspacesData
const static wchar_t* AppPathID = L"application-path";
const static wchar_t* AppPackageFullNameID = L"package-full-name";
const static wchar_t* AppUserModelId = L"app-user-model-id";
const static wchar_t* PwaAppId = L"pwa-app-id";
const static wchar_t* AppTitleID = L"title";
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
const static wchar_t* ElevatedID = L"is-elevated";
@@ -98,6 +99,7 @@ namespace WorkspacesData
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title));
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
json.SetNamedValue(NonLocalizable::AppUserModelId, json::value(data.appUserModelId));
json.SetNamedValue(NonLocalizable::PwaAppId, json::value(data.pwaAppId));
json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs));
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
json.SetNamedValue(NonLocalizable::CanLaunchElevatedID, json::value(data.canLaunchElevated));
@@ -136,6 +138,11 @@ namespace WorkspacesData
result.appUserModelId = json.GetNamedString(NonLocalizable::AppUserModelId);
}
if (json.HasKey(NonLocalizable::PwaAppId))
{
result.pwaAppId = json.GetNamedString(NonLocalizable::PwaAppId);
}
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
if (json.HasKey(NonLocalizable::ElevatedID))
@@ -330,11 +337,11 @@ namespace WorkspacesData
{
result.isShortcutNeeded = json.GetNamedBoolean(NonLocalizable::IsShortcutNeededID);
}
if (json.HasKey(NonLocalizable::MoveExistingWindowsID))
{
result.moveExistingWindows = json.GetNamedBoolean(NonLocalizable::MoveExistingWindowsID);
}
result.moveExistingWindows = json.GetNamedBoolean(NonLocalizable::MoveExistingWindowsID);
}
auto appsArray = json.GetNamedArray(NonLocalizable::AppsID);
for (uint32_t i = 0; i < appsArray.Size(); ++i)

View File

@@ -31,6 +31,7 @@ namespace WorkspacesData
std::wstring path;
std::wstring packageFullName;
std::wstring appUserModelId;
std::wstring pwaAppId;
std::wstring commandLineArgs;
bool isElevated{};
bool canLaunchElevated{};

View File

@@ -0,0 +1,409 @@
#include "pch.h"
#include "PwaHelper.h"
#include <ShlObj.h>
#include <tlhelp32.h>
#include <winternl.h>
#include <initguid.h>
#include <filesystem>
#include <wil/result_macros.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include <wil\com.h>
#pragma comment(lib, "ntdll.lib")
namespace SnapshotUtils
{
namespace NonLocalizable
{
const std::wstring EdgeAppIdIdentifier = L"--app-id=";
const std::wstring ChromeAppIdIdentifier = L"Chrome._crx_";
const std::wstring ChromeBase = L"Google\\Chrome\\User Data\\Default\\Web Applications";
const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications";
const std::wstring ChromeDirPrefix = L"_crx_";
const std::wstring EdgeDirPrefix = L"_crx__";
}
// {c8900b66-a973-584b-8cae-355b7f55341b}
DEFINE_GUID(CLSID_StartMenuCacheAndAppResolver, 0x660b90c8, 0x73a9, 0x4b58, 0x8c, 0xae, 0x35, 0x5b, 0x7f, 0x55, 0x34, 0x1b);
// {46a6eeff-908e-4dc6-92a6-64be9177b41c}
DEFINE_GUID(IID_IAppResolver_7, 0x46a6eeff, 0x908e, 0x4dc6, 0x92, 0xa6, 0x64, 0xbe, 0x91, 0x77, 0xb4, 0x1c);
// {de25675a-72de-44b4-9373-05170450c140}
DEFINE_GUID(IID_IAppResolver_8, 0xde25675a, 0x72de, 0x44b4, 0x93, 0x73, 0x05, 0x17, 0x04, 0x50, 0xc1, 0x40);
struct IAppResolver_7 : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
};
struct IAppResolver_8 : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcutObject() = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
};
BOOL GetAppId_7(HWND hWnd, std::wstring* result)
{
HRESULT hr;
wil::com_ptr<IAppResolver_7> appResolver;
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast<void**>(appResolver.put()));
if (SUCCEEDED(hr))
{
wil::unique_cotaskmem_string pszAppId;
hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL);
if (SUCCEEDED(hr))
{
*result = std::wstring(pszAppId.get());
}
appResolver->Release();
}
return SUCCEEDED(hr);
}
BOOL GetAppId_8(HWND hWnd, std::wstring* result)
{
HRESULT hr;
*result = L"";
wil::com_ptr<IAppResolver_8> appResolver;
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast<void**>(appResolver.put()));
if (SUCCEEDED(hr))
{
wil::unique_cotaskmem_string pszAppId;
hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL);
if (SUCCEEDED(hr))
{
*result = std::wstring(pszAppId.get());
}
appResolver->Release();
}
return SUCCEEDED(hr);
}
BOOL PwaHelper::GetAppId(HWND hWnd, std::wstring* result)
{
HRESULT hr = GetAppId_8(hWnd, result);
if (!SUCCEEDED(hr))
{
hr = GetAppId_7(hWnd, result);
}
return SUCCEEDED(hr);
}
BOOL GetProcessId_7(DWORD dwProcessId, std::wstring* result)
{
HRESULT hr;
*result = L"";
wil::com_ptr<IAppResolver_7> appResolver;
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast<void**>(appResolver.put()));
if (SUCCEEDED(hr))
{
wil::unique_cotaskmem_string pszAppId;
hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL);
if (SUCCEEDED(hr))
{
*result = std::wstring(pszAppId.get());
}
appResolver->Release();
}
return SUCCEEDED(hr);
}
BOOL GetProcessId_8(DWORD dwProcessId, std::wstring* result)
{
HRESULT hr;
*result = L"";
wil::com_ptr<IAppResolver_8> appResolver;
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast<void**>(appResolver.put()));
if (SUCCEEDED(hr))
{
wil::unique_cotaskmem_string pszAppId;
hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL);
if (SUCCEEDED(hr))
{
*result = std::wstring(pszAppId.get());
}
appResolver->Release();
}
return SUCCEEDED(hr);
}
BOOL GetProcessId(DWORD dwProcessId, std::wstring* result)
{
HRESULT hr = GetProcessId_8(dwProcessId, result);
if (!SUCCEEDED(hr))
{
hr = GetProcessId_7(dwProcessId, result);
}
return SUCCEEDED(hr);
}
std::wstring GetProcCommandLine(DWORD pid)
{
std::wstring commandLine;
// Open a handle to the process
const HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (process == NULL)
{
Logger::error(L"Failed to open the process, error: {}", get_last_error_or_default(GetLastError()));
}
else
{
// Get the address of the ProcessEnvironmentBlock
PROCESS_BASIC_INFORMATION pbi = {};
NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
if (status != STATUS_SUCCESS)
{
Logger::error(L"Failed to query the process, error: {}", status);
}
else
{
// Get the address of the process parameters in the ProcessEnvironmentBlock
PEB processEnvironmentBlock = {};
if (!ReadProcessMemory(process, pbi.PebBaseAddress, &processEnvironmentBlock, sizeof(processEnvironmentBlock), NULL))
{
Logger::error(L"Failed to read the process ProcessEnvironmentBlock, error: {}", get_last_error_or_default(GetLastError()));
}
else
{
// Get the command line arguments from the process parameters
RTL_USER_PROCESS_PARAMETERS params = {};
if (!ReadProcessMemory(process, processEnvironmentBlock.ProcessParameters, &params, sizeof(params), NULL))
{
Logger::error(L"Failed to read the process params, error: {}", get_last_error_or_default(GetLastError()));
}
else
{
UNICODE_STRING& commandLineArgs = params.CommandLine;
std::vector<WCHAR> buffer(commandLineArgs.Length / sizeof(WCHAR));
if (!ReadProcessMemory(process, commandLineArgs.Buffer, buffer.data(), commandLineArgs.Length, NULL))
{
Logger::error(L"Failed to read the process command line, error: {}", get_last_error_or_default(GetLastError()));
}
else
{
commandLine.assign(buffer.data(), buffer.size());
}
}
}
}
CloseHandle(process);
}
return commandLine;
}
// Finds all PwaHelper.exe processes with the specified parent process ID
std::vector<DWORD> FindPwaHelperProcessIds()
{
std::vector<DWORD> pwaHelperProcessIds;
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
Logger::info(L"Invalid handle when creating snapshot for the search for PwaHelper processes");
return pwaHelperProcessIds;
}
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe))
{
do
{
if (_wcsicmp(pe.szExeFile, L"PwaHelper.exe") == 0)
{
Logger::info(L"Found a PWA process with id {}", pe.th32ProcessID);
pwaHelperProcessIds.push_back(pe.th32ProcessID);
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return pwaHelperProcessIds;
}
void PwaHelper::InitAumidToAppId()
{
if (pwaAumidToAppId.size() > 0)
{
return;
}
const auto pwaHelperProcessIds = FindPwaHelperProcessIds();
Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size());
for (const auto subProcessID : pwaHelperProcessIds)
{
std::wstring aumidID;
GetProcessId(subProcessID, &aumidID);
std::wstring commandLineArg = GetProcCommandLine(subProcessID);
auto appIdIndexStart = commandLineArg.find(NonLocalizable::EdgeAppIdIdentifier);
if (appIdIndexStart != std::wstring::npos)
{
commandLineArg = commandLineArg.substr(appIdIndexStart + NonLocalizable::EdgeAppIdIdentifier.size());
auto appIdIndexEnd = commandLineArg.find(L" ");
if (appIdIndexEnd != std::wstring::npos)
{
commandLineArg = commandLineArg.substr(0, appIdIndexEnd);
}
}
std::wstring appId{ commandLineArg };
pwaAumidToAppId.insert(std::map<std::wstring, std::wstring>::value_type(aumidID, appId));
Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId);
PWSTR path = NULL;
HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path);
if (SUCCEEDED(hres))
{
std::filesystem::path fsPath(path);
fsPath /= NonLocalizable::EdgeBase;
for (const auto& directory : std::filesystem::directory_iterator(fsPath))
{
if (directory.is_directory())
{
const std::filesystem::path directoryName = directory.path().filename();
if (directoryName.wstring().find(NonLocalizable::EdgeDirPrefix) == 0)
{
const std::wstring appIdDir = directoryName.wstring().substr(NonLocalizable::EdgeDirPrefix.size());
if (appIdDir == appId)
{
for (const auto& filename : std::filesystem::directory_iterator(directory))
{
if (!filename.is_directory())
{
const std::filesystem::path filenameString = filename.path().filename();
if (filenameString.extension().wstring() == L".ico")
{
pwaAppIdsToAppNames.insert(std::map<std::wstring, std::wstring>::value_type(appId, filenameString.stem().wstring()));
Logger::info(L"Storing an edge Pwa app name {} for PwaAppId {}", filenameString.stem().wstring(), appId);
}
}
}
}
}
}
}
CoTaskMemFree(path);
}
}
}
BOOL PwaHelper::GetPwaAppId(std::wstring windowAumid, std::wstring* result)
{
const auto pwaIndex = pwaAumidToAppId.find(windowAumid);
if (pwaIndex != pwaAumidToAppId.end())
{
*result = pwaIndex->second;
return true;
}
return false;
}
BOOL PwaHelper::SearchPwaName(std::wstring pwaAppId, std::wstring windowAumid, std::wstring* pwaName)
{
const auto index = pwaAppIdsToAppNames.find(pwaAppId);
if (index != pwaAppIdsToAppNames.end())
{
*pwaName = index->second;
return true;
}
std::wstring nameFromAumid{ windowAumid };
const std::size_t delimiterPos = nameFromAumid.find(L"-");
if (delimiterPos != std::string::npos)
{
nameFromAumid = nameFromAumid.substr(0, delimiterPos);
}
*pwaName = nameFromAumid;
return false;
}
void PwaHelper::InitChromeAppIds()
{
if (chromeAppIds.size() > 0)
{
return;
}
PWSTR path = NULL;
HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path);
if (SUCCEEDED(hres))
{
std::filesystem::path fsPath(path);
fsPath /= NonLocalizable::ChromeBase;
for (const auto& directory : std::filesystem::directory_iterator(fsPath))
{
if (directory.is_directory())
{
const std::filesystem::path directoryName = directory.path().filename();
if (directoryName.wstring().find(NonLocalizable::ChromeDirPrefix) == 0)
{
const std::wstring appId = directoryName.wstring().substr(NonLocalizable::ChromeDirPrefix.size());
chromeAppIds.push_back(appId);
for (const auto& filename : std::filesystem::directory_iterator(directory))
{
if (!filename.is_directory())
{
const std::filesystem::path filenameString = filename.path().filename();
if (filenameString.extension().wstring() == L".ico")
{
pwaAppIdsToAppNames.insert(std::map<std::wstring, std::wstring>::value_type(appId, filenameString.stem().wstring()));
Logger::info(L"Found an installed chrome Pwa app {} with PwaAppId {}", filenameString.stem().wstring(), appId);
}
}
}
}
}
}
CoTaskMemFree(path);
}
}
BOOL PwaHelper::SearchPwaAppId(std::wstring windowAumid, std::wstring* pwaAppId)
{
const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier);
if (appIdIndexStart != std::wstring::npos)
{
windowAumid = windowAumid.substr(appIdIndexStart + NonLocalizable::ChromeAppIdIdentifier.size());
const auto appIdIndexEnd = windowAumid.find(L" ");
if (appIdIndexEnd != std::wstring::npos)
{
windowAumid = windowAumid.substr(0, appIdIndexEnd);
}
const std::wstring windowAumidBegin = windowAumid.substr(0, 10);
for (const auto chromeAppId : chromeAppIds)
{
if (chromeAppId.find(windowAumidBegin) == 0)
{
*pwaAppId = chromeAppId;
return true;
}
}
}
*pwaAppId = L"";
return false;
}
}

View File

@@ -0,0 +1,20 @@
#pragma once
namespace SnapshotUtils
{
class PwaHelper
{
public:
void InitAumidToAppId();
BOOL GetAppId(HWND hWnd, std::wstring* result);
BOOL GetPwaAppId(std::wstring windowAumid, std::wstring* result);
BOOL SearchPwaName(std::wstring pwaAppId, std::wstring windowAumid, std::wstring* pwaName);
void InitChromeAppIds();
BOOL SearchPwaAppId(std::wstring windowAumid, std::wstring* pwaAppId);
private:
std::map<std::wstring, std::wstring> pwaAumidToAppId;
std::vector<std::wstring> chromeAppIds;
std::map<std::wstring, std::wstring> pwaAppIdsToAppNames;
};
}

View File

@@ -1,9 +1,6 @@
#include "pch.h"
#include "SnapshotUtils.h"
#include <comdef.h>
#include <Wbemidl.h>
#include <common/utils/elevation.h>
#include <common/utils/process_path.h>
#include <common/notifications/NotificationUtil.h>
@@ -12,142 +9,17 @@
#include <workspaces-common/WindowFilter.h>
#include <WorkspacesLib/AppUtils.h>
#include <PwaHelper.h>
#pragma comment(lib, "ntdll.lib")
namespace SnapshotUtils
{
namespace NonLocalizable
{
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
}
class WbemHelper
{
public:
WbemHelper() = default;
~WbemHelper()
{
if (m_services)
{
m_services->Release();
}
if (m_locator)
{
m_locator->Release();
}
}
bool Initialize()
{
// Obtain the initial locator to WMI.
HRESULT hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast<LPVOID*>(&m_locator));
if (FAILED(hres))
{
Logger::error(L"Failed to create IWbemLocator object. Error: {}", get_last_error_or_default(hres));
return false;
}
// Connect to WMI through the IWbemLocator::ConnectServer method.
hres = m_locator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &m_services);
if (FAILED(hres))
{
Logger::error(L"Could not connect to WMI. Error: {}", get_last_error_or_default(hres));
return false;
}
// Set security levels on the proxy.
hres = CoSetProxyBlanket(m_services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
if (FAILED(hres))
{
Logger::error(L"Could not set proxy blanket. Error: {}", get_last_error_or_default(hres));
return false;
}
return true;
}
std::wstring GetCommandLineArgs(DWORD processID) const
{
static std::wstring property = L"CommandLine";
std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID);
return Query(query, property);
}
std::wstring GetExecutablePath(DWORD processID) const
{
static std::wstring property = L"ExecutablePath";
std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID);
return Query(query, property);
}
private:
std::wstring Query(const std::wstring& query, const std::wstring& propertyName) const
{
if (!m_locator || !m_services)
{
return L"";
}
IEnumWbemClassObject* pEnumerator = NULL;
HRESULT hres = m_services->ExecQuery(bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
if (FAILED(hres))
{
Logger::error(L"Query for process failed. Error: {}", get_last_error_or_default(hres));
return L"";
}
IWbemClassObject* pClassObject = NULL;
ULONG uReturn = 0;
std::wstring result = L"";
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObject, &uReturn);
if (uReturn == 0)
{
break;
}
VARIANT vtProp;
hr = pClassObject->Get(propertyName.c_str(), 0, &vtProp, 0, 0);
if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR)
{
result = vtProp.bstrVal;
}
VariantClear(&vtProp);
pClassObject->Release();
}
pEnumerator->Release();
return result;
}
IWbemLocator* m_locator = NULL;
IWbemServices* m_services = NULL;
};
std::wstring GetCommandLineArgs(DWORD processID, const WbemHelper& wbemHelper)
{
std::wstring executablePath = wbemHelper.GetExecutablePath(processID);
std::wstring commandLineArgs = wbemHelper.GetCommandLineArgs(processID);
if (!commandLineArgs.empty())
{
auto pos = commandLineArgs.find(executablePath);
if (pos != std::wstring::npos)
{
commandLineArgs = commandLineArgs.substr(pos + executablePath.size());
auto spacePos = commandLineArgs.find_first_of(' ');
if (spacePos != std::wstring::npos)
{
commandLineArgs = commandLineArgs.substr(spacePos + 1);
}
}
}
return commandLineArgs;
const std::wstring EdgeFilename = L"msedge.exe";
const std::wstring ChromeFilename = L"chrome.exe";
}
bool IsProcessElevated(DWORD processID)
@@ -168,16 +40,23 @@ namespace SnapshotUtils
return false;
}
bool IsEdge(Utils::Apps::AppData appData)
{
return appData.installPath.ends_with(NonLocalizable::EdgeFilename);
}
bool IsChrome(Utils::Apps::AppData appData)
{
return appData.installPath.ends_with(NonLocalizable::ChromeFilename);
}
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
{
PwaHelper pwaHelper{};
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
auto installedApps = Utils::Apps::GetAppsList();
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
// for command line args detection
// WbemHelper wbemHelper;
// wbemHelper.Initialize();
for (const auto window : windows)
{
@@ -232,7 +111,7 @@ namespace SnapshotUtils
if (pid != otherPid && title == WindowUtils::GetWindowTitle(otherWindow))
{
processPath = get_process_path(otherPid);
break;
break;
}
}
}
@@ -249,6 +128,48 @@ namespace SnapshotUtils
continue;
}
std::wstring pwaAppId = L"";
std::wstring finalName = data.value().name;
std::wstring pwaName = L"";
if (IsEdge(data.value()))
{
pwaHelper.InitAumidToAppId();
std::wstring windowAumid;
pwaHelper.GetAppId(window, &windowAumid);
Logger::info(L"Found an edge window with aumid {}", windowAumid);
if (pwaHelper.GetPwaAppId(windowAumid, &pwaAppId))
{
Logger::info(L"The found edge window is a PWA app with appId {}", pwaAppId);
if (pwaHelper.SearchPwaName(pwaAppId, windowAumid ,& pwaName))
{
Logger::info(L"The found edge window is a PWA app with name {}", finalName);
}
finalName = pwaName + L" (" + finalName + L")";
}
else
{
Logger::info(L"The found edge window does not contain a PWA app", pwaAppId);
}
}
else if (IsChrome(data.value()))
{
pwaHelper.InitChromeAppIds();
std::wstring windowAumid;
pwaHelper.GetAppId(window, &windowAumid);
Logger::info(L"Found a chrome window with aumid {}", windowAumid);
if (pwaHelper.SearchPwaAppId(windowAumid, &pwaAppId))
{
if (pwaHelper.SearchPwaName(pwaAppId, windowAumid, &pwaName))
{
finalName = pwaName + L" (" + finalName + L")";
}
}
}
bool isMinimized = WindowUtils::IsMinimized(window);
unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window);
@@ -263,12 +184,13 @@ namespace SnapshotUtils
}
WorkspacesData::WorkspacesProject::Application app{
.name = data.value().name,
.name = finalName,
.title = title,
.path = data.value().installPath,
.packageFullName = data.value().packageFullName,
.appUserModelId = data.value().appUserModelId,
.commandLineArgs = L"", // GetCommandLineArgs(pid, wbemHelper),
.pwaAppId = pwaAppId,
.commandLineArgs = L"",
.isElevated = IsProcessElevated(pid),
.canLaunchElevated = data.value().canLaunchElevated,
.isMinimized = isMinimized,

View File

@@ -104,7 +104,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;wbemuuid.lib</AdditionalDependencies>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
@@ -130,10 +130,12 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="PwaHelper.cpp" />
<ClCompile Include="SnapshotUtils.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="PwaHelper.h" />
<ClInclude Include="resource.base.h" />
<ClInclude Include="SnapshotUtils.h" />
</ItemGroup>

View File

@@ -24,6 +24,9 @@
<ClInclude Include="resource.base.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PwaHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -35,6 +38,9 @@
<ClCompile Include="SnapshotUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PwaHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -68,6 +68,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
searchResult.Result.SwitchToWindow();
return true;
},
Score = searchResult.Score,
// For debugging you can set the second parameter to true to see more information.
ToolTipData = GetToolTip(searchResult.Result, false),

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -22,6 +23,7 @@ using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Helpers;
using Peek.FilePreviewer.Previewers.Interfaces;
using Windows.Foundation;
using Windows.Graphics.Imaging;
namespace Peek.FilePreviewer.Previewers
{
@@ -58,6 +60,12 @@ namespace Peek.FilePreviewer.Previewers
private DispatcherQueue Dispatcher { get; }
private static readonly HashSet<string> _supportedFileTypes =
BitmapDecoder.GetDecoderInformationEnumerator()
.SelectMany(di => di.FileExtensions)
.Union([".svg", ".qoi"])
.ToHashSet(StringComparer.OrdinalIgnoreCase);
public static bool IsItemSupported(IFileSystemItem item)
{
return _supportedFileTypes.Contains(item.Extension);
@@ -199,74 +207,5 @@ namespace Peek.FilePreviewer.Previewers
});
});
}
private static readonly HashSet<string> _supportedFileTypes = new HashSet<string>
{
// Image types
".bmp",
".gif",
".jpg",
".jfif",
".jfi",
".jif",
".jpeg",
".jpe",
".png",
".tif", // very slow for large files: no thumbnail?
".tiff", // NEED TO TEST
".dib", // NEED TO TEST
".heic",
".heif",
".hif", // NEED TO TEST
".avif", // NEED TO TEST
".jxr",
".wdp",
".ico", // NEED TO TEST
".thumb", // NEED TO TEST
".webp",
// Raw types
".arw",
".cr2",
".crw",
".erf",
".kdc", // NEED TO TEST
".mrw",
".nef",
".nrw",
".orf",
".pef",
".raf",
".raw",
".rw2",
".rwl",
".sr2",
".srw",
".srf",
".dcs", // NEED TO TEST
".dcr",
".drf", // NEED TO TEST
".k25",
".3fr",
".ari", // NEED TO TEST
".bay", // NEED TO TEST
".cap", // NEED TO TEST
".iiq",
".eip", // NEED TO TEST
".fff",
".mef",
// ".mdc", // Crashes in GetFullBitmapFromPathAsync
".mos",
".R3D",
".rwz", // NEED TO TEST
".x3f",
".ori",
".cr3",
".svg",
".qoi",
};
}
}

View File

@@ -20,10 +20,11 @@ namespace Peek.FilePreviewer.Previewers
public static HashSet<string> GetExtensions()
{
HashSet<string> set = new HashSet<string>();
HashSet<string> set = [];
try
{
JsonDocument languageListDocument = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguages();
using JsonDocument languageListDocument = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguages();
JsonElement languageList = languageListDocument.RootElement.GetProperty("list");
foreach (JsonElement e in languageList.EnumerateArray())
{

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
@@ -209,7 +209,7 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -223,8 +223,8 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.241114003\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target>
<Target Name="AddWildCardItems" AfterTargets="BuildGenerateSources">
<ItemGroup>

View File

@@ -65,4 +65,7 @@
<ItemGroup>
<ResourceCompile Include="PowerRenameUI.rc" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -6,5 +6,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240923002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.241114003" targetFramework="native" />
</packages>

View File

@@ -14,10 +14,113 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x44);
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; }
public HotkeySettings ActivationShortcut
{
get;
set;
}
[JsonPropertyName("thumbnail_size")]
public MouseJumpThumbnailSize ThumbnailSize { get; set; }
public MouseJumpThumbnailSize ThumbnailSize
{
get;
set;
}
/// <summary>
/// Gets or sets the preview type.
/// Allowed values are "compact", "bezelled", "custom"
/// </summary>
[JsonPropertyName("preview_type")]
public string PreviewType
{
get;
set;
}
[JsonPropertyName("background_color_1")]
public string BackgroundColor1
{
get;
set;
}
[JsonPropertyName("background_color_2")]
public string BackgroundColor2
{
get;
set;
}
[JsonPropertyName("border_thickness")]
public int BorderThickness
{
get;
set;
}
[JsonPropertyName("border_color")]
public string BorderColor
{
get;
set;
}
[JsonPropertyName("border_3d_depth")]
public int Border3dDepth
{
get;
set;
}
[JsonPropertyName("border_padding")]
public int BorderPadding
{
get;
set;
}
[JsonPropertyName("bezel_thickness")]
public int BezelThickness
{
get;
set;
}
[JsonPropertyName("bezel_color")]
public string BezelColor
{
get;
set;
}
[JsonPropertyName("bezel_3d_depth")]
public int Bezel3dDepth
{
get;
set;
}
[JsonPropertyName("screen_margin")]
public int ScreenMargin
{
get;
set;
}
[JsonPropertyName("screen_color_1")]
public string ScreenColor1
{
get;
set;
}
[JsonPropertyName("screen_color_2")]
public string ScreenColor2
{
get;
set;
}
public MouseJumpProperties()
{

View File

@@ -7,6 +7,8 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using MouseJump.Common.Helpers;
using MouseJump.Common.Models.Settings;
namespace Microsoft.PowerToys.Settings.UI.Library
{
@@ -26,7 +28,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
Name = ModuleName;
Properties = new MouseJumpProperties();
Version = "1.0";
Version = "1.1";
}
public void Save(ISettingsUtils settingsUtils)
@@ -47,7 +49,74 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
/*
v1.0 - initial version
* DefaultActivationShortcut
* activation_shortcut
* thumbnail_size
* name
* version
*/
var upgraded = false;
if (this.Version == "1.0")
{
/*
v1.1 - added preview style settings
* preview_type
* background_color_1
* background_color_2
* border_thickness
* border_color
* border_3d_depth
* border_padding
* bezel_thickness
* bezel_color
* bezel_3d_depth
* screen_margin
* screen_color_1
* screen_color_2
*/
this.Version = "1.1";
// note - there's an issue where ITwoWayPipeMessageIPCManagedMethods.Send overwrites
// the settings file version as "1.0" regardless of the actual version. as a result,
// the UpgradeSettingsConfiguration can get triggered even if the config has already
// been upgraded, so we need to do an additional check to make sure values haven't
// already been upgraded before we overwrite them with default values.
if (string.IsNullOrEmpty(this.Properties.PreviewType))
{
// set default values for custom preview style
var previewStyle = StyleHelper.BezelledPreviewStyle;
this.Properties.PreviewType = PreviewType.Bezelled.ToString();
this.Properties.BackgroundColor1 = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.CanvasStyle.BackgroundStyle.Color1));
this.Properties.BackgroundColor2 = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.CanvasStyle.BackgroundStyle.Color2));
this.Properties.BorderThickness = (int)previewStyle.CanvasStyle.BorderStyle.Top;
this.Properties.BorderColor = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.CanvasStyle.BorderStyle.Color));
this.Properties.Border3dDepth = (int)previewStyle.CanvasStyle.BorderStyle.Depth;
this.Properties.BorderPadding = (int)previewStyle.CanvasStyle.PaddingStyle.Top;
this.Properties.BezelThickness = (int)previewStyle.ScreenStyle.BorderStyle.Top;
this.Properties.BezelColor = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.ScreenStyle.BorderStyle.Color));
this.Properties.Bezel3dDepth = (int)previewStyle.ScreenStyle.BorderStyle.Depth;
this.Properties.ScreenMargin = (int)previewStyle.ScreenStyle.MarginStyle.Top;
this.Properties.ScreenColor1 = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.ScreenStyle.BackgroundStyle.Color1));
this.Properties.ScreenColor2 = ConfigHelper.SerializeToConfigColorString(
ConfigHelper.ToUnnamedColor(previewStyle.ScreenStyle.BackgroundStyle.Color2));
}
// we still need to flag the settings as "upgraded" so that the new version gets written
// back to the config file, even if we didn't actually change and setting values
upgraded = true;
}
return upgraded;
}
}
}

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