Compare commits

..

37 Commits

Author SHA1 Message Date
Yu Leng (from Dev Box)
a19fc93346 Rename func 2025-04-15 16:09:42 +08:00
Yu Leng (from Dev Box)
85bdeeaacf init 2025-04-15 15:14:34 +08:00
Stefan Markovic
f1bda8d71f [cmdpal] Setting a new alias should remove the old one (#38193)
* [cmdpal] Fix alias update

* Fix the command palette extension alias update issue

* clean code

---------

Co-authored-by: vanzue <vanzue@outlook.com>
2025-04-15 13:09:35 +08:00
PesBandi
21aa49cefb [ColorPicker]Only close on escape if focused (#37895) 2025-04-15 11:15:41 +08:00
ruslanlap
293fa262bb [PowerToysRun][Docs] Add Weather and Pomodoro to Third-Party plugins (#38760)
* [PowerToysRun][Docs] Add Weather and Pomodoro to Third-Party plugins

* [Spell-check] Add ruslanlap to allowed names and update expect.txt

* Update expect.txt

* Update names.txt

* Update names.txt

* Update names.txt

* Update names.txt
2025-04-14 17:02:29 +00:00
Clint Rutkas
578d99f3b3 upgrading to adjust fix vulnerability (#38784)
* upgrading to adjust fix vulnerablitlity

* Update NOTICE.md

* Update NuGet packages in NOTICE.md
2025-04-14 17:01:54 +00:00
Clint Rutkas
badb029bcf Upgrading some of the Testing framework items (#38779)
* starting to get some of the baseline

* Update NOTICE.md

* Upgrading streamjson gets the others on same version of newtonsoft.json

* Update PowerToys.Settings.csproj

* Update NOTICE.md
2025-04-14 17:01:37 +00:00
dcog989
bec6754aa3 Fix Color Picker resource leak (#38122) (#38147)
* Fix Color Picker resource leak (#38122)

Added a using statement to properly dispose of the Graphics object created from the Bitmap. This fixes resource leak.

* Fix CI complain

* Update MouseInfoProvider.cs

fix whitespace

---------

Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com>
2025-04-14 18:20:55 +08:00
OlegHarchevkin
d986592737 "ǔ" changed to "ŭ" in GetDefaultLetterKeyEPO (#37791) 2025-04-14 16:50:00 +08:00
Ionuț Manța
662f04ed34 [KeyboardManager] Fix modifier Key (Not right or left) stuck (#37930)
* Fix ctr,alt,shift getting stuck

* more changes

* Update src/modules/keyboardmanager/common/Helpers.h

Co-authored-by: Hao Liu <liuhaobupt@163.com>

---------

Co-authored-by: Hao Liu <liuhaobupt@163.com>
2025-04-14 16:48:10 +08:00
Bennett Blodinger
03bc72c436 Change log extension from .txt => .log (#33813)
* Change log extension

From .txt to .log

* Also add workspace logs

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
2025-04-14 07:20:43 +08:00
Muhammad Danish
5c8aa67781 Add securityContext to configuration files (#38017)
For the winget DSC, Setting developer mode, installing Visual Studio 2022 & fetching and installing VS components all require elevation. Added securityContext: elevated for these resources. These configurations can now be invoked from user context, and will prompt for a single UAC to run resources that require elevation in a separate process.
2025-04-14 07:19:22 +08:00
ruslanlap
b32c04fca1 [PowerToysRun][Docs] Add QuickNotes to Third-Party plugins (#38663)
* Add QuickNotes plugin to third-party Run plugins documentation

* chore: add ruslanlap to spelling allow-list

* chore: add ruslanlap to spelling allow-list

* chore: add ruslanlap to spelling allow-list

* Add ruslanlap to allowed names and remove from expected words list
2025-04-11 18:45:25 +00:00
Gordon Lam
4cb72ee126 Add Zhiwei as part of PowerToys! (#38744)
* Add Zhiwei as part of PowerToys!

* Fix the expect.txt for Zhiwei

* Fix the case problem on expect.txt zhiwei => Zhiwei
2025-04-11 12:58:10 +08:00
Davide Giacometti
55f8f3a53e [CmdPal] Tray icon settings (#38672)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

Added a settings to enable/disable the system tray icon (enabled by default).
Adopter the term "system tray icon" for consistency with Windows 11 settings.

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

- [x] **Closes:** #38407
2025-04-10 16:44:54 -05:00
Yu Leng
a7994402fe [cmdpal] Add Open URL fallback command for WebSearch ext (#38685)
## Summary of the Pull Request
1. Add new fallback command for websearch

https://github.com/user-attachments/assets/39362d66-db59-42d4-b07c-7bfd60b2e420

## PR Checklist

- [x] **Closes:** #38497

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-04-10 10:34:52 -05:00
RokyZevon
41472a483c [cmdpal] fix a broken link in README.md (#38714)
related issue: #38713
2025-04-10 09:00:33 -05:00
Laszlo Nemeth
94e8559796 [Bug report] - Auto fill bug report parameters. (#37991) 2025-04-10 16:29:06 +08:00
Clint Rutkas
fe53a9c89a empowering users to maximize OOBE to their heart desire (#37823)
empowering users to maximize to their heart desire
2025-04-09 22:27:17 -07:00
dependabot[bot]
a708a3afaa Bump msstore-submissions.yml actions/setup-dotnet from 3 to 4 (#38626)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-09 19:16:32 +08:00
Jeremy Sinclair
b7f99e88ef [Deps] Update .NET packages from 9.0.3 to 9.0.4 (#38676)
* [Deps] Update .NET packages from 9.0.3 to 9.0.4

* [Deps] Update NOTICE.md
2025-04-09 12:36:13 +08:00
Kai Tao
6f43aac26a [CmdPal] [Install] do not install dependency if already satisfied. (#38531)
* do not install dependency if already satisfied.
* self contain winappsdk
2025-04-09 07:16:08 +08:00
Mike Griese
d7e826d2ac Fix the WinGet missing / Admin crash (#38493)
This is a fix for a pair of related crashes. 

Basically, we'd crash on startup if we failed to initialize WinGet. This could happen in two different places:
* If WinGet wasn't installed, then we'd explode, cause obviously we can't call its APIs
* If we're running as Admin, we won't be able to instantiate it's COM server. 

Regardless of how it happens, I've defaulted us to just _not enabling the winget built-in_. That's the simplest solution here. 

As I was helpfully reminded, there's also an elevated WindowsPackageManagerFactory we could use too - though, that wouldn't solve the case of "winget isn't installed"

Closes #38460
Closes #38440 (most likely)
2025-04-08 04:22:29 -07:00
Clint Rutkas
9e8754a592 Fixing settings for mouse pointer for pt run and cmdpal (#38649)
In CmdPal and PT Run, if you currently try to go to mouse pointer, it fails.  This looks to be due to capitalization in the command.  This can be validated via run dialog also.

shifting to lowercase fixes the bug.

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

- [x] **Closes:** #38223
2025-04-08 04:21:03 -07:00
moooyo
49687251d3 [cmdpal] Simplify time And Date extension code to make it easier to understand (#38075)
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-04-08 16:19:13 +08:00
DanielEScherzer
f72ccd12fd doc/devdocs/UITests.md: add missing period [skip ci] (#38605) 2025-04-08 15:29:18 +08:00
Clint Rutkas
4b44858a48 Adding in Hawker for CmdPal to Readme (#38647)
Update README.md

We missed Hawker!!!
2025-04-07 18:02:30 -07:00
Kayla Cinnamon
53ae118e72 Update version placeholder in bug_report.yml (#38504) 2025-04-07 17:06:34 -07:00
Clint Rutkas
31df702704 Updating the community md for recent adjustments (#38366)
* Update COMMUNITY.md

Adjusting team

* Update names.txt

* Update expect.txt
2025-04-07 12:42:56 -07:00
Clint Rutkas
ea2542b235 Adding Pedro for his hard 3D file work (#38599)
Adding Pedro
2025-04-07 12:42:38 -07:00
Davide Giacometti
c6776d0d45 [CmdPal] Fix Up/Down keyboard navigation + continuous navigation (#38499)
## Summary of the Pull Request

Fix #38337 and implement continuous navigation like PT Run v1

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

- [x] **Closes:** #38337
2025-04-03 06:47:23 -05:00
Mike Griese
d48286a3eb Scale the toast window for DPI (#38198)
Ah of course, AppWindow.Resize doesn't use DIPs. Why would it? It's not like literally everything else in XAML does.

Related to half of https://github.com/zadjii-msft/PowerToys/issues/508
2025-04-03 06:44:10 -05:00
Jaime Bernardo
7a5b25cd3e [Settings]Fix blank icon in the taskbar (#38259) 2025-04-02 17:59:46 -07:00
Clint Rutkas
1eccbc3021 WinGet installer fix (#38422)
Update package.h
2025-04-01 16:21:15 -07:00
Heiko
ce620e427f [PT Run > Time and Date plugin] Custom formats and other improvements (#37743)
* add settings definition

* fix typos and improve settings

* make spell checker happy

* new icon type error

* first code to handle custom formats

* support parsing of new formats

* spelling and typos

* comment fix

* spell check

* start implement custom format results

* last changes

* finish implementation

* spell checker

* settings name

* add missing format

* reorder settings

* dev docs

* change ELF to EAB

* update dev docs

* last changes

* test cases

* fix typos

* fix typo

* port changes

* fixes

* changes

* fixes

* leap year support

* days in month

* tests

* comment

* fix comment
2025-04-01 21:31:35 +08:00
Hao Liu
721c84d3a6 Install .NET 9 for MSStore release (#38361)
fix dotnet 9
2025-04-01 10:08:25 +08:00
Hao Liu
96ba445cfa 0.90 changelog (#38072)
* First draft. Some PRs are still under review.

* update wording

* remove the Svg thumbnail since it has been reverted

* update cmdpal

* add cmdpal

* clear old hashes

* address comments

* Update README.md

Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com>

* address more comments

* Update README.md

Co-authored-by: Mike Griese <migrie@microsoft.com>

* Add Command Palette gif to README

* upload gif of cmdpal

* Update README.md

Co-authored-by: Mike Griese <migrie@microsoft.com>

* Address comments

Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>

* Update README.md

Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>

* Update CmdPal description

Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>

* Remove extra spacing

Co-authored-by: PesBandi <127593627+PesBandi@users.noreply.github.com>

* update hash

* update gif with new icon

---------

Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>
Co-authored-by: PesBandi <127593627+PesBandi@users.noreply.github.com>
2025-03-31 16:09:26 -07:00
91 changed files with 1994 additions and 917 deletions

View File

@@ -6,12 +6,16 @@ properties:
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Enterprise (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Enterprise
source: winget
@@ -21,6 +25,8 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Enterprise
channelId: VisualStudio.17.Release

View File

@@ -6,12 +6,16 @@ properties:
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Professional (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Professional
source: winget
@@ -21,6 +25,8 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Professional
channelId: VisualStudio.17.Release

View File

@@ -6,12 +6,16 @@ properties:
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Community (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.2022.Community
source: winget
@@ -21,6 +25,8 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Community
channelId: VisualStudio.17.Release

View File

@@ -1,6 +1,5 @@
name: "🕷️ Bug report"
description: Report errors or unexpected behavior
type: Bug
labels:
- Issue-Bug
- Needs-Triage
@@ -8,15 +7,17 @@ body:
- type: markdown
attributes:
value: Please make sure to [search for existing issues](https://github.com/microsoft/PowerToys/issues) before filing a new one!
- type: input
- id: version
type: input
attributes:
label: Microsoft PowerToys version
placeholder: 0.70.0
placeholder: X.XX.X
description: Hover over system tray icon or look at Settings
validations:
required: true
- type: dropdown
- id: installed
type: dropdown
attributes:
label: Installation method
description: How / Where was PowerToys installed from?
@@ -33,14 +34,6 @@ body:
validations:
required: true
- type: dropdown
attributes:
label: Running as admin
description: Are you running PowerToys as Admin?
options:
- "Yes"
- "No"
- type: dropdown
attributes:
label: Area(s) with issue?
@@ -67,7 +60,7 @@ body:
- Keyboard Manager
- Mouse Utilities
- Mouse Without Borders
- New+
- New+
- Peek
- PowerRename
- PowerToys Run
@@ -106,6 +99,19 @@ body:
validations:
required: false
- id: additionalInfo
type: textarea
attributes:
label: Additional Information
placeholder: |
OS version
.Net version
System Language
User or System Installation
Running as admin
validations:
required: false
- type: textarea
attributes:
label: Other Software
@@ -116,3 +122,4 @@ body:
My Cool Application v0.3 (include a code snippet if it would help!)
validations:
required: false

View File

@@ -110,6 +110,7 @@ Lambson
Laute
laviusmotileng
Leilei
Loewen
Luecking
Mahalingam
Markovic
@@ -187,6 +188,7 @@ zhaoqpcn
Zoltan
Zykova
Sameerjs
ruslanlap
# OTHERS

View File

@@ -15,6 +15,7 @@ ACTIVATEAPP
activationaction
ACVS
adaptivecards
ADate
ADDSTRING
ADDUNDORECORD
ADifferent
@@ -55,12 +56,10 @@ APIIs
Apm
APPBARDATA
APPEXECLINK
APPICONREFERENCE
APPLICATIONFRAMEHOST
appmanifest
APPMODEL
APPNAME
APPPUBLISHER
appref
appsettings
appwindow
@@ -172,7 +171,6 @@ CCHFORMNAME
CCom
CContext
CDeclaration
cdn
CElems
CENTERALIGN
certlm
@@ -199,8 +197,7 @@ CLIPCHILDREN
CLIPSIBLINGS
closesocket
CLSCTX
CLSIDs
Clsids
clsids
Clusion
cmder
CMDNOTFOUNDMODULEINTERFACE
@@ -254,8 +251,6 @@ createdump
CREATEPROCESS
CREATESCHEDULEDTASK
CREATESTRUCT
CREATETHREAD
CREATEWINDOW
CREATEWINDOWFAILED
CRECT
CRH
@@ -308,11 +303,7 @@ DCOM
DComposition
DCR
ddd
DDEAPPLICATION
DDECOMMAND
DDEIf
DDEIFEXEC
DDETOPIC
DDevice
DDxgi
Deact
@@ -326,16 +317,13 @@ DEFAULTFLAGS
DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTTOFOLDER
DEFAULTTONEAREST
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFAULTTOSTAR
DEFERERASE
DEFPUSHBUTTON
deinitialization
DELA
DELEGATEEXECUTE
DELETEDKEYIMAGE
DELETESCANS
deletethis
@@ -389,7 +377,6 @@ dreamsofameaningfullife
drivedetectionwarning
Droid
DROPFILES
DROPTARGET
DSTINVERT
DSurface
DTexture
@@ -417,6 +404,7 @@ DWORDLONG
dworigin
dwrite
dxgi
eab
easeofaccess
ecount
Edid
@@ -466,6 +454,7 @@ EXAND
EXCLUDEFROMCAPTURE
executionpolicy
exename
exf
EXITSIZEMOVE
exlist
EXPCMDFLAGS
@@ -508,12 +497,10 @@ Fira
FIXEDFILEINFO
FIXEDSYS
flac
flaticon
flyouts
FMask
fmtid
FOF
WANTNUKEWARNING
FOFX
FOLDERID
folderpath
@@ -523,7 +510,6 @@ FORCEMINIMIZE
FORMATDLGORD
formatetc
FORPARSING
fpvm
Fqc
FRAMECHANGED
frm
@@ -557,7 +543,6 @@ GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
GHND
gifv
GMEM
GNumber
gpedit
@@ -651,7 +636,6 @@ HROW
hsb
HSCROLL
hsi
HSSH
HTCLIENT
hthumbnail
HTOUCHINPUT
@@ -689,7 +673,6 @@ iextn
IFACEMETHOD
IFACEMETHODIMP
IFile
IGNOREBASECLASS
IGNOREUNKNOWN
IGo
iid
@@ -738,8 +721,6 @@ Inste
Interlop
INTRESOURCE
INVALIDARG
INVALIDCALL
INVALIDINDEX
invalidoperatioexception
ipcmanager
IPREVIEW
@@ -801,7 +782,6 @@ LEVELID
LExit
lhwnd
LIBID
libraryincludes
LIMITSIZE
LIMITTEXT
lindex
@@ -898,11 +878,11 @@ MARKDOWNPREVIEWHANDLERCPP
MAXIMIZEBOX
MAXSHORTCUTSIZE
maxversiontested
mber
MBM
MBR
MDICHILD
MDL
mdpvm
mdtext
mdtxt
mdwn
@@ -1050,7 +1030,6 @@ NOCRLF
nodeca
NODRAWCAPTION
NODRAWICON
NOFIXUPS
NOINHERITLAYOUT
NOINTERFACE
NOINVERT
@@ -1066,13 +1045,11 @@ NONELEVATED
NONINFRINGEMENT
nonspace
nonstd
NOOPEN
NOOWNERZORDER
NOPARENTNOTIFY
NOPREFIX
NOREDIRECTIONBITMAP
NOREDRAW
NOREMAPCLSID
NOREMOVE
norename
NOREPEAT
@@ -1091,11 +1068,9 @@ NOTIFYICONDATAW
NOTIMPL
NOTOPMOST
NOTRACK
NOTRUNCATE
NOTSRCCOPY
NOTSRCERASE
NOTXORPEN
NOUSERSETTINGS
NOZORDER
NPH
npmjs
@@ -1221,6 +1196,7 @@ Podcasts
POINTERID
POINTERUPDATE
Pokedex
Pomodoro
Popups
POPUPWINDOW
POSITIONITEM
@@ -1315,7 +1291,6 @@ Quarternary
QUERYENDSESSION
QUERYOPEN
QUEUESYNC
QUICKTIP
QUNS
QXZ
RAII
@@ -1344,7 +1319,6 @@ REFCLSID
REFIID
REGCLS
regfile
REGISTERCLASSEX
REGISTERCLASSFAILED
REGISTRYHEADER
registrypath
@@ -1356,7 +1330,6 @@ REINSTALLMODE
reloadable
Relogger
remappings
REMAPRUNDLL
REMAPSUCCESSFUL
REMAPUNSUCCESSFUL
Remotable
@@ -1433,7 +1406,6 @@ SDDL
SDKDDK
sdns
searchterm
searchtext
SEARCHUI
SECONDARYDISPLAY
secpol
@@ -1481,8 +1453,6 @@ SHELLDLL
shellex
SHELLEXECUTEINFO
SHELLEXECUTEINFOW
SHELLEXTENSION
SHELLNEWVALUE
SHFILEINFO
SHFILEOPSTRUCT
SHGDN
@@ -1492,7 +1462,6 @@ SHGFIICON
SHGFILARGEICON
shinfo
shlwapi
SHNAMEMAPPING
shobjidl
SHORTCUTATLEAST
SHORTCUTMAXONEACTIONKEY
@@ -1543,7 +1512,6 @@ SNAPPROCESS
snwprintf
softline
SOURCECLIENTAREAONLY
sourced
sourcedoc
SOURCEHEADER
sourcesdirectory
@@ -1682,7 +1650,6 @@ THotkey
throughs
TIcon
TILEDWINDOW
TILEINFO
TILLSON
timedate
timediff
@@ -1696,9 +1663,6 @@ TLayout
tlb
tlbimp
tlc
TPMLEFTALIGN
TPMRETURNCMD
TMPVAR
TNP
Toolhelp
toolkitconverters
@@ -1706,6 +1670,8 @@ toolwindow
TOPDOWNDIB
TOUCHEVENTF
TOUCHINPUT
TPMLEFTALIGN
TPMRETURNCMD
TRACEHANDLE
tracelogging
tracerpt
@@ -1779,6 +1745,7 @@ USRDLL
UType
uuidv
uwp
uxt
uxtheme
vabdq
validmodulename
@@ -1804,7 +1771,6 @@ VERTSIZE
VFT
vget
vgetq
videourl
viewmodel
VIRTKEY
VIRTUALDESK
@@ -1838,7 +1804,7 @@ vstprintf
VSTT
vswhere
Vtbl
WANTMAPPINGHANDLE
WANTNUKEWARNING
WANTPALM
wasdk
wbem
@@ -1859,10 +1825,10 @@ webbrowsers
webpage
websites
wekyb
wft
wgpocpl
WHEREID
Wholegrain
WIC
wic
wifi
wil
@@ -1919,6 +1885,7 @@ WNDCLASSEXW
WNDCLASSW
WNDPROC
wnode
wom
WORKSPACESEDITOR
WORKSPACESLAUNCHER
WORKSPACESSNAPSHOTTOOL
@@ -1952,7 +1919,6 @@ WUX
Wwanpp
XAxis
xclip
xdoc
XDocument
XElement
xfd
@@ -1975,13 +1941,13 @@ Yeet
YIncrement
yinle
yinyue
youtube
YPels
YResolution
YStr
YTM
YVIRTUALSCREEN
ZEROINIT
Zhiwei
zonable
zoneset
Zoneszonabletester
@@ -1990,4 +1956,4 @@ zoomit
ZOOMITX
ZXk
ZXNs
zzz
zzz

View File

@@ -39,6 +39,11 @@ jobs:
echo powerToysInstallerX64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("x64"))][0].browser_download_url') >> $GITHUB_OUTPUT
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- uses: microsoft/setup-msstore-cli@v1
- name: Fetch Store Credential

View File

@@ -16,7 +16,7 @@ Christian contributed New+ utility
CleanCodeDeveloper helped do massive amounts of code stability and image resizer work.
### [@plante-msft](https://github.com/plante-msft) - Connor Plante
Connor was the creator of Workspaces and helped create PowerToys Run v2
Connor was the creator of Workspaces and helped create Command Palette (PowerToys Run v2)
### [@damienleroy](https://github.com/damienleroy) - [Damien Leroy](https://www.linkedin.com/in/Damien-Leroy-b2734416a/)
Damien has helped out by developing and contributing the Quick Accent utility.
@@ -46,7 +46,7 @@ Jeff added in multiple new features into Keyboard manager, such as key chord sup
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
### [@joadoumie](https://github.com/joadoumie) - Jordi Adoumie
Jordi helped innovate amazing new features into Advanced Paste and helped create PowerToys Run v2
Jordi helped innovate amazing new features into Advanced Paste and helped create Command Palette (PowerToys Run v2)
### [@jsoref](https://github.com/jsoref) - [Josh Soref](https://check-spelling.dev/)
Helping keep our spelling correct :)
@@ -57,6 +57,9 @@ Color Picker is from Martin.
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
### [@PesBandi](https://github.com/PesBandi/) - PesBandi
PesBandi has helped do massive amounts of Quick Accent and bug fixes.
@@ -184,15 +187,11 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@crutkas](https://github.com/crutkas/) - Clint Rutkas - Lead
- [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Lead
- [@nguyen-dows](https://github.com/nguyen-dows) - Christopher Nguyen - Product Manager
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev lead
- [@craigloewen-msft](https://github.com/craigloewen-msft) - Craig Loewen - Product Manager
- [@zhiwei-ms](https://github.com/zhiwei-ms) - Zhiwei Yu - Product Manager
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev lead
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev lead
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev lead
- [@drawbyperpetual](https://github.com/drawbyperpetual) - Anirudha Shankar - Dev
- [@mantaionut](https://github.com/mantaionut) - Ionut Manta - Dev
- [@donlaci](https://github.com/donlaci) - Laszlo Nemeth - Dev
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
@@ -206,7 +205,7 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
# Former PowerToys core team members
## Former PowerToys core team members
- [@indierawk2k2](https://github.com/indierawk2k2) - Mike Harsh - Product Manager
- [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang - Product Manager
@@ -219,3 +218,9 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@taras-janea](https://github.com/taras-janea) - Taras Sich - Dev
- [@yuyoyuppe](https://github.com/yuyoyuppe) - Andrey Nekrasov - Dev
- [@gokcekantarci](https://github.com/gokcekantarci) - Gokce Kantarci - Dev
- [@drawbyperpetual](https://github.com/drawbyperpetual) - Anirudha Shankar - Dev
- [@mantaionut](https://github.com/mantaionut) - Ionut Manta - Dev
- [@donlaci](https://github.com/donlaci) - Laszlo Nemeth - Dev
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev lead

View File

@@ -29,24 +29,24 @@
<PackageVersion Include="Mages" Version="3.0.0" />
<PackageVersion Include="Markdig.Signed" Version="0.34.0" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="2.5.187" />
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.3" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.3" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.3" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.4" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.120-preview" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.3" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.4" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -61,7 +61,7 @@
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="MSTest" Version="3.6.3" />
<PackageVersion Include="MSTest" Version="3.8.3" />
<PackageVersion Include="NLog" Version="5.0.4" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
@@ -69,29 +69,32 @@
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
<PackageVersion Include="SharpCompress" Version="0.37.2" />
<PackageVersion Include="StreamJsonRpc" Version="2.19.27" />
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.3" />
<PackageVersion Include="System.CodeDom" Version="9.0.4" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.3" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.3" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.3" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.4" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.4" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.4" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.3" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.4" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.3" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.3" />
<PackageVersion Include="System.IO.Abstractions" Version="21.0.29" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />
<PackageVersion Include="System.Management" Version="9.0.3" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.4" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.4" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.4" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.3" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.3" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.3" />
<PackageVersion Include="System.Text.Json" Version="9.0.3" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.4" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.4" />
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />

View File

@@ -1393,7 +1393,6 @@ SOFTWARE.
## NuGet Packages used by PowerToys
- AdaptiveCards.ObjectModel.WinUI3 2.0.0-beta
- AdaptiveCards.Rendering.WinUI3 2.1.0-beta
- AdaptiveCards.Templating 2.0.2
@@ -1419,23 +1418,23 @@ SOFTWARE.
- LazyCache 2.4.0
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 2.5.187
- Microsoft.Bcl.AsyncInterfaces 9.0.3
- MessagePack 3.1.3
- Microsoft.Bcl.AsyncInterfaces 9.0.4
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
- Microsoft.Data.Sqlite 9.0.3
- Microsoft.Data.Sqlite 9.0.4
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
- Microsoft.DotNet.ILCompiler (A)
- Microsoft.Extensions.DependencyInjection 9.0.3
- Microsoft.Extensions.Hosting 9.0.3
- Microsoft.Extensions.Hosting.WindowsServices 9.0.3
- Microsoft.Extensions.Logging 9.0.3
- Microsoft.Extensions.Logging.Abstractions 9.0.3
- Microsoft.Extensions.DependencyInjection 9.0.4
- Microsoft.Extensions.Hosting 9.0.4
- Microsoft.Extensions.Hosting.WindowsServices 9.0.4
- Microsoft.Extensions.Logging 9.0.4
- Microsoft.Extensions.Logging.Abstractions 9.0.4
- Microsoft.NET.ILLink.Tasks (A)
- Microsoft.SemanticKernel 1.15.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.3
- Microsoft.Windows.Compatibility 9.0.3
- Microsoft.Win32.SystemEvents 9.0.4
- Microsoft.Windows.Compatibility 9.0.4
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
@@ -1445,35 +1444,39 @@ SOFTWARE.
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4
- Moq 4.18.4
- MSTest 3.6.3
- MSTest 3.8.3
- NLog.Extensions.Logging 5.3.8
- NLog.Schema 5.2.8
- OpenAI 2.0.0
- ReverseMarkdown 4.1.0
- ScipBe.Common.Office.OneNote 3.0.1
- SharpCompress 0.37.2
- StreamJsonRpc 2.19.27
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
- System.CodeDom 9.0.3
- System.CodeDom 9.0.4
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 9.0.3
- System.Configuration.ConfigurationManager 9.0.3
- System.Data.OleDb 9.0.3
- System.ComponentModel.Composition 9.0.4
- System.Configuration.ConfigurationManager 9.0.4
- System.Data.OleDb 9.0.4
- System.Data.SqlClient 4.8.6
- System.Diagnostics.EventLog 9.0.3
- System.Diagnostics.PerformanceCounter 9.0.3
- System.Drawing.Common 9.0.3
- System.IO.Abstractions 21.0.29
- System.IO.Abstractions.TestingHelpers 21.0.29
- System.Management 9.0.3
- System.Diagnostics.EventLog 9.0.4
- System.Diagnostics.PerformanceCounter 9.0.4
- System.Drawing.Common 9.0.4
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
- System.Management 9.0.4
- System.Net.Http 4.3.4
- System.Private.Uri 4.3.2
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.3
- System.ServiceProcess.ServiceController 9.0.3
- System.Text.Encoding.CodePages 9.0.3
- System.Text.Json 9.0.3
- System.Runtime.Caching 9.0.4
- System.ServiceProcess.ServiceController 9.0.4
- System.Text.Encoding.CodePages 9.0.4
- System.Text.Json 9.0.4
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UTF.Unknown 2.5.1
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

148
README.md
View File

@@ -11,14 +11,15 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| | Current utilities: | |
|--------------|--------------------|--------------|
| [Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) |
| [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) |
| [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) |
| [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) |
| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
| [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
| [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
| [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) | [ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |
| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |
| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |
| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |
| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
| [ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |
## Installing and running Microsoft PowerToys
@@ -34,19 +35,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.90%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.89%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysUserSetup-0.89.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysUserSetup-0.89.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysSetup-0.89.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysSetup-0.89.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.91%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.90%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-arm64.exe
| Description | Filename | sha256 hash |
|----------------|----------|-------------|
| Per user - x64 | [PowerToysUserSetup-0.89.0-x64.exe][ptUserX64] | B4F130CC96F321024A257499247F6FF6DA56612215ED3882E868AAE26C689E33 |
| Per user - ARM64 | [PowerToysUserSetup-0.89.0-arm64.exe][ptUserArm64] | F69B00F4E520EB09FA0D1D1669E21910C5225FE7A2EEDC0FA7C283B201A5F9C6 |
| Machine wide - x64 | [PowerToysSetup-0.89.0-x64.exe][ptMachineX64] | E18AC8F9023E341CF7DAD35367FB9DDDB6565D83D8155DBCDDB40AE8A24AE731 |
| Machine wide - ARM64 | [PowerToysSetup-0.89.0-arm64.exe][ptMachineArm64] | 17DEADEC601D6061D7AF4F487595CC36D9191813003CC2ECE381017F0EC71FBB |
| Per user - x64 | [PowerToysUserSetup-0.90.0-x64.exe][ptUserX64] | 2A6036F5B2D454084E55816C306E1E57EF1D14C916691CBDA42B469797605CE0 |
| Per user - ARM64 | [PowerToysUserSetup-0.90.0-arm64.exe][ptUserArm64] | AB2E4DC87A9D764BE897C5170E2890E174C89CA912A1916FA3AE1E427536EA4A |
| Machine wide - x64 | [PowerToysSetup-0.90.0-x64.exe][ptMachineX64] | 12801C44F43D0CC61E90DF1EFDC40E4F3C88341E0199D5B20791042D9B173DCF |
| Machine wide - ARM64 | [PowerToysSetup-0.90.0-arm64.exe][ptMachineArm64] | 2998007C8FCD7BD2770767C6502AAA2CC75B85EC30DE62986EC7005EB0014EDB |
This is our preferred method.
@@ -92,102 +93,95 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.89 - February 2025 Update
### 0.90 - March 2025 Update
In this release, we focused on new features, stability, accessibility and automation.
In this release, we focused on new features, stability, and automation.
**✨Highlights**
- Enhanced Advanced Paste by adding media transcoding support to convert different video and audio file formats! Thanks [@snickler](https://github.com/snickler) for your help!
- Fixed crashes when loading thumbnails after the .NET 9 update and resolved PowerLauncher.exe blocking other MSI installers from creating shortcuts!
- Fixed accessibility issues across FancyZones, Image Resizer, and Settings to improve screen reader support and clarity!
- Enhanced UI automation framework across modules and added new tests to cover manual checks, with more improvements coming!
![Gif for Command Palette](doc/images/overview/CmdPal_Hero.gif)
### General
- New module: Command Palette ("CmdPal") - Created as the evolution of PowerToys Run with extensibility at the forefront, Command Palette is a quick launcher with a richer display and additional capabilities without sacrificing performance, allowing you to start anything with the shortcut **Win+Alt+Space**! Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), [@ethanfangg](https://github.com/ethanfangg) and [@krschau](https://github.com/krschau)!
- Enhanced the Color Picker by switching from WPF UI to .NET WPF, resulting in improved themes and visual consistency across different modes. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review!
- Added the ability to delete files directly from Peek, enhancing file management efficiency. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for the review!
- Added support for variables in template filenames, enabling dynamic elements like date components and environment variables for enhanced customization in New+. Thanks [@cgaarden](https://github.com/cgaarden)!
- Fixed an issue where updating PowerToys on Windows 11 did not properly update context menu entries, impacting New+, PowerRename, Image Resizer, and File Locksmith.
- Updated .NET Packages from 9.0.1 to 9.0.2. Thanks [@snickler](https://github.com/snickler) for this.
- Enabled compatibility with VS17.3 and later, for C++23. Thanks [@LNKLEO](https://github.com/LNKLEO) for this.
### Color Picker
### Advanced Paste
- Replaced WPF UI with .NET WPF for the Color Picker, enhancing compatibility and improving theme support. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review!
- Added media transcoding support to convert different video and audio file formats, improved UI layouts, refined clipboard handling, and integrated Semantic Kernel for smarter pasting. Thanks [@snickler](https://github.com/snickler) for your help!
### Command Palette
- Introduced the Windows Command Palette ("CmdPal"), the next iteration of PowerToys Run, designed with extensibility at its core. CmdPal includes features such as searching for installed apps, shell commands, files and WinGet package installation. This module aims to provide a more powerful and flexible launcher experience. Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), and the whole team!
### FancyZones
- Fixed accessibility by improving the text for monitors, ensuring clearer naming and help text for screen readers.
- Fixed a bug where deleting a layout resulted in incorrect data being written to the JSON file.
- Fixed a bug where layout hotkeys were displayed incorrectly, ensuring the hotkey list does not include invalid entries.
- Fixed an issue where the "None" option was missing in the editor layout.
### Image Resizer
- Fixed issues with Width and Height fields in Image Resizer's Custom preset, ensuring empty values no longer cause errors, settings save correctly, and auto-scaling behaves as expected. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed accessibility by ensuring screen readers announce selected image dimensions in the combo-box for better navigation.
### Monaco Preview
- Fixed open link in default browser rather than Microsoft Edge. Thanks [@OldUser101](https://github.com/OldUser101)!
### Mouse Highlighter
- Fixed a highlight released on an Administrator window will start fading, instead of staying on the screen indefinitely until the mouse button is pressed again on an unelevated window.
- Fixed warnings in ImageResizer regarding the use of variables "shellItem" and "itemName" without being initialized.
### Mouse Without Borders
- Fixed an issue in service mode where copy-paste and drag-drop file transfers didnt work, ensuring seamless file operations.
- Enabled GPO for enable/disable for Mouse Without Borders in Service Mode. Thanks [@htcfreek](https://github.com/htcfreek) for review and comments!
- Fixed code maintainability by refactoring the oversized 'Common' class in Mouse Without Borders into smaller, focused classes for better structure and clarity. Thanks [@mikeclayton](https://github.com/mikeclayton) and thanks [@htcfreek](https://github.com/htcfreek) for review!
### PowerRename
- Supported negative value as Start value in regular expression, e.g. ${start=-1314}
- Enhanced RegEx help by adding $, ^, quantifiers, and common patterns for better usability. Thanks [@PesBandi](https://github.com/PesBandi) and thanks [@htcfreek](https://github.com/htcfreek) for review.
- Enhanced the logger to properly track the file path for easier debugging.
- Refactored the "Common" class into distinct individual classes to enhance maintainability, and updated all references and unit tests to reflect these changes. Thanks [@mikeclayton](https://github.com/mikeclayton) for this!
### New+
- Added support for variables in template filenames, including date/time components, parent folder name, and environment variables. Thanks [@cgaarden](https://github.com/cgaarden)!
### Peek
- Added the ability to delete the file currently being previewed in Peek, including navigation updates and handling for deleted items. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for your help reviewing this!
### PowerToys Run
- Fixed crashes when loading thumbnails after the .NET 9 update by disabling CETCompat.
- Fixed PowerLauncher.exe blocking other MSI installers creating shortcuts. Thanks [@OneBlue](https://github.com/OneBlue)!
- Fixed Runs dark mode detection to work reliably, preventing issues with incorrect theme detection and ensuring a smoother user experience. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed list separator handling in Calculator, allowing functions with multiple arguments to work correctly across different locales. For example pow(2;3) would be replaced with pow(2,3). Thanks [@PesBandi](https://github.com/PesBandi) and thanks [@htcfreek](https://github.com/htcfreek) for review!
- Fixed angle unit conversions in the PowerToys Run calculator, allowing quick conversions between radians, degrees, and gradians. Thanks [@OldUser101](https://github.com/OldUser101)!
### Quick Accent
- Added ǎ, ǒ and ǔ to the IPA character set. Thanks [@PesBandi](https://github.com/PesBandi)!
- Added ` (backtick) and ~ (tilde) to the VK_OEM_5 character set. Thanks [@xanatos](https://github.com/xanatos)!
- Added ς (final sigma) to the Greek character set. Thanks [@IamSmeagol](https://github.com/IamSmeagol)!
- Fixed an issue where duplicated applications were shown by ensuring the shell link helper opens .ink files non-exclusively and correctly retrieves the "FullPath". Thanks [@htcfreek](https://github.com/htcfreek) and [@davidegiacometti](https://github.com/davidegiacometti) for review!
- Fixed an issue where applying round corners on Windows 11 build 22000 caused crashes.
- Async the OnRename method to unblock the thread. Thanks [@davidegiacometti](https://github.com/davidegiacometti) for review!
- Added support for using `sq` instead of `^2` in the Unit Converter. Thanks [@PesBandi](https://github.com/PesBandi)!
### Settings
- Enabled GPO for the "run at startup" setting. Thanks [@htcfreek](https://github.com/htcfreek) for review and comments!
- Fixed accessibility issue by allowing screen readers to announce the group name for secondary links in Settings pages, instead of reading link descriptions without context.
- Fixed an issue where the Color Picker shortcut was not displaying correctly in the Dashboard.
- Disabled the spell check feature in the text boxes of plugin settings for PowerToys Run. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed an issue where InfoBars for release notes errors were not displayed properly, and added a retry button. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
### Workspaces
- Fixed if a window was last placed on a disconnected monitor, it launches minimized and repositions within the main monitor's visible area when restored, instead of remaining off-screen and invisible.
- Fixed on ARM64 to correctly display icons for packaged apps by resolving path mismatches.
### ZoomIt
- Fixed warning C4706 and related error C2220 during build. Thanks [@xanatos](https://github.com/xanatos)!
- Fixed an issue where some minimized packaged apps (e.g., Microsoft ToDo, Settings) were not snapshotted.
### Documentation
- Fixed runner-ipc.md doc on the broken link. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed the new plugin checklist by updating the target framework, removing duplicates, and improving statement organization. Thanks [@hlaueriksson](https://github.com/hlaueriksson)!
- Updated runner documentation to align with the latest code structure.
- Added the FirefoxBookmark plugin to the list of Third-Party plugins for PowerToys Run. Thanks [@8LWXpg](https://github.com/8LWXpg)!
- Added the SVGL third-party plugin to PowerToys Run, enabling users to search, browse, and copy SVG logos. Thanks [@SameerJS6](https://github.com/SameerJS6)!
- Added Monaco usage for the Registry Preview.
### Development
- Stabilized pipeline on ARM64 and forked build.
- Added fuzz testing for HostUILib, added as part of pipeline for OneFuzz.
- Fixed and improved UI-Test automation framework, and added new test cases for the FancyZones and Hosts module.
- Optimized Logger function as AOT compatible, improving performance by 18%.
- Made Common.UI and Setting.UI to be AOT compatible.
- Updated WinGet configuration file location and extension. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
- Removed the Markdown file bypass to ensure CI runs for commits that only update Markdown files.
- Fixed an issue where the default generated file path exceeded the length limit of 260 characters for EnvironmentVariablesUILib.csproj, causing build failures.
- Upgraded WindowsAppSDK to 1.6.250205002 and CsWinRT to 2.2.0. Thanks [@htcfreek](https://github.com/htcfreek) for review!
- Upgraded XamlStyler to 3.2501.8 and dotnet-consolidate to 4.2.0. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Updated .NET Packages from 9.0.2 to 9.0.3.
- Optimized the UI Test Automation Framework and added UI test cases for the Hosts File Editor module.
- Added fuzz testing for RegistryPreview.
- Added new UI tests for the FancyZones editor, including tests for creating, duplicating, editing, and deleting layouts.
- Added telemetry code to measure the module editor open time and evaluate the benefits of applying AOT.
### What is being planned for version 0.90
### What is being planned for version 0.91
For [v0.90][github-next-release-work], we'll work on the items below:
For [v0.91][github-next-release-work], we'll work on the items below:
- New module: PowerToys Run v2
- New module: File Actions Menu
- New UI Automation tests
- Working on installer upgrades
- Upgrading keyboard manager's editor UI
- Upgrading Keyboard Manager's editor UI
- Stability / bug fixes
## PowerToys Community

View File

@@ -88,4 +88,4 @@ namespace UITests_KeyboardManager
## Extra tools and information
**Accessibility Tools**:
While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use [AccessibilityInsights](https://accessibilityinsights.io/docs/windows/overview)
While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use [AccessibilityInsights](https://accessibilityinsights.io/docs/windows/overview).

View File

@@ -16,49 +16,70 @@ The 'Time and Date' plugin shows the date and time in different formats. For the
### Available formats
**Remarks**
- The following formats requires a prefix in the query:
- The following formats requires a prefix in the query when using them as date input:
- Unix Timestamp: `u`
- Unix Timestamp in milliseconds: `ums`
- Windows file time: `ft`
- OLE Automation date: `oa`
- Excel 1900 date value: `exc`
- Excel 1904 date value: `exf`
- On invalid number inputs we show a warning that tells the user which prefixes are allowed/required.
**List of available formats**
The following formats are currently available:
| Format | Example (Based on default settings) | As result | As input |
| Format | Example (Based on default settings) | As result | As input | Result as custom format only
|--------------|-----------|------------|------------|
| Time | 5:10 PM | x | x |
| Date | 3/5/2022 | x | x |
| Now | 3/5/2022 5:10 PM | x | x |
| Time UTC | 4:10 PM | x | x |
| Now UTC | 3/5/2022 4:10 PM | x | x |
| Unix Timestamp | 1646496622 | x | x |
| Unix Timestamp in milliseconds | 1646496622500 | x | x |
| Hour | 10 | x | |
| Minute | 30 | x | |
| Second | 45 | x | |
| Millisecond | 678 | x | |
| Day (Week day) | Saturday | x | |
| Day of the week | 6 | x | |
| Day of the month | 5 | x | |
| Day of the year | 64 | x | |
| Week of the month | 1 | x | |
| Week of the year (Calendar week, Week number) | 10 | x | |
| Month | March | x | |
| Month of the year | 3 | x | |
| Month and day | March 7 | x | x |
| Year | 2022 | x | |
| Era | AD | x | |
| Era abbreviation | A | x | |
| Month and year | March 2022 | x | x |
| Windows file time (Int64 number) | 637820976123938199 | x | x |
| Universal time format: YYYY-MM-DD hh:mm:ss| 2022-03-05 16:20:12Z | x | x |
| ISO 8601 | 2022-03-05T17:23:04 | x | x |
| ISO 8601 UTC | 2022-03-05T16:23:04 | x | x |
| ISO 8601 with time zone | 2022-03-05T17:23:04+01:00 | x | x |
| ISO 8601 UTC with time zone | 2022-03-05T16:23:04Z | x | x |
| RFC1123 | Sat, 05 Mar 2022 16:23:04 GMT | x | x |
| Time | 5:10 PM | x | x | |
| Date | 3/5/2022 | x | x | |
| Now | 3/5/2022 5:10 PM | x | x | |
| Time UTC | 4:10 PM | x | x | |
| Now UTC | 3/5/2022 4:10 PM | x | x | |
| Unix Timestamp | 1646496622 | x | x | |
| Unix Timestamp in milliseconds | 1646496622500 | x | x | |
| Hour | 10 | x | | |
| Minute | 30 | x | | |
| Second | 45 | x | | |
| Millisecond | 678 | x | | |
| Day (Week day) | Saturday | x | | |
| Day of the week | 6 | x | | |
| Day of the month | 5 | x | | |
| Day of the year | 64 | x | | |
| Week of the month | 1 | x | | |
| Week of the year (Calendar week, Week number) | 10 | x | | |
| Month | March | x | | |
| Month of the year | 3 | x | | |
| Month and day | March 7 | x | x | |
| Year | 2022 | x | | |
| Era | AD | x | | |
| Era abbreviation | A | x | | |
| Month and year | March 2022 | x | x | |
| Windows file time (Int64 number) | 637820976123938199 | x | x | |
| Universal time format: YYYY-MM-DD hh:mm:ss| 2022-03-05 16:20:12Z | x | x | |
| ISO 8601 | 2022-03-05T17:23:04 | x | x | |
| ISO 8601 UTC | 2022-03-05T16:23:04 | x | x | |
| ISO 8601 with time zone | 2022-03-05T17:23:04+01:00 | x | x | |
| ISO 8601 UTC with time zone | 2022-03-05T16:23:04Z | x | x | |
| RFC1123 | Sat, 05 Mar 2022 16:23:04 GMT | x | x | |
| OLE Automation date | 45723.44143763889 | | x | x |
| Excel's 1900 date value | 45723.44143763889 | | x | x |
| Excel's 1904 date value | 44261.44143763889 | | x | x |
**Custom format definition**
The user can create its own formats. One per line in the settings text box. The format of each line is `<name>=<syntax pattern>`.
If the syntax pattern starting with `UTC:` then we use the UTC time instead of the local time.
As syntax pattern the pattern from `DateTime.ToString()` and the following custom pattern are available:
- DOW: Number of the day in the week.
- WOM: Number of week in the month.
- WOY: Number of the week in the year.
- EAB: Era abbreviation.
- WFT: Windows file time.
- UXT: Unix time stamp.
- UMS: Unix time stamp in milliseconds.
- OAD: OLE Automation date.
- EXC: Excel's 1900 based date value.
- EXF: Excel's 1904 based date value.
### Add new formats
- To add a new formats you have to add them to the method `GetList()` of the [`AvailableResultsList`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs) class.
@@ -73,13 +94,13 @@ The following formats are currently available:
| Key | Type | Default value | Name | Description |
|--------------|--------------|-----------|------------|------------|
| `CalendarFirstWeekRule` | Combo box | `-1` (Use system settings) | First week of the year | Configure the calendar rule for the first week of the year. |
| `FirstDayOfWeek` | Combo box | `-1` (Use system settings) | First day of the week | |
| `OnlyDateTimeNowGlobal` | Checkbox | `true` | Show only 'Time', 'Date', and 'Now' result for system time on global queries | Regardless of this setting, for global queries the first word of the query has to be a complete match. |
| `TimeWithSeconds` | Checkbox | `false` | Show time with seconds | This setting applies to the 'Time' and 'Now' result. |
| `DateWithWeekday` | Checkbox | `false` | Show date with weekday and name of month | This setting applies to the 'Date' and 'Now' result. |
| `HideNumberMessageOnGlobalQuery` | Checkbox | `false` | Hide 'Invalid number input' error message on global queries | |
| `CalendarFirstWeekRule` | Combo box | `-1` (Use system settings) | First week of the year | Configure the calendar rule for the first week of the year. |
| `FirstDayOfWeek` | Combo box | `-1` (Use system settings) | First day of the week | |
| `CustomFormats` | Multiline text box | `string.Empty` | Custom formats | Use date and time string format syntax and DOW (Day of Week), WOM (Week of Month), WOY (Week of the year), EAB (Era abbreviation), WFT (Windows File Time), UXT (Unix Time), UMS (Unix Time in milliseconds), OAD (OLE Automation date), EXC (Excel's 1900 based date value), EXF (Excel's 1904 based date value). If the format starts with UTC:, then Universal Time (UTC) is used. (Use a backslash to escape format sequences and the backslash character as text.) |
## Classes

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@@ -43,6 +43,9 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [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 |
| [SVGL](https://github.com/Sameerjs6/powertoys-svgl) | [SameerJS6](https://github.com/SameerJS6) | Search, Browse and copy SVG logos from SVGL. |
| [QuickNotes](https://github.com/ruslanlap/CommunityPowerToysRunPlugin-QuickNotes) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and search notes directly from PowerToys Run. |
| [Weather](https://github.com/ruslanlap/PowerToysRun-Weather) | [ruslanlap](https://github.com/ruslanlap) | Get real-time weather information directly from PowerToys Run. |
| [Pomodoro](https://github.com/ruslanlap/PowerToysRun-Pomodoro) | [ruslanlap](https://github.com/ruslanlap) | Manage Pomodoro productivity sessions directly from PowerToys Run. |
## Extending software plugins

View File

@@ -40,7 +40,6 @@
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" />
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\x64\Microsoft.WindowsAppRuntime.1.6.msix" />
</Component>
</DirectoryRef>
<?else ?>
@@ -50,7 +49,6 @@
<RegistryValue Type="string" Name="Module_CmdPal_Deps" Value="" KeyPath="yes"/>
</RegistryKey>
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\arm64\Microsoft.VCLibs.ARM64.14.00.Desktop.appx" />
<File Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion).0_Test\Dependencies\arm64\Microsoft.WindowsAppRuntime.1.6.msix" />
</Component>
</DirectoryRef>
<?endif ?>

View File

@@ -25,7 +25,7 @@ namespace AllExperiments
}
// Using InvariantCulture since this is used for a log file name
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));

View File

@@ -54,7 +54,7 @@ namespace ManagedCommon
Directory.CreateDirectory(applicationLogPath);
}
var logFilePath = Path.Combine(applicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
var logFilePath = Path.Combine(applicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));

View File

@@ -14,6 +14,9 @@
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
</Project>

View File

@@ -8,49 +8,49 @@ struct LogSettings
inline const static std::wstring logLevelOption = L"logLevel";
inline const static std::string runnerLoggerName = "runner";
inline const static std::wstring logPath = L"Logs\\";
inline const static std::wstring runnerLogPath = L"RunnerLogs\\runner-log.txt";
inline const static std::wstring runnerLogPath = L"RunnerLogs\\runner-log.log";
inline const static std::string actionRunnerLoggerName = "action-runner";
inline const static std::wstring actionRunnerLogPath = L"RunnerLogs\\action-runner-log.txt";
inline const static std::wstring actionRunnerLogPath = L"RunnerLogs\\action-runner-log.log";
inline const static std::string updateLoggerName = "update";
inline const static std::wstring updateLogPath = L"UpdateLogs\\update-log.txt";
inline const static std::wstring updateLogPath = L"UpdateLogs\\update-log.log";
inline const static std::string fileExplorerLoggerName = "FileExplorer";
inline const static std::wstring fileExplorerLogPath = L"Logs\\file-explorer-log.txt";
inline const static std::wstring fileExplorerLogPath = L"Logs\\file-explorer-log.log";
inline const static std::string gcodePrevLoggerName = "GcodePrevHandler";
inline const static std::wstring gcodePrevLogPath = L"logs\\FileExplorer_localLow\\GcodePreviewHandler\\gcode-prev-handler-log.txt";
inline const static std::wstring gcodePrevLogPath = L"logs\\FileExplorer_localLow\\GcodePreviewHandler\\gcode-prev-handler-log.log";
inline const static std::string gcodeThumbLoggerName = "GcodeThumbnailProvider";
inline const static std::wstring gcodeThumbLogPath = L"logs\\FileExplorer_localLow\\GcodeThumbnailProvider\\gcode-thumbnail-provider-log.txt";
inline const static std::wstring gcodeThumbLogPath = L"logs\\FileExplorer_localLow\\GcodeThumbnailProvider\\gcode-thumbnail-provider-log.log";
inline const static std::string mdPrevLoggerName = "MDPrevHandler";
inline const static std::wstring mdPrevLogPath = L"logs\\FileExplorer_localLow\\MDPrevHandler\\md-prev-handler-log.txt";
inline const static std::wstring mdPrevLogPath = L"logs\\FileExplorer_localLow\\MDPrevHandler\\md-prev-handler-log.log";
inline const static std::string monacoPrevLoggerName = "MonacoPrevHandler";
inline const static std::wstring monacoPrevLogPath = L"logs\\FileExplorer_localLow\\MonacoPrevHandler\\monaco-prev-handler-log.txt";
inline const static std::wstring monacoPrevLogPath = L"logs\\FileExplorer_localLow\\MonacoPrevHandler\\monaco-prev-handler-log.log";
inline const static std::string pdfPrevLoggerName = "PdfPrevHandler";
inline const static std::wstring pdfPrevLogPath = L"logs\\FileExplorer_localLow\\PdfPrevHandler\\pdf-prev-handler-log.txt";
inline const static std::wstring pdfPrevLogPath = L"logs\\FileExplorer_localLow\\PdfPrevHandler\\pdf-prev-handler-log.log";
inline const static std::string pdfThumbLoggerName = "PdfThumbnailProvider";
inline const static std::wstring pdfThumbLogPath = L"logs\\FileExplorer_localLow\\PdfThumbnailProvider\\pdf-thumbnail-provider-log.txt";
inline const static std::wstring pdfThumbLogPath = L"logs\\FileExplorer_localLow\\PdfThumbnailProvider\\pdf-thumbnail-provider-log.log";
inline const static std::string qoiPrevLoggerName = "QoiPrevHandler";
inline const static std::wstring qoiPrevLogPath = L"logs\\FileExplorer_localLow\\QoiPreviewHandler\\qoi-prev-handler-log.txt";
inline const static std::wstring qoiPrevLogPath = L"logs\\FileExplorer_localLow\\QoiPreviewHandler\\qoi-prev-handler-log.log";
inline const static std::string qoiThumbLoggerName = "QoiThumbnailProvider";
inline const static std::wstring qoiThumbLogPath = L"logs\\FileExplorer_localLow\\QoiThumbnailProvider\\qoi-thumbnail-provider-log.txt";
inline const static std::wstring qoiThumbLogPath = L"logs\\FileExplorer_localLow\\QoiThumbnailProvider\\qoi-thumbnail-provider-log.log";
inline const static std::string stlThumbLoggerName = "StlThumbnailProvider";
inline const static std::wstring stlThumbLogPath = L"logs\\FileExplorer_localLow\\StlThumbnailProvider\\stl-thumbnail-provider-log.txt";
inline const static std::wstring stlThumbLogPath = L"logs\\FileExplorer_localLow\\StlThumbnailProvider\\stl-thumbnail-provider-log.log";
inline const static std::string svgPrevLoggerName = "SvgPrevHandler";
inline const static std::wstring svgPrevLogPath = L"logs\\FileExplorer_localLow\\SvgPrevHandler\\svg-prev-handler-log.txt";
inline const static std::wstring svgPrevLogPath = L"logs\\FileExplorer_localLow\\SvgPrevHandler\\svg-prev-handler-log.log";
inline const static std::string svgThumbLoggerName = "SvgThumbnailProvider";
inline const static std::wstring svgThumbLogPath = L"logs\\FileExplorer_localLow\\SvgThumbnailProvider\\svg-thumbnail-provider-log.txt";
inline const static std::wstring svgThumbLogPath = L"logs\\FileExplorer_localLow\\SvgThumbnailProvider\\svg-thumbnail-provider-log.log";
inline const static std::string launcherLoggerName = "launcher";
inline const static std::wstring launcherLogPath = L"LogsModuleInterface\\launcher-log.txt";
inline const static std::wstring launcherLogPath = L"LogsModuleInterface\\launcher-log.log";
inline const static std::string mouseWithoutBordersLoggerName = "mouseWithoutBorders";
inline const static std::wstring mouseWithoutBordersLogPath = L"LogsModuleInterface\\mouseWithoutBorders-log.txt";
inline const static std::wstring awakeLogPath = L"Logs\\awake-log.txt";
inline const static std::wstring powerAccentLogPath = L"quick-accent-log.txt";
inline const static std::wstring mouseWithoutBordersLogPath = L"LogsModuleInterface\\mouseWithoutBorders-log.log";
inline const static std::wstring awakeLogPath = L"Logs\\awake-log.log";
inline const static std::wstring powerAccentLogPath = L"quick-accent-log.log";
inline const static std::string fancyZonesLoggerName = "fancyzones";
inline const static std::wstring fancyZonesLogPath = L"fancyzones-log.txt";
inline const static std::wstring fancyZonesLogPath = L"fancyzones-log.log";
inline const static std::wstring fancyZonesOldLogPath = L"FancyZonesLogs\\"; // needed to clean up old logs
inline const static std::string shortcutGuideLoggerName = "shortcut-guide";
inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.txt";
inline const static std::wstring powerOcrLogPath = L"Logs\\text-extractor-log.txt";
inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.log";
inline const static std::wstring powerOcrLogPath = L"Logs\\text-extractor-log.log";
inline const static std::string keyboardManagerLoggerName = "keyboard-manager";
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt";
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.log";
inline const static std::string findMyMouseLoggerName = "find-my-mouse";
inline const static std::string mouseHighlighterLoggerName = "mouse-highlighter";
inline const static std::string mouseJumpLoggerName = "mouse-jump";
@@ -60,22 +60,22 @@ struct LogSettings
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
inline const static std::string powerOcrLoggerName = "TextExtractor";
inline const static std::string fileLocksmithLoggerName = "FileLocksmith";
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.log";
inline const static std::string hostsLoggerName = "hosts";
inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.txt";
inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.log";
inline const static std::string registryPreviewLoggerName = "registrypreview";
inline const static std::string cropAndLockLoggerName = "crop-and-lock";
inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.txt";
inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.log";
inline const static std::string environmentVariablesLoggerName = "environment-variables";
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.log";
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
inline const static std::string newLoggerName = "NewPlus";
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.log";
inline const static std::string workspacesWindowArrangerLoggerName = "workspaces-window-arranger";
inline const static std::wstring workspacesWindowArrangerLogPath = L"workspaces-window-arranger-log.txt";
inline const static std::wstring workspacesWindowArrangerLogPath = L"workspaces-window-arranger-log.log";
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt";
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.log";
inline const static std::string zoomItLoggerName = "zoom-it";
inline const static int retention = 30;
std::wstring logLevel;

View File

@@ -91,7 +91,7 @@ namespace LoggerHelpers
currentFolder.append(get_product_version());
auto logsPath = currentFolder;
logsPath.append(L"log.txt");
logsPath.append(L"log.log");
Logger::init(loggerName, logsPath.wstring(), PTSettingsHelper::get_log_settings_file_location());
delete_other_versions_log_folders(rootFolder.wstring(), currentFolder);

View File

@@ -2,11 +2,14 @@
#include <Windows.h>
#include <appxpackaging.h>
#include <exception>
#include <filesystem>
#include <regex>
#include <string>
#include <optional>
#include <Shlwapi.h>
#include <wrl/client.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
@@ -15,11 +18,12 @@
#include "../logger/logger.h"
#include "../version/version.h"
namespace package {
namespace package
{
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::Management::Deployment;
using Microsoft::WRL::ComPtr;
inline BOOL IsWin11OrGreater()
{
@@ -46,6 +50,118 @@ namespace package {
dwlConditionMask);
}
struct PACKAGE_VERSION
{
UINT16 Major;
UINT16 Minor;
UINT16 Build;
UINT16 Revision;
};
class ComInitializer
{
public:
explicit ComInitializer(DWORD coInitFlags = COINIT_MULTITHREADED) :
_initialized(false)
{
const HRESULT hr = CoInitializeEx(nullptr, coInitFlags);
_initialized = SUCCEEDED(hr);
}
~ComInitializer()
{
if (_initialized)
{
CoUninitialize();
}
}
bool Succeeded() const { return _initialized; }
private:
bool _initialized;
};
inline bool GetPackageNameAndVersionFromAppx(
const std::wstring& appxPath,
std::wstring& outName,
PACKAGE_VERSION& outVersion)
{
try
{
ComInitializer comInit;
if (!comInit.Succeeded())
{
Logger::error(L"COM initialization failed.");
return false;
}
ComPtr<IAppxFactory> factory;
ComPtr<IStream> stream;
ComPtr<IAppxPackageReader> reader;
ComPtr<IAppxManifestReader> manifest;
ComPtr<IAppxManifestPackageId> packageId;
HRESULT hr = CoCreateInstance(__uuidof(AppxFactory), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));
if (FAILED(hr))
return false;
hr = SHCreateStreamOnFileEx(appxPath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &stream);
if (FAILED(hr))
return false;
hr = factory->CreatePackageReader(stream.Get(), &reader);
if (FAILED(hr))
return false;
hr = reader->GetManifest(&manifest);
if (FAILED(hr))
return false;
hr = manifest->GetPackageId(&packageId);
if (FAILED(hr))
return false;
LPWSTR name = nullptr;
hr = packageId->GetName(&name);
if (FAILED(hr))
return false;
UINT64 version = 0;
hr = packageId->GetVersion(&version);
if (FAILED(hr))
return false;
outName = std::wstring(name);
CoTaskMemFree(name);
outVersion.Major = static_cast<UINT16>((version >> 48) & 0xFFFF);
outVersion.Minor = static_cast<UINT16>((version >> 32) & 0xFFFF);
outVersion.Build = static_cast<UINT16>((version >> 16) & 0xFFFF);
outVersion.Revision = static_cast<UINT16>(version & 0xFFFF);
Logger::info(L"Package name: {}, version: {}.{}.{}.{}, appxPath: {}",
outName,
outVersion.Major,
outVersion.Minor,
outVersion.Build,
outVersion.Revision,
appxPath);
return true;
}
catch (const std::exception& ex)
{
Logger::error(L"Standard exception: {}", winrt::to_hstring(ex.what()));
return false;
}
catch (...)
{
Logger::error(L"Unknown or non-standard exception occurred.");
return false;
}
}
inline std::optional<Package> GetRegisteredPackage(std::wstring packageDisplayName, bool checkVersion)
{
PackageManager packageManager;
@@ -229,6 +345,59 @@ namespace package {
return matchedFiles;
}
inline bool IsPackageSatisfied(const std::wstring& appxPath)
{
std::wstring targetName;
PACKAGE_VERSION targetVersion{};
if (!GetPackageNameAndVersionFromAppx(appxPath, targetName, targetVersion))
{
Logger::error(L"Failed to get package name and version from appx: " + appxPath);
return false;
}
PackageManager pm;
for (const auto& package : pm.FindPackagesForUser({}))
{
const auto& id = package.Id();
if (std::wstring(id.Name()) == targetName)
{
const auto& version = id.Version();
if (version.Major > targetVersion.Major ||
(version.Major == targetVersion.Major && version.Minor > targetVersion.Minor) ||
(version.Major == targetVersion.Major && version.Minor == targetVersion.Minor && version.Build > targetVersion.Build) ||
(version.Major == targetVersion.Major && version.Minor == targetVersion.Minor && version.Build == targetVersion.Build && version.Revision >= targetVersion.Revision))
{
Logger::info(
L"Package {} is already satisfied with version {}.{}.{}.{}; target version {}.{}.{}.{}; appxPath: {}",
id.Name(),
version.Major,
version.Minor,
version.Build,
version.Revision,
targetVersion.Major,
targetVersion.Minor,
targetVersion.Build,
targetVersion.Revision,
appxPath);
return true;
}
}
}
Logger::info(
L"Package {} is not satisfied. Target version: {}.{}.{}.{}; appxPath: {}",
targetName,
targetVersion.Major,
targetVersion.Minor,
targetVersion.Build,
targetVersion.Revision,
appxPath);
return false;
}
inline bool RegisterPackage(std::wstring pkgPath, std::vector<std::wstring> dependencies)
{
try
@@ -238,7 +407,7 @@ namespace package {
PackageManager packageManager;
// Declare use of an external location
DeploymentOptions options = DeploymentOptions::ForceApplicationShutdown;
DeploymentOptions options = DeploymentOptions::ForceTargetApplicationShutdown;
Collections::IVector<Uri> uris = winrt::single_threaded_vector<Uri>();
if (!dependencies.empty())
@@ -247,7 +416,14 @@ namespace package {
{
try
{
uris.Append(Uri(dependency));
if (IsPackageSatisfied(dependency))
{
Logger::info(L"Dependency already satisfied: {}", dependency);
}
else
{
uris.Append(Uri(dependency));
}
}
catch (const winrt::hresult_error& ex)
{
@@ -282,7 +458,6 @@ namespace package {
{
Logger::debug(L"Register {} package started.", pkgPath);
}
}
catch (std::exception& e)
{
@@ -293,4 +468,4 @@ namespace package {
return true;
}
}
}

View File

@@ -20,6 +20,9 @@
<ItemGroup>
<PackageReference Include="MSTest" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>

View File

@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -12,6 +14,12 @@ namespace Microsoft.CmdPal.Ext.TimeDate.Pages;
internal sealed partial class TimeDateExtensionPage : DynamicListPage
{
private readonly Lock _resultsLock = new();
private IList<ListItem> _results = new List<ListItem>();
private bool initialized;
private SettingsManager _settingsManager;
public TimeDateExtensionPage(SettingsManager settingsManager)
@@ -24,20 +32,36 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage
_settingsManager = settingsManager;
}
public override IListItem[] GetItems() => DoExecuteSearch(SearchText).ToArray();
public override IListItem[] GetItems()
{
if (!initialized)
{
DoExecuteSearch(string.Empty);
}
lock (_resultsLock)
{
ListItem[] results = _results.ToArray();
return results;
}
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
if (newSearch == oldSearch)
{
return;
}
DoExecuteSearch(newSearch);
RaiseItemsChanged(0);
}
private List<ListItem> DoExecuteSearch(string query)
private void DoExecuteSearch(string query)
{
try
{
var result = TimeDateCalculator.ExecuteSearch(_settingsManager, query);
return result;
UpdateResult(result);
}
catch (Exception)
{
@@ -51,7 +75,18 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage
ResultHelper.CreateInvalidInputErrorResult(),
};
return items;
UpdateResult(items);
}
}
private void UpdateResult(IList<ListItem> result)
{
lock (_resultsLock)
{
initialized = true;
this._results = result;
}
RaiseItemsChanged(this._results.Count);
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
namespace Microsoft.CmdPal.Ext.WebSearch.Commands;
internal sealed partial class OpenURLCommand : InvokableCommand
{
private readonly SettingsManager _settingsManager;
public string Url { get; internal set; } = string.Empty;
internal OpenURLCommand(string url, SettingsManager settingsManager)
{
Url = url;
BrowserInfo.UpdateIfTimePassed();
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
Name = string.Empty;
_settingsManager = settingsManager;
}
public override CommandResult Invoke()
{
if (!ShellHelpers.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, $"{Url}"))
{
// TODO GH# 138 --> actually display feedback from the extension somewhere.
return CommandResult.KeepOpen();
}
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,88 @@
// 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.Globalization;
using System.Text;
using Microsoft.CmdPal.Ext.WebSearch.Commands;
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
using Microsoft.CmdPal.Ext.WebSearch.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
namespace Microsoft.CmdPal.Ext.WebSearch;
internal sealed partial class FallbackOpenURLItem : FallbackCommandItem
{
private readonly OpenURLCommand _executeItem;
private static readonly CompositeFormat PluginOpenURL = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open_url);
private static readonly CompositeFormat PluginOpenUrlInBrowser = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open_url_in_browser);
public FallbackOpenURLItem(SettingsManager settings)
: base(new OpenURLCommand(string.Empty, settings), string.Empty)
{
_executeItem = (OpenURLCommand)this.Command!;
Title = string.Empty;
_executeItem.Name = string.Empty;
Subtitle = string.Empty;
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
}
public override void UpdateQuery(string query)
{
if (!IsValidUrl(query))
{
Title = string.Empty;
Subtitle = string.Empty;
return;
}
var success = Uri.TryCreate(query, UriKind.Absolute, out var uri);
// if url not contain schema, add http:// by default.
if (!success)
{
query = "https://" + query;
}
_executeItem.Url = query;
_executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.open_in_default_browser;
Title = string.Format(CultureInfo.CurrentCulture, PluginOpenURL, query);
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpenUrlInBrowser, BrowserInfo.Name ?? BrowserInfo.MSEdgeName);
}
public static bool IsValidUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return false;
}
if (!url.Contains('.', StringComparison.OrdinalIgnoreCase))
{
// eg: 'com', 'org'. We don't think it's a valid url.
// This can simplify the logic of checking if the url is valid.
return false;
}
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
{
return true;
}
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
if (Uri.IsWellFormedUriString("https://" + url, UriKind.Absolute))
{
return true;
}
}
return false;
}
}

View File

@@ -195,6 +195,24 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Open &quot;{0}&quot;.
/// </summary>
public static string plugin_open_url {
get {
return ResourceManager.GetString("plugin_open_url", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open url in {0}.
/// </summary>
public static string plugin_open_url_in_browser {
get {
return ResourceManager.GetString("plugin_open_url_in_browser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to open {0}..
/// </summary>

View File

@@ -163,6 +163,12 @@
<data name="plugin_open" xml:space="preserve">
<value>Search the web in {0}</value>
</data>
<data name="plugin_open_url" xml:space="preserve">
<value>Open "{0}"</value>
</data>
<data name="plugin_open_url_in_browser" xml:space="preserve">
<value>Open url in {0}</value>
</data>
<data name="plugin_search_failed" xml:space="preserve">
<value>Failed to open {0}.</value>
</data>

View File

@@ -14,6 +14,7 @@ public partial class WebSearchCommandsProvider : CommandProvider
{
private readonly SettingsManager _settingsManager = new();
private readonly FallbackExecuteSearchItem _fallbackItem;
private readonly FallbackOpenURLItem _openUrlFallbackItem;
public WebSearchCommandsProvider()
{
@@ -23,6 +24,7 @@ public partial class WebSearchCommandsProvider : CommandProvider
Settings = _settingsManager.Settings;
_fallbackItem = new FallbackExecuteSearchItem(_settingsManager);
_openUrlFallbackItem = new FallbackOpenURLItem(_settingsManager);
}
public override ICommandItem[] TopLevelCommands()
@@ -36,5 +38,5 @@ public partial class WebSearchCommandsProvider : CommandProvider
];
}
public override IFallbackCommandItem[]? FallbackCommands() => [_fallbackItem];
public override IFallbackCommandItem[]? FallbackCommands() => [_openUrlFallbackItem, _fallbackItem];
}

View File

@@ -295,7 +295,7 @@
"Areas": [ "AreaEaseOfAccess" ],
"Type": "AppSettingsApp",
"AltNames": [ "TouchFeedback" ],
"Command": "ms-settings:easeofaccess-MousePointer"
"Command": "ms-settings:easeofaccess-mousepointer"
},
{
"Name": "Display",

View File

@@ -16,31 +16,21 @@ public partial class SamplesListPage : ListPage
{
Title = "List Page Sample Command",
Subtitle = "Display a list of items",
Section = "Lists",
},
new ListItem(new SampleListPageWithDetails())
{
Title = "List Page With Details",
Subtitle = "A list of items, each with additional details to display",
Section = "Lists",
},
new ListItem(new SampleUpdatingItemsPage())
{
Title = "List page with items that change",
Subtitle = "The items on the list update themselves in real time",
Section = "Lists",
},
new ListItem(new SampleDynamicListPage())
{
Title = "Dynamic List Page Command",
Subtitle = "Changes the list of items in response to the typed query",
Section = "Lists",
},
new ListItem(new FizzBuzzListPage())
{
Title = "Sections sample",
Subtitle = "Changing list of items, with sections",
Section = "Lists",
},
// Content pages
@@ -48,38 +38,32 @@ public partial class SamplesListPage : ListPage
{
Title = "Sample content page",
Subtitle = "Display mixed forms, markdown, and other types of content",
Section = "Content",
},
new ListItem(new SampleTreeContentPage())
{
Title = "Sample nested content",
Subtitle = "Example of nesting a tree of content",
Section = "Content",
},
new ListItem(new SampleCommentsPage())
{
Title = "Sample of nested comments",
Subtitle = "Demo of using nested trees of content to create a comment thread-like experience",
Icon = new IconInfo("\uE90A"), // Comment
Section = "Content",
},
new ListItem(new SampleMarkdownPage())
{
Title = "Markdown Page Sample Command",
Subtitle = "Display a page of rendered markdown",
Section = "Content",
},
new ListItem(new SampleMarkdownManyBodies())
{
Title = "Markdown with multiple blocks",
Subtitle = "A page with multiple blocks of rendered markdown",
Section = "Content",
},
new ListItem(new SampleMarkdownDetails())
{
Title = "Markdown with details",
Subtitle = "A page with markdown and details",
Section = "Content",
},
// Settings helpers
@@ -87,7 +71,6 @@ public partial class SamplesListPage : ListPage
{
Title = "Sample settings page",
Subtitle = "A demo of the settings helpers",
Section = "Settings",
},
// Evil edge cases
@@ -96,7 +79,6 @@ public partial class SamplesListPage : ListPage
{
Title = "Evil samples",
Subtitle = "Samples designed to break the palette in many different evil ways",
Section = "Debugging",
}
];

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -28,69 +29,69 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
"body": [
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_page_title}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_page_title)}},
"size": "large"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_page_text}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_page_text)}},
"wrap": true
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_name_header}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_header)}},
"weight": "bolder",
"size": "default"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_name_description}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_description)}},
"wrap": true
},
{
"type": "Input.Text",
"label": "{{Properties.Resources.builtin_create_extension_name_label}}",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_label)}},
"isRequired": true,
"errorMessage": "{{Properties.Resources.builtin_create_extension_name_required}}",
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_name_required)}},
"id": "ExtensionName",
"placeholder": "ExtensionName",
"regex": "^[^\\s]+$"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_display_name_header}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_header)}},
"weight": "bolder",
"size": "default"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_display_name_description}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_description)}},
"wrap": true
},
{
"type": "Input.Text",
"label": "{{Properties.Resources.builtin_create_extension_display_name_label}}",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_label)}},
"isRequired": true,
"errorMessage": "{{Properties.Resources.builtin_create_extension_display_name_required}}",
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_display_name_required)}},
"id": "DisplayName",
"placeholder": "My new extension"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_directory_header}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_header)}},
"weight": "bolder",
"size": "default"
},
{
"type": "TextBlock",
"text": "{{Properties.Resources.builtin_create_extension_directory_description}}",
"text": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_description)}},
"wrap": true
},
{
"type": "Input.Text",
"label": "{{Properties.Resources.builtin_create_extension_directory_label}}",
"label": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_label)}},
"isRequired": true,
"errorMessage": "{{Properties.Resources.builtin_create_extension_directory_required}}",
"errorMessage": {{FormatJsonString(Properties.Resources.builtin_create_extension_directory_required)}},
"id": "OutputPath",
"placeholder": "C:\\users\\me\\dev"
}
@@ -98,7 +99,7 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
"actions": [
{
"type": "Action.Submit",
"title": "{{Properties.Resources.builtin_create_extension_submit}}",
"title": {{FormatJsonString(Properties.Resources.builtin_create_extension_submit)}},
"associatedInputs": "auto"
}
]
@@ -192,4 +193,10 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
// Delete the temp dir
Directory.Delete(tempDir, true);
}
private string FormatJsonString(string str)
{
// Escape the string for JSON
return JsonSerializer.Serialize(str);
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ListGroup : ObservableObject
{
public string Key { get; set; } = string.Empty;
[ObservableProperty]
public partial ObservableCollection<ListItemViewModel> Items { get; set; } = [];
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -27,12 +27,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
private ObservableCollection<ListItemViewModel> Items { get; set; } = [];
[ObservableProperty]
public partial bool HasGrouping { get; private set; } = false;
[ObservableProperty]
public partial ObservableCollection<ListGroup> Groups { get; set; } = [];
private readonly ExtensionObject<IListPage> _model;
private readonly Lock _listLock = new();
@@ -254,53 +248,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public void UpdateGroupsIfNeeded()
{
HasGrouping = FilteredItems.Any(i => !string.IsNullOrEmpty(i.Section));
if (HasGrouping)
{
lock (_listLock)
{
if (FilteredItems.Count == 0)
{
Groups.Clear();
return;
}
// get current groups
var groups = FilteredItems.GroupBy(item => item.Section).Select(group => group);
// Remove any groups that no longer exist
foreach (var group in Groups)
{
if (!groups.Any(g => g.Key == group.Key))
{
Groups.Remove(group);
}
}
// Update lists for each existing group
foreach (var group in groups)
{
var existingGroup = Groups.FirstOrDefault(groupItem => groupItem.Key == group.Key);
if (existingGroup == null)
{
// Add a new group if it doesn't exist
Groups.Add(new ListGroup { Key = group.Key, Items = new ObservableCollection<ListItemViewModel>(group) });
existingGroup = Groups.FirstOrDefault(groupItem => groupItem.Key == group.Key);
}
if (existingGroup != null)
{
// Update the existing group
ListHelpers.InPlaceUpdateList(existingGroup.Items, FilteredItems.Where(item => item.Section == group.Key));
}
}
}
}
}
/// <summary>
/// Apply our current filter text to the list of items, and update
/// FilteredItems to match the results.
@@ -540,18 +487,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
FilteredItems.Clear();
foreach (ListGroup group in Groups)
{
foreach (ListItemViewModel item in group.Items)
{
item.SafeCleanup();
}
group.Items.Clear();
}
Groups.Clear();
}
IListPage? model = _model.Unsafe;

View File

@@ -36,6 +36,8 @@ public partial class SettingsModel : ObservableObject
public bool HighlightSearchOnActivate { get; set; } = true;
public bool ShowSystemTrayIcon { get; set; } = true;
public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];
public Dictionary<string, CommandAlias> Aliases { get; set; } = [];

View File

@@ -87,6 +87,16 @@ public partial class SettingsViewModel : INotifyPropertyChanged
}
}
public bool ShowSystemTrayIcon
{
get => _settings.ShowSystemTrayIcon;
set
{
_settings.ShowSystemTrayIcon = value;
Save();
}
}
public ObservableCollection<ProviderSettingsViewModel> CommandProviders { get; } = [];
public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler)

View File

@@ -152,7 +152,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
GenerateId();
UpdateAlias();
FetchAliasFromAliasManager();
UpdateHotkey();
UpdateTags();
}
@@ -163,24 +163,31 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private void HandleChangeAlias()
{
SetAlias(Alias);
SetAlias();
Save();
}
public void SetAlias(CommandAlias? newAlias)
public void SetAlias()
{
_serviceProvider.GetService<AliasManager>()!.UpdateAlias(Id, newAlias);
UpdateAlias();
var commandAlias = Alias is null
? null
: new CommandAlias(Alias.Alias, Alias.CommandId, Alias.IsDirect);
_serviceProvider.GetService<AliasManager>()!.UpdateAlias(Id, commandAlias);
UpdateTags();
}
private void UpdateAlias()
private void FetchAliasFromAliasManager()
{
// Add tags for the alias, if we have one.
var aliases = _serviceProvider.GetService<AliasManager>();
if (aliases != null)
var am = _serviceProvider.GetService<AliasManager>();
if (am != null)
{
Alias = aliases.AliasFromId(Id);
var commandAlias = am.AliasFromId(Id);
if (commandAlias is not null)
{
// Decouple from the alias manager alias object
Alias = new CommandAlias(commandAlias.Alias, commandAlias.CommandId, commandAlias.IsDirect);
}
}
}

View File

@@ -11,7 +11,6 @@
<!-- Other merged dictionaries here -->
<ResourceDictionary Source="Styles/Button.xaml" />
<ResourceDictionary Source="Styles/Colors.xaml" />
<ResourceDictionary Source="Styles/Generic.xaml" />
<ResourceDictionary Source="Styles/TextBox.xaml" />
<ResourceDictionary Source="Styles/Settings.xaml" />
<ResourceDictionary Source="Controls/Tag.xaml" />

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Ext.Apps;
@@ -78,7 +79,7 @@ public partial class App : Application
var cmdArgs = Environment.GetCommandLineArgs();
bool runFromPT = false;
var runFromPT = false;
foreach (var arg in cmdArgs)
{
if (arg == "RunFromPT")
@@ -107,9 +108,6 @@ public partial class App : Application
// Built-in Commands. Order matters - this is the order they'll be presented by default.
var allApps = new AllAppsCommandProvider();
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
services.AddSingleton<ICommandProvider>(allApps);
services.AddSingleton<ICommandProvider, ShellCommandsProvider>();
services.AddSingleton<ICommandProvider, CalculatorCommandProvider>();
@@ -120,7 +118,25 @@ public partial class App : Application
// services.AddSingleton<ICommandProvider, ClipboardHistoryCommandsProvider>();
services.AddSingleton<ICommandProvider, WindowWalkerCommandsProvider>();
services.AddSingleton<ICommandProvider, WebSearchCommandsProvider>();
services.AddSingleton<ICommandProvider>(winget);
// GH #38440: Users might not have WinGet installed! Or they might have
// a ridiculously old version. Or might be running as admin.
// We shouldn't explode in the App ctor if we fail to instantiate an
// instance of PackageManager, which will happen in the static ctor
// for WinGetStatics
try
{
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
services.AddSingleton<ICommandProvider>(winget);
}
catch (Exception ex)
{
Logger.LogError("Couldn't load winget");
Logger.LogError(ex.ToString());
}
services.AddSingleton<ICommandProvider, WindowsTerminalCommandsProvider>();
services.AddSingleton<ICommandProvider, WindowsSettingsCommandsProvider>();
services.AddSingleton<ICommandProvider, RegistryCommandsProvider>();

View File

@@ -105,19 +105,8 @@ public sealed partial class SearchBar : UserControl,
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
if (e.Key == VirtualKey.Down)
{
WeakReferenceMessenger.Default.Send<NavigateNextCommand>();
e.Handled = true;
}
else if (e.Key == VirtualKey.Up)
{
WeakReferenceMessenger.Default.Send<NavigatePreviousCommand>();
e.Handled = true;
}
else if (ctrlPressed && e.Key == VirtualKey.Enter)
if (ctrlPressed && e.Key == VirtualKey.Enter)
{
// ctrl+enter
WeakReferenceMessenger.Default.Send<ActivateSecondaryCommandMessage>();
@@ -197,6 +186,18 @@ public sealed partial class SearchBar : UserControl,
_isBackspaceHeld = true;
}
}
else if (e.Key == VirtualKey.Up)
{
WeakReferenceMessenger.Default.Send<NavigatePreviousCommand>();
e.Handled = true;
}
else if (e.Key == VirtualKey.Down)
{
WeakReferenceMessenger.Default.Send<NavigateNextCommand>();
e.Handled = true;
}
}
private void FilterBox_PreviewKeyUp(object sender, KeyRoutedEventArgs e)

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="Microsoft.CmdPal.UI.ListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -17,16 +17,10 @@
<Page.Resources>
<!-- TODO: Figure out what we want to do here for filtering/grouping and where -->
<!-- https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.collectionviewsource -->
<CollectionViewSource
x:Name="GroupedItemsCVS"
<!--<CollectionViewSource
x:Name="ItemsCVS"
IsSourceGrouped="True"
ItemsPath="Items"
Source="{x:Bind ViewModel.Groups, Mode=OneWay}" />
<CollectionViewSource
x:Name="UngroupedItemsCVS"
IsSourceGrouped="False"
Source="{x:Bind ViewModel.FilteredItems, Mode=OneWay}" />
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
<converters:StringVisibilityConverter
x:Key="StringVisibilityConverter"
@@ -113,74 +107,32 @@
TargetType="x:Boolean"
Value="{x:Bind ViewModel.ShowEmptyContent, Mode=OneWay}">
<controls:Case Value="False">
<controls:SwitchPresenter
HorizontalAlignment="Stretch"
TargetType="x:Boolean"
Value="{x:Bind ViewModel.HasGrouping, Mode=OneWay}">
<controls:Case Value="True">
<ListView
x:Name="GroupedItemsList"
Padding="0,2,0,0"
DoubleTapped="GroupedItemsList_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{Binding ElementName=GroupedItemsCVS, Path=View}"
SelectionChanged="GroupedItemsList_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />
</ListView.ItemContainerTransitions>
<ListView.GroupStyle>
<!--<GroupStyle HeaderContainerStyle="{StaticResource CustomHeaderContainerStyle}" HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Padding="20,12,0,8"
AutomationProperties.AccessibilityView="Raw"
FontSize="14"
FontWeight="SemiBold"
Text="{x:Bind Key}"
Visibility="{x:Bind Key, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>-->
<GroupStyle HeaderContainerStyle="{StaticResource CustomHeaderContainerStyle}" HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="0,16,0,0"
Padding="20,8,0,4"
AutomationProperties.AccessibilityView="Raw"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Key, Mode=OneWay}"
Visibility="{Binding Key, Converter={StaticResource StringVisibilityConverter}}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</controls:Case>
<controls:Case Value="False">
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
DoubleTapped="ItemsList_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{Binding ElementName=UngroupedItemsCVS, Path=View}"
SelectionChanged="ItemsList_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />
</ListView.ItemContainerTransitions>
</ListView>
</controls:Case>
</controls:SwitchPresenter>
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
DoubleTapped="ItemsList_DoubleTapped"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="ItemsList_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
SelectionChanged="ItemsList_SelectionChanged">
<ListView.ItemContainerTransitions>
<TransitionCollection />
</ListView.ItemContainerTransitions>
<!--<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="0,16,0,0"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Key, Mode=OneWay}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>-->
</ListView>
</controls:Case>
<controls:Case Value="True">
<StackPanel

View File

@@ -37,7 +37,6 @@ public sealed partial class ListPage : Page,
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Disabled;
this.ItemsList.Loaded += ItemsList_Loaded;
this.GroupedItemsList.Loaded += GroupedItemsList_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -122,18 +121,6 @@ public sealed partial class ListPage : Page,
}
}
private void GroupedItemsList_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
if (GroupedItemsList.SelectedItem is ListItemViewModel vm)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (!settings.SingleClickActivates)
{
ViewModel?.InvokeItemCommand.Execute(vm);
}
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
@@ -161,24 +148,6 @@ public sealed partial class ListPage : Page,
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
private void GroupedItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (GroupedItemsList.SelectedItem is ListItemViewModel item)
{
var vm = ViewModel;
_ = Task.Run(() =>
{
vm?.UpdateSelectedItemCommand.Execute(item);
});
}
if (GroupedItemsList.SelectedItem != null)
{
GroupedItemsList.ScrollIntoView(GroupedItemsList.SelectedItem);
}
}
private void ItemsList_Loaded(object sender, RoutedEventArgs e)
{
// Find the ScrollViewer in the ListView
@@ -190,17 +159,6 @@ public sealed partial class ListPage : Page,
}
}
private void GroupedItemsList_Loaded(object sender, RoutedEventArgs e)
{
// Find the ScrollViewer in the ListView
var listViewScrollViewer = FindScrollViewer(this.GroupedItemsList);
if (listViewScrollViewer != null)
{
listViewScrollViewer.ViewChanged += ListViewScrollViewer_ViewChanged;
}
}
private void ListViewScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
{
var scrollView = sender as ScrollViewer;
@@ -229,10 +187,9 @@ public sealed partial class ListPage : Page,
{
ItemsList.SelectedIndex++;
}
if (GroupedItemsList.SelectedIndex < GroupedItemsList.Items.Count - 1)
else
{
GroupedItemsList.SelectedIndex++;
ItemsList.SelectedIndex = 0;
}
}
@@ -242,10 +199,9 @@ public sealed partial class ListPage : Page,
{
ItemsList.SelectedIndex--;
}
if (GroupedItemsList.SelectedIndex > 0)
else
{
GroupedItemsList.SelectedIndex--;
ItemsList.SelectedIndex = ItemsList.Items.Count - 1;
}
}
@@ -259,10 +215,6 @@ public sealed partial class ListPage : Page,
{
ViewModel?.InvokeItemCommand.Execute(item);
}
else if (GroupedItemsList.SelectedItem is ListItemViewModel groupedItem)
{
ViewModel?.InvokeItemCommand.Execute(groupedItem);
}
}
public void Receive(ActivateSecondaryCommandMessage message)
@@ -275,10 +227,6 @@ public sealed partial class ListPage : Page,
{
ViewModel?.InvokeSecondaryCommandCommand.Execute(item);
}
else if (GroupedItemsList.SelectedItem is ListItemViewModel groupedItem)
{
ViewModel?.InvokeSecondaryCommandCommand.Execute(groupedItem);
}
}
private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -307,11 +255,6 @@ public sealed partial class ListPage : Page,
// GetItems or a change in the filter.
private void Page_ItemsUpdated(ListViewModel sender, object args)
{
if (ViewModel != null)
{
ViewModel?.UpdateGroupsIfNeeded();
}
// If for some reason, we don't have a selected item, fix that.
//
// It's important to do this here, because once there's no selection
@@ -323,11 +266,6 @@ public sealed partial class ListPage : Page,
{
ItemsList.SelectedIndex = 0;
}
if (GroupedItemsList.SelectedItem == null)
{
GroupedItemsList.SelectedIndex = 0;
}
}
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)

View File

@@ -54,7 +54,6 @@ public sealed partial class MainWindow : Window,
// Notification Area ("Tray") icon data
private NOTIFYICONDATAW? _trayIconData;
private bool _createdIcon;
private DestroyIconSafeHandle? _largeIcon;
private DesktopAcrylicController? _acrylicController;
@@ -99,7 +98,6 @@ public sealed partial class MainWindow : Window,
_hotkeyWndProc = HotKeyPrc;
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_hotkeyWndProc);
_originalWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
AddNotificationIcon();
// Load our settings, and then also wire up a settings changed handler
HotReloadSettings();
@@ -149,6 +147,7 @@ public sealed partial class MainWindow : Window,
var settings = App.Current.Services.GetService<SettingsModel>()!;
SetupHotkey(settings);
SetupTrayIcon(settings.ShowSystemTrayIcon);
// This will prevent our window from appearing in alt+tab or the taskbar.
// You'll _need_ to use the hotkey to summon it.
@@ -299,7 +298,7 @@ public sealed partial class MainWindow : Window,
var extensionService = serviceProvider.GetService<IExtensionService>()!;
extensionService.SignalStopExtensionsAsync();
RemoveNotificationIcon();
RemoveTrayIcon();
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
// Workaround by turning it off before shutdown.
@@ -491,9 +490,9 @@ public sealed partial class MainWindow : Window,
// WM_WINDOWPOSCHANGING which is always received on explorer startup sequence.
case PInvoke.WM_WINDOWPOSCHANGING:
{
if (!_createdIcon)
if (_trayIconData == null)
{
AddNotificationIcon();
SetupTrayIcon();
}
}
@@ -505,7 +504,7 @@ public sealed partial class MainWindow : Window,
{
// Handle the case where explorer.exe restarts.
// Even if we created it before, do it again
AddNotificationIcon();
SetupTrayIcon();
}
else if (uMsg == WM_TRAY_ICON)
{
@@ -525,55 +524,60 @@ public sealed partial class MainWindow : Window,
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
}
private void AddNotificationIcon()
private void SetupTrayIcon(bool? showSystemTrayIcon = null)
{
// We only need to build the tray data once.
if (_trayIconData == null)
if (showSystemTrayIcon ?? App.Current.Services.GetService<SettingsModel>()!.ShowSystemTrayIcon)
{
// We need to stash this handle, so it doesn't clean itself up. If
// explorer restarts, we'll come back through here, and we don't
// really need to re-load the icon in that case. We can just use
// the handle from the first time.
_largeIcon = GetAppIconHandle();
_trayIconData = new NOTIFYICONDATAW()
// We only need to build the tray data once.
if (_trayIconData == null)
{
cbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATAW)),
hWnd = _hwnd,
uID = MY_NOTIFY_ID,
uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP,
uCallbackMessage = WM_TRAY_ICON,
hIcon = (HICON)_largeIcon.DangerousGetHandle(),
szTip = RS_.GetString("AppStoreName"),
};
// We need to stash this handle, so it doesn't clean itself up. If
// explorer restarts, we'll come back through here, and we don't
// really need to re-load the icon in that case. We can just use
// the handle from the first time.
_largeIcon = GetAppIconHandle();
_trayIconData = new NOTIFYICONDATAW()
{
cbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATAW)),
hWnd = _hwnd,
uID = MY_NOTIFY_ID,
uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP,
uCallbackMessage = WM_TRAY_ICON,
hIcon = (HICON)_largeIcon.DangerousGetHandle(),
szTip = RS_.GetString("AppStoreName"),
};
}
var d = (NOTIFYICONDATAW)_trayIconData;
// Add the notification icon
PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d);
}
var d = (NOTIFYICONDATAW)_trayIconData;
// Add the notification icon
if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d))
else
{
_createdIcon = true;
RemoveTrayIcon();
}
}
private void RemoveNotificationIcon()
private void RemoveTrayIcon()
{
if (_trayIconData != null && _createdIcon)
if (_trayIconData != null)
{
var d = (NOTIFYICONDATAW)_trayIconData;
if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, in d))
{
_createdIcon = false;
_trayIconData = null;
}
}
_largeIcon?.Close();
}
private DestroyIconSafeHandle GetAppIconHandle()
{
var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
DestroyIconSafeHandle largeIcon;
DestroyIconSafeHandle smallIcon;
PInvoke.ExtractIconEx(exePath, 0, out largeIcon, out smallIcon, 1);
PInvoke.ExtractIconEx(exePath, 0, out largeIcon, out _, 1);
return largeIcon;
}
}

View File

@@ -13,6 +13,7 @@
<EnableMsixTooling>true</EnableMsixTooling>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<Version>$(CmdPalVersion)</Version>
@@ -72,12 +73,17 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.Json" />
<!-- LOAD BEARING: GeneratePathProperty=true on BOTH the AC dependencies. Don't forget the AdaptiveCardsWorkaround below -->
<PackageReference Include="AdaptiveCards.ObjectModel.WinUI3" GeneratePathProperty="true" />
<PackageReference Include="AdaptiveCards.Rendering.WinUI3" GeneratePathProperty="True" />
<PackageReference Include="AdaptiveCards.Templating" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<!--

View File

@@ -19,6 +19,7 @@ SetFocus
SetActiveWindow
MonitorFromWindow
GetMonitorInfo
GetDpiForMonitor
SHCreateStreamOnFileEx
CoAllowSetForegroundWindow
SHCreateStreamOnFileEx

View File

@@ -68,6 +68,10 @@
<ToggleSwitch IsOn="{x:Bind viewModel.SingleClickActivates, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE75B;}">
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Example 'About' section -->
<TextBlock x:Uid="AboutSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />

View File

@@ -385,4 +385,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="BehaviorSettingsHeader.Text" xml:space="preserve">
<value>Behavior</value>
</data>
<data name="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard.Header" xml:space="preserve">
<value>Show system tray icon</value>
</data>
<data name="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard.Description" xml:space="preserve">
<value>Choose if Command Palette is visible in the system tray</value>
</data>
</root>

View File

@@ -1,29 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Using custom style to remove header divider from list view headers -->
<Style x:Key="CustomHeaderContainerStyle" TargetType="ListViewHeaderItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ListViewHeaderItemThemeFontSize}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="12,8,12,0" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewHeaderItem">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
@@ -13,6 +14,8 @@ using Microsoft.UI.Xaml;
using Windows.Graphics;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.WindowsAndMessaging;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
@@ -44,6 +47,21 @@ public sealed partial class ToastWindow : Window,
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
}
private static double GetScaleFactor(HWND hwnd)
{
try
{
var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
_ = PInvoke.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
return dpiX / 96.0;
}
catch (Exception ex)
{
Logger.LogError($"Failed to get scale factor, error: {ex.Message}");
return 1.0;
}
}
private void PositionCentered()
{
var intSize = new SizeInt32
@@ -51,7 +69,14 @@ public sealed partial class ToastWindow : Window,
Width = Convert.ToInt32(ToastText.ActualWidth),
Height = Convert.ToInt32(ToastText.ActualHeight),
};
AppWindow.Resize(intSize);
var scaleAdjustment = GetScaleFactor(_hwnd);
var scaled = new SizeInt32
{
Width = (int)Math.Round(intSize.Width * scaleAdjustment),
Height = (int)Math.Round(intSize.Height * scaleAdjustment),
};
AppWindow.Resize(scaled);
var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest);
if (displayArea is not null)

View File

@@ -40,7 +40,7 @@ Projects of interest are:
[Initial SDK Spec]: ./doc/initial-sdk-spec/initial-sdk-spec.md
[generic samples]: ./Exts/SamplePagesExtension
[real samples]: .Exts/ProcessMonitorExtension
[real samples]: ./Exts/ProcessMonitorExtension
[real extensions that we've "shipped" already]: https://github.com/zadjii/CmdPalExtensions/blob/main/src/extensions

View File

@@ -189,7 +189,7 @@ public partial class InstallPackageListItem : ListItem
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)
{
if (!ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment", 12))
if (!ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment.WindowsPackageManagerContract", 12))
{
Logger.LogError($"RefreshPackageCatalogAsync isn't available");
e.FakeChangeStatus();

View File

@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation.Metadata;
using WindowsPackageManager.Interop;
namespace Microsoft.CmdPal.Ext.WinGet;
@@ -51,9 +52,12 @@ internal static class WinGetStatics
_storeCatalog,
];
foreach (var catalogReference in AvailableCatalogs)
if (ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment.WindowsPackageManagerContract", 8))
{
catalogReference.PackageCatalogBackgroundUpdateInterval = new(0);
foreach (var catalogReference in AvailableCatalogs)
{
catalogReference.PackageCatalogBackgroundUpdateInterval = new(0);
}
}
// Immediately start the lazy-init of the all packages catalog, but

View File

@@ -1,90 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace SamplePagesExtension;
internal sealed partial class FizzBuzzListPage : ListPage
{
public override string Title => "FizzBuzz Page";
public override IconInfo Icon => new("\uE94C"); // % symbol
public override string Name => "Open";
private readonly List<ListItem> _items;
internal FizzBuzzListPage()
{
var addNewItem = new ListItem(new AnonymousCommand(() =>
{
var c = _items.Count;
var f = c % 3 == 0;
var b = c % 5 == 0;
var s = string.Empty;
if (f)
{
s += "Fizz";
}
if (b)
{
s += "Buzz";
}
_items.Add(new ListItem(new NoOpCommand())
{
Title = $"{c}",
Icon = IconFromIndex(_items.Count),
Section = s,
});
RaiseItemsChanged();
})
{ Result = CommandResult.KeepOpen() })
{
Title = "Add item",
Subtitle = "Each item will be sorted into sections. Add at least three",
Icon = new IconInfo("\uED0E"),
};
_items = [addNewItem];
}
public override IListItem[] GetItems()
{
return _items.ToArray();
}
private IconInfo IconFromIndex(int index)
{
return _icons[index % _icons.Length];
}
private readonly IconInfo[] _icons =
[
new IconInfo("\ue700"),
new IconInfo("\ue701"),
new IconInfo("\ue702"),
new IconInfo("\ue703"),
new IconInfo("\ue704"),
new IconInfo("\ue705"),
new IconInfo("\ue706"),
new IconInfo("\ue707"),
new IconInfo("\ue708"),
new IconInfo("\ue709"),
new IconInfo("\ue70a"),
new IconInfo("\ue70b"),
new IconInfo("\ue70c"),
new IconInfo("\ue70d"),
new IconInfo("\ue70e"),
new IconInfo("\ue70f"),
new IconInfo("\ue710"),
new IconInfo("\ue711"),
new IconInfo("\ue712"),
new IconInfo("\ue713"),
];
}

View File

@@ -222,7 +222,7 @@ namespace ColorPicker.Helpers
public bool HandleEnterPressed()
{
if (!IsColorPickerVisible())
if (!_colorPickerShown)
{
return false;
}
@@ -233,14 +233,13 @@ namespace ColorPicker.Helpers
public bool HandleEscPressed()
{
if (!BlockEscapeKeyClosingColorPickerEditor)
if (!BlockEscapeKeyClosingColorPickerEditor
&& (_colorPickerShown || (_colorEditorWindow != null && _colorEditorWindow.IsActive)))
{
return EndUserSession();
}
else
{
return false;
}
return false;
}
internal void MoveCursor(int xOffset, int yOffset)

View File

@@ -70,17 +70,10 @@ namespace ColorPicker.Keyboard
var virtualCode = e.KeyboardData.VirtualCode;
// ESC pressed
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Escape)
&& e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown
)
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Escape) && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
{
if (_appStateHandler.IsColorPickerVisible()
|| !AppStateHandler.BlockEscapeKeyClosingColorPickerEditor
)
{
e.Handled = _appStateHandler.EndUserSession();
return;
}
e.Handled = _appStateHandler.HandleEscPressed();
return;
}
if ((virtualCode == KeyInterop.VirtualKeyFromKey(Key.Space) || virtualCode == KeyInterop.VirtualKeyFromKey(Key.Enter)) && (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown))

View File

@@ -104,8 +104,10 @@ namespace ColorPicker.Mouse
var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
{
var g = Graphics.FromImage(bmp);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
using (var g = Graphics.FromImage(bmp)) // Ensure Graphics object is disposed
{
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
}
return bmp.GetPixel(0, 0);
}

View File

@@ -20,6 +20,9 @@
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>

View File

@@ -21,6 +21,9 @@
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>

View File

@@ -117,15 +117,15 @@ namespace EditorHelpers
}
if (shortcut.ctrlKey != ModifierKey::Disabled)
{
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetCtrlKey()).c_str()));
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetCtrlKey(ModifierKey::Both)).c_str()));
}
if (shortcut.altKey != ModifierKey::Disabled)
{
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetAltKey()).c_str()));
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetAltKey(ModifierKey::Both)).c_str()));
}
if (shortcut.shiftKey != ModifierKey::Disabled)
{
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetShiftKey()).c_str()));
keys.push_back(winrt::to_hstring(keyboardMap.GetKeyName(shortcut.GetShiftKey(ModifierKey::Both)).c_str()));
}
if (shortcut.actionKey != NULL)
{

View File

@@ -158,13 +158,13 @@ namespace KeyboardEventHandlers
if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
{
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetModifierKeyEvents(targetShortcut, Modifiers(), keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
// Dummy key is not required here since SetModifierKeyEvents will only add key-up events for the modifiers here, and the action key key-up is already sent before it
}
else
{
// Dummy key is not required here since SetModifierKeyEvents will only add key-down events for the modifiers here, and the action key key-down is already sent after it
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetModifierKeyEvents(targetShortcut, Modifiers(), keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
}
}
@@ -371,11 +371,35 @@ namespace KeyboardEventHandlers
// Remember which win key was pressed initially
if (ii.GetVirtualKeyState(VK_RWIN))
{
it->second.winKeyInvoked = ModifierKey::Right;
it->second.modifierKeysInvoked.winKey = ModifierKey::Right;
}
else if (ii.GetVirtualKeyState(VK_LWIN))
{
it->second.winKeyInvoked = ModifierKey::Left;
it->second.modifierKeysInvoked.winKey = ModifierKey::Left;
}
if (ii.GetVirtualKeyState(VK_RCONTROL))
{
it->second.modifierKeysInvoked.ctrlKey = ModifierKey::Right;
}
else if (ii.GetVirtualKeyState(VK_LCONTROL))
{
it->second.modifierKeysInvoked.ctrlKey = ModifierKey::Left;
}
if (ii.GetVirtualKeyState(VK_RSHIFT))
{
it->second.modifierKeysInvoked.shiftKey = ModifierKey::Right;
}
else if (ii.GetVirtualKeyState(VK_LSHIFT))
{
it->second.modifierKeysInvoked.shiftKey = ModifierKey::Left;
}
if (ii.GetVirtualKeyState(VK_RMENU))
{
it->second.modifierKeysInvoked.altKey = ModifierKey::Right;
}
else if (ii.GetVirtualKeyState(VK_LMENU))
{
it->second.modifierKeysInvoked.altKey = ModifierKey::Left;
}
if (isRunProgram)
@@ -450,7 +474,7 @@ namespace KeyboardEventHandlers
{
// key down for all new shortcut keys except the common modifiers
keyEventList = std::vector<INPUT>{};
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
@@ -460,15 +484,15 @@ namespace KeyboardEventHandlers
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Release original shortcut state (release in reverse order of shortcut to be accurate)
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
// Set new shortcut key down state
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
// Modifier state reset might be required for this key depending on the shortcut's action and target modifiers - ex: Win+Caps -> Ctrl+A
if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL)
if (it->first.GetCtrlKey(it->second.modifierKeysInvoked.ctrlKey) == NULL && it->first.GetAltKey(it->second.modifierKeysInvoked.altKey) == NULL && it->first.GetShiftKey(it->second.modifierKeysInvoked.shiftKey) == NULL)
{
Shortcut temp = std::get<Shortcut>(it->second.targetShortcut);
for (auto keys : temp.GetKeyCodes())
@@ -490,7 +514,7 @@ namespace KeyboardEventHandlers
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Release original shortcut state (release in reverse order of shortcut to be accurate)
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Set target key down state
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED)
@@ -499,7 +523,7 @@ namespace KeyboardEventHandlers
}
// Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl
if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL)
if (it->first.GetCtrlKey(it->second.modifierKeysInvoked.ctrlKey) == NULL && it->first.GetAltKey(it->second.modifierKeysInvoked.altKey) == NULL && it->first.GetShiftKey(it->second.modifierKeysInvoked.shiftKey) == NULL)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), data->lParam->vkCode);
}
@@ -512,7 +536,7 @@ namespace KeyboardEventHandlers
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Release original shortcut state (release in reverse order of shortcut to be accurate)
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetTextKeyEvents(keyEventList, remapping);
}
@@ -614,12 +638,12 @@ namespace KeyboardEventHandlers
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode);
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode);
if (!isAltRightKeyInvoked)
{
// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate its own key message
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut), data->lParam->vkCode);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut), data->lParam->vkCode);
}
else
{
@@ -643,7 +667,7 @@ namespace KeyboardEventHandlers
if (!isAltRightKeyInvoked)
{
// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate its own key message
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
}
else
{
@@ -656,7 +680,7 @@ namespace KeyboardEventHandlers
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.modifierKeysInvoked.Reset();
it->second.isOriginalActionKeyPressed = false;
// If app specific shortcut has finished invoking, reset the target application
@@ -719,14 +743,14 @@ namespace KeyboardEventHandlers
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Release new shortcut state (release in reverse order of shortcut to be accurate)
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
// Set old shortcut key down state
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.modifierKeysInvoked.Reset();
it->second.isOriginalActionKeyPressed = false;
// If app specific shortcut has finished invoking, reset the target application
@@ -763,7 +787,7 @@ namespace KeyboardEventHandlers
if (!isAltRightKeyInvoked)
{
// Set original shortcut key down state except the action key
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Shift+Win+A and release A, since Win will be pressed here we need to send a dummy event after it
@@ -773,7 +797,7 @@ namespace KeyboardEventHandlers
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.modifierKeysInvoked.Reset();
it->second.isOriginalActionKeyPressed = false;
}
@@ -795,7 +819,7 @@ namespace KeyboardEventHandlers
if (remapToShortcut)
{
// Modifier state reset might be required for this key depending on the target shortcut action key - ex: Ctrl+A -> Win+Caps
if (std::get<Shortcut>(it->second.targetShortcut).GetCtrlKey() == NULL && std::get<Shortcut>(it->second.targetShortcut).GetAltKey() == NULL && std::get<Shortcut>(it->second.targetShortcut).GetShiftKey() == NULL)
if (std::get<Shortcut>(it->second.targetShortcut).GetCtrlKey(it->second.modifierKeysInvoked.ctrlKey) == NULL && std::get<Shortcut>(it->second.targetShortcut).GetAltKey(it->second.modifierKeysInvoked.altKey) == NULL && std::get<Shortcut>(it->second.targetShortcut).GetShiftKey(it->second.modifierKeysInvoked.shiftKey) == NULL)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, std::get<Shortcut>(it->second.targetShortcut).GetActionKey());
}
@@ -817,7 +841,7 @@ namespace KeyboardEventHandlers
if (remapToShortcut)
{
// Modifier state reset might be required for this key depending on the target shortcut action key - ex: Ctrl+A -> Win+Caps, Shift is pressed. System should not see Shift and Caps pressed together
if (std::get<Shortcut>(it->second.targetShortcut).GetCtrlKey() == NULL && std::get<Shortcut>(it->second.targetShortcut).GetAltKey() == NULL && std::get<Shortcut>(it->second.targetShortcut).GetShiftKey() == NULL)
if (std::get<Shortcut>(it->second.targetShortcut).GetCtrlKey(it->second.modifierKeysInvoked.ctrlKey) == NULL && std::get<Shortcut>(it->second.targetShortcut).GetAltKey(it->second.modifierKeysInvoked.altKey) == NULL && std::get<Shortcut>(it->second.targetShortcut).GetShiftKey(it->second.modifierKeysInvoked.shiftKey) == NULL)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, std::get<Shortcut>(it->second.targetShortcut).GetActionKey());
}
@@ -837,7 +861,7 @@ namespace KeyboardEventHandlers
DWORD to = std::get<0>(newRemapping.targetShortcut);
if (!isAltRightKeyInvoked)
{
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetModifierKeyEvents(from, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
{
@@ -851,7 +875,7 @@ namespace KeyboardEventHandlers
Shortcut to = std::get<Shortcut>(newRemapping.targetShortcut);
if (!isAltRightKeyInvoked)
{
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
Helpers::SetModifierKeyEvents(from, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
}
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
{
@@ -860,21 +884,11 @@ namespace KeyboardEventHandlers
}
if (!isAltRightKeyInvoked)
{
Helpers::SetModifierKeyEvents(to, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, from);
Helpers::SetModifierKeyEvents(to, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, from);
}
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(to.actionKey), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
newRemapping.isShortcutInvoked = true;
}
// Remember which win key was pressed initially
if (ii.GetVirtualKeyState(VK_RWIN))
{
newRemapping.winKeyInvoked = ModifierKey::Right;
}
else if (ii.GetVirtualKeyState(VK_LWIN))
{
newRemapping.winKeyInvoked = ModifierKey::Left;
}
}
else
{
@@ -888,10 +902,10 @@ namespace KeyboardEventHandlers
}
if (!isAltRightKeyInvoked)
{
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
// Set old shortcut key down state
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
}
// key down for original shortcut action key with shortcut flag so that we don't invoke the same shortcut remap again
@@ -910,7 +924,7 @@ namespace KeyboardEventHandlers
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.modifierKeysInvoked.Reset();
it->second.isOriginalActionKeyPressed = false;
}
@@ -960,7 +974,7 @@ namespace KeyboardEventHandlers
if (!isAltRightKeyInvoked)
{
// Set original shortcut key down state
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
// Send the original action key only if it is physically pressed. For remappings to keys other than disabled we already check earlier that it is not pressed in this scenario. For remap to disable
@@ -979,7 +993,7 @@ namespace KeyboardEventHandlers
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.modifierKeysInvoked.Reset();
it->second.isOriginalActionKeyPressed = false;
}

View File

@@ -145,15 +145,15 @@ std::wstring GetShortcutHumanReadableString(Shortcut const& shortcut, LayoutMap&
}
if (shortcut.ctrlKey != ModifierKey::Disabled)
{
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetCtrlKey()) + L" + ";
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetCtrlKey(ModifierKey::Both)) + L" + ";
}
if (shortcut.altKey != ModifierKey::Disabled)
{
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetAltKey()) + L" + ";
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetAltKey(ModifierKey::Both)) + L" + ";
}
if (shortcut.shiftKey != ModifierKey::Disabled)
{
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetShiftKey()) + L" + ";
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.GetShiftKey(ModifierKey::Both)) + L" + ";
}
if (shortcut.actionKey != NULL)
{

View File

@@ -262,27 +262,27 @@ namespace Helpers
}
// Function to set key events for modifier keys: When shortcutToCompare is passed (non-empty shortcut), then the key event is sent only if both shortcut's don't have the same modifier key. When keyToBeReleased is passed (non-NULL), then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare, const DWORD& keyToBeReleased)
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const Modifiers& modifiersKeys, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare, const DWORD& keyToBeReleased)
{
// If key down is to be sent, send in the order Win, Ctrl, Alt, Shift
if (isKeyDown)
{
// If shortcutToCompare is non-empty, then the key event is sent only if both shortcut's don't have the same modifier key. If keyToBeReleased is non-NULL, then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
if (shortcutToBeSent.GetWinKey(winKeyInvoked) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(winKeyInvoked) != shortcutToCompare.GetWinKey(winKeyInvoked)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckWinKey(keyToBeReleased)))
if (shortcutToBeSent.GetWinKey(modifiersKeys.winKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(modifiersKeys.winKey) != shortcutToCompare.GetWinKey(modifiersKeys.winKey)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckWinKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), 0, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(modifiersKeys.winKey)), 0, extraInfoFlag);
}
if (shortcutToBeSent.GetCtrlKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey() != shortcutToCompare.GetCtrlKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
if (shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey) != shortcutToCompare.GetCtrlKey(modifiersKeys.ctrlKey)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), 0, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey)), 0, extraInfoFlag);
}
if (shortcutToBeSent.GetAltKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey() != shortcutToCompare.GetAltKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckAltKey(keyToBeReleased)))
if (shortcutToBeSent.GetAltKey(modifiersKeys.altKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey(modifiersKeys.altKey) != shortcutToCompare.GetAltKey(modifiersKeys.altKey)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckAltKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), 0, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey(modifiersKeys.altKey)), 0, extraInfoFlag);
}
if (shortcutToBeSent.GetShiftKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey() != shortcutToCompare.GetShiftKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
if (shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey) != shortcutToCompare.GetShiftKey(modifiersKeys.shiftKey)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), 0, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey)), 0, extraInfoFlag);
}
}
@@ -290,21 +290,21 @@ namespace Helpers
else
{
// If shortcutToCompare is non-empty, then the key event is sent only if both shortcut's don't have the same modifier key. If keyToBeReleased is non-NULL, then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
if (shortcutToBeSent.GetShiftKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey() != shortcutToCompare.GetShiftKey() || shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
if (shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey) != shortcutToCompare.GetShiftKey(modifiersKeys.shiftKey) || shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), KEYEVENTF_KEYUP, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey(modifiersKeys.shiftKey)), KEYEVENTF_KEYUP, extraInfoFlag);
}
if (shortcutToBeSent.GetAltKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey() != shortcutToCompare.GetAltKey() || shortcutToBeSent.CheckAltKey(keyToBeReleased)))
if (shortcutToBeSent.GetAltKey(modifiersKeys.altKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey(modifiersKeys.altKey) != shortcutToCompare.GetAltKey(modifiersKeys.altKey) || shortcutToBeSent.CheckAltKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), KEYEVENTF_KEYUP, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey(modifiersKeys.altKey)), KEYEVENTF_KEYUP, extraInfoFlag);
}
if (shortcutToBeSent.GetCtrlKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey() != shortcutToCompare.GetCtrlKey() || shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
if (shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey) != shortcutToCompare.GetCtrlKey(modifiersKeys.ctrlKey) || shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), KEYEVENTF_KEYUP, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey(modifiersKeys.ctrlKey)), KEYEVENTF_KEYUP, extraInfoFlag);
}
if (shortcutToBeSent.GetWinKey(winKeyInvoked) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(winKeyInvoked) != shortcutToCompare.GetWinKey(winKeyInvoked) || shortcutToBeSent.CheckWinKey(keyToBeReleased)))
if (shortcutToBeSent.GetWinKey(modifiersKeys.winKey) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(modifiersKeys.winKey) != shortcutToCompare.GetWinKey(modifiersKeys.winKey) || shortcutToBeSent.CheckWinKey(keyToBeReleased)))
{
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), KEYEVENTF_KEYUP, extraInfoFlag);
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(modifiersKeys.winKey)), KEYEVENTF_KEYUP, extraInfoFlag);
}
}
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Shortcut.h"
#include "RemapShortcut.h"
class LayoutMap;
@@ -47,7 +48,8 @@ namespace Helpers
std::wstring GetCurrentApplication(bool keepPath);
// Function to set key events for modifier keys: When shortcutToCompare is passed (non-empty shortcut), then the key event is sent only if both shortcut's don't have the same modifier key. When keyToBeReleased is passed (non-NULL), then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare = Shortcut(), const DWORD& keyToBeReleased = NULL);
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const Modifiers& modifiersKeys, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare = Shortcut(), const DWORD& keyToBeReleased = NULL);
// Function to filter the key codes for artificial key codes
int32_t FilterArtificialKeys(const int32_t& key);

View File

@@ -54,6 +54,7 @@
<ClInclude Include="InputInterface.h" />
<ClInclude Include="Helpers.h" />
<ClInclude Include="KeyboardManagerConstants.h" />
<ClInclude Include="Modifiers.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="RemapShortcut.h" />
<ClInclude Include="Shortcut.h" />

View File

@@ -65,6 +65,9 @@
<ClInclude Include="MappingConfiguration.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Modifiers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -0,0 +1,26 @@
#pragma once
#include "ModifierKey.h"
#include <vector>
class Modifiers
{
public:
ModifierKey winKey = ModifierKey::Disabled;
ModifierKey ctrlKey = ModifierKey::Disabled;
ModifierKey altKey = ModifierKey::Disabled;
ModifierKey shiftKey = ModifierKey::Disabled;
void Reset()
{
winKey = ModifierKey::Disabled;
ctrlKey = ModifierKey::Disabled;
altKey = ModifierKey::Disabled;
shiftKey = ModifierKey::Disabled;
}
inline bool operator==(const Modifiers& other) const
{
return winKey == other.winKey && ctrlKey == other.ctrlKey && altKey == other.altKey && shiftKey == other.shiftKey;
}
};

View File

@@ -1,6 +1,8 @@
#pragma once
#include "Shortcut.h"
#include "Modifiers.h"
#include <variant>
#include <vector>
// This class stores all the variables associated with each shortcut remapping
class RemapShortcut
@@ -8,23 +10,24 @@ class RemapShortcut
public:
KeyShortcutTextUnion targetShortcut;
bool isShortcutInvoked;
ModifierKey winKeyInvoked;
Modifiers modifierKeysInvoked;
// This bool value is only required for remapping shortcuts to Disable
bool isOriginalActionKeyPressed;
RemapShortcut(const KeyShortcutTextUnion& sc) :
targetShortcut(sc), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled), isOriginalActionKeyPressed(false)
targetShortcut(sc), isShortcutInvoked(false), isOriginalActionKeyPressed(false)
{
}
RemapShortcut() :
targetShortcut(Shortcut()), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled), isOriginalActionKeyPressed(false)
targetShortcut(Shortcut()), isShortcutInvoked(false), isOriginalActionKeyPressed(false)
{
}
inline bool operator==(const RemapShortcut& sc) const
{
return targetShortcut == sc.targetShortcut && isShortcutInvoked == sc.isShortcutInvoked && winKeyInvoked == sc.winKeyInvoked;
return targetShortcut == sc.targetShortcut && isShortcutInvoked == sc.isShortcutInvoked && modifierKeysInvoked == sc.modifierKeysInvoked;
}
bool RemapToKey()

View File

@@ -185,7 +185,7 @@ DWORD Shortcut::GetWinKey(const ModifierKey& input) const
}
// Function to return the virtual key code of the ctrl key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD Shortcut::GetCtrlKey() const
DWORD Shortcut::GetCtrlKey(const ModifierKey& input) const
{
if (ctrlKey == ModifierKey::Disabled)
{
@@ -201,12 +201,20 @@ DWORD Shortcut::GetCtrlKey() const
}
else
{
if (input == ModifierKey::Right)
{
return VK_RCONTROL;
}
if (input == ModifierKey::Left)
{
return VK_LCONTROL;
}
return VK_CONTROL;
}
}
// Function to return the virtual key code of the alt key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD Shortcut::GetAltKey() const
DWORD Shortcut::GetAltKey(const ModifierKey& input) const
{
if (altKey == ModifierKey::Disabled)
{
@@ -220,6 +228,14 @@ DWORD Shortcut::GetAltKey() const
{
return VK_RMENU;
}
if (input == ModifierKey::Right)
{
return VK_RMENU;
}
else if (input == ModifierKey::Left || input == ModifierKey::Disabled)
{
return VK_LMENU;
}
else
{
return VK_MENU;
@@ -227,7 +243,7 @@ DWORD Shortcut::GetAltKey() const
}
// Function to return the virtual key code of the shift key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD Shortcut::GetShiftKey() const
DWORD Shortcut::GetShiftKey(const ModifierKey& input) const
{
if (shiftKey == ModifierKey::Disabled)
{
@@ -243,6 +259,14 @@ DWORD Shortcut::GetShiftKey() const
}
else
{
if (input == ModifierKey::Right)
{
return VK_RSHIFT;
}
if (input == ModifierKey::Left)
{
return VK_LSHIFT;
}
return VK_SHIFT;
}
}
@@ -493,15 +517,15 @@ winrt::hstring Shortcut::ToHstringVK() const
}
if (ctrlKey != ModifierKey::Disabled)
{
output = output + winrt::to_hstring(static_cast<unsigned int>(GetCtrlKey())) + winrt::to_hstring(L";");
output = output + winrt::to_hstring(static_cast<unsigned int>(GetCtrlKey(ModifierKey::Both))) + winrt::to_hstring(L";");
}
if (altKey != ModifierKey::Disabled)
{
output = output + winrt::to_hstring(static_cast<unsigned int>(GetAltKey())) + winrt::to_hstring(L";");
output = output + winrt::to_hstring(static_cast<unsigned int>(GetAltKey(ModifierKey::Both))) + winrt::to_hstring(L";");
}
if (shiftKey != ModifierKey::Disabled)
{
output = output + winrt::to_hstring(static_cast<unsigned int>(GetShiftKey())) + winrt::to_hstring(L";");
output = output + winrt::to_hstring(static_cast<unsigned int>(GetShiftKey(ModifierKey::Both))) + winrt::to_hstring(L";");
}
if (actionKey != NULL)
{
@@ -531,15 +555,15 @@ std::vector<DWORD> Shortcut::GetKeyCodes()
}
if (ctrlKey != ModifierKey::Disabled)
{
keys.push_back(GetCtrlKey());
keys.push_back(GetCtrlKey(ModifierKey::Both));
}
if (altKey != ModifierKey::Disabled)
{
keys.push_back(GetAltKey());
keys.push_back(GetAltKey(ModifierKey::Both));
}
if (shiftKey != ModifierKey::Disabled)
{
keys.push_back(GetShiftKey());
keys.push_back(GetShiftKey(ModifierKey::Both));
}
if (actionKey != NULL)
{

View File

@@ -4,7 +4,6 @@
#include <compare>
#include <tuple>
#include <variant>
namespace KeyboardManagerInput
{
class InputInterface;
@@ -142,13 +141,13 @@ public:
DWORD GetWinKey(const ModifierKey& input) const;
// Function to return the virtual key code of the ctrl key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD GetCtrlKey() const;
DWORD GetCtrlKey(const ModifierKey& input) const;
// Function to return the virtual key code of the alt key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD GetAltKey() const;
DWORD GetAltKey(const ModifierKey& input) const;
// Function to return the virtual key code of the shift key state expected in the shortcut. Return NULL if it is not a part of the shortcut
DWORD GetShiftKey() const;
DWORD GetShiftKey(const ModifierKey& input) const;
// Function to check if the input key matches the win key expected in the shortcut
bool CheckWinKey(const DWORD input) const;

View File

@@ -82,7 +82,7 @@ namespace Microsoft.Plugin.Program.Programs
public ApplicationType AppType { get; set; }
// Wrappers for File Operations
public static IFileVersionInfoWrapper FileVersionInfoWrapper { get; set; } = new FileVersionInfoWrapper();
public static IFileVersionInfoWrapper FileVersionInfoWrapper { get; set; } = new Wox.Infrastructure.FileSystemHelper.FileVersionInfoWrapper();
public static IFile FileWrapper { get; set; } = new FileSystem().File;

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.PowerToys.Run.Plugin.TimeDate.Components;
@@ -23,7 +24,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
var result = settings?.Length;
// Assert
Assert.AreEqual(6, result);
Assert.AreEqual(7, result);
}
[DataTestMethod]
@@ -33,6 +34,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
[DataRow("TimeWithSeconds")]
[DataRow("DateWithWeekday")]
[DataRow("HideNumberMessageOnGlobalQuery")]
[DataRow("CustomFormats")]
public void DoesSettingExist(string name)
{
// Setup
@@ -78,5 +80,20 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
// Assert
Assert.AreEqual(valueExpected, result);
}
[DataTestMethod]
[DataRow("CustomFormats")]
public void DefaultEmptyMultilineTextValues(string name)
{
// Setup
TimeDateSettings setting = TimeDateSettings.Instance;
// Act
PropertyInfo propertyInfo = setting?.GetType()?.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
List<string> result = (List<string>)propertyInfo?.GetValue(setting);
// Assert
Assert.AreEqual(0, result.Count);
}
}
}

View File

@@ -54,11 +54,11 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
[DataTestMethod]
[DataRow("(time", 18)]
[DataRow("(date", 26)]
[DataRow("(year", 7)]
[DataRow("(now", 32)]
[DataRow("(current", 32)]
[DataRow("(", 32)]
[DataRow("(date", 28)]
[DataRow("(year", 8)]
[DataRow("(now", 34)]
[DataRow("(current", 34)]
[DataRow("(", 34)]
[DataRow("(now::10:10:10", 1)] // Windows file time
[DataRow("(current::10:10:10", 0)]
public void CountWithPluginKeyword(string typedString, int expectedResultCount)
@@ -140,6 +140,8 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
[DataRow("(week day", "Day of the week (Week day) -")]
[DataRow("(cal week", "Week of the year (Calendar week, Week number) -")]
[DataRow("(week num", "Week of the year (Calendar week, Week number) -")]
[DataRow("(days in mo", "Days in month -")]
[DataRow("(Leap y", "Leap year -")]
public void CanFindFormatResult(string typedString, string expectedResult)
{
// Setup

View File

@@ -41,10 +41,29 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
[DataRow("ums-10456", true, "", "")] // Value is UTC and can be different based on system
[DataRow("ums+10456", true, "", "")] // Value is UTC and can be different based on system
[DataRow("ft10456", true, "", "")] // Value is UTC and can be different based on system
[DataRow("oa-657434.99999999", true, "G", "1/1/0100 11:59:59 PM")]
[DataRow("oa2958465.99999999", true, "G", "12/31/9999 11:59:59 PM")]
[DataRow("oa-657435", false, "", "")] // Value to low
[DataRow("oa2958466", false, "", "")] // Value to large
[DataRow("exc1.99998843", true, "G", "1/1/1900 11:59:59 PM")]
[DataRow("exc59.99998843", true, "G", "2/28/1900 11:59:59 PM")]
[DataRow("exc61", true, "G", "3/1/1900 12:00:00 AM")]
[DataRow("exc62.99998843", true, "G", "3/2/1900 11:59:59 PM")]
[DataRow("exc2958465.99998843", true, "G", "12/31/9999 11:59:59 PM")]
[DataRow("exc0", false, "", "")] // Day 0 means in Excel 0/1/1900 and this is a fake date.
[DataRow("exc0.99998843", false, "", "")] // Day 0 means in Excel 0/1/1900 and this is a fake date.
[DataRow("exc60.99998843", false, "", "")] // Day 60 means in Excel 2/29/1900 and this is a fake date in Excel which we cannot support.
[DataRow("exc60", false, "", "")] // Day 60 means in Excel 2/29/1900 and this is a fake date in Excel which we cannot support.
[DataRow("exc-1", false, "", "")] // Value to low
[DataRow("exc2958466", false, "", "")] // Value to large
[DataRow("exf0.99998843", true, "G", "1/1/1904 11:59:59 PM")]
[DataRow("exf2957003.99998843", true, "G", "12/31/9999 11:59:59 PM")]
[DataRow("exf-0.5", false, "", "")] // Value to low
[DataRow("exf2957004", false, "", "")] // Value to large
public void ConvertStringToDateTime(string typedString, bool expectedBool, string stringType, string expectedString)
{
// Act
bool boolResult = TimeAndDateHelper.ParseStringAsDateTime(in typedString, out DateTime result);
bool boolResult = TimeAndDateHelper.ParseStringAsDateTime(in typedString, out DateTime result, out string _);
// Assert
Assert.AreEqual(expectedBool, boolResult);

View File

@@ -13,6 +13,19 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
[TestClass]
public class TimeAndDateHelperTests
{
private CultureInfo originalCulture;
private CultureInfo originalUiCulture;
[TestInitialize]
public void Setup()
{
// Set culture to 'en-us'
originalCulture = CultureInfo.CurrentCulture;
CultureInfo.CurrentCulture = new CultureInfo("en-us", false);
originalUiCulture = CultureInfo.CurrentUICulture;
CultureInfo.CurrentUICulture = new CultureInfo("en-us", false);
}
[DataTestMethod]
[DataRow(-1, null)] // default setting
[DataRow(0, CalendarWeekRule.FirstDay)]
@@ -62,5 +75,103 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
Assert.AreEqual(valueExpected, result);
}
}
[DataTestMethod]
[DataRow(0, "12/30/1899 12:00 PM", 0.5)] // OLE Automation date
[DataRow(1, "12/31/1898 12:00 PM", null)] // Excel based 1900: Date to low
[DataRow(1, "1/1/1900, 00:00 AM", 1.0)] // Excel based 1900
[DataRow(2, "12/31/1898 12:00 PM", null)] // Excel based 1904: Date to low
[DataRow(2, "1/1/1904, 00:00 AM", 0.0)] // Excel based 1904
public void ConvertToOADateFormat(int type, string date, double? valueExpected)
{
// Act
DateTime dt = DateTime.Parse(date, DateTimeFormatInfo.CurrentInfo);
// Assert
if (valueExpected == null)
{
Assert.ThrowsException<ArgumentOutOfRangeException>(() => TimeAndDateHelper.ConvertToOleAutomationFormat(dt, (OADateFormats)type));
}
else
{
var result = TimeAndDateHelper.ConvertToOleAutomationFormat(dt, (OADateFormats)type);
Assert.AreEqual(valueExpected, result);
}
}
[DataTestMethod]
[DataRow("dow")]
[DataRow("\\DOW")]
[DataRow("wom")]
[DataRow("\\WOM")]
[DataRow("woy")]
[DataRow("\\WOY")]
[DataRow("eab")]
[DataRow("\\EAB")]
[DataRow("wft")]
[DataRow("\\WFT")]
[DataRow("uxt")]
[DataRow("\\UXT")]
[DataRow("ums")]
[DataRow("\\UMS")]
[DataRow("oad")]
[DataRow("\\OAD")]
[DataRow("exc")]
[DataRow("\\EXC")]
[DataRow("exf")]
[DataRow("\\EXF")]
[DataRow("My super Test String with \\EXC pattern.")]
public void CustomFormatIgnoreInvalidPattern(string format)
{
// Act
string result = TimeAndDateHelper.ConvertToCustomFormat(DateTime.Now, 0, 0, 1, "AD", format, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
// Assert
Assert.AreEqual(format, result);
}
[DataTestMethod]
[DataRow("DOW")]
[DataRow("DIM")]
[DataRow("WOM")]
[DataRow("WOY")]
[DataRow("EAB")]
[DataRow("WFT")]
[DataRow("UXT")]
[DataRow("UMS")]
[DataRow("OAD")]
[DataRow("EXC")]
[DataRow("EXF")]
public void CustomFormatReplacesValidPattern(string format)
{
// Act
string result = TimeAndDateHelper.ConvertToCustomFormat(DateTime.Now, 0, 0, 1, "AD", format, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
// Assert
Assert.IsFalse(result.Contains(format, StringComparison.CurrentCulture));
}
[DataTestMethod]
[DataRow("01/01/0001", 1)] // First possible date
[DataRow("12/31/9999", 5)] // Last possible date
[DataRow("03/20/2025", 4)]
[DataRow("09/01/2025", 1)] // First day in month is first day of week
[DataRow("03/03/2025", 2)] // First monday is in second week
public void GetWeekOfMonth(string date, int week)
{
// Act
int result = TimeAndDateHelper.GetWeekOfMonth(DateTime.Parse(date, CultureInfo.GetCultureInfo("en-us")), DayOfWeek.Monday);
// Assert
Assert.AreEqual(result, week);
}
[TestCleanup]
public void CleanUp()
{
// Set culture to original value
CultureInfo.CurrentCulture = originalCulture;
CultureInfo.CurrentUICulture = originalUiCulture;
}
}
}

View File

@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
{
internal class AvailableResult
internal sealed class AvailableResult
{
/// <summary>
/// Gets or sets the time/date value
@@ -41,6 +41,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
ResultIconType.Time => $"Images\\time.{theme}.png",
ResultIconType.Date => $"Images\\calendar.{theme}.png",
ResultIconType.DateTime => $"Images\\timeDate.{theme}.png",
ResultIconType.Error => $"Images\\Warning.{theme}.png",
_ => string.Empty,
};
}
@@ -51,5 +52,6 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
Time,
Date,
DateTime,
Error,
}
}

View File

@@ -6,7 +6,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
@@ -72,6 +72,86 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
string era = DateTimeFormatInfo.CurrentInfo.GetEraName(calendar.GetEra(dateTimeNow));
string eraShort = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedEraName(calendar.GetEra(dateTimeNow));
// Custom formats
foreach (string f in TimeDateSettings.Instance.CustomFormats)
{
string[] formatParts = f.Split("=", 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
string formatSyntax = formatParts.Length == 2 ? formatParts[1] : string.Empty;
string searchTags = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagCustom");
DateTime dtObject = dateTimeNow;
// If Length = 0 then empty string.
if (formatParts.Length >= 1)
{
try
{
// Verify and check input and update search tags
if (formatParts.Length == 1)
{
throw new FormatException("Format syntax part after equal sign is missing.");
}
bool containsCustomSyntax = TimeAndDateHelper.StringContainsCustomFormatSyntax(formatSyntax);
if (formatSyntax.StartsWith("UTC:", StringComparison.InvariantCulture))
{
searchTags = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagCustomUtc");
dtObject = dateTimeNowUtc;
}
// Get formated date
var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, Regex.Replace(formatSyntax, "^UTC:", string.Empty), firstWeekRule, firstDayOfTheWeek);
try
{
value = dtObject.ToString(value, CultureInfo.CurrentCulture);
}
catch
{
if (!containsCustomSyntax)
{
throw;
}
else
{
// Do not fail as we have custom format syntax. Instead fix backslashes.
value = Regex.Replace(value, @"(?<!\\)\\", string.Empty).Replace("\\\\", "\\");
}
}
// Add result
results.Add(new AvailableResult()
{
Value = value,
Label = formatParts[0],
AlternativeSearchTag = searchTags,
IconType = ResultIconType.DateTime,
});
}
catch (ArgumentOutOfRangeException e)
{
Wox.Plugin.Logger.Log.Exception($"Failed to convert into custom format {formatParts[0]}: {formatSyntax}", e, typeof(AvailableResultsList));
results.Add(new AvailableResult()
{
Value = Resources.Microsoft_plugin_timedate_ErrorConvertCustomFormat + " " + e.Message,
Label = formatParts[0],
AlternativeSearchTag = searchTags,
IconType = ResultIconType.Error,
});
}
catch (Exception e)
{
Wox.Plugin.Logger.Log.Exception($"Failed to convert into custom format {formatParts[0]}: {formatSyntax}", e, typeof(AvailableResultsList));
results.Add(new AvailableResult()
{
Value = Resources.Microsoft_plugin_timedate_InvalidCustomFormat + " " + formatSyntax,
Label = formatParts[0],
AlternativeSearchTag = searchTags,
IconType = ResultIconType.Error,
});
}
}
}
// Predefined formats
results.AddRange(new[]
{
new AvailableResult()
@@ -152,6 +232,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
IconType = ResultIconType.Date,
},
new AvailableResult()
{
Value = DateTime.DaysInMonth(dateTimeNow.Year, dateTimeNow.Month).ToString(CultureInfo.CurrentCulture),
Label = Resources.Microsoft_plugin_timedate_DaysInMonth,
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
IconType = ResultIconType.Date,
},
new AvailableResult()
{
Value = dateTimeNow.DayOfYear.ToString(CultureInfo.CurrentCulture),
Label = Resources.Microsoft_plugin_timedate_DayOfYear,
@@ -201,6 +288,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
IconType = ResultIconType.Date,
},
new AvailableResult()
{
Value = DateTime.IsLeapYear(dateTimeNow.Year) ? Resources.Microsoft_plugin_timedate_LeapYear : Resources.Microsoft_plugin_timedate_NoLeapYear,
Label = Resources.Microsoft_plugin_timedate_LeapYear,
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
IconType = ResultIconType.Date,
},
new AvailableResult()
{
Value = era,
Label = Resources.Microsoft_plugin_timedate_Era,
@@ -221,13 +315,31 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
IconType = ResultIconType.Date,
},
new AvailableResult()
});
try
{
results.Add(new AvailableResult()
{
Value = dateTimeNow.ToFileTime().ToString(CultureInfo.CurrentCulture),
Label = Resources.Microsoft_plugin_timedate_WindowsFileTime,
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
IconType = ResultIconType.DateTime,
},
});
}
catch
{
results.Add(new AvailableResult()
{
Value = Resources.Microsoft_plugin_timedate_ErrorConvertWft,
Label = Resources.Microsoft_plugin_timedate_WindowsFileTime,
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
IconType = ResultIconType.Error,
});
}
results.AddRange(new[]
{
new AvailableResult()
{
Value = dateTimeNowUtc.ToString("u"),

View File

@@ -83,10 +83,10 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
/// Gets a result with an error message that only numbers can't be parsed
/// </summary>
/// <returns>Element of type <see cref="Result"/>.</returns>
internal static Result CreateNumberErrorResult(string theme) => new Result()
internal static Result CreateNumberErrorResult(string theme, string title, string subtitle) => new Result()
{
Title = Resources.Microsoft_plugin_timedate_ErrorResultTitle,
SubTitle = Resources.Microsoft_plugin_timedate_ErrorResultSubTitle,
Title = title,
SubTitle = subtitle,
ToolTipData = new ToolTipData(Resources.Microsoft_plugin_timedate_ErrorResultTitle, Resources.Microsoft_plugin_timedate_ErrorResultSubTitle),
IcoPath = $"Images\\Warning.{theme}.png",
};

View File

@@ -40,9 +40,12 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
List<AvailableResult> availableFormats = new List<AvailableResult>();
List<Result> results = new List<Result>();
bool isKeywordSearch = !string.IsNullOrEmpty(query.ActionKeyword);
bool isEmptySearchInput = string.IsNullOrEmpty(query.Search);
bool isEmptySearchInput = string.IsNullOrWhiteSpace(query.Search);
string searchTerm = query.Search;
// Last input parsing error
string lastInputParsingErrorReason = string.Empty;
// Conjunction search without keyword => return no results
// (This improves the results on global queries.)
if (!isKeywordSearch && _conjunctionList.Any(x => x.Equals(searchTerm, StringComparison.CurrentCultureIgnoreCase)))
@@ -61,13 +64,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
{
// Search for specified format with specified time/date value
var userInput = searchTerm.Split(InputDelimiter);
if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp))
if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp, out lastInputParsingErrorReason))
{
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
searchTerm = userInput[0];
}
}
else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp))
else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp, out lastInputParsingErrorReason))
{
// Return all formats for specified time/date value
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
@@ -122,12 +125,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
}
// If search term is only a number that can't be parsed return an error message
if (!isEmptySearchInput && results.Count == 0 && Regex.IsMatch(searchTerm, @"\w+\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+")))
if (!isEmptySearchInput && results.Count == 0 && Regex.IsMatch(searchTerm, @"\w+[+-]?\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+")))
{
// Without plugin key word show only if message is not hidden by setting
string title = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? Resources.Microsoft_plugin_timedate_ErrorResultValue : Resources.Microsoft_plugin_timedate_ErrorResultTitle;
string message = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? lastInputParsingErrorReason : Resources.Microsoft_plugin_timedate_ErrorResultSubTitle;
// Without plugin key word show only if not hidden by setting
if (isKeywordSearch || !TimeDateSettings.Instance.HideNumberMessageOnGlobalQuery)
{
results.Add(ResultHelper.CreateNumberErrorResult(iconTheme));
results.Add(ResultHelper.CreateNumberErrorResult(iconTheme, title, message));
}
}

View File

@@ -5,7 +5,9 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
@@ -13,6 +15,33 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
{
internal static class TimeAndDateHelper
{
private static readonly Regex _regexSpecialInputFormats = new Regex(@"^.*(::)?(u|ums|ft|oa|exc|exf)[+-]\d");
private static readonly Regex _regexCustomDateTimeFormats = new Regex(@"(?<!\\)(DOW|DIM|WOM|WOY|EAB|WFT|UXT|UMS|OAD|EXC|EXF)");
private static readonly Regex _regexCustomDateTimeDow = new Regex(@"(?<!\\)DOW");
private static readonly Regex _regexCustomDateTimeDim = new Regex(@"(?<!\\)DIM");
private static readonly Regex _regexCustomDateTimeWom = new Regex(@"(?<!\\)WOM");
private static readonly Regex _regexCustomDateTimeWoy = new Regex(@"(?<!\\)WOY");
private static readonly Regex _regexCustomDateTimeEab = new Regex(@"(?<!\\)EAB");
private static readonly Regex _regexCustomDateTimeWft = new Regex(@"(?<!\\)WFT");
private static readonly Regex _regexCustomDateTimeUxt = new Regex(@"(?<!\\)UXT");
private static readonly Regex _regexCustomDateTimeUms = new Regex(@"(?<!\\)UMS");
private static readonly Regex _regexCustomDateTimeOad = new Regex(@"(?<!\\)OAD");
private static readonly Regex _regexCustomDateTimeExc = new Regex(@"(?<!\\)EXC");
private static readonly Regex _regexCustomDateTimeExf = new Regex(@"(?<!\\)EXF");
private const long UnixTimeSecondsMin = -62135596800;
private const long UnixTimeSecondsMax = 253402300799;
private const long UnixTimeMillisecondsMin = -62135596800000;
private const long UnixTimeMillisecondsMax = 253402300799999;
private const long WindowsFileTimeMin = 0;
private const long WindowsFileTimeMax = 2650467707991000000;
private const double OADateMin = -657434.99999999;
private const double OADateMax = 2958465.99999999;
private const double Excel1900DateMin = 1;
private const double Excel1900DateMax = 2958465.99998843;
private const double Excel1904DateMin = 0;
private const double Excel1904DateMax = 2957003.99998843;
/// <summary>
/// Get the format for the time string
/// </summary>
@@ -56,18 +85,25 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
/// Returns the number week in the month (Used code from 'David Morton' from <see href="https://social.msdn.microsoft.com/Forums/vstudio/bf504bba-85cb-492d-a8f7-4ccabdf882cb/get-week-number-for-month"/>)
/// </summary>
/// <param name="date">date</param>
/// <param name="formatSettingFirstDayOfWeek">Setting for the first day in the week.</param>
/// <returns>Number of week in the month</returns>
internal static int GetWeekOfMonth(DateTime date, DayOfWeek formatSettingFirstDayOfWeek)
{
DateTime beginningOfMonth = new DateTime(date.Year, date.Month, 1);
int adjustment = 1; // We count from 1 to 7 and not from 0 to 6
int weekCount = 1;
while (date.Date.AddDays(1).DayOfWeek != formatSettingFirstDayOfWeek)
for (int i = 1; i <= date.Day; i++)
{
date = date.AddDays(1);
DateTime d = new(date.Year, date.Month, i);
// Count week number +1 if day is the first day of a week and not day 1 of the month.
// (If we count on day one of a month we would start the month with week number 2.)
if (i > 1 && d.DayOfWeek == formatSettingFirstDayOfWeek)
{
weekCount += 1;
}
}
return (int)Math.Truncate((double)date.Subtract(beginningOfMonth).TotalDays / 7f) + adjustment;
return weekCount;
}
/// <summary>
@@ -83,40 +119,170 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
return ((date.DayOfWeek + daysInWeek - formatSettingFirstDayOfWeek) % daysInWeek) + adjustment;
}
internal static double ConvertToOleAutomationFormat(DateTime date, OADateFormats type)
{
double v = date.ToOADate();
switch (type)
{
case OADateFormats.Excel1904:
// Excel with base 1904: Adjust by -1462
v -= 1462;
// Date starts at 1/1/1904 = 0
if (Math.Truncate(v) < 0)
{
throw new ArgumentOutOfRangeException("Not a valid Excel date.", innerException: null);
}
return v;
case OADateFormats.Excel1900:
// Excel with base 1900: Adjust by -1 if v < 61
v = v < 61 ? v - 1 : v;
// Date starts at 1/1/1900 = 1
if (Math.Truncate(v) < 1)
{
throw new ArgumentOutOfRangeException("Not a valid Excel date.", innerException: null);
}
return v;
default:
// OLE Automation date: Return as is.
return v;
}
}
/// <summary>
/// Convert input string to a <see cref="DateTime"/> object in local time
/// </summary>
/// <param name="input">String with date/time</param>
/// <param name="timestamp">The new <see cref="DateTime"/> object</param>
/// <param name="inputParsingErrorMsg">Error message shown to the user</param>
/// <returns>True on success, otherwise false</returns>
internal static bool ParseStringAsDateTime(in string input, out DateTime timestamp)
internal static bool ParseStringAsDateTime(in string input, out DateTime timestamp, out string inputParsingErrorMsg)
{
inputParsingErrorMsg = string.Empty;
CompositeFormat errorMessage = CompositeFormat.Parse(Resources.Microsoft_plugin_timedate_InvalidInput_SupportedRange);
if (DateTime.TryParse(input, out timestamp))
{
// Known date/time format
return true;
}
else if (Regex.IsMatch(input, @"^u[\+-]?\d{1,10}$") && long.TryParse(input.TrimStart('u'), out long secondsU))
else if (Regex.IsMatch(input, @"^u[\+-]?\d+$"))
{
// Unix time stamp
// We use long instead of int, because int is too small after 03:14:07 UTC 2038-01-19
var canParse = long.TryParse(input.TrimStart('u'), out var secondsU);
// Value has to be in the range from -62135596800 to 253402300799
if (!canParse || secondsU < UnixTimeSecondsMin || secondsU > UnixTimeSecondsMax)
{
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Unix, UnixTimeSecondsMin, UnixTimeSecondsMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
timestamp = DateTimeOffset.FromUnixTimeSeconds(secondsU).LocalDateTime;
return true;
}
else if (Regex.IsMatch(input, @"^ums[\+-]?\d{1,13}$") && long.TryParse(input.TrimStart("ums".ToCharArray()), out long millisecondsUms))
else if (Regex.IsMatch(input, @"^ums[\+-]?\d+$"))
{
// Unix time stamp in milliseconds
// We use long instead of int because int is too small after 03:14:07 UTC 2038-01-19
var canParse = long.TryParse(input.TrimStart("ums".ToCharArray()), out var millisecondsUms);
// Value has to be in the range from -62135596800000 to 253402300799999
if (!canParse || millisecondsUms < UnixTimeMillisecondsMin || millisecondsUms > UnixTimeMillisecondsMax)
{
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Unix_Milliseconds, UnixTimeMillisecondsMin, UnixTimeMillisecondsMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
timestamp = DateTimeOffset.FromUnixTimeMilliseconds(millisecondsUms).LocalDateTime;
return true;
}
else if (Regex.IsMatch(input, @"^ft\d+$") && long.TryParse(input.TrimStart("ft".ToCharArray()), out long secondsFt))
else if (Regex.IsMatch(input, @"^ft\d+$"))
{
var canParse = long.TryParse(input.TrimStart("ft".ToCharArray()), out var secondsFt);
// Windows file time
// Value has to be in the range from 0 to 2650467707991000000
if (!canParse || secondsFt < WindowsFileTimeMin || secondsFt > WindowsFileTimeMax)
{
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_WindowsFileTime, WindowsFileTimeMin, WindowsFileTimeMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
// DateTime.FromFileTime returns as local time.
timestamp = DateTime.FromFileTime(secondsFt);
return true;
}
else if (Regex.IsMatch(input, @"^oa[+-]?\d+[,.0-9]*$"))
{
var canParse = double.TryParse(input.TrimStart("oa".ToCharArray()), out var oADate);
// OLE Automation date
// Input has to be in the range from -657434.99999999 to 2958465.99999999
// DateTime.FromOADate returns as local time.
if (!canParse || oADate < OADateMin || oADate > OADateMax)
{
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_OADate, OADateMin, OADateMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
timestamp = DateTime.FromOADate(oADate);
return true;
}
else if (Regex.IsMatch(input, @"^exc[+-]?\d+[,.0-9]*$"))
{
var canParse = double.TryParse(input.TrimStart("exc".ToCharArray()), out var excDate);
// Excel's 1900 date value
// Input has to be in the range from 1 (0 = Fake date) to 2958465.99998843 and not 60 whole number
// Because of a bug in Excel and the way it behaves before 3/1/1900 we have to adjust all inputs lower than 61 for +1
// DateTime.FromOADate returns as local time.
if (!canParse || excDate < 0 || excDate > Excel1900DateMax)
{
// For the if itself we use 0 as min value that we can show a special message if input is 0.
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Excel1900, Excel1900DateMin, Excel1900DateMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
if (Math.Truncate(excDate) == 0 || Math.Truncate(excDate) == 60)
{
inputParsingErrorMsg = Resources.Microsoft_plugin_timedate_InvalidInput_FakeExcel1900;
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
excDate = excDate <= 60 ? excDate + 1 : excDate;
timestamp = DateTime.FromOADate(excDate);
return true;
}
else if (Regex.IsMatch(input, @"^exf[+-]?\d+[,.0-9]*$"))
{
var canParse = double.TryParse(input.TrimStart("exf".ToCharArray()), out var exfDate);
// Excel's 1904 date value
// Input has to be in the range from 0 to 2957003.99998843
// Because Excel uses 01/01/1904 as base we need to adjust for +1462
// DateTime.FromOADate returns as local time.
if (!canParse || exfDate < Excel1904DateMin || exfDate > Excel1904DateMax)
{
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Excel1904, Excel1904DateMin, Excel1904DateMax);
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
return false;
}
timestamp = DateTime.FromOADate(exfDate + 1462);
return true;
}
else
{
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
@@ -125,13 +291,85 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
}
/// <summary>
/// Test if input is special parsing for Unix time, Unix time in milliseconds or File time.
/// Test if input is special parsing for Unix time, Unix time in milliseconds, file time, ...
/// </summary>
/// <param name="input">String with date/time</param>
/// <returns>True if yes, otherwise false</returns>
internal static bool IsSpecialInputParsing(string input)
{
return Regex.IsMatch(input, @"^.*(u|ums|ft)\d");
return _regexSpecialInputFormats.IsMatch(input);
}
/// <summary>
/// Converts a DateTime object based on the format string
/// </summary>
/// <param name="date">Date/time object.</param>
/// <param name="unix">Value for replacing "Unix Time Stamp".</param>
/// <param name="unixMilliseconds">Value for replacing "Unix Time Stamp in milliseconds".</param>
/// <param name="calWeek">Value for relacing calendar week.</param>
/// <param name="eraShortFormat">Era abbreviation.</param>
/// <param name="format">Format definition.</param>
/// <returns>Formated date/time string.</returns>
internal static string ConvertToCustomFormat(DateTime date, long unix, long unixMilliseconds, int calWeek, string eraShortFormat, string format, CalendarWeekRule firstWeekRule, DayOfWeek firstDayOfTheWeek)
{
string result = format;
// DOW: Number of day in week
result = _regexCustomDateTimeDow.Replace(result, GetNumberOfDayInWeek(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture));
// DIM: Days in Month
result = _regexCustomDateTimeDim.Replace(result, DateTime.DaysInMonth(date.Year, date.Month).ToString(CultureInfo.CurrentCulture));
// WOM: Week of Month
result = _regexCustomDateTimeWom.Replace(result, GetWeekOfMonth(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture));
// WOY: Week of Year
result = _regexCustomDateTimeWoy.Replace(result, calWeek.ToString(CultureInfo.CurrentCulture));
// EAB: Era abbreviation
result = _regexCustomDateTimeEab.Replace(result, eraShortFormat);
// WFT: Week of Month
if (_regexCustomDateTimeWft.IsMatch(result))
{
// Special handling as very early dates can't convert.
result = _regexCustomDateTimeWft.Replace(result, date.ToFileTime().ToString(CultureInfo.CurrentCulture));
}
// UXT: Unix time stamp
result = _regexCustomDateTimeUxt.Replace(result, unix.ToString(CultureInfo.CurrentCulture));
// UMS: Unix time stamp milli seconds
result = _regexCustomDateTimeUms.Replace(result, unixMilliseconds.ToString(CultureInfo.CurrentCulture));
// OAD: OLE Automation date
result = _regexCustomDateTimeOad.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.OLEAutomation).ToString(CultureInfo.CurrentCulture));
// EXC: Excel date value with base 1900
if (_regexCustomDateTimeExc.IsMatch(result))
{
// Special handling as very early dates can't convert.
result = _regexCustomDateTimeExc.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1900).ToString(CultureInfo.CurrentCulture));
}
// EXF: Excel date value with base 1904
if (_regexCustomDateTimeExf.IsMatch(result))
{
// Special handling as very early dates can't convert.
result = _regexCustomDateTimeExf.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1904).ToString(CultureInfo.CurrentCulture));
}
return result;
}
/// <summary>
/// Test a string for our custom date and time format syntax
/// </summary>
/// <param name="str">String to test.</param>
/// <returns>True if yes and otherwise false</returns>
internal static bool StringContainsCustomFormatSyntax(string str)
{
return _regexCustomDateTimeFormats.IsMatch(str);
}
/// <summary>
@@ -190,4 +428,14 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
Date,
DateTime,
}
/// <summary>
/// Different versions of Date formats based on OLE Automation date
/// </summary>
internal enum OADateFormats
{
OLEAutomation,
Excel1900,
Excel1904,
}
}

View File

@@ -61,6 +61,11 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
/// </summary>
internal bool HideNumberMessageOnGlobalQuery { get; private set; }
/// <summary>
/// Gets a value containing the custom format definitions
/// </summary>
internal List<string> CustomFormats { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="TimeDateSettings"/> class.
/// Private constructor to make sure there is never more than one instance of this class
@@ -100,29 +105,6 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
{
var optionList = new List<PluginAdditionalOption>
{
new PluginAdditionalOption()
{
Key = nameof(CalendarFirstWeekRule),
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule,
DisplayDescription = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_Description,
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
ComboBoxItems = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_Setting_UseSystemSetting, "-1"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstDay, "0"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFullWeek, "1"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFourDayWeek, "2"),
},
ComboBoxValue = -1,
},
new PluginAdditionalOption()
{
Key = nameof(FirstDayOfWeek),
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstDayOfWeek,
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
ComboBoxItems = GetSortedListForWeekDaySetting(),
ComboBoxValue = -1,
},
new PluginAdditionalOption()
{
Key = nameof(OnlyDateTimeNowGlobal),
@@ -150,6 +132,38 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingHideNumberMessageOnGlobalQuery,
Value = false,
},
new PluginAdditionalOption()
{
Key = nameof(CalendarFirstWeekRule),
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule,
DisplayDescription = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_Description,
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
ComboBoxItems = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_Setting_UseSystemSetting, "-1"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstDay, "0"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFullWeek, "1"),
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFourDayWeek, "2"),
},
ComboBoxValue = -1,
},
new PluginAdditionalOption()
{
Key = nameof(FirstDayOfWeek),
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstDayOfWeek,
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
ComboBoxItems = GetSortedListForWeekDaySetting(),
ComboBoxValue = -1,
},
new PluginAdditionalOption()
{
Key = nameof(CustomFormats),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.MultilineTextbox,
DisplayLabel = Resources.Microsoft_plugin_timedate_Setting_CustomFormats,
DisplayDescription = string.Format(CultureInfo.CurrentCulture, Resources.Microsoft_plugin_timedate_Setting_CustomFormatsDescription.ToString(), "DOW", "DIM", "WOM", "WOY", "EAB", "WFT", "UXT", "UMS", "OAD", "EXC", "EXF", "UTC:"),
PlaceholderText = "MyFormat=dd-MMM-yyyy\rMySecondFormat=dddd (Da\\y nu\\mber: DOW)\rMyUtcFormat=UTC:hh:mm:ss",
TextValue = string.Empty,
},
};
return optionList;
@@ -172,6 +186,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
TimeWithSeconds = GetSettingOrDefault(settings, nameof(TimeWithSeconds));
DateWithWeekday = GetSettingOrDefault(settings, nameof(DateWithWeekday));
HideNumberMessageOnGlobalQuery = GetSettingOrDefault(settings, nameof(HideNumberMessageOnGlobalQuery));
CustomFormats = GetMultilineTextSettingOrDefault(settings, nameof(CustomFormats));
}
/// <summary>
@@ -204,6 +219,21 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
return option?.ComboBoxValue ?? GetAdditionalOptions().First(x => x.Key == name).ComboBoxValue;
}
/// <summary>
/// Return the combobox value of the given settings list with the given name.
/// </summary>
/// <param name="settings">The object that contain all settings.</param>
/// <param name="name">The name of the setting.</param>
/// <returns>A settings value.</returns>
private static List<string> GetMultilineTextSettingOrDefault(PowerLauncherPluginSettings settings, string name)
{
var option = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == name);
// If a setting isn't available, we use the value defined in the method GetAdditionalOptions() as fallback.
// We can use First() instead of FirstOrDefault() because the values must exist. Otherwise, we made a mistake when defining the settings.
return option?.TextValueAsMultilineList ?? GetAdditionalOptions().First(x => x.Key == name).TextValueAsMultilineList;
}
/// <summary>
/// Returns a sorted list of values for the combo box of 'first day of week' setting.
/// The list is sorted based on the current system culture setting.

View File

@@ -150,6 +150,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Days in month.
/// </summary>
internal static string Microsoft_plugin_timedate_DaysInMonth {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_DaysInMonth", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Era.
/// </summary>
@@ -169,7 +178,25 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
/// <summary>
/// Looks up a localized string similar to Valid prefixes: &apos;u&apos; for Unix Timestamp, &apos;ums&apos; for Unix Timestamp in milliseconds, &apos;ft&apos; for Windows file time.
/// Looks up a localized string similar to Failed to convert into custom format:.
/// </summary>
internal static string Microsoft_plugin_timedate_ErrorConvertCustomFormat {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_ErrorConvertCustomFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not a valid Windows file time.
/// </summary>
internal static string Microsoft_plugin_timedate_ErrorConvertWft {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_ErrorConvertWft", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Valid prefixes: &apos;u&apos; for Unix Timestamp, &apos;ums&apos; for Unix Timestamp in milliseconds, &apos;ft&apos; for Windows file time, &apos;oa&apos; for OLE Automation date, &apos;exc&apos; for Excel&apos;s 1900 date value, &apos;exf&apos; for Excel&apos;s 1904 date value.
/// </summary>
internal static string Microsoft_plugin_timedate_ErrorResultSubTitle {
get {
@@ -178,7 +205,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
/// <summary>
/// Looks up a localized string similar to Error: Invalid number input.
/// Looks up a localized string similar to Error: Invalid input.
/// </summary>
internal static string Microsoft_plugin_timedate_ErrorResultTitle {
get {
@@ -186,6 +213,33 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Error: Invalid number.
/// </summary>
internal static string Microsoft_plugin_timedate_ErrorResultValue {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_ErrorResultValue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Excel&apos;s 1900 date value.
/// </summary>
internal static string Microsoft_plugin_timedate_Excel1900 {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_Excel1900", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Excel&apos;s 1904 date value.
/// </summary>
internal static string Microsoft_plugin_timedate_Excel1904 {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_Excel1904", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Date and time in filename-compatible format.
/// </summary>
@@ -204,6 +258,33 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Invalid custom format:.
/// </summary>
internal static string Microsoft_plugin_timedate_InvalidCustomFormat {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_InvalidCustomFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot parse the input as Excel&apos;s 1900 date value because it is a fake date. (In Excel 0 stands for 0/1/1900 and this date doesn&apos;t exist. And 60 stands for 2/29/1900 and this date only exists in Excel for compatibility with Lotus 123.).
/// </summary>
internal static string Microsoft_plugin_timedate_InvalidInput_FakeExcel1900 {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_InvalidInput_FakeExcel1900", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your input for {0} is outside the range from {1} to {2}..
/// </summary>
internal static string Microsoft_plugin_timedate_InvalidInput_SupportedRange {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_InvalidInput_SupportedRange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ISO 8601.
/// </summary>
@@ -240,6 +321,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Leap year.
/// </summary>
internal static string Microsoft_plugin_timedate_LeapYear {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_LeapYear", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Millisecond.
/// </summary>
@@ -285,6 +375,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Not a leap year.
/// </summary>
internal static string Microsoft_plugin_timedate_NoLeapYear {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_NoLeapYear", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Now.
/// </summary>
@@ -303,6 +402,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to OLE Automation Date.
/// </summary>
internal static string Microsoft_plugin_timedate_OADate {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_OADate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Provides time and date values for the system time or a custom time stamp (e.g.&apos;{0}&apos;, &apos;{1}&apos;, &apos;{2}&apos;, &apos;{3}&apos;).
/// </summary>
@@ -366,6 +474,42 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Date and time; Time and Date; Custom format.
/// </summary>
internal static string Microsoft_plugin_timedate_SearchTagCustom {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagCustom", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Current date and time; Current time and date; Now; Custom format.
/// </summary>
internal static string Microsoft_plugin_timedate_SearchTagCustomNow {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagCustomNow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Date and time UTC; Time UTC and Date; Custom UTC format.
/// </summary>
internal static string Microsoft_plugin_timedate_SearchTagCustomUtc {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagCustomUtc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Current date and time UTC; Current time UTC and date; Now UTC; Custom UTC format.
/// </summary>
internal static string Microsoft_plugin_timedate_SearchTagCustomUtcNow {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagCustomUtcNow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Date.
/// </summary>
@@ -447,6 +591,24 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Custom formats.
/// </summary>
internal static string Microsoft_plugin_timedate_Setting_CustomFormats {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_Setting_CustomFormats", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use date and time string format syntax and {0} (Day of Week), {1} (Days in Month), {2} (Week of Month), {3} (Week of the year), {4} (Era abbreviation), {5} (Windows File Time), {6} (Unix Time), {7} (Unix Time in milliseconds), {8} (OLE Automation date), {9} (Excel&apos;s 1900 based date value), {10} (Excel&apos;s 1904 based date value). If the format starts with {11}, then Universal Time (UTC) is used. (Use a backslash to escape format sequences and the backslash character as text.).
/// </summary>
internal static string Microsoft_plugin_timedate_Setting_CustomFormatsDescription {
get {
return ResourceManager.GetString("Microsoft_plugin_timedate_Setting_CustomFormatsDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use system setting.
/// </summary>

View File

@@ -156,10 +156,13 @@
<value>Era abbreviation</value>
</data>
<data name="Microsoft_plugin_timedate_ErrorResultSubTitle" xml:space="preserve">
<value>Valid prefixes: 'u' for Unix Timestamp, 'ums' for Unix Timestamp in milliseconds, 'ft' for Windows file time</value>
<value>Valid prefixes: 'u' for Unix Timestamp, 'ums' for Unix Timestamp in milliseconds, 'ft' for Windows file time, 'oa' for OLE Automation date, 'exc' for Excel's 1900 date value, 'exf' for Excel's 1904 date value</value>
</data>
<data name="Microsoft_plugin_timedate_ErrorResultTitle" xml:space="preserve">
<value>Error: Invalid number input</value>
<value>Error: Invalid input</value>
</data>
<data name="Microsoft_plugin_timedate_ErrorResultValue" xml:space="preserve">
<value>Error: Invalid number</value>
</data>
<data name="Microsoft_plugin_timedate_Hour" xml:space="preserve">
<value>Hour</value>
@@ -243,10 +246,26 @@
<value>Date and time; Time and Date</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagCustom" xml:space="preserve">
<value>Date and time; Time and Date; Custom format</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagCustomUtc" xml:space="preserve">
<value>Date and time UTC; Time UTC and Date; Custom UTC format</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagFormatNow" xml:space="preserve">
<value>Current date and time; Current time and date; Now</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagCustomNow" xml:space="preserve">
<value>Current date and time; Current time and date; Now; Custom format</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagCustomUtcNow" xml:space="preserve">
<value>Current date and time UTC; Current time UTC and date; Now UTC; Custom UTC format</value>
<comment>Don't change order</comment>
</data>
<data name="Microsoft_plugin_timedate_SearchTagTime" xml:space="preserve">
<value>Time</value>
<comment>Don't change order</comment>
@@ -262,6 +281,9 @@
<data name="Microsoft_plugin_timedate_Second" xml:space="preserve">
<value>Second</value>
</data>
<data name="Microsoft_plugin_timedate_InvalidCustomFormat" xml:space="preserve">
<value>Invalid custom format:</value>
</data>
<data name="Microsoft_plugin_timedate_SettingDateWithWeekday" xml:space="preserve">
<value>Show date with weekday and name of month</value>
</data>
@@ -360,4 +382,42 @@
<data name="Microsoft_plugin_timedate_Setting_UseSystemSetting" xml:space="preserve">
<value>Use system setting</value>
</data>
<data name="Microsoft_plugin_timedate_Setting_CustomFormats" xml:space="preserve">
<value>Custom formats</value>
</data>
<data name="Microsoft_plugin_timedate_Setting_CustomFormatsDescription" xml:space="preserve">
<value>Use date and time string format syntax and {0} (Day of Week), {1} (Days in Month), {2} (Week of Month), {3} (Week of the year), {4} (Era abbreviation), {5} (Windows File Time), {6} (Unix Time), {7} (Unix Time in milliseconds), {8} (OLE Automation date), {9} (Excel's 1900 based date value), {10} (Excel's 1904 based date value). If the format starts with {11}, then Universal Time (UTC) is used. (Use a backslash to escape format sequences and the backslash character as text.)</value>
<comment>The {n} parts are place holders and get replaced in the code.</comment>
</data>
<data name="Microsoft_plugin_timedate_ErrorConvertCustomFormat" xml:space="preserve">
<value>Failed to convert into custom format:</value>
</data>
<data name="Microsoft_plugin_timedate_ErrorConvertWft" xml:space="preserve">
<value>Not a valid Windows file time</value>
</data>
<data name="Microsoft_plugin_timedate_InvalidInput_SupportedRange" xml:space="preserve">
<value>Your input for {0} is outside the range from {1} to {2}.</value>
<comment>The placeholder will be replace in code.</comment>
</data>
<data name="Microsoft_plugin_timedate_OADate" xml:space="preserve">
<value>OLE Automation Date</value>
</data>
<data name="Microsoft_plugin_timedate_Excel1900" xml:space="preserve">
<value>Excel's 1900 date value</value>
</data>
<data name="Microsoft_plugin_timedate_InvalidInput_FakeExcel1900" xml:space="preserve">
<value>Cannot parse the input as Excel's 1900 date value because it is a fake date. (In Excel 0 stands for 0/1/1900 and this date doesn't exist. And 60 stands for 2/29/1900 and this date only exists in Excel for compatibility with Lotus 123.)</value>
</data>
<data name="Microsoft_plugin_timedate_Excel1904" xml:space="preserve">
<value>Excel's 1904 date value</value>
</data>
<data name="Microsoft_plugin_timedate_LeapYear" xml:space="preserve">
<value>Leap year</value>
</data>
<data name="Microsoft_plugin_timedate_NoLeapYear" xml:space="preserve">
<value>Not a leap year</value>
</data>
<data name="Microsoft_plugin_timedate_DaysInMonth" xml:space="preserve">
<value>Days in month</value>
</data>
</root>

View File

@@ -295,7 +295,7 @@
"Areas": [ "AreaEaseOfAccess" ],
"Type": "AppSettingsApp",
"AltNames": [ "TouchFeedback" ],
"Command": "ms-settings:easeofaccess-MousePointer"
"Command": "ms-settings:easeofaccess-mousepointer"
},
{
"Name": "Display",

View File

@@ -325,7 +325,7 @@ namespace PowerAccent.Core
LetterKey.VK_H => new[] { "ĥ" },
LetterKey.VK_J => new[] { "ĵ" },
LetterKey.VK_S => new[] { "ŝ" },
LetterKey.VK_U => new[] { "ǔ" },
LetterKey.VK_U => new[] { "ŭ" },
_ => Array.Empty<string>(),
};
}

View File

@@ -16,6 +16,9 @@
<PackageReference Include="Moq" />
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
</ItemGroup>
<ItemGroup>

View File

@@ -60,6 +60,9 @@
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="WinUIEx" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageReference Include="MessagePack" />
@@ -75,6 +78,8 @@
<PackageReference Include="Microsoft.Web.WebView2" />
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->
<PackageReference Include="CommunityToolkit.Common" />
<!-- HACK: MWB and Advanced Paste. Align the version. got flagged when https://github.com/microsoft/PowerToys/pull/38779 was done -->
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging

View File

@@ -28,18 +28,15 @@ namespace Microsoft.PowerToys.Settings.UI
var bootTime = new System.Diagnostics.Stopwatch();
bootTime.Start();
this.Activated += Window_Activated_SetIcon;
App.ThemeService.ThemeChanged += OnThemeChanged;
App.ThemeService.ApplyTheme();
ShellPage.SetElevationStatus(App.IsElevated);
ShellPage.SetIsUserAnAdmin(App.IsUserAnAdmin);
// Set window icon
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\Settings\\icon.ico");
var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
if (createHidden)
{
@@ -221,6 +218,15 @@ namespace Microsoft.PowerToys.Settings.UI
App.ThemeService.ThemeChanged -= OnThemeChanged;
}
private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args)
{
// Set window icon
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\Settings\\icon.ico");
}
private void Window_Activated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState != WindowActivationState.Deactivated)

View File

@@ -31,7 +31,6 @@ namespace Microsoft.PowerToys.Settings.UI
private WindowId _windowId;
private IntPtr _hWnd;
private AppWindow _appWindow;
private WindowMessageMonitor _msgMonitor;
private bool disposedValue;
public OobeWindow(PowerToysModules initialModule)
@@ -41,15 +40,10 @@ namespace Microsoft.PowerToys.Settings.UI
this.InitializeComponent();
// Set window icon
_hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
_windowId = Win32Interop.GetWindowIdFromWindow(_hWnd);
_appWindow = AppWindow.GetFromWindowId(_windowId);
_appWindow.SetIcon("Assets\\Settings\\icon.ico");
OverlappedPresenter presenter = _appWindow.Presenter as OverlappedPresenter;
presenter.IsMinimizable = false;
presenter.IsMaximizable = false;
this.Activated += Window_Activated_SetIcon;
var dpi = NativeMethods.GetDpiForWindow(_hWnd);
_currentDPI = dpi;
@@ -64,18 +58,6 @@ namespace Microsoft.PowerToys.Settings.UI
this.initialModule = initialModule;
_msgMonitor = new WindowMessageMonitor(this);
_msgMonitor.WindowMessageReceived += (_, e) =>
{
const int WM_NCLBUTTONDBLCLK = 0x00A3;
if (e.Message.MessageId == WM_NCLBUTTONDBLCLK)
{
// Disable double click on title bar to maximize window
e.Result = 0;
e.Handled = true;
}
};
this.SizeChanged += OobeWindow_SizeChanged;
var loader = Helpers.ResourceLoaderInstance.ResourceLoader;
@@ -110,6 +92,12 @@ namespace Microsoft.PowerToys.Settings.UI
}
}
private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args)
{
// Set window icon
_appWindow.SetIcon("Assets\\Settings\\icon.ico");
}
private void OobeWindow_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
var dpi = NativeMethods.GetDpiForWindow(_hWnd);
@@ -149,8 +137,6 @@ namespace Microsoft.PowerToys.Settings.UI
{
if (!disposedValue)
{
_msgMonitor?.Dispose();
disposedValue = true;
}
}

View File

@@ -458,7 +458,7 @@
<controls:SettingsPageControl.PrimaryLinks>
<controls:PageLink x:Uid="GeneralPage_Documentation" Link="https://aka.ms/PowerToysOverview" />
<controls:PageLink x:Uid="General_Repository" Link="https://aka.ms/powertoys" />
<controls:PageLink x:Uid="GeneralPage_ReportAbug" Link="https://aka.ms/powerToysReportBug" />
<controls:PageLink x:Uid="GeneralPage_ReportAbug" Link="{x:Bind ViewModel.ReportBugLink, Mode=OneWay}" />
<controls:PageLink x:Uid="GeneralPage_RequestAFeature_URL" Link="https://aka.ms/powerToysRequestFeature" />
</controls:SettingsPageControl.PrimaryLinks>
<controls:SettingsPageControl.SecondaryLinks>

View File

@@ -84,6 +84,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DataContext = ViewModel;
ViewModel.InitializeReportBugLink();
doRefreshBackupRestoreStatus(100);
}

View File

@@ -9,6 +9,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
@@ -25,11 +26,19 @@ using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Win32;
using Windows.System.Profile;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class GeneralViewModel : Observable
{
public enum InstallScope
{
PerMachine = 0,
PerUser,
}
private GeneralSettings GeneralSettingsConfig { get; set; }
private UpdatingSettings UpdatingSettingsConfig { get; set; }
@@ -72,6 +81,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private SettingsBackupAndRestoreUtils settingsBackupAndRestoreUtils = SettingsBackupAndRestoreUtils.Instance;
private const string InstallScopeRegKey = @"Software\Classes\powertoys\";
public GeneralViewModel(ISettingsRepository<GeneralSettings> settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func<string, int> ipcMSGCallBackFunc, Func<string, int> ipcMSGRestartAsAdminMSGCallBackFunc, Func<string, int> ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "", Action dispatcherAction = null, Action hideBackupAndRestoreMessageAreaAction = null, Action<int> doBackupAndRestoreDryRun = null, Func<Task<string>> pickSingleFolderDialog = null, Windows.ApplicationModel.Resources.ResourceLoader resourceLoader = null)
{
CheckForUpdatesEventHandler = new ButtonClickCommand(CheckForUpdatesClick);
@@ -256,6 +267,73 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private int _initLanguagesIndex;
private bool _languageChanged;
private string reportBugLink;
// Gets or sets a value indicating whether run powertoys on start-up.
public string ReportBugLink
{
get => reportBugLink;
set
{
reportBugLink = value;
OnPropertyChanged(nameof(ReportBugLink));
}
}
public void InitializeReportBugLink()
{
var version = GetPowerToysVersion();
string isElevatedString = "PowerToys is running " + (IsElevated ? "as admin (elevated)" : "as user (non-elevated)");
string installScope = GetCurrentInstallScope() == InstallScope.PerMachine ? "per machine (system)" : "per user";
var info = $"OS Version: {GetOSVersion()} \n.NET Version: {GetDotNetVersion()}\n{isElevatedString}\nInstall scope: {installScope}\nOperating System Language: {CultureInfo.InstalledUICulture.DisplayName}\nSystem locale: {CultureInfo.InstalledUICulture.Name}";
var gitHubURL = "https://github.com/microsoft/PowerToys/issues/new?template=bug_report.yml&labels=Issue-Bug%2CTriage-Needed" +
"&version=" + version + "&additionalInfo=" + System.Web.HttpUtility.UrlEncode(info);
ReportBugLink = gitHubURL;
}
private string GetPowerToysVersion()
{
return Helper.GetProductVersion().TrimStart('v');
}
private string GetOSVersion()
{
return Environment.OSVersion.VersionString;
}
public static string GetDotNetVersion()
{
return $".NET {Environment.Version}";
}
public static InstallScope GetCurrentInstallScope()
{
// Check HKLM first
if (Registry.LocalMachine.OpenSubKey(InstallScopeRegKey) != null)
{
return InstallScope.PerMachine;
}
// If not found, check HKCU
var userKey = Registry.CurrentUser.OpenSubKey(InstallScopeRegKey);
if (userKey != null)
{
var installScope = userKey.GetValue("InstallScope") as string;
userKey.Close();
if (!string.IsNullOrEmpty(installScope) && installScope.Contains("perUser"))
{
return InstallScope.PerUser;
}
}
return InstallScope.PerMachine; // Default if no specific registry key found
}
// Gets or sets a value indicating whether run powertoys on start-up.
public bool Startup
{