Compare commits

..

306 Commits

Author SHA1 Message Date
donlaci
d34d4e2d54 spell checker 2024-08-23 14:47:43 +02:00
donlaci
8588dff066 creating temporal workspace for launch. 2024-08-23 14:31:23 +02:00
Seraphima
cbf6624ddd Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 20:11:33 +02:00
Seraphima
010fccd12a fix launch and edit: save new project 2024-08-22 20:11:18 +02:00
Seraphima
b68b7724c9 fix launch and edit for the new project 2024-08-22 19:02:38 +02:00
Niels Laute
ab47d91b5f New icon 2024-08-22 18:44:34 +02:00
Seraphima
f6282773c1 logs 2024-08-22 16:43:19 +02:00
Seraphima
868ba040d8 remove unused argument 2024-08-22 16:40:02 +02:00
Seraphima
179b4ed197 comment out unused 2024-08-22 16:39:05 +02:00
Seraphima
dc5bc12896 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 16:37:41 +02:00
Seraphima
558ea132d1 get process path without waiting 2024-08-22 16:37:33 +02:00
Niels Laute
919fe830a2 Sentence case labels 2024-08-22 16:26:41 +02:00
Seraphima
f665966db6 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 15:07:24 +02:00
donlaci
40a2510c52 Removing old shortcut on workspace renaming 2024-08-22 15:05:05 +02:00
Seraphima
cec9a73a6b fix terminating UI 2024-08-22 14:41:42 +02:00
Seraphima
02f792b13e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 13:32:26 +02:00
Seraphima
b7743d28b5 reordered launching 2024-08-22 13:32:17 +02:00
donlaci
f180d51367 Adding cancellation to launcher UI. 2024-08-22 10:36:50 +02:00
donlaci
8911a6eb63 Delete icon on workspace removal 2024-08-22 09:58:45 +02:00
Stefan Markovic
c38412c7cb Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-21 17:23:37 +02:00
Stefan Markovic
3bc0a518fc BugReportTool - omit cmd arg data 2024-08-21 17:20:53 +02:00
Seraphima
e49c1e1d60 UI closing 2024-08-21 16:57:44 +02:00
Seraphima
fcf5c5c05c clean up 2024-08-21 11:34:29 +02:00
Seraphima
56d87133af fix topmost LauncherUI 2024-08-21 11:34:26 +02:00
donlaci
2093dd4cde [Workspaces] Close launcher if there are failed launches. Plus adding new spinner gif 2024-08-21 11:13:22 +02:00
Seraphima
7beae3e470 fix xaml formatting 2024-08-20 22:06:49 +02:00
Seraphima
34f4514ebb fixed module order in settings 2024-08-20 21:39:27 +02:00
Seraphima
62d7b40ab0 launching with AppUserModel.ID 2024-08-20 21:16:14 +02:00
Seraphima
cd0b8aa6d9 moved AppUtils 2024-08-20 19:52:09 +02:00
Seraphima
7b23e5ba99 update OOBE image with current module name 2024-08-19 18:52:29 +02:00
Seraphima
d3faa86600 address PR comment: rename oobe view 2024-08-19 18:09:20 +02:00
Seraphima
2bc3d1228d address PR comment: uncomment gpo in settings 2024-08-19 18:05:10 +02:00
Seraphima
169c231509 address PR comments: updated projects 2024-08-19 18:02:03 +02:00
Seraphima
690dce5482 addressPR comment: use BringToForeground 2024-08-19 15:10:25 +02:00
Seraphima
a20fd15800 added supported version definition 2024-08-19 14:47:04 +02:00
Seraphima
8c30da743c address PR comment: changed version for workspaces and revision 2024-08-19 14:31:54 +02:00
Seraphima
601eab5c88 address PR comment: rename function 2024-08-19 14:26:23 +02:00
Seraphima
e23cf3c914 indentation 2024-08-19 14:19:55 +02:00
Seraphima
e17747cc1e address PR comment: fix isdigit 2024-08-19 14:16:56 +02:00
Stefan Markovic
817bec9c4c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-18 14:36:24 +02:00
Stefan Markovic
ca49cab49b build fix - align CppWinRT version 2024-08-18 14:35:50 +02:00
Davide Giacometti
ff225044f9 [Run][PowerToys] Fix Workspaces utility (#34336)
polished workspaces utility
2024-08-18 14:24:36 +02:00
Stefan Markovic
aee1eb82a2 more .sln cleanup 2024-08-18 12:26:41 +02:00
Stefan Markovic
1f58a9e8c9 Add missing method defition to interop::Constants idl
Remove Any CPU config
2024-08-18 11:44:53 +02:00
Stefan Markovic
114ddbeeba xaml formatting 2024-08-17 10:15:32 +02:00
Seraphima
e12bf70081 added launch editor event constant 2024-08-16 18:49:48 +02:00
Seraphima
24533e4344 fix custom actions build 2024-08-16 18:49:26 +02:00
Seraphima
fa50208973 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-08-16 18:09:44 +02:00
Seraphima
2d4d49f783 rename: module interface 2024-08-16 18:03:46 +02:00
Seraphima
af85be3b90 rename: json 2024-08-16 17:56:53 +02:00
Seraphima
33442e8853 fix empty file crash 2024-08-16 17:54:06 +02:00
Seraphima
ec049aa27b rename: editor resources 2024-08-16 17:53:58 +02:00
Seraphima
71d40b96cd rename: launcher 2024-08-16 17:36:27 +02:00
Seraphima
f6afee9012 rename: common files 2024-08-16 17:27:32 +02:00
Seraphima
d8b4798476 rename: editor 2024-08-16 17:27:12 +02:00
Seraphima
344506b1db rename: snapshot tool 2024-08-16 17:10:40 +02:00
Seraphima
23b6a4bedb rename: icon 2024-08-16 16:59:28 +02:00
Seraphima
c28afa1fc8 rename: module interface 2024-08-16 16:58:22 +02:00
Seraphima
2d812fa6d8 rename: fz 2024-08-16 16:42:31 +02:00
Seraphima
dc39ec771e rename: pt run 2024-08-16 16:40:14 +02:00
Seraphima
a4e07a7b5d rename: other 2024-08-16 16:39:39 +02:00
Seraphima
4825c62c50 rename: launcher UI 2024-08-16 15:55:15 +02:00
Seraphima
74c891b0f3 rename: settings 2024-08-16 15:32:16 +02:00
Seraphima
d1266cf080 rename: gpo 2024-08-16 15:07:07 +02:00
Seraphima
26119ea0e0 rename ProjectsLib -> WorkspacesLib 2024-08-16 14:18:51 +02:00
Seraphima
5420beb700 rename: workspaces data 2024-08-16 14:06:04 +02:00
Seraphima
b72ec989fa rename: telemetry namespace 2024-08-16 14:00:30 +02:00
Seraphima
8675a93f69 rename: common data structures 2024-08-16 13:56:35 +02:00
Seraphima
877d5c0506 rename: files and folders 2024-08-16 13:51:25 +02:00
Seraphima
6820e0cd80 rename: window property 2024-08-16 12:06:00 +02:00
Seraphima
98eaffc475 remove outdated 2024-08-16 11:57:17 +02:00
Seraphima
bc86e4fb04 rename: folders 2024-08-16 11:57:00 +02:00
Seraphima
95d9521cce resources 2024-08-16 11:23:05 +02:00
Seraphima
19c660a8ff MonitorRect comparison 2024-08-16 11:22:56 +02:00
Seraphima
8bd74accc0 telemetry: launch event 2024-08-16 11:22:43 +02:00
Seraphima
443f709899 replaced tuple with struct 2024-08-16 11:20:26 +02:00
Seraphima
86b1bff927 parse invoke point 2024-08-16 11:17:43 +02:00
Seraphima
00aa8b4b88 added utils 2024-08-16 11:15:58 +02:00
Seraphima
6db620c79f added invoke point to launcher args 2024-08-16 10:57:23 +02:00
Seraphima
b72e6bb7d6 updated Edit telemetry event 2024-08-15 20:48:42 +02:00
Seraphima
b67b7a4ee7 telemetry: delete event 2024-08-15 15:43:52 +02:00
Seraphima
65be209c2d fix muted Launch as admin checkbox 2024-08-15 15:37:54 +02:00
Seraphima
3b7f7f6851 telemetry: edit event 2024-08-15 15:31:35 +02:00
Seraphima
f10610e1e1 WindowPosition comparison 2024-08-15 13:50:03 +02:00
Seraphima
903c64b29a telemetry: create event 2024-08-15 13:32:18 +02:00
Seraphima
13e8743802 removed unused sln 2024-08-12 19:40:05 +02:00
Seraphima
05ddd019af consistent file name 2024-08-12 19:39:38 +02:00
donlaci
55d8631650 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-08 16:00:17 +02:00
donlaci
666d0c2840 opening apps in minimized state which are placed on a monitor, which is not found at the moment of launching 2024-08-08 15:47:44 +02:00
Jaime Bernardo
ed18f10fa9 Add ProjectsLauncherUI to signing 2024-08-06 22:34:59 +01:00
donlaci
ef511d6318 spell checker 2024-08-06 17:13:20 +02:00
donlaci
f80eaf7b23 commenting out feature "move apps if exist" 2024-08-06 14:44:33 +02:00
donlaci
ad88f71398 setting window size according to the screen (making it bigger) 2024-08-06 14:32:21 +02:00
donlaci
26b7a48278 Re-ordering modules after Renaming Projects + spell checker 2024-08-06 12:32:01 +02:00
donlaci
b407bf0f2b Renaming Projects to App Layouts. Replacing only string values, not the variable names 2024-08-06 12:16:21 +02:00
donlaci
82cfa4cbfe spell checker 2024-08-05 14:38:49 +02:00
donlaci
ac5d9dbef2 Adding new app which is launched when launching a project. It shows the status of the launch process 2024-08-05 14:02:32 +02:00
donlaci
2af86355ac Adding launch button to projects settings, dashboard and flyout 2024-07-30 13:03:52 +02:00
seraphima
e16f3ab4e5 fix default shortcut 2024-07-25 15:26:07 +02:00
seraphima
6686c5b5f5 launcher error messages 2024-07-25 15:17:02 +02:00
seraphima
f922f56890 show screen numbers instead of monitor names 2024-07-25 12:10:03 +02:00
seraphima
193581a9ac clean up 2024-07-25 12:01:56 +02:00
seraphima
78c3f95179 project launch on "launch and edit" 2024-07-25 11:57:40 +02:00
seraphima
0212892c94 fix corrupted data: project id and monitor id 2024-07-25 11:57:12 +02:00
seraphima
50792b9520 use common theme 2024-07-25 11:51:45 +02:00
seraphima
885c4ffd77 handle powertoys settings 2024-07-24 14:39:47 +02:00
seraphima
d3c9fe8ea5 keep opened windows at the same positions 2024-07-23 23:39:37 +02:00
seraphima
475a059ef6 move existing windows 2024-07-23 22:40:35 +02:00
seraphima
39086f702e restart unelevated 2024-07-22 20:37:49 +02:00
seraphima
ed98a94d2a notification about elevated apps 2024-07-20 12:57:12 +02:00
donlaci
04ff5373d9 add move apps checkbox 2024-07-19 14:00:01 +02:00
seraphima
3fb51eea9e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 17:51:14 +02:00
seraphima
a208742012 telemetry 2024-07-18 17:50:00 +02:00
seraphima
3320dc2d0d moved projects data parsing 2024-07-18 17:11:51 +02:00
donlaci
4c12db9fa6 minor string correction 2024-07-18 14:54:56 +02:00
donlaci
abc5a80a1e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 14:47:37 +02:00
donlaci
548bfdfcee Implementing store of setting "order by". 2024-07-18 14:46:21 +02:00
seraphima
ea6f5c8d88 projects module interface telemetry 2024-07-18 14:28:50 +02:00
seraphima
93c81bb93e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 13:05:49 +02:00
seraphima
81fb58ed20 can-launch-elevated check 2024-07-18 13:00:31 +02:00
donlaci
23aa46a4a5 enhancing shortcut handling 2024-07-18 12:50:38 +02:00
donlaci
ae7eb60b0c minor fix 2024-07-17 21:12:09 +02:00
donlaci
6c09b251b5 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 18:48:44 +02:00
donlaci
e5c8305c5c minor fixes 2024-07-17 18:48:13 +02:00
seraphima
3c422438e8 launch elevated apps 2024-07-17 17:32:14 +02:00
seraphima
82e0749779 change scroll speed to 15 2024-07-17 17:08:55 +02:00
seraphima
4fc83786e3 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 16:41:49 +02:00
donlaci
513dd0cbf4 fixing scroll speed issue 2024-07-17 16:32:48 +02:00
seraphima
672206d17b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 15:11:43 +02:00
donlaci
9f93e238c3 Fix for packaged app's icons 2024-07-17 14:58:19 +02:00
seraphima
158f4ee222 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 14:31:09 +02:00
donlaci
5aa4580bdb fixing: update of listed projects on the main page after hitting save in editor 2024-07-17 13:32:40 +02:00
donlaci
dcc0634fb1 Implementing Launch and Edit feature 2024-07-16 09:37:40 +02:00
seraphima
d218e36a77 get app elevated property 2024-07-16 07:17:50 +02:00
seraphima
89d6aed118 Revert "Hide Admin checkbox"
This reverts commit 3036df9d7f.
2024-07-15 20:20:26 +02:00
seraphima
a7e5c0330b spell-check 2024-07-15 16:47:39 +02:00
seraphima
b5251c27e9 Merge branches 'dev/feature/projects' and 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-15 16:44:56 +02:00
seraphima
16a71e2acd hide WIP 2024-07-15 16:44:52 +02:00
donlaci
3036df9d7f Hide Admin checkbox 2024-07-15 16:37:00 +02:00
seraphima
12308babc7 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-07-15 15:43:21 +02:00
seraphima
5576c7b23f clean up 2024-07-13 21:07:37 +02:00
seraphima
8620d80047 launcher project filters 2024-07-11 14:01:15 +02:00
seraphima
be202df44b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-11 13:35:41 +02:00
donlaci
3fa5aae70c modifying colors 2024-07-11 13:25:47 +02:00
donlaci
dae91d2874 Implementing major new features: remove button, position manipulation, arguments, admin, minimized, maximized 2024-07-11 12:45:19 +02:00
seraphima
2e2e47a039 FZ: ignore projects launched windows 2024-07-10 19:03:36 +02:00
seraphima
06ecbd58b8 set window property after launching 2024-07-10 19:02:52 +02:00
seraphima
fc8918e538 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-09 20:19:48 +02:00
seraphima
f93aa42b19 minimized settings snap fix 2024-07-09 20:19:06 +02:00
seraphima
cbedf45796 get command line args in the snapshot 2024-07-09 20:18:42 +02:00
seraphima
7b8e229544 remove unused from the project 2024-07-09 20:15:57 +02:00
donlaci
d7467e24ae Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-09 15:22:57 +02:00
donlaci
c493d27dd3 Remove checkboxes, delete feature 2024-07-09 15:22:10 +02:00
seraphima
b48c8aa80b spell-check 2024-07-09 11:35:32 +02:00
seraphima
deb1680f91 changed project creation save-cancel handles
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 16:01:07 +02:00
seraphima
647ea2db1e changed project data
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 16:00:24 +02:00
seraphima
6516af8a7a change snapshot project saving
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 15:58:14 +02:00
seraphima
d0775dac28 clean up 2024-07-04 12:25:22 +02:00
seraphima
9906141322 launcher resources 2024-07-04 12:24:47 +02:00
seraphima
353aeb268b localizable default project prefix 2024-07-04 12:01:00 +02:00
seraphima
83ee449b1b projects common lib 2024-07-04 11:42:37 +02:00
seraphima
9a3519cd0a refactoring 2024-07-03 14:50:39 +02:00
seraphima
54ed4834a8 clean up 2024-07-02 15:48:06 +02:00
seraphima
c00ed8f996 changed window filter
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/2
2024-07-02 15:47:24 +02:00
seraphima
db8935dca3 refactoring: added utils 2024-07-02 14:26:47 +02:00
seraphima
10a3f1162f get dpi unaware screen bounds 2024-07-01 19:53:26 +02:00
donlaci
fd5e4590d7 Minor fixes in the capture dialog 2024-06-29 11:37:42 +02:00
donlaci
907a6f5ec9 spell checker 2024-06-28 18:07:37 +02:00
donlaci
3ea27571f3 spell checker 2024-06-28 18:03:02 +02:00
donlaci
ed423d8264 Adding helper class for getting the right bounds for screens 2024-06-28 17:58:28 +02:00
donlaci
9733c1035b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 16:15:48 +02:00
donlaci
477094057d Fixing bug: re-draw the preview on app deletion in the editor 2024-06-28 16:15:06 +02:00
seraphima
1912f1c533 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 16:12:08 +02:00
donlaci
550f7ec08d modifying highlight for minimized apps 2024-06-28 16:11:10 +02:00
seraphima
aae69ab125 mutex fix 2024-06-28 15:41:26 +02:00
seraphima
8344060e5e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 15:40:57 +02:00
donlaci
54a8f47df7 minor bugfix 2024-06-28 15:25:04 +02:00
donlaci
d9e92eca01 Add intermediate step to project creation 2024-06-28 15:00:48 +02:00
seraphima
2643f00fa1 exclude help windows
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/49
2024-06-28 14:50:10 +02:00
seraphima
8f076cb5da Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 14:49:30 +02:00
seraphima
9b8d86858f logs 2024-06-28 14:49:20 +02:00
donlaci
c94c0431f4 spell checker 2024-06-28 13:07:30 +02:00
donlaci
64c5439b60 modifying highlight in preview 2024-06-28 13:02:32 +02:00
donlaci
e0bdba9772 Add Checkbox for per monitor selection 2024-06-27 20:10:44 +02:00
donlaci
c4a93d2482 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-27 18:59:12 +02:00
donlaci
4960af8a13 [Projects] Add Select all checkbox, Delete selected button 2024-06-27 18:58:16 +02:00
seraphima
a6b223083c projects editor single instance 2024-06-27 14:02:49 +02:00
seraphima
dc9af4bc30 update packaged apps path 2024-06-26 17:40:57 +02:00
seraphima
18e1d851fe spell-check 2024-06-26 16:20:56 +02:00
seraphima
663b0281c2 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-26 15:56:37 +02:00
seraphima
3c694e428c launch packaged apps using names when possible 2024-06-26 15:56:30 +02:00
Jaime Bernardo
98ef8c4e70 Fix ARM64 CI build 2024-06-26 11:21:13 +01:00
seraphima
c89b4bb9fd fix launching with command line args 2024-06-24 16:34:17 +02:00
donlaci
17a607fda0 [Projects] fix boundary calculation, use DPI aware values 2024-06-24 10:38:08 +02:00
donlaci
371a05ac5e [Projects] Re-implementing preview drawing - one common image 2024-06-24 09:34:47 +02:00
seraphima
65e75d1c42 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-23 23:15:32 +02:00
seraphima
15ae5a03dc app placement 2024-06-23 23:15:26 +02:00
seraphima
f39e4bb058 set awareness 2024-06-23 23:15:06 +02:00
seraphima
0679664d4c unsigned monitor number 2024-06-23 23:14:57 +02:00
seraphima
5e012b8891 added convert rect 2024-06-23 23:11:24 +02:00
seraphima
77c59faa8a moved display utils 2024-06-23 23:10:54 +02:00
seraphima
ed7d3ec973 moved on thread executor to common 2024-06-23 13:17:29 +02:00
donlaci
26bbefa004 [Projects] Removing not selected apps on save 2024-06-20 13:31:56 +02:00
donlaci
e6b56c4152 [Projects] optimizing click handlers 2024-06-20 13:25:32 +02:00
donlaci
4c4630ef75 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-20 13:23:18 +02:00
donlaci
6fc164df98 [Projects] bringing back the breadcrumb on the editor page. Make it clickable. 2024-06-20 13:22:02 +02:00
seraphima
5f42c6ef66 fix placing windows of the same app in the project 2024-06-20 12:54:47 +02:00
seraphima
1e28cb8c40 remove opening the first proj 2024-06-20 12:54:39 +02:00
seraphima
0c83e632ed changed "no projects" text color and position 2024-06-20 11:29:13 +02:00
donlaci
852d67b569 [Projects] Making popup having rounded corners 2024-06-20 11:02:04 +02:00
donlaci
01d6c916d6 [Projects] Adding Edit button to the popup. + minor changes 2024-06-19 15:17:25 +02:00
donlaci
90979cbecb [projects] Adding info message for cases: there are no projects or no results for the search 2024-06-19 14:58:29 +02:00
donlaci
20a7dd4a7f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 12:29:17 +02:00
donlaci
ba19333ad7 [Projects] re-implementing icon size calculation to have similar sized icons for every app. 2024-06-19 12:28:09 +02:00
seraphima
6eafa57c68 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:17:20 +02:00
seraphima
8c450f3457 spellcheck 2024-06-19 11:16:58 +02:00
donlaci
0137bc9697 spell checker 2024-06-19 11:11:27 +02:00
donlaci
e4ccde49f7 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:03:55 +02:00
donlaci
0efe6c91e8 [Projects] Editor: brining the highlighted app's icon into the foreground. + minor UI fixes 2024-06-19 11:02:41 +02:00
seraphima
a62d95c8fa shortcut saving fix 2024-06-19 00:06:57 +02:00
seraphima
ba13f5c0c4 update projects names to filter them out 2024-06-18 22:10:48 +02:00
seraphima
bd50d6961d editor dll signing 2024-06-18 16:44:23 +02:00
seraphima
fbb9f4188f spellcheck 2024-06-17 20:40:16 +02:00
seraphima
ae91aa3869 editor version 2024-06-17 20:40:07 +02:00
seraphima
4cccbecf54 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 19:57:14 +02:00
seraphima
0e74b2ee6b version 2024-06-17 19:57:09 +02:00
donlaci
f7cab16fb6 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 15:22:34 +02:00
donlaci
bfb569e894 Do not allow saving project if name or applist is empty. Also minor UI changes 2024-06-17 15:21:13 +02:00
seraphima
ce39ef2360 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 14:09:35 +02:00
seraphima
0c3108bca1 arm build fix 2024-06-17 14:09:31 +02:00
donlaci
28bf6de36c [Projects] fixing general settings gpo handling in runner + minor changes 2024-06-17 10:31:34 +02:00
seraphima
3de23f1c82 guid prefix 2024-06-14 16:26:20 +02:00
seraphima
198b7a6890 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-06-14 12:52:38 +02:00
seraphima
f9fe9cc204 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-14 12:15:10 +02:00
seraphima
5d09e2e821 pipeline 2024-06-14 12:14:44 +02:00
seraphima
9914d31d6b github 2024-06-14 12:14:13 +02:00
seraphima
add5078f43 dsc 2024-06-14 12:13:41 +02:00
donlaci
68a66ff03d [Projects] Editor: Main page: fix layout if there are many apps, launch button not disappearing on the right side 2024-06-14 11:50:56 +02:00
donlaci
9cac577b7f [Projects] fix grammatical issue #43 (1 app - many apps) 2024-06-14 10:50:26 +02:00
donlaci
cc9b7d62df extend search for projects by search over the containing apps' names 2024-06-14 10:36:09 +02:00
seraphima
27d0620cac exit event constant 2024-06-13 23:11:26 +02:00
seraphima
6a353e0941 gpo 2024-06-13 23:07:43 +02:00
seraphima
73146e844e installer 2024-06-13 22:55:36 +02:00
seraphima
07ab6191d0 bug report tool 2024-06-13 21:52:04 +02:00
seraphima
4a672e2ed5 rename projects editor 2024-06-13 21:30:26 +02:00
seraphima
f869f99144 module interface 2024-06-13 21:30:03 +02:00
seraphima
2493fd6a1a changed the default hotkey 2024-06-13 21:29:08 +02:00
donlaci
64c0ca77bf Adding OOBE Projects page 2024-06-13 17:32:34 +02:00
donlaci
0bdfa32cd0 spell checker 2024-06-13 13:31:06 +02:00
donlaci
8f69109689 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-13 13:24:25 +02:00
donlaci
5abba947d8 adding GPO role + wrappers + GPO-check methods 2024-06-13 13:20:21 +02:00
seraphima
12bc9d7206 spelling 2024-06-13 12:34:08 +02:00
seraphima
a5c21d3432 snapshot logger 2024-06-12 22:00:11 +02:00
seraphima
6bfe924234 remove quotation marks 2024-06-12 21:47:31 +02:00
seraphima
3cc3701465 launcher logger 2024-06-12 21:45:39 +02:00
seraphima
71c7241fe1 common dependencies 2024-06-12 19:31:43 +02:00
seraphima
ae6cb122c8 init projects 2024-06-12 17:52:28 +02:00
seraphima
6fba21d9ad update data folders 2024-06-12 17:52:04 +02:00
seraphima
7e93d1f767 updated app names in editor 2024-06-12 17:45:27 +02:00
seraphima
e08aa76085 changed editor output path 2024-06-12 17:42:38 +02:00
seraphima
e5bbeb738e updated SnapshotTool project 2024-06-12 17:24:50 +02:00
seraphima
404a189c87 update Launcher project 2024-06-12 17:24:19 +02:00
seraphima
26dd8ea18e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-12 14:13:10 +02:00
seraphima
80f4611b0a shortcut icons 2024-06-12 13:16:34 +02:00
seraphima
a5b69d9be5 optimized launching 2024-06-11 15:43:42 +02:00
donlaci
927ac511c8 First steps toward integration of the projects utility to PT 2024-06-11 13:32:40 +02:00
seraphima
07e0ef8a2c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 15:58:59 +02:00
donlaci
1cf5d88507 minor adjustments on the not found warning icon 2024-06-10 15:57:28 +02:00
seraphima
1a33257314 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 14:19:34 +02:00
donlaci
6c7b99fbbc Adding not found icon + logic for not present detection. Moving icon drawing into drawhelper class. adjusting "selected cursor" size for preview drawing 2024-06-10 10:19:57 +02:00
seraphima
29e0bad3ea changed waiting time 2024-06-06 16:47:48 +02:00
seraphima
67a336bc46 checkbox default style 2024-06-05 19:10:50 +02:00
seraphima
40f2572b22 save-cancel buttons style 2024-06-05 19:10:38 +02:00
seraphima
cdb40a91fa remove unused 2024-06-05 19:10:23 +02:00
seraphima
3dea02a981 updated light theme colors 2024-06-05 18:58:30 +02:00
seraphima
4b371726c2 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 16:30:37 +02:00
seraphima
93bda77554 removed unused package 2024-06-05 16:30:25 +02:00
seraphima
47ccfde156 removed log 2024-06-05 16:30:14 +02:00
seraphima
782a2e1b1e check single-instance apps running 2024-06-05 16:28:00 +02:00
seraphima
5c600ebc92 moved app utils 2024-06-05 16:27:40 +02:00
donlaci
b07b15cf42 Modifying preview drawing colors for light theme. 2024-06-05 13:58:39 +02:00
donlaci
910b259ce1 fixing colors of the remove button (dark and light mode) 2024-06-05 12:53:59 +02:00
seraphima
3d75b8471c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 11:28:45 +02:00
seraphima
f5cc0cba40 detecting and placing new windows 2024-06-05 11:28:35 +02:00
donlaci
449078be8e minor modification in light mode colors 2024-06-05 09:53:01 +02:00
donlaci
690b537e9f spell check 2024-06-04 12:55:05 +02:00
donlaci
af49b36d20 adding shortcut icon drawing. Minor changes 2024-06-04 12:45:50 +02:00
seraphima
9867395b5b moved utils to common 2024-06-03 16:49:08 +02:00
donlaci
cb2a4ec6e9 corrections in the snippet tool.
extending icon handling in the editor: for packaged apps look for the exe in the current path
adding shortcut icon creation.
2024-05-31 19:09:45 +02:00
donlaci
7e80c1bf73 spell checker 2024-05-29 13:51:28 +02:00
donlaci
24add7d4f8 adding dependent projects to the projects solution 2024-05-28 19:43:32 +02:00
donlaci
e11deec96f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-28 18:26:36 +02:00
donlaci
feba2e6f17 adding logs 2024-05-28 18:21:30 +02:00
seraphima
c18c70afdb spellcheck 2024-05-27 21:44:42 +02:00
seraphima
aa788dad04 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-27 17:01:06 +02:00
seraphima
42905045b4 launch 2024-05-27 16:33:56 +02:00
seraphima
ccaf327baa run launcher 2024-05-27 16:33:30 +02:00
seraphima
6f813d9e66 app name fix 2024-05-27 16:33:13 +02:00
seraphima
2b71d97449 removed hwnd from json 2024-05-27 16:23:03 +02:00
seraphima
9f61f0793d updated app data 2024-05-27 15:15:34 +02:00
seraphima
cc553a60d5 get packaged app info 2024-05-27 15:07:53 +02:00
donlaci
3cd57eee63 creating drawing helper class to separate drawing functionallity 2024-05-24 15:36:01 +02:00
seraphima
6f6c32f989 update packages 2024-05-24 13:50:27 +02:00
seraphima
15c3956eda spelling 2024-05-21 17:48:58 +02:00
seraphima
ce19c4e5ff spelling 2024-05-21 17:45:07 +02:00
seraphima
c157e64f28 Projects 2024-05-21 16:55:15 +02:00
105 changed files with 1011 additions and 2749 deletions

View File

@@ -28,7 +28,7 @@ videoconference
# USERS
LWXpg # (number eight)LWXpg is actual user name but spell checker throws error with a numeric leading value ... which is kinda odd
8LWXpg
Adoumie
Advaith
alekhyareddy
@@ -62,7 +62,6 @@ DHowett
ductdo
Essey
ethanfangg
ferraridavide
frankychen
gabime
Galaxi

View File

@@ -117,7 +117,6 @@
^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$
^\Qtools/project_template/ModuleTemplate/resource.h\E$
^doc/devdocs/akaLinks\.md$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$

View File

@@ -192,6 +192,7 @@ CLIPBOARDUPDATE
CLIPCHILDREN
CLIPSIBLINGS
closesocket
clrcall
CLSCTX
Clusion
cmder
@@ -202,8 +203,8 @@ CMINVOKECOMMANDINFO
CMINVOKECOMMANDINFOEX
CMock
CMONITORS
cmpgt
cmph
cmpgt
cne
CNF
coclass
@@ -250,7 +251,10 @@ countof
cph
CPower
cppblog
cppruntime
cppstd
cppwinrt
CProj
createdump
CREATESCHEDULEDTASK
CREATESTRUCT
@@ -605,6 +609,7 @@ hmenu
hmodule
hmonitor
homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
Hostbackdropbrush
hotkeycontrol
hotkeys
@@ -672,6 +677,7 @@ imageresizerinput
imageresizersettings
imagingdevices
ime
imperialounce
inetcpl
Infobar
INFOEXAMPLE
@@ -933,6 +939,7 @@ MRT
mru
mrw
msc
msclr
mscorlib
msdata
msedge
@@ -1112,7 +1119,6 @@ PATINVERT
PATPAINT
PAUDIO
pbc
pbi
PBlob
pcb
pcch
@@ -1126,7 +1132,6 @@ pdo
pdto
pdtobj
pdw
Peb
pef
PElems
Pels
@@ -1509,6 +1514,7 @@ STATICEDGE
STATSTG
stdafx
STDAPI
stdcpp
stdcpplatest
STDMETHODCALLTYPE
STDMETHODIMP
@@ -1680,6 +1686,7 @@ Userenv
USESHOWWINDOW
USESIZE
USESTDHANDLES
usounce
USRDLL
UType
uuidv
@@ -1716,7 +1723,6 @@ VIDEOINFOHEADER
viewmodel
vih
VIRTUALDESK
VISEGRADRELAY
visiblecolorformats
Visibletrue
visualeffects

View File

@@ -79,7 +79,7 @@
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UnitsNet" Version="5.50.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="WinUIEx" Version="2.2.0" />
<PackageVersion Include="WPF-UI" Version="3.0.0" />

View File

@@ -1363,7 +1363,7 @@ EXHIBIT A -Mozilla Public License.
- System.Text.Encoding.CodePages 8.0.0
- System.Text.Json 8.0.4
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UnitsNet 5.50.0
- UTF.Unknown 2.5.1
- WinUIEx 2.2.0
- WPF-UI 3.0.0

View File

@@ -10,23 +10,12 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
| Build ID | Build Date |
|:-------------------------------------------------------------------|:----------------|
| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
### `VISEGRADRELAY_08152024` (August 15, 2024)
>[!NOTE]
>See pull request: [Awake - `VISEGRADRELAY_08152024`](https://github.com/microsoft/PowerToys/pull/34316)
- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixes the issue where the Awake icon is not displayed.
- [#17969](https://github.com/microsoft/PowerToys/issues/17969) Add the ability to bind the process target to the parent of the Awake launcher.
- PID binding now correctly ignores irrelevant parameters (e.g., expiration, interval) and only works for indefinite periods.
- Amending the native API surface to make sure that the Win32 error is set correctly.
| Build ID | Build Date |
|:----------------------------------------------------------|:-----------------|
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
### `DAISY023_04102024` (April 10, 2024)

View File

@@ -38,7 +38,6 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [PowerHexInspector](https://github.com/NaroZeol/PowerHexInspector) | [NaroZeol](https://github.com/NaroZeol) | Peek other forms of an input number |
| [GitHubRepo](https://github.com/8LWXpg/PowerToysRun-GitHubRepo) | [8LWXpg](https://github.com/8LWXpg) | Search and open GitHub repositories |
| [ProcessKiller](https://github.com/8LWXpg/PowerToysRun-ProcessKiller) | [8LWXpg](https://github.com/8LWXpg) | Search and kill processes |
| [ChatGPT](https://github.com/ferraridavide/ChatGPTPowerToys) | [ferraridavide](https://github.com/ferraridavide) | Ask a question to ChatGPT |
## Extending software plugins

View File

@@ -51,21 +51,17 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT;
}
hstring Constants::AdvancedPasteShowUIMessage()
hstring Constants::ShowAdvancedPasteSharedEvent()
{
return CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE;
return CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT;
}
hstring Constants::AdvancedPasteMarkdownMessage()
hstring Constants::AdvancedPasteMarkdownEvent()
{
return CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE;
return CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT;
}
hstring Constants::AdvancedPasteJsonMessage()
hstring Constants::AdvancedPasteJsonEvent()
{
return CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE;
}
hstring Constants::AdvancedPasteCustomActionMessage()
{
return CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE;
return CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT;
}
hstring Constants::ShowPowerOCRSharedEvent()
{
@@ -151,8 +147,4 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT;
}
hstring Constants::WorkspacesHotkeyEvent()
{
return CommonSharedConstants::WORKSPACES_HOTKEY_EVENT;
}
}

View File

@@ -16,10 +16,9 @@ namespace winrt::PowerToys::Interop::implementation
static hstring FZEToggleEvent();
static hstring ColorPickerSendSettingsTelemetryEvent();
static hstring ShowColorPickerSharedEvent();
static hstring AdvancedPasteShowUIMessage();
static hstring AdvancedPasteMarkdownMessage();
static hstring AdvancedPasteJsonMessage();
static hstring AdvancedPasteCustomActionMessage();
static hstring ShowAdvancedPasteSharedEvent();
static hstring AdvancedPasteMarkdownEvent();
static hstring AdvancedPasteJsonEvent();
static hstring ShowPowerOCRSharedEvent();
static hstring MouseJumpShowPreviewEvent();
static hstring AwakeExitEvent();
@@ -41,7 +40,6 @@ namespace winrt::PowerToys::Interop::implementation
static hstring ShowEnvironmentVariablesSharedEvent();
static hstring ShowEnvironmentVariablesAdminSharedEvent();
static hstring WorkspacesLaunchEditorEvent();
static hstring WorkspacesHotkeyEvent();
};
}

View File

@@ -13,10 +13,9 @@ namespace PowerToys
static String FZEToggleEvent();
static String ColorPickerSendSettingsTelemetryEvent();
static String ShowColorPickerSharedEvent();
static String AdvancedPasteShowUIMessage();
static String AdvancedPasteMarkdownMessage();
static String AdvancedPasteJsonMessage();
static String AdvancedPasteCustomActionMessage();
static String ShowAdvancedPasteSharedEvent();
static String AdvancedPasteMarkdownEvent();
static String AdvancedPasteJsonEvent();
static String ShowPowerOCRSharedEvent();
static String MouseJumpShowPreviewEvent();
static String AwakeExitEvent();
@@ -38,7 +37,6 @@ namespace PowerToys
static String ShowEnvironmentVariablesSharedEvent();
static String ShowEnvironmentVariablesAdminSharedEvent();
static String WorkspacesLaunchEditorEvent();
static String WorkspacesHotkeyEvent();
}
}
}

View File

@@ -25,14 +25,12 @@ namespace CommonSharedConstants
const wchar_t COLOR_PICKER_SEND_SETTINGS_TELEMETRY_EVENT[] = L"Local\\ColorPickerSettingsTelemetryEvent-6c7071d8-4014-46ec-b687-913bd8a422f1";
// IPC Messages used in Advanced Paste
const wchar_t ADVANCED_PASTE_SHOW_UI_MESSAGE[] = L"ShowUI";
// Path to the event used to show Advanced Paste UI
const wchar_t SHOW_ADVANCED_PASTE_SHARED_EVENT[] = L"Local\\ShowAdvancedPasteEvent-9a46be2a-3e05-4186-b56b-4ae986ef2526";
const wchar_t ADVANCED_PASTE_MARKDOWN_MESSAGE[] = L"PasteMarkdown";
const wchar_t ADVANCED_PASTE_MARKDOWN_EVENT[] = L"Local\\AdvancedPasteJsonEvent-a18c0798-3ee6-4fc5-bb9f-114c57ac0d47";
const wchar_t ADVANCED_PASTE_JSON_MESSAGE[] = L"PasteJson";
const wchar_t ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE[] = L"CustomAction";
const wchar_t ADVANCED_PASTE_JSON_EVENT[] = L"Local\\AdvancedPasteJsonEvent-9ed021ab-b711-4cf3-9f33-135a698a9d21";
// Path to the event used to show Color Picker
const wchar_t SHOW_COLOR_PICKER_SHARED_EVENT[] = L"Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525";
@@ -43,9 +41,7 @@ namespace CommonSharedConstants
const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14";
// Path to the event used by Workspaces
const wchar_t WORKSPACES_LAUNCH_EDITOR_EVENT[] = L"Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
const wchar_t WORKSPACES_HOTKEY_EVENT[] = L"Local\\PowerToys-Workspaces-HotkeyEvent-2625C3C8-BAC9-4DB3-BCD6-3B4391A26FD0";
const wchar_t SHOW_HOSTS_EVENT[] = L"Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0";
@@ -102,6 +98,8 @@ namespace CommonSharedConstants
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
const wchar_t WORKSPACES_EXIT_EVENT[] = L"Local\\PowerToys-Workspaces-ExitEvent-29a1566f-f4f8-4d56-9435-d2a437f727c6";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@@ -24,17 +24,4 @@ properties:
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
FileLocksmith:
Enabled: false
ImageResizer:
ImageResizerSizes:
- Name: Square2x
Width: 200
Height: 200
Unit: "Percent"
Fit: "Stretch"
- Name: MyInchSize
Width: 1024
Height: 1024
Unit: "Inch"
Fit: "Fit"
configurationVersion: 0.2.0

View File

@@ -21,7 +21,7 @@ internal sealed class DSCGeneration
public string Type;
}
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } }, { "ImageResizer", new AdditionalPropertiesInfo { Name = "ImageresizerSizes", Type = "Hashtable[]" } } };
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } } };
private static string EmitEnumDefinition(Type type)
{

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.12" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.11" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@@ -10,6 +10,7 @@
<string id="InstallerUpdates">Installer and Updates</string>
<string id="PowerToysRun">PowerToys Run</string>
<string id="AdvancedPaste">Advanced Paste</string>
<string id="Workspaces">Workspaces</string>
<string id="MouseWithoutBorders">Mouse Without Borders</string>
<string id="GeneralSettings">General settings</string>
@@ -111,6 +112,12 @@ If you don't configure this setting, users are able to enable or disable the plu
You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins".
Note: Changes require a restart of PowerToys Run.
</string>
<string id="ConfigureEnabledUtilityWorkspaces">This policy configures the enabled disable state for the Workspaces utility.
If you enable or don't configure this policy, the user takes control over the enabled state of the Workspaces utility.
If you disable this policy, the user won't be able to enable Enable and use the Workspaces utility.
</string>
<string id="PowerToysRunIndividualPluginEnabledStateDescription">With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list.

View File

@@ -3,10 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Settings;
using AdvancedPaste.ViewModels;
@@ -18,7 +14,6 @@ using Microsoft.UI.Xaml;
using Windows.Graphics;
using WinUIEx;
using static AdvancedPaste.Helpers.NativeMethods;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -31,13 +26,12 @@ namespace AdvancedPaste
{
public IHost Host { get; private set; }
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly OptionsViewModel viewModel;
private MainWindow window;
private nint windowHwnd;
private OptionsViewModel viewModel;
private bool disposedValue;
/// <summary>
@@ -80,7 +74,7 @@ namespace AdvancedPaste
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs?.Length > 1)
@@ -94,44 +88,9 @@ namespace AdvancedPaste
}
}
if (cmdArgs?.Length > 2)
{
ProcessNamedPipe(cmdArgs[2]);
}
}
private void ProcessNamedPipe(string pipeName)
{
void OnMessage(string message) => _dispatcherQueue.TryEnqueue(() => OnNamedPipeMessage(message));
Task.Run(async () =>
{
var connectTimeout = TimeSpan.FromSeconds(10);
await NamedPipeProcessor.ProcessNamedPipeAsync(pipeName, connectTimeout, OnMessage, CancellationToken.None);
});
}
private void OnNamedPipeMessage(string message)
{
var messageParts = message.Split();
var messageType = messageParts.First();
if (messageType == PowerToys.Interop.Constants.AdvancedPasteShowUIMessage())
{
OnAdvancedPasteHotkey();
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
{
OnAdvancedPasteMarkdownHotkey();
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
{
OnAdvancedPasteJsonHotkey();
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteCustomActionMessage())
{
OnAdvancedPasteCustomActionHotkey(messageParts);
}
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.ShowAdvancedPasteSharedEvent(), OnAdvancedPasteHotkey);
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteMarkdownEvent(), OnAdvancedPasteMarkdownHotkey);
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteJsonEvent(), OnAdvancedPasteJsonHotkey);
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
@@ -141,43 +100,17 @@ namespace AdvancedPaste
private void OnAdvancedPasteJsonHotkey()
{
viewModel.ReadClipboard();
viewModel.GetClipboardData();
viewModel.ToJsonFunction(true);
}
private void OnAdvancedPasteMarkdownHotkey()
{
viewModel.ReadClipboard();
viewModel.GetClipboardData();
viewModel.ToMarkdownFunction(true);
}
private void OnAdvancedPasteHotkey()
{
ShowWindow();
}
private void OnAdvancedPasteCustomActionHotkey(string[] messageParts)
{
if (messageParts.Length != 2)
{
Logger.LogWarning("Unexpected custom action message");
}
else
{
if (!int.TryParse(messageParts[1], CultureInfo.InvariantCulture, out int customActionId))
{
Logger.LogWarning($"Unexpected custom action message id {messageParts[1]}");
}
else
{
ShowWindow();
viewModel.ReadClipboard();
viewModel.ExecuteCustomActionWithPaste(customActionId);
}
}
}
private void ShowWindow()
{
viewModel.OnShow();

View File

@@ -3,11 +3,10 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
xmlns:converters="using:AdvancedPaste.Converters"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AdvancedPaste.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<UserControl.Resources>
@@ -324,12 +323,7 @@
</Setter.Value>
</Setter>
</Style>
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<tkconverters:BoolToVisibilityConverter
x:Key="BoolToInvertedVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
<converters:CountToVisibilityConverter x:Key="CountToVisibilityConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="PromptBoxGrid" Loaded="Grid_Loaded">
@@ -346,12 +340,13 @@
x:Name="InputTxtBox"
HorizontalAlignment="Stretch"
x:FieldModifier="public"
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
KeyDown="InputTxtBox_KeyDown"
PlaceholderText="{x:Bind ViewModel.InputTxtBoxPlaceholderText, Mode=OneWay}"
Style="{StaticResource CustomTextBoxStyle}"
TabIndex="0"
Text="{x:Bind ViewModel.Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
Text="{x:Bind Prompt, Mode=TwoWay}"
TextChanging="InputTxtBox_TextChanging">
<ToolTipService.ToolTip>
<TextBlock x:Uid="InputTxtBoxTooltip" />
</ToolTipService.ToolTip>
@@ -536,63 +531,48 @@
<animations:OffsetAnimation Duration="0:0:1" />
</animations:Implicit.Animations>
</Button>-->
<Grid
<Button
x:Name="SendBtn"
x:Uid="SendButtonAutomation"
Width="32"
Height="32"
Margin="0,0,4,0"
Padding="0"
HorizontalAlignment="Right"
VerticalAlignment="Stretch">
<Button
x:Name="SendBtn"
x:Uid="SendButtonAutomation"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
Command="{x:Bind GenerateCustomCommand}"
Content="{ui:FontIcon Glyph=&#xE724;,
FontSize=16}"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
Style="{StaticResource SubtleButtonStyle}"
TabIndex="1"
Visibility="{x:Bind ViewModel.Query.Length, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
</ToolTipService.ToolTip>
<animations:Implicit.ShowAnimations>
<animations:ScaleAnimation
From="0.4"
To="1"
Duration="0:0:0.167" />
<animations:OpacityAnimation
From="0.0"
To="1.0"
Duration="0:0:0.167" />
</animations:Implicit.ShowAnimations>
<animations:Implicit.HideAnimations>
<animations:ScaleAnimation
From="1"
To="0.4"
Duration="0:0:0.167" />
<animations:OpacityAnimation
From="1.0"
To="0.0"
Duration="0:0:0.167" />
</animations:Implicit.HideAnimations>
</Button>
<!-- Transparent overlay to show tooltip -->
<Grid
x:Name="SendBtnOverlay"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
Visibility="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ViewModel.GeneralErrorText}" />
</ToolTipService.ToolTip>
</Grid>
</Grid>
VerticalAlignment="Stretch"
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
Command="{x:Bind GenerateCustomCommand}"
Content="{ui:FontIcon Glyph=&#xE724;,
FontSize=16}"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Style="{StaticResource SubtleButtonStyle}"
TabIndex="1"
Visibility="Collapsed">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
</ToolTipService.ToolTip>
<animations:Implicit.ShowAnimations>
<animations:ScaleAnimation
From="0.4"
To="1"
Duration="0:0:0.167" />
<animations:OpacityAnimation
From="0.0"
To="1.0"
Duration="0:0:0.167" />
</animations:Implicit.ShowAnimations>
<animations:Implicit.HideAnimations>
<animations:ScaleAnimation
From="1"
To="0.4"
Duration="0:0:0.167" />
<animations:OpacityAnimation
From="1.0"
To="0.0"
Duration="0:0:0.167" />
</animations:Implicit.HideAnimations>
</Button>
<!--</StackPanel>-->
</Grid>
</local:AnimatedContentControl>
<ContentPresenter
@@ -638,7 +618,7 @@
FontWeight="SemiBold"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.ApiErrorText, Mode=OneWay}" />
Text="{x:Bind ViewModel.InputTxtBoxErrorText, Mode=OneWay}" />
<HyperlinkButton
x:Uid="SettingsBtn"
Grid.Column="1"
@@ -655,6 +635,7 @@
<animations:OpacityAnimation To="0.0" Duration="0:0:0.167" />
</animations:Implicit.HideAnimations>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="DefaultState" />

View File

@@ -2,7 +2,6 @@
// 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.Net;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
@@ -19,14 +18,23 @@ namespace AdvancedPaste.Controls
{
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
{
// Minimum time to show spinner when generating custom format using forcePasteCustom
private static readonly TimeSpan MinTaskTime = TimeSpan.FromSeconds(2);
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly IUserSettings _userSettings;
public static readonly DependencyProperty PromptProperty = DependencyProperty.Register(
nameof(Prompt),
typeof(string),
typeof(PromptBox),
new PropertyMetadata(defaultValue: string.Empty));
public OptionsViewModel ViewModel { get; private set; }
public string Prompt
{
get => (string)GetValue(PromptProperty);
set => SetValue(PromptProperty, value);
}
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
nameof(PlaceholderText),
typeof(string),
@@ -58,7 +66,6 @@ namespace AdvancedPaste.Controls
_userSettings = App.GetService<IUserSettings>();
ViewModel = App.GetService<OptionsViewModel>();
ViewModel.CustomActionActivated += (_, e) => GenerateCustom(e.ForcePasteCustom);
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
@@ -67,30 +74,27 @@ namespace AdvancedPaste.Controls
}
[RelayCommand]
private void GenerateCustom() => GenerateCustom(false);
private void GenerateCustom(bool forcePasteCustom)
private void GenerateCustom()
{
Logger.LogTrace();
VisualStateManager.GoToState(this, "LoadingState", true);
string inputInstructions = ViewModel.Query;
string inputInstructions = InputTxtBox.Text;
ViewModel.SaveQuery(inputInstructions);
var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions);
var delayTask = forcePasteCustom ? Task.Delay(MinTaskTime) : Task.CompletedTask;
Task.WhenAll(customFormatTask, delayTask)
.ContinueWith(
_ =>
customFormatTask.ContinueWith(
t =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ViewModel.CustomFormatResult = customFormatTask.Result;
ViewModel.CustomFormatResult = t.Result;
if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK)
{
VisualStateManager.GoToState(this, "DefaultState", true);
if (_userSettings.ShowCustomPreview && !forcePasteCustom)
if (_userSettings.ShowCustomPreview)
{
PreviewGrid.Width = InputTxtBox.ActualWidth;
PreviewFlyout.ShowAt(InputTxtBox);
@@ -126,9 +130,14 @@ namespace AdvancedPaste.Controls
ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData);
}
private void InputTxtBox_TextChanging(Microsoft.UI.Xaml.Controls.TextBox sender, TextBoxTextChangingEventArgs args)
{
SendBtn.Visibility = InputTxtBox.Text.Length > 0 ? Visibility.Visible : Visibility.Collapsed;
}
private void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0 && ViewModel.IsCustomAIEnabled)
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0)
{
GenerateCustom();
}

View File

@@ -1,25 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using Microsoft.UI.Xaml.Data;
namespace AdvancedPaste.Converters;
public sealed class CountToDoubleConverter : IValueConverter
{
public double ValueIfZero { get; set; }
public double ValueIfNonZero { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
return hasCount ? ValueIfNonZero : ValueIfZero;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

View File

@@ -1,33 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace AdvancedPaste.Converters;
public sealed class CountToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
if (targetType == typeof(Visibility))
{
return hasCount ? Visibility.Visible : Visibility.Collapsed;
}
else if (targetType == typeof(bool))
{
return hasCount;
}
else
{
throw new ArgumentOutOfRangeException(nameof(targetType));
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
namespace AdvancedPaste.Converters
{
public sealed class ListViewIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var presenter = value as ListViewItemPresenter;
var item = VisualTreeHelper.GetParent(presenter) as ListViewItem;
var listView = ItemsControl.ItemsControlFromItemContainer(item);
int index = listView.IndexFromContainer(item) + 1;
#pragma warning disable CA1305 // Specify IFormatProvider
return index.ToString();
#pragma warning restore CA1305 // Specify IFormatProvider
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -26,18 +26,6 @@ namespace AdvancedPaste
_userSettings = App.GetService<IUserSettings>();
var baseHeight = MinHeight;
void UpdateHeight()
{
var trimmedCustomActionCount = Math.Min(_userSettings.CustomActions.Count, 5);
Height = MinHeight = baseHeight + (trimmedCustomActionCount * 40);
}
UpdateHeight();
_userSettings.CustomActions.CollectionChanged += (_, _) => UpdateHeight();
AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico");
this.ExtendsContentIntoTitleBar = true;
this.SetTitleBar(titleBar);

View File

@@ -5,21 +5,14 @@
xmlns:controls="using:AdvancedPaste.Controls"
xmlns:converters="using:AdvancedPaste.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:library="using:Microsoft.PowerToys.Settings.UI.Library"
xmlns:local="using:AdvancedPaste.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
KeyDown="Page_KeyDown"
KeyboardAcceleratorPlacementMode="Hidden"
mc:Ignorable="d">
<Page.Resources>
<tkconverters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
<converters:CountToVisibilityConverter x:Name="countToVisibilityConverter" />
<converters:CountToDoubleConverter
x:Name="customActionsCountToMinHeightConverter"
ValueIfNonZero="40"
ValueIfZero="0" />
<converters:ListViewIndexConverter x:Name="listViewIndexConverter" />
<Style
x:Key="PaddingLessFlyoutPresenterStyle"
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
@@ -28,38 +21,6 @@
<Setter Property="Padding" Value="0" />
</Style.Setters>
</Style>
<DataTemplate x:Key="PasteFormatTemplate" x:DataType="local:PasteFormat">
<Grid>
<ToolTipService.ToolTip>
<TextBlock Text="{x:Bind ToolTip}" />
</ToolTipService.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
Margin="0,0,0,0"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontSize="16"
Glyph="{x:Bind IconGlyph}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
x:Phase="1"
Text="{x:Bind Name}" />
<TextBlock
Grid.Column="2"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ShortcutText, Mode=OneWay}"
Visibility="{x:Bind ShortcutText.Length, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
</Grid>
</DataTemplate>
</Page.Resources>
<Page.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked" />
@@ -75,30 +36,6 @@
Key="Number3"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number4"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number5"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number6"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number7"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number8"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
<KeyboardAccelerator
Key="Number9"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
</Page.KeyboardAccelerators>
<Grid>
<Grid.RowDefinitions>
@@ -166,55 +103,73 @@
BorderThickness="0,1,0,0"
RowSpacing="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsCountToMinHeightConverter}}" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView
x:Name="PasteOptionsListView"
Grid.Row="0"
VerticalAlignment="Bottom"
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
IsItemClickEnabled="True"
ItemClick="ListView_Click"
ItemClick="PasteOptionsListView_ItemClick"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource PasteFormatTemplate}"
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
ItemsSource="{x:Bind pasteFormats, Mode=OneWay}"
SelectionMode="None"
TabIndex="1" />
TabIndex="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PasteFormat">
<Grid>
<ToolTipService.ToolTip>
<TextBlock>
<Run Text="{x:Bind Name}" />
<Run Text="(" /><Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" /><Run Text=")" />
</TextBlock>
</ToolTipService.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox
x:Name="IconHolderBox"
MaxWidth="16"
MaxHeight="16"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<ContentPresenter
x:Name="IconHolder"
x:Phase="2"
Content="{x:Bind Icon}" />
</Viewbox>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
x:Phase="1"
Text="{x:Bind Name}" />
<TextBlock
Grid.Column="2"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}">
<Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" />
</TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Rectangle
Grid.Row="1"
Height="1"
HorizontalAlignment="Stretch"
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
<ListView
x:Name="CustomActionsListView"
Grid.Row="2"
VerticalAlignment="Top"
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
IsItemClickEnabled="True"
ItemClick="ListView_Click"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource PasteFormatTemplate}"
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Auto"
SelectionMode="None"
TabIndex="2" />
<Rectangle
Grid.Row="3"
Height="1"
HorizontalAlignment="Stretch"
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
<!-- x:Uid="ClipboardHistoryButton" -->
<Button
Grid.Row="4"
Grid.Row="2"
Height="32"
Margin="4,0,4,4"
Padding="{StaticResource ButtonPadding}"

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
@@ -24,8 +25,8 @@ namespace AdvancedPaste.Pages
public sealed partial class MainPage : Page
{
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
private readonly ObservableCollection<PasteFormat> pasteFormats;
private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
private (VirtualKey Key, DateTime Timestamp) _lastKeyEvent = (VirtualKey.None, DateTime.MinValue);
public OptionsViewModel ViewModel { get; private set; }
@@ -33,6 +34,13 @@ namespace AdvancedPaste.Pages
{
this.InitializeComponent();
pasteFormats =
[
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE8E9" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
new PasteFormat { Icon = new FontIcon() { Glyph = "\ue8a5" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE943" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
];
ViewModel = App.GetService<OptionsViewModel>();
clipboardHistory = new ObservableCollection<ClipboardItem>();
@@ -113,8 +121,6 @@ namespace AdvancedPaste.Pages
}
}
private static MainWindow GetMainWindow() => (App.Current as App)?.GetMainWindow();
private void ClipboardHistoryItemDeleteButton_Click(object sender, RoutedEventArgs e)
{
Logger.LogTrace();
@@ -129,49 +135,83 @@ namespace AdvancedPaste.Pages
}
}
private void ListView_Click(object sender, ItemClickEventArgs e)
private void PasteAsPlain()
{
ViewModel.ToPlainTextFunction();
}
private void PasteAsMarkdown()
{
ViewModel.ToMarkdownFunction();
}
private void PasteAsJson()
{
ViewModel.ToJsonFunction();
}
private void PasteOptionsListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is PasteFormat format)
{
ViewModel.ExecutePasteFormat(format);
switch (format.Format)
{
case PasteFormats.PlainText:
{
PasteAsPlain();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.PlainText));
break;
}
case PasteFormats.Markdown:
{
PasteAsMarkdown();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Markdown));
break;
}
case PasteFormats.Json:
{
PasteAsJson();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Json));
break;
}
}
}
}
private void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
{
if (GetMainWindow()?.Visible is false)
{
return;
}
Logger.LogTrace();
var thisKeyEvent = (sender.Key, Timestamp: DateTime.Now);
if (thisKeyEvent.Key == _lastKeyEvent.Key && (thisKeyEvent.Timestamp - _lastKeyEvent.Timestamp) < TimeSpan.FromMilliseconds(200))
{
// Sometimes, multiple keyboard accelerator events are raised for a single Ctrl + VirtualKey press.
return;
}
_lastKeyEvent = thisKeyEvent;
switch (sender.Key)
{
case VirtualKey.Escape:
GetMainWindow()?.Close();
break;
{
(App.Current as App).GetMainWindow().Close();
break;
}
case VirtualKey.Number1:
{
PasteAsPlain();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.PlainText));
break;
}
case VirtualKey.Number2:
{
PasteAsMarkdown();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Markdown));
break;
}
case VirtualKey.Number3:
case VirtualKey.Number4:
case VirtualKey.Number5:
case VirtualKey.Number6:
case VirtualKey.Number7:
case VirtualKey.Number8:
case VirtualKey.Number9:
ViewModel.ExecutePasteFormat(sender.Key);
break;
{
PasteAsJson();
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Json));
break;
}
default:
break;
@@ -182,7 +222,7 @@ namespace AdvancedPaste.Pages
{
if (e.Key == VirtualKey.Escape)
{
GetMainWindow()?.Close();
(App.Current as App).GetMainWindow().Close();
}
}

View File

@@ -2,9 +2,6 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
namespace AdvancedPaste.Settings
{
public interface IUserSettings
@@ -14,7 +11,5 @@ namespace AdvancedPaste.Settings
public bool SendPasteKeyCombination { get; }
public bool CloseAfterLosingFocus { get; }
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; }
}
}

View File

@@ -1,37 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AdvancedPaste.Helpers;
public static class NamedPipeProcessor
{
public static async Task ProcessNamedPipeAsync(string pipeName, TimeSpan connectTimeout, Action<string> messageHandler, CancellationToken cancellationToken)
{
using NamedPipeClientStream pipeClient = new(".", pipeName, PipeDirection.In);
await pipeClient.ConnectAsync(connectTimeout, cancellationToken);
using StreamReader streamReader = new(pipeClient, Encoding.Unicode);
while (true)
{
var message = await streamReader.ReadLineAsync(cancellationToken);
if (message != null)
{
messageHandler(message);
}
var intraMessageDelay = TimeSpan.FromMilliseconds(10);
await Task.Delay(intraMessageDelay, cancellationToken);
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using Microsoft.UI.Dispatching;
namespace AdvancedPaste.Helpers
{
public static class NativeEventWaiter
{
public static void WaitForEventLoop(string eventName, Action callback)
{
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
new Thread(() =>
{
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (true)
{
if (eventHandle.WaitOne())
{
dispatcherQueue.TryEnqueue(() => callback());
}
}
}).Start();
}
}
}

View File

@@ -3,37 +3,29 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.IO.Abstractions;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace AdvancedPaste.Settings
{
internal sealed class UserSettings : IUserSettings, IDisposable
internal sealed class UserSettings : IUserSettings
{
private readonly SettingsUtils _settingsUtils;
private readonly TaskScheduler _taskScheduler;
private readonly IFileSystemWatcher _watcher;
private readonly object _loadingSettingsLock = new();
private readonly object _loadingSettingsLock = new object();
private const string AdvancedPasteModuleName = "AdvancedPaste";
private const int MaxNumberOfRetry = 5;
private bool _disposedValue;
private CancellationTokenSource _cancellationTokenSource;
public bool ShowCustomPreview { get; private set; }
public bool SendPasteKeyCombination { get; private set; }
public bool CloseAfterLosingFocus { get; private set; }
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; private set; }
public UserSettings()
{
_settingsUtils = new SettingsUtils();
@@ -41,25 +33,10 @@ namespace AdvancedPaste.Settings
ShowCustomPreview = true;
SendPasteKeyCombination = true;
CloseAfterLosingFocus = false;
CustomActions = [];
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
LoadSettingsFromJson();
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", OnSettingsFileChanged);
}
private void OnSettingsFileChanged()
{
lock (_loadingSettingsLock)
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
Task.Delay(TimeSpan.FromMilliseconds(500))
.ContinueWith(_ => LoadSettingsFromJson(), _cancellationTokenSource.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
}
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", () => LoadSettingsFromJson());
}
private void LoadSettingsFromJson()
@@ -85,25 +62,9 @@ namespace AdvancedPaste.Settings
var settings = _settingsUtils.GetSettingsOrDefault<AdvancedPasteSettings>(AdvancedPasteModuleName);
if (settings != null)
{
void UpdateSettings()
{
ShowCustomPreview = settings.Properties.ShowCustomPreview;
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
CustomActions.Clear();
foreach (var customAction in settings.Properties.CustomActions.Value)
{
if (customAction.IsShown && customAction.IsValid)
{
CustomActions.Add(customAction);
}
}
}
Task.Factory
.StartNew(UpdateSettings, CancellationToken.None, TaskCreationOptions.None, _taskScheduler)
.Wait();
ShowCustomPreview = settings.Properties.ShowCustomPreview;
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
}
retry = false;
@@ -121,30 +82,5 @@ namespace AdvancedPaste.Settings
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_cancellationTokenSource.Dispose();
_watcher.Dispose();
}
_disposedValue = true;
}
}
~UserSettings()
{
Dispose(false);
}
}
}

View File

@@ -1,14 +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;
namespace AdvancedPaste.Models;
public sealed class CustomActionActivatedEventArgs(string text, bool forcePasteCustom) : EventArgs
{
public string Text { get; private set; } = text;
public bool ForcePasteCustom { get; private set; } = forcePasteCustom;
}

View File

@@ -2,38 +2,16 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Controls;
namespace AdvancedPaste.Models;
public partial class PasteFormat : ObservableObject
namespace AdvancedPaste.Models
{
[ObservableProperty]
private string _shortcutText = string.Empty;
[ObservableProperty]
private string _toolTip = string.Empty;
public PasteFormat()
public class PasteFormat
{
public IconElement Icon { get; set; }
public string Name { get; set; }
public PasteFormats Format { get; set; }
}
public PasteFormat(AdvancedPasteCustomAction customAction, string shortcutText)
{
IconGlyph = "\uE945";
Name = customAction.Name;
Prompt = customAction.Prompt;
Format = PasteFormats.Custom;
ShortcutText = shortcutText;
ToolTip = customAction.Prompt;
}
public string IconGlyph { get; init; }
public string Name { get; init; }
public PasteFormats Format { get; init; }
public string Prompt { get; init; } = string.Empty;
}

View File

@@ -59,7 +59,10 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
@@ -225,7 +228,4 @@
<data name="OpenAIGpoDisabled" xml:space="preserve">
<value>To custom with AI is disabled by your organization</value>
</data>
<data name="CtrlKey" xml:space="preserve">
<value>Ctrl</value>
</data>
</root>
</root>

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
@@ -16,72 +15,48 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Dispatching;
using Microsoft.Win32;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using WinUIEx;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
namespace AdvancedPaste.ViewModels
{
public partial class OptionsViewModel : ObservableObject, IDisposable
public partial class OptionsViewModel : ObservableObject
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly DispatcherTimer _clipboardTimer;
private readonly IUserSettings _userSettings;
private readonly AICompletionsHelper aiHelper;
private readonly App app = App.Current as App;
private readonly PasteFormat[] _allStandardPasteFormats;
private App app = App.Current as App;
private AICompletionsHelper aiHelper;
public DataPackageView ClipboardData { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
private bool _isClipboardDataText;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
private bool _isCustomAIEnabled;
[ObservableProperty]
private bool _clipboardHistoryEnabled;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
private bool _isAllowedByGPO;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ApiErrorText))]
[NotifyPropertyChangedFor(nameof(InputTxtBoxErrorText))]
private int _apiRequestStatus;
[ObservableProperty]
private string _query = string.Empty;
private bool _pasteFormatsDirty;
public ObservableCollection<PasteFormat> StandardPasteFormats { get; } = [];
public ObservableCollection<PasteFormat> CustomActionPasteFormats { get; } = [];
public bool IsCustomAIEnabled => IsAllowedByGPO && IsClipboardDataText && aiHelper.IsAIEnabled;
public event EventHandler<CustomActionActivatedEventArgs> CustomActionActivated;
public OptionsViewModel(IUserSettings userSettings)
{
aiHelper = new AICompletionsHelper();
_userSettings = userSettings;
ApiRequestStatus = (int)HttpStatusCode.OK;
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
_allStandardPasteFormats =
[
new PasteFormat { IconGlyph = "\uE8E9", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
new PasteFormat { IconGlyph = "\ue8a5", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
new PasteFormat { IconGlyph = "\uE943", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
];
ApiRequestStatus = (int)HttpStatusCode.OK;
GeneratedResponses = new ObservableCollection<string>();
GeneratedResponses.CollectionChanged += (s, e) =>
@@ -91,87 +66,10 @@ namespace AdvancedPaste.ViewModels
};
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
ReadClipboard();
_clipboardTimer = new() { Interval = TimeSpan.FromSeconds(1) };
_clipboardTimer.Tick += ClipboardTimer_Tick;
_clipboardTimer.Start();
RefreshPasteFormats();
_userSettings.CustomActions.CollectionChanged += (_, _) => EnqueueRefreshPasteFormats();
PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(Query))
{
EnqueueRefreshPasteFormats();
}
};
GetClipboardData();
}
private void ClipboardTimer_Tick(object sender, object e)
{
if (app.GetMainWindow()?.Visible is true)
{
ReadClipboard();
UpdateAllowedByGPO();
}
}
private void EnqueueRefreshPasteFormats()
{
if (_pasteFormatsDirty)
{
return;
}
_pasteFormatsDirty = true;
_dispatcherQueue.TryEnqueue(() =>
{
RefreshPasteFormats();
_pasteFormatsDirty = false;
});
}
private void RefreshPasteFormats()
{
bool Filter(string text) => text.Contains(Query, StringComparison.CurrentCultureIgnoreCase);
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
int shortcutNum = 0;
string GetNextShortcutText()
{
shortcutNum++;
return shortcutNum <= 9 ? $"{ctrlString}+{shortcutNum}" : string.Empty;
}
StandardPasteFormats.Clear();
foreach (var format in _allStandardPasteFormats)
{
if (Filter(format.Name))
{
format.ShortcutText = GetNextShortcutText();
format.ToolTip = $"{format.Name} ({format.ShortcutText})";
StandardPasteFormats.Add(format);
}
}
CustomActionPasteFormats.Clear();
foreach (var customAction in _userSettings.CustomActions)
{
if (Filter(customAction.Name) || Filter(customAction.Prompt))
{
CustomActionPasteFormats.Add(new PasteFormat(customAction, GetNextShortcutText()));
}
}
}
public void Dispose()
{
_clipboardTimer.Stop();
GC.SuppressFinalize(this);
}
public void ReadClipboard()
public void GetClipboardData()
{
ClipboardData = Clipboard.GetContent();
IsClipboardDataText = ClipboardData.Contains(StandardDataFormats.Text);
@@ -179,10 +77,14 @@ namespace AdvancedPaste.ViewModels
public void OnShow()
{
ReadClipboard();
UpdateAllowedByGPO();
GetClipboardData();
if (IsAllowedByGPO)
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
IsCustomAIEnabled = false;
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
}
else
{
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
var currentKey = aiHelper.GetKey();
@@ -202,12 +104,15 @@ namespace AdvancedPaste.ViewModels
{
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
OnPropertyChanged(nameof(GeneralErrorText));
OnPropertyChanged(nameof(IsCustomAIEnabled));
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
});
},
TaskScheduler.Default);
}
else
{
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
}
}
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
@@ -247,44 +152,47 @@ namespace AdvancedPaste.ViewModels
{
app.GetMainWindow().ClearInputText();
return IsClipboardDataText ? ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText") : GeneralErrorText;
}
}
public string GeneralErrorText
{
get
{
if (!IsClipboardDataText)
{
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
}
if (!IsAllowedByGPO)
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
}
if (!aiHelper.IsAIEnabled)
else if (!aiHelper.IsAIEnabled)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
}
else if (!IsClipboardDataText)
{
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
}
else
{
return string.Empty;
return ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText");
}
}
}
public string ApiErrorText
public string InputTxtBoxErrorText
{
get => (HttpStatusCode)ApiRequestStatus switch
get
{
HttpStatusCode.TooManyRequests => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests"),
HttpStatusCode.Unauthorized => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized"),
HttpStatusCode.OK => string.Empty,
_ => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture),
};
if (ApiRequestStatus != (int)HttpStatusCode.OK)
{
if (ApiRequestStatus == (int)HttpStatusCode.TooManyRequests)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests");
}
else if (ApiRequestStatus == (int)HttpStatusCode.Unauthorized)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized");
}
else
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture);
}
}
return string.Empty;
}
}
[ObservableProperty]
@@ -293,12 +201,7 @@ namespace AdvancedPaste.ViewModels
[RelayCommand]
public void PasteCustom()
{
var text = GeneratedResponses.ElementAtOrDefault(CurrentResponseIndex);
if (text != null)
{
PasteCustomFunction(text);
}
PasteCustomFunction(GeneratedResponses[CurrentResponseIndex]);
}
// Command to select the previous custom format
@@ -403,59 +306,6 @@ namespace AdvancedPaste.ViewModels
}
}
internal void ExecutePasteFormat(VirtualKey key)
{
var index = key - VirtualKey.Number1;
var pasteFormat = StandardPasteFormats.ElementAtOrDefault(index) ?? CustomActionPasteFormats.ElementAtOrDefault(index - StandardPasteFormats.Count);
if (pasteFormat != null)
{
ExecutePasteFormat(pasteFormat);
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(pasteFormat.Format));
}
}
internal void ExecutePasteFormat(PasteFormat pasteFormat)
{
if (!IsClipboardDataText || (pasteFormat.Format == PasteFormats.Custom && !IsCustomAIEnabled))
{
return;
}
switch (pasteFormat.Format)
{
case PasteFormats.PlainText:
ToPlainTextFunction();
break;
case PasteFormats.Markdown:
ToMarkdownFunction();
break;
case PasteFormats.Json:
ToJsonFunction();
break;
case PasteFormats.Custom:
Query = pasteFormat.Prompt;
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(pasteFormat.Prompt, false));
break;
}
}
internal void ExecuteCustomActionWithPaste(int customActionId)
{
Logger.LogTrace();
var customAction = _userSettings.CustomActions.FirstOrDefault(customAction => customAction.Id == customActionId);
if (customAction != null)
{
Query = customAction.Prompt;
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(customAction.Prompt, true));
}
}
internal async Task<string> GenerateCustomFunction(string inputInstructions)
{
Logger.LogTrace();
@@ -465,7 +315,7 @@ namespace AdvancedPaste.ViewModels
return string.Empty;
}
if (!IsClipboardDataText)
if (ClipboardData == null || !ClipboardData.Contains(StandardDataFormats.Text))
{
Logger.LogWarning("Clipboard does not contain text data");
return string.Empty;
@@ -566,10 +416,5 @@ namespace AdvancedPaste.ViewModels
return false;
}
}
private void UpdateAllowedByGPO()
{
IsAllowedByGPO = PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled;
}
}
}

View File

@@ -14,10 +14,6 @@
#include <common/utils/logger_helper.h>
#include <common/utils/winapi_error.h>
#include <atlfile.h>
#include <atlstr.h>
#include <vector>
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
@@ -39,10 +35,6 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lp
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_CUSTOM_ACTIONS[] = L"custom-actions";
const wchar_t JSON_KEY_SHORTCUT[] = L"shortcut";
const wchar_t JSON_KEY_IS_SHOWN[] = L"isShown";
const wchar_t JSON_KEY_ID[] = L"id";
const wchar_t JSON_KEY_WIN[] = L"win";
const wchar_t JSON_KEY_ALT[] = L"alt";
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
@@ -68,30 +60,33 @@ private:
HANDLE m_hProcess;
std::thread create_pipe_thread;
std::unique_ptr<CAtlFile> m_write_pipe;
// Time to wait for process to close after sending WM_CLOSE signal
static const constexpr int MAX_WAIT_MILLISEC = 10000;
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
static const int MAX_WAIT_MILLISEC = 10000;
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' };
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V' };
Hotkey m_paste_as_markdown_hotkey{};
Hotkey m_paste_as_json_hotkey{};
std::vector<Hotkey> m_custom_action_hotkeys;
std::vector<int> m_custom_action_ids;
bool m_preview_custom_format_output = true;
Hotkey parse_single_hotkey(const wchar_t* keyName, const winrt::Windows::Data::Json::JsonObject& settingsObject)
// Handle to event used to invoke AdvancedPaste
HANDLE m_hShowUIEvent;
HANDLE m_hPasteMarkdownEvent;
HANDLE m_hPasteJsonEvent;
Hotkey parse_single_hotkey(const wchar_t* hotkey, const winrt::Windows::Data::Json::JsonObject& settingsObject)
{
try
{
const auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(keyName);
return parse_single_hotkey(jsonHotkeyObject);
Hotkey _temp_paste_as_plain;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(hotkey);
_temp_paste_as_plain.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_paste_as_plain.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_paste_as_plain.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_paste_as_plain.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_paste_as_plain.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
return _temp_paste_as_plain;
}
catch (...)
{
@@ -101,38 +96,6 @@ private:
return {};
}
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
{
try
{
Hotkey hotkey;
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
return hotkey;
}
catch (...)
{
Logger::error("Failed to initialize AdvancedPaste shortcut from settings. Value will keep unchanged.");
}
return {};
}
static json::JsonObject to_json_object(const Hotkey& hotkey)
{
json::JsonObject jsonObject;
jsonObject.SetNamedValue(JSON_KEY_WIN, json::value(hotkey.win));
jsonObject.SetNamedValue(JSON_KEY_ALT, json::value(hotkey.alt));
jsonObject.SetNamedValue(JSON_KEY_SHIFT, json::value(hotkey.shift));
jsonObject.SetNamedValue(JSON_KEY_CTRL, json::value(hotkey.ctrl));
jsonObject.SetNamedValue(JSON_KEY_CODE, json::value(hotkey.key));
return jsonObject;
}
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
@@ -168,7 +131,7 @@ private:
{
auto settingsObject = settings.get_raw_json();
// Migrate Paste As Plain text shortcut
// Migrate Paste As PLain text shortcut
Hotkey old_paste_as_plain_hotkey;
bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey);
if (old_data_migrated)
@@ -176,7 +139,12 @@ private:
m_paste_as_plain_hotkey = old_paste_as_plain_hotkey;
// override settings file
const auto new_hotkey_value = to_json_object(old_paste_as_plain_hotkey);
json::JsonObject new_hotkey_value;
new_hotkey_value.SetNamedValue(JSON_KEY_WIN, json::value(old_paste_as_plain_hotkey.win));
new_hotkey_value.SetNamedValue(JSON_KEY_ALT, json::value(old_paste_as_plain_hotkey.alt));
new_hotkey_value.SetNamedValue(JSON_KEY_SHIFT, json::value(old_paste_as_plain_hotkey.shift));
new_hotkey_value.SetNamedValue(JSON_KEY_CTRL, json::value(old_paste_as_plain_hotkey.ctrl));
new_hotkey_value.SetNamedValue(JSON_KEY_CODE, json::value(old_paste_as_plain_hotkey.key));
if (!settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
@@ -185,7 +153,13 @@ private:
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, new_hotkey_value);
const auto ui_hotkey = to_json_object(m_advanced_paste_ui_hotkey);
json::JsonObject ui_hotkey;
ui_hotkey.SetNamedValue(JSON_KEY_WIN, json::value(m_advanced_paste_ui_hotkey.win));
ui_hotkey.SetNamedValue(JSON_KEY_ALT, json::value(m_advanced_paste_ui_hotkey.alt));
ui_hotkey.SetNamedValue(JSON_KEY_SHIFT, json::value(m_advanced_paste_ui_hotkey.shift));
ui_hotkey.SetNamedValue(JSON_KEY_CTRL, json::value(m_advanced_paste_ui_hotkey.ctrl));
ui_hotkey.SetNamedValue(JSON_KEY_CODE, json::value(m_advanced_paste_ui_hotkey.key));
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, ui_hotkey);
settings.save_to_settings_file();
@@ -194,56 +168,40 @@ private:
{
if (settingsObject.GetView().Size())
{
const std::array<std::pair<Hotkey*, LPCWSTR>, NUM_DEFAULT_HOTKEYS> defaultHotkeys{
{ { &m_paste_as_plain_hotkey, JSON_KEY_PASTE_AS_PLAIN_HOTKEY },
{ &m_advanced_paste_ui_hotkey, JSON_KEY_ADVANCED_PASTE_UI_HOTKEY },
{ &m_paste_as_markdown_hotkey, JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY },
{ &m_paste_as_json_hotkey, JSON_KEY_PASTE_AS_JSON_HOTKEY } }
};
for (auto& [hotkey, keyName] : defaultHotkeys)
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY))
{
*hotkey = parse_single_hotkey(keyName, settingsObject);
m_paste_as_plain_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, settingsObject);
}
m_custom_action_hotkeys.clear();
m_custom_action_ids.clear();
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY))
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
for (const auto& customAction : customActions)
{
const auto object = customAction.GetObjectW();
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
m_custom_action_hotkeys.push_back(parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT)));
m_custom_action_ids.push_back(static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)));
}
}
}
m_advanced_paste_ui_hotkey = parse_single_hotkey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, settingsObject);
}
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY))
{
m_paste_as_markdown_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY, settingsObject);
}
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_JSON_HOTKEY))
{
m_paste_as_json_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_JSON_HOTKEY, settingsObject);
}
}
}
}
bool is_process_running() const
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void launch_process(const std::wstring& pipe_name)
void launch_process(const std::wstring& arg = L"")
{
Logger::trace(L"Starting AdvancedPaste process");
const unsigned long powertoys_pid = GetCurrentProcessId();
unsigned long powertoys_pid = GetCurrentProcessId();
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
executable_args += L" " + arg;
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@@ -263,55 +221,6 @@ private:
m_hProcess = sei.hProcess;
}
std::optional<std::wstring> get_pipe_name(const std::wstring& prefix) const
{
UUID temp_uuid;
wchar_t* uuid_chars = nullptr;
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
{
const auto val = get_last_error_message(GetLastError());
Logger::error(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
return std::nullopt;
}
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
{
const auto val = get_last_error_message(GetLastError());
Logger::error(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
return std::nullopt;
}
const auto pipe_name = std::format(L"{}{}", prefix, std::wstring(uuid_chars));
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
return pipe_name;
}
void launch_process_and_named_pipe()
{
const auto pipe_name = get_pipe_name(L"powertoys_advanced_paste_");
if (!pipe_name)
{
return;
}
create_pipe_thread = std::thread([&] { start_named_pipe_server(pipe_name.value()); });
launch_process(pipe_name.value());
create_pipe_thread.join();
}
void send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg = L"")
{
if (m_write_pipe)
{
const auto message = message_arg.empty() ? std::format(L"{}\r\n", message_type) : std::format(L"{} {}\r\n", message_type, message_arg);
const CString file_name(message.c_str());
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
}
}
// Load the settings file.
void init_settings()
{
@@ -349,7 +258,7 @@ private:
}
}
void try_inject_modifier_key_restore(std::vector<INPUT>& inputs, short modifier)
void try_inject_modifier_key_restore(std::vector<INPUT> &inputs, short modifier)
{
// Most significant bit is set if key is down
if ((GetAsyncKeyState(static_cast<int>(modifier)) & 0x8000) != 0)
@@ -578,54 +487,15 @@ private:
EnumWindows(enum_windows, (LPARAM)m_hProcess);
}
HRESULT start_named_pipe_server(const std::wstring& pipe_name)
{
const constexpr DWORD BUFSIZE = 4096 * 4;
const auto full_pipe_name = std::format(L"\\\\.\\pipe\\{}", pipe_name);
const auto hPipe = CreateNamedPipe(
full_pipe_name.c_str(), // pipe name
PIPE_ACCESS_OUTBOUND, // write access
PIPE_TYPE_MESSAGE | // message type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
1, // max. instances
BUFSIZE, // output buffer size
0, // input buffer size
0, // client time-out
NULL); // default security attribute
if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
// This call blocks until a client process connects to the pipe
BOOL connected = ConnectNamedPipe(hPipe, NULL);
if (!connected)
{
if (GetLastError() == ERROR_PIPE_CONNECTED)
{
return S_OK;
}
else
{
CloseHandle(hPipe);
}
return E_FAIL;
}
m_write_pipe = std::make_unique<CAtlFile>(hPipe);
return S_OK;
}
public:
AdvancedPaste()
{
app_name = GET_RESOURCE_STRING(IDS_ADVANCED_PASTE_NAME);
app_key = AdvancedPasteConstants::ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "AdvancedPaste");
m_hShowUIEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT);
m_hPasteMarkdownEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT);
m_hPasteJsonEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT);
init_settings();
}
@@ -689,7 +559,7 @@ public:
parse_hotkeys(values);
const auto settingsObject = values.get_raw_json();
auto settingsObject = values.get_raw_json();
if (settingsObject.GetView().Size() && settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
{
m_preview_custom_format_output = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
@@ -697,10 +567,10 @@ public:
// order of args matter
Trace::AdvancedPaste_SettingsTelemetry(m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey,
m_preview_custom_format_output);
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey,
m_preview_custom_format_output);
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
@@ -718,9 +588,12 @@ public:
{
Logger::trace("AdvancedPaste::enable()");
Trace::AdvancedPaste_Enable(true);
ResetEvent(m_hShowUIEvent);
ResetEvent(m_hPasteMarkdownEvent);
ResetEvent(m_hPasteJsonEvent);
m_enabled = true;
launch_process_and_named_pipe();
launch_process();
};
virtual void disable()
@@ -728,8 +601,9 @@ public:
Logger::trace("AdvancedPaste::disable()");
if (m_enabled)
{
m_write_pipe = nullptr;
ResetEvent(m_hShowUIEvent);
ResetEvent(m_hPasteMarkdownEvent);
ResetEvent(m_hPasteJsonEvent);
TerminateProcess(m_hProcess, 1);
Trace::AdvancedPaste_Enable(false);
@@ -748,14 +622,13 @@ public:
if (!is_process_running())
{
Logger::trace(L"Launching new process");
launch_process_and_named_pipe();
launch_process();
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUI");
}
// hotkeyId in same order as set by get_hotkeys
if (hotkeyId == 0)
{ // m_paste_as_plain_hotkey
if (hotkeyId == 0) { // m_paste_as_plain_hotkey
Logger::trace(L"Paste as plain text hotkey pressed");
std::thread([=]() {
@@ -768,36 +641,21 @@ public:
return true;
}
if (hotkeyId == 1)
{ // m_advanced_paste_ui_hotkey
if (hotkeyId == 1) { // m_advanced_paste_ui_hotkey
Logger::trace(L"Setting start up event");
bring_process_to_front();
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
SetEvent(m_hShowUIEvent);
return true;
}
if (hotkeyId == 2)
{ // m_paste_as_markdown_hotkey
if (hotkeyId == 2) { // m_paste_as_markdown_hotkey
Logger::trace(L"Starting paste as markdown directly");
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE);
SetEvent(m_hPasteMarkdownEvent);
return true;
}
if (hotkeyId == 3)
{ // m_paste_as_json_hotkey
if (hotkeyId == 3) { // m_paste_as_json_hotkey
Logger::trace(L"Starting paste as json directly");
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE);
return true;
}
const auto custom_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (custom_action_index < m_custom_action_ids.size())
{
const auto id = m_custom_action_ids.at(custom_action_index);
Logger::trace(L"Starting custom action id={}", id);
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id));
SetEvent(m_hPasteJsonEvent);
return true;
}
}
@@ -807,20 +665,14 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_custom_action_hotkeys.size();
if (hotkeys && buffer_size >= num_hotkeys)
if (hotkeys && buffer_size >= 4)
{
const std::array default_hotkeys = { m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey };
std::copy(default_hotkeys.begin(), default_hotkeys.end(), hotkeys);
std::copy(m_custom_action_hotkeys.begin(), m_custom_action_hotkeys.end(), hotkeys + NUM_DEFAULT_HOTKEYS);
hotkeys[0] = m_paste_as_plain_hotkey;
hotkeys[1] = m_advanced_paste_ui_hotkey;
hotkeys[2] = m_paste_as_markdown_hotkey;
hotkeys[3] = m_paste_as_json_hotkey;
}
return num_hotkeys;
return 4;
}
virtual bool is_enabled() override

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
using System.Threading.Tasks;
@@ -249,53 +248,25 @@ namespace Hosts.Tests
{
var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.ReadOnly,
};
var hostsFile = new MockFileData(string.Empty);
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly;
fileSystem.AddFile(service.HostsFilePath, hostsFile);
await Assert.ThrowsExceptionAsync<ReadOnlyHostsException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>()));
}
[TestMethod]
public void Remove_ReadOnly_Attribute()
public void Remove_ReadOnly()
{
var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.ReadOnly,
};
var hostsFile = new MockFileData(string.Empty);
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly;
fileSystem.AddFile(service.HostsFilePath, hostsFile);
service.RemoveReadOnlyAttribute();
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.ReadOnly);
service.RemoveReadOnly();
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(System.IO.FileAttributes.ReadOnly);
Assert.IsFalse(readOnly);
}
[TestMethod]
public async Task Save_Hidden_Hosts()
{
var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.Hidden,
};
fileSystem.AddFile(service.HostsFilePath, hostsFile);
await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>());
var hidden = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
Assert.IsTrue(hidden);
}
}
}

View File

@@ -23,7 +23,6 @@ namespace HostsUILib.Helpers
public class HostsService : IHostsService, IDisposable
{
private const string _backupSuffix = $"_PowerToysBackup_";
private const int _defaultBufferSize = 4096; // From System.IO.File source code
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
private readonly IFileSystem _fileSystem;
@@ -198,16 +197,7 @@ namespace HostsUILib.Helpers
_backupDone = true;
}
// FileMode.OpenOrCreate is necessary to prevent UnauthorizedAccessException when the hosts file is hidden
using var stream = _fileSystem.FileStream.Create(HostsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, _defaultBufferSize, FileOptions.Asynchronous);
using var writer = new StreamWriter(stream, Encoding);
foreach (var line in lines)
{
await writer.WriteLineAsync(line.AsMemory());
}
stream.SetLength(stream.Position);
await writer.FlushAsync();
await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines, Encoding);
}
finally
{
@@ -302,7 +292,7 @@ namespace HostsUILib.Helpers
}
}
public void RemoveReadOnlyAttribute()
public void RemoveReadOnly()
{
var fileInfo = _fileSystem.FileInfo.FromFileName(HostsFilePath);
if (fileInfo.IsReadOnly)

View File

@@ -25,6 +25,6 @@ namespace HostsUILib.Helpers
void OpenHostsFile();
void RemoveReadOnlyAttribute();
void RemoveReadOnly();
}
}

View File

@@ -283,7 +283,7 @@ namespace HostsUILib.ViewModels
[RelayCommand]
public void OverwriteHosts()
{
_hostsService.RemoveReadOnlyAttribute();
_hostsService.RemoveReadOnly();
_ = Task.Run(SaveAsync);
}

View File

@@ -2,40 +2,13 @@
#include "BoundsToolOverlayUI.h"
#include "CoordinateSystemConversion.h"
#include "Clipboard.h"
#include "constants.h"
#include <common/utils/window.h>
#include <vector>
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
namespace
{
Measurement GetMeasurement(const CursorDrag& currentBounds, POINT cursorPos)
{
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) =
std::minmax(static_cast<float>(cursorPos.x), currentBounds.startPos.x);
std::tie(rect.top, rect.bottom) =
std::minmax(static_cast<float>(cursorPos.y), currentBounds.startPos.y);
return Measurement(rect);
}
void CopyToClipboard(HWND window, const BoundsToolState& toolState, POINT cursorPos)
{
std::vector<Measurement> allMeasurements;
for (const auto& [handle, perScreen] : toolState.perScreen)
{
allMeasurements.append_range(perScreen.measurements);
if (handle == window && perScreen.currentBounds)
{
allMeasurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos));
}
}
SetClipboardToMeasurements(allMeasurements, true, true, toolState.commonState->units);
}
void ToggleCursor(const bool show)
{
if (show)
@@ -79,16 +52,22 @@ namespace
{
ToggleCursor(true);
ClipCursor(nullptr);
CopyToClipboard(window, *toolState, cursorPos);
auto& perScreen = toolState->perScreen[window];
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer.data());
});
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x80000; shiftPress && perScreen.currentBounds)
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress && toolState->perScreen[window].currentBounds)
{
perScreen.measurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos));
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) =
std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentBounds->startPos.x);
std::tie(rect.top, rect.bottom) =
std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentBounds->startPos.y);
toolState->perScreen[window].measurements.push_back(Measurement{ rect });
}
perScreen.currentBounds = std::nullopt;
toolState->perScreen[window].currentBounds = std::nullopt;
}
}
@@ -107,17 +86,12 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
case WM_KEYUP:
if (wparam == VK_ESCAPE)
{
if (const auto* toolState = GetWindowParam<BoundsToolState*>(window))
{
CopyToClipboard(window, *toolState, convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
}
PostMessageW(window, WM_CLOSE, {}, {});
}
break;
case WM_LBUTTONDOWN:
{
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
@@ -190,7 +164,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
case WM_MOUSEMOVE:
{
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
@@ -206,7 +180,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
case WM_LBUTTONUP:
{
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
@@ -221,32 +195,24 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
}
case WM_RBUTTONUP:
{
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
ToggleCursor(true);
auto* toolState = GetWindowParam<BoundsToolState*>(window);
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
auto& perScreen = toolState->perScreen[window];
if (perScreen.currentBounds)
{
perScreen.currentBounds = std::nullopt;
}
if (toolState->perScreen[window].currentBounds)
toolState->perScreen[window].currentBounds = std::nullopt;
else
{
if (perScreen.measurements.empty())
{
if (toolState->perScreen[window].measurements.empty())
PostMessageW(window, WM_CLOSE, {}, {});
}
else
{
perScreen.measurements.clear();
}
toolState->perScreen[window].measurements.clear();
}
break;
}
@@ -276,6 +242,10 @@ namespace
true,
commonState.units);
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
});
D2D_POINT_2F textBoxPos;
if (textBoxCenter)
textBoxPos = *textBoxCenter;

View File

@@ -2,8 +2,6 @@
#include "Clipboard.h"
#include <sstream>
void SetClipBoardToText(const std::wstring_view text)
{
if (!OpenClipboard(nullptr))
@@ -28,25 +26,3 @@ void SetClipBoardToText(const std::wstring_view text)
SetClipboardData(CF_UNICODETEXT, handle.get());
CloseClipboard();
}
void SetClipboardToMeasurements(const std::vector<Measurement>& measurements,
bool printWidth,
bool printHeight,
Measurement::Unit units)
{
if (measurements.empty())
{
return;
}
std::wostringstream stream;
bool isFirst = true;
for (const auto& measurement : measurements)
{
measurement.PrintToStream(stream, !isFirst, printWidth, printHeight, units);
isFirst = false;
}
SetClipBoardToText(stream.str());
}

View File

@@ -1,13 +1,5 @@
#pragma once
#include "Measurement.h"
#include <string_view>
#include <vector>
void SetClipBoardToText(const std::wstring_view text);
void SetClipboardToMeasurements(const std::vector<Measurement>& measurements,
bool printWidth,
bool printHeight,
Measurement::Unit units);
void SetClipBoardToText(const std::wstring_view text);

View File

@@ -8,51 +8,8 @@
#include <common/utils/window.h>
#include <exception>
#include <iostream>
#include <utility>
#include <vector>
namespace
{
constexpr std::pair<bool, bool> GetHorizontalVerticalLines(MeasureToolState::Mode mode)
{
switch (mode)
{
case MeasureToolState::Mode::Cross:
return { true, true };
case MeasureToolState::Mode::Vertical:
return { false, true };
case MeasureToolState::Mode::Horizontal:
return { true, false };
default:
throw std::runtime_error("Unknown MeasureToolState Mode");
}
}
void CopyToClipboard(HWND window, const MeasureToolState& toolState)
{
std::vector<Measurement> allMeasurements;
for (const auto& [handle, perScreen] : toolState.perScreen)
{
for (const auto& [_, measurement] : perScreen.prevMeasurements)
{
allMeasurements.push_back(measurement);
}
if (handle == window && perScreen.measuredEdges)
{
allMeasurements.push_back(*perScreen.measuredEdges);
}
}
const auto [printWidth, printHeight] = GetHorizontalVerticalLines(toolState.global.mode);
SetClipboardToMeasurements(allMeasurements, printWidth, printHeight, toolState.commonState->units);
}
inline std::pair<D2D_POINT_2F, D2D_POINT_2F> ComputeCrossFeetLine(D2D_POINT_2F center, const bool horizontal)
{
D2D_POINT_2F start = center, end = center;
@@ -70,92 +27,6 @@ namespace
return { start, end };
}
bool HandleCursorUp(HWND window, MeasureToolState* toolState, const POINT cursorPos)
{
ClipCursor(nullptr);
CopyToClipboard(window, *toolState);
auto& perScreen = toolState->perScreen[window];
const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000;
if (shiftPress && perScreen.measuredEdges)
{
perScreen.prevMeasurements.push_back(MeasureToolState::PerScreen::PrevMeasurement(cursorPos, perScreen.measuredEdges.value()));
}
perScreen.measuredEdges = std::nullopt;
return !shiftPress;
}
void DrawMeasurement(const Measurement& measurement,
D2DState& d2dState,
bool drawFeetOnCross,
MeasureToolState::Mode mode,
POINT cursorPos,
const CommonState& commonState,
HWND window)
{
const auto [drawHorizontalCrossLine, drawVerticalCrossLine] = GetHorizontalVerticalLines(mode);
const float hMeasure = measurement.Width(Measurement::Unit::Pixel);
const float vMeasure = measurement.Height(Measurement::Unit::Pixel);
d2dState.ToggleAliasedLinesMode(true);
if (drawHorizontalCrossLine)
{
const D2D_POINT_2F hLineStart{ .x = measurement.rect.left, .y = static_cast<float>(cursorPos.y) };
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
// feet *on* the last pixel row, so we must subtract 1px from the corresponding axis.
hLineEnd.x -= 1.f;
const auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
const auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
}
}
if (drawVerticalCrossLine)
{
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measurement.rect.top };
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
vLineEnd.y -= 1.f;
const auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
const auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
}
}
d2dState.ToggleAliasedLinesMode(false);
OverlayBoxText text;
const auto [crossSymbolPos, measureStringBufLen] =
measurement.Print(text.buffer.data(),
text.buffer.size(),
drawHorizontalCrossLine,
drawVerticalCrossLine,
commonState.units);
d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen,
crossSymbolPos,
D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
true,
window);
}
}
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(winrt::com_ptr<ID2D1RenderTarget> rt,
@@ -214,29 +85,17 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
}
break;
case WM_RBUTTONUP:
{
PostMessageW(window, WM_CLOSE, {}, {});
break;
}
case WM_LBUTTONUP:
{
bool shouldClose = true;
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
{
state->Access([&](MeasureToolState& s) {
shouldClose = HandleCursorUp(window,
&s,
convert::FromSystemToWindow(window, s.commonState->cursorPosSystemSpace));
});
}
if (shouldClose)
{
PostMessageW(window, WM_CLOSE, {}, {});
state->Read([](const MeasureToolState& s) { s.commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer.data());
}); });
}
PostMessageW(window, WM_CLOSE, {}, {});
break;
}
case WM_MOUSEWHEEL:
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
{
@@ -260,29 +119,29 @@ void DrawMeasureToolTick(const CommonState& commonState,
{
bool continuousCapture = {};
bool drawFeetOnCross = {};
bool drawHorizontalCrossLine = true;
bool drawVerticalCrossLine = true;
std::optional<Measurement> measuredEdges{};
Measurement measuredEdges{};
MeasureToolState::Mode mode = {};
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
const MappedTextureView* backgroundTextureToConvert = nullptr;
std::vector<MeasureToolState::PerScreen::PrevMeasurement> prevMeasurements;
bool gotMeasurement = false;
toolState.Read([&](const MeasureToolState& state) {
continuousCapture = state.global.continuousCapture;
drawFeetOnCross = state.global.drawFeetOnCross;
mode = state.global.mode;
if (const auto it = state.perScreen.find(window); it != end(state.perScreen))
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
{
const auto& perScreen = it->second;
prevMeasurements = perScreen.prevMeasurements;
if (!perScreen.measuredEdges)
{
return;
}
measuredEdges = perScreen.measuredEdges;
gotMeasurement = true;
measuredEdges = *perScreen.measuredEdges;
if (continuousCapture)
return;
@@ -298,9 +157,23 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
});
if (!measuredEdges && prevMeasurements.empty())
{
if (!gotMeasurement)
return;
switch (mode)
{
case MeasureToolState::Mode::Cross:
drawHorizontalCrossLine = true;
drawVerticalCrossLine = true;
break;
case MeasureToolState::Mode::Vertical:
drawHorizontalCrossLine = false;
drawVerticalCrossLine = true;
break;
case MeasureToolState::Mode::Horizontal:
drawHorizontalCrossLine = true;
drawVerticalCrossLine = false;
break;
}
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
@@ -316,23 +189,73 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
if (continuousCapture || !backgroundBitmap)
{
d2dState.dxgiWindowState.rt->Clear();
}
const float hMeasure = measuredEdges.Width(Measurement::Unit::Pixel);
const float vMeasure = measuredEdges.Height(Measurement::Unit::Pixel);
if (!continuousCapture && backgroundBitmap)
{
d2dState.dxgiWindowState.rt->DrawBitmap(backgroundBitmap.get());
}
for (const auto& [prevCursorPos, prevMeasurement] : prevMeasurements)
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
d2dState.ToggleAliasedLinesMode(true);
if (drawHorizontalCrossLine)
{
DrawMeasurement(prevMeasurement, d2dState, drawFeetOnCross, mode, prevCursorPos, commonState, window);
const D2D_POINT_2F hLineStart{ .x = measuredEdges.rect.left, .y = static_cast<float>(cursorPos.y) };
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
// feet *on* the last pixel row, so we must subtract 1px from the corresponding axis.
hLineEnd.x -= 1.f;
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
}
}
if (measuredEdges)
if (drawVerticalCrossLine)
{
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
DrawMeasurement(*measuredEdges, d2dState, drawFeetOnCross, mode, cursorPos, commonState, window);
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measuredEdges.rect.top };
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
vLineEnd.y -= 1.f;
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
}
}
d2dState.ToggleAliasedLinesMode(false);
OverlayBoxText text;
const auto [crossSymbolPos, measureStringBufLen] =
measuredEdges.Print(text.buffer.data(),
text.buffer.size(),
drawHorizontalCrossLine,
drawVerticalCrossLine,
commonState.units);
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
});
d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen,
crossSymbolPos,
D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
true,
window);
}

View File

@@ -2,8 +2,6 @@
#include "Measurement.h"
#include <iostream>
Measurement::Measurement(RECT winRect)
{
rect.left = static_cast<float>(winRect.left);
@@ -91,41 +89,3 @@ Measurement::PrintResult Measurement::Print(wchar_t* buf,
return result;
}
void Measurement::PrintToStream(std::wostream& stream,
const bool prependNewLine,
const bool printWidth,
const bool printHeight,
const Unit units) const
{
if (prependNewLine)
{
stream << std::endl;
}
if (printWidth)
{
stream << Width(units);
if (printHeight)
{
stream << L" \x00D7 ";
}
}
if (printHeight)
{
stream << Height(units);
}
switch (units)
{
case Measurement::Unit::Inch:
stream << L" in";
break;
case Measurement::Unit::Centimetre:
stream << L" cm";
break;
}
}

View File

@@ -2,7 +2,6 @@
#include <dcommon.h>
#include <windef.h>
#include <iosfwd>
struct Measurement
{
@@ -36,10 +35,4 @@ struct Measurement
const bool printWidth,
const bool printHeight,
const Unit units) const;
void PrintToStream(std::wostream& stream,
const bool prependNewLine,
const bool printWidth,
const bool printHeight,
const Unit units) const;
};

View File

@@ -31,6 +31,7 @@ struct CommonState
Measurement::Unit units = Measurement::Unit::Pixel;
mutable Serialized<OverlayBoxText> overlayBoxText;
POINT cursorPosSystemSpace = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
};
@@ -76,13 +77,9 @@ struct MeasureToolState
struct PerScreen
{
using PrevMeasurement = std::pair<POINT, Measurement>;
bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false;
std::optional<Measurement> measuredEdges;
std::vector<PrevMeasurement> prevMeasurements;
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
// directly from a capturing thread.
const MappedTextureView* capturedScreenTexture = nullptr;

View File

@@ -1,6 +1,5 @@
#pragma once
#include <windef.h>
#include <chrono>
namespace consts
@@ -19,6 +18,4 @@ namespace consts
/* Offset to not try not to use the cursor immediate pixels in measuring, but it seems only necessary for continuous mode. */
constexpr inline long CURSOR_OFFSET_AMOUNT_X = 4;
constexpr inline long CURSOR_OFFSET_AMOUNT_Y = 4;
constexpr inline LPARAM MOUSEEVENTF_FROMTOUCH = 0xFF515700;
}

View File

@@ -57,20 +57,6 @@ namespace WorkspacesEditor
return;
}
var args = e?.Args;
int powerToysRunnerPid;
if (args?.Length > 0)
{
_ = int.TryParse(args[0], out powerToysRunnerPid);
Logger.LogInfo($"WorkspacesEditor started from the PowerToys Runner. Runner pid={powerToysRunnerPid}");
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
{
Logger.LogInfo("PowerToys Runner exited. Exiting WorkspacesEditor");
Dispatcher.Invoke(Shutdown);
});
}
ThemeManager = new ThemeManager(this);
if (_mainViewModel == null)
@@ -103,7 +89,6 @@ namespace WorkspacesEditor
}
Dispose();
Environment.Exit(0);
}
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)

View File

@@ -16,6 +16,14 @@ namespace WorkspacesEditor.Data
}
}
public static string LaunchFile
{
get
{
return FolderUtils.DataFolder() + "\\tempLaunch-workspaces.json";
}
}
public static void DeleteTempFile()
{
if (System.IO.File.Exists(File))

View File

@@ -1,4 +1,4 @@
<Page
<Page
x:Class="WorkspacesEditor.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -198,7 +198,7 @@
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="110" />
</Grid.ColumnDefinitions>
<StackPanel
Margin="12,14,10,10"

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
using ManagedCommon;
@@ -15,12 +14,10 @@ namespace WorkspacesEditor
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, IDisposable
public partial class MainWindow : Window
{
public MainViewModel MainViewModel { get; set; }
private CancellationTokenSource cancellationToken = new CancellationTokenSource();
private static MainPage _mainPage;
public MainWindow(MainViewModel mainViewModel)
@@ -44,36 +41,10 @@ namespace WorkspacesEditor
MaxWidth = SystemParameters.PrimaryScreenWidth;
MaxHeight = SystemParameters.PrimaryScreenHeight;
Common.UI.NativeEventWaiter.WaitForEventLoop(
PowerToys.Interop.Constants.WorkspacesHotkeyEvent(),
() =>
{
if (ApplicationIsInFocus())
{
Environment.Exit(0);
}
else
{
if (WindowState == WindowState.Minimized)
{
WindowState = WindowState.Normal;
}
// Get the window handle of the Workspaces Editor window
IntPtr handle = new WindowInteropHelper(this).Handle;
WindowHelpers.BringToForeground(handle);
InvalidateVisual();
}
},
Application.Current.Dispatcher,
cancellationToken.Token);
}
private void OnClosing(object sender, EventArgs e)
{
cancellationToken.Dispose();
App.Current.Shutdown();
}
@@ -96,25 +67,5 @@ namespace WorkspacesEditor
{
ContentFrame.GoBack();
}
public static bool ApplicationIsInFocus()
{
var activatedHandle = NativeMethods.GetForegroundWindow();
if (activatedHandle == IntPtr.Zero)
{
return false; // No window is currently activated
}
var procId = Environment.ProcessId;
int activeProcId;
_ = NativeMethods.GetWindowThreadProcessId(activatedHandle, out activeProcId);
return activeProcId == procId;
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

@@ -207,9 +207,9 @@ namespace WorkspacesEditor.Models
private BitmapImage _previewImage;
private double _previewImageWidth;
public Project(Project selectedProject)
public Project(Project selectedProject, string newId = "")
{
Id = selectedProject.Id;
Id = newId == string.Empty ? selectedProject.Id : newId;
Name = selectedProject.Name;
PreviewIcons = selectedProject.PreviewIcons;
PreviewImage = selectedProject.PreviewImage;

View File

@@ -9,6 +9,7 @@
xmlns:ui="http://schemas.modernwpf.com/2019"
Title="{x:Static props:Resources.SnapshotWindowTitle}"
Width="350"
Height="140"
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
ui:WindowHelper.UseModernWindowStyle="True"
@@ -17,7 +18,6 @@
BorderThickness="5"
Closing="Window_Closing"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid Margin="5" Background="Transparent">
@@ -31,10 +31,9 @@
</Grid.ColumnDefinitions>
<TextBlock
Grid.ColumnSpan="2"
Margin="5,5,5,15"
Margin="5"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Text="{x:Static props:Resources.SnapshotDescription}"
TextWrapping="Wrap" />
Text="{x:Static props:Resources.SnapshotDescription}" />
<Button
x:Name="CancelButton"
Grid.Row="1"

View File

@@ -30,7 +30,7 @@ namespace WorkspacesEditor.Utils
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int processId);
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();

View File

@@ -15,6 +15,13 @@ namespace WorkspacesEditor.Utils
{
public class WorkspacesEditorIO
{
public enum StorageFile
{
Common,
Temporal,
TemporalLaunch,
}
public WorkspacesEditorIO()
{
}
@@ -72,7 +79,7 @@ namespace WorkspacesEditor.Utils
}
}
public void SerializeWorkspaces(List<Project> workspaces, bool useTempFile = false)
public void SerializeWorkspaces(List<Project> workspaces, StorageFile storageFile = StorageFile.Common)
{
WorkspacesData serializer = new WorkspacesData();
WorkspacesData.WorkspacesListWrapper workspacesWrapper = new WorkspacesData.WorkspacesListWrapper { };
@@ -148,7 +155,14 @@ namespace WorkspacesEditor.Utils
try
{
IOUtils ioUtils = new IOUtils();
ioUtils.WriteFile(useTempFile ? TempProjectData.File : serializer.File, serializer.Serialize(workspacesWrapper));
string fileName = storageFile switch
{
StorageFile.Temporal => TempProjectData.File,
StorageFile.TemporalLaunch => TempProjectData.LaunchFile,
_ => serializer.File,
};
ioUtils.WriteFile(fileName, serializer.Serialize(workspacesWrapper));
}
catch (Exception e)
{
@@ -176,7 +190,22 @@ namespace WorkspacesEditor.Utils
internal void SerializeTempProject(Project project)
{
SerializeWorkspaces(new List<Project>() { project }, true);
SerializeWorkspaces(new List<Project>() { project }, StorageFile.Temporal);
}
internal void RemoveFile(StorageFile storageFile)
{
string fileName = storageFile switch
{
StorageFile.Temporal => TempProjectData.File,
StorageFile.TemporalLaunch => TempProjectData.LaunchFile,
_ => string.Empty,
};
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}
}
}

View File

@@ -509,7 +509,13 @@ namespace WorkspacesEditor.ViewModels
internal async void LaunchAndEdit(Project project)
{
await Task.Run(() => RunLauncher(project.Id, InvokePoint.LaunchAndEdit));
// the project might contain removed apps, creating a temporal copy of it (without removed apps) and launching the copy.
Project launchProject = new Project(project, Guid.NewGuid().ToString());
_workspacesEditorIO.SerializeWorkspaces(new List<Project>() { launchProject }, WorkspacesEditorIO.StorageFile.TemporalLaunch);
await Task.Run(() => RunLauncher(launchProject.Id, InvokePoint.LaunchAndEdit));
_workspacesEditorIO.RemoveFile(WorkspacesEditorIO.StorageFile.TemporalLaunch);
projectBeforeLaunch = new Project(project);
EnterSnapshotMode(true);
}

View File

@@ -104,7 +104,7 @@
</StackPanel>
<controls:ResetIsEnabled Grid.Column="4">
<Button
Width="Auto"
Width="120"
Margin="10,5"
Padding="24,6"
AutomationProperties.Name="{x:Static props:Resources.Delete}"

View File

@@ -51,7 +51,7 @@
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">System</dpiAwareness>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -240,8 +240,6 @@ namespace
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, ErrorList& launchErrors)
{
std::wstring dir = std::filesystem::path(appPath).parent_path();
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.hwnd = nullptr;
@@ -249,7 +247,7 @@ bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs,
sei.lpVerb = elevated ? L"runas" : L"open";
sei.lpFile = appPath.c_str();
sei.lpParameters = commandLineArgs.c_str();
sei.lpDirectory = dir.c_str();
sei.lpDirectory = nullptr;
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei))

View File

@@ -24,7 +24,7 @@ const std::wstring internalPath = L"";
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cmdShow)
{
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesLauncherLoggerName);
InitUnhandledExceptionHandler();
InitUnhandledExceptionHandler();
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
@@ -41,7 +41,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
GetModuleFileNameW(nullptr, exe_path.get(), exe_path_size);
const auto modulePath = get_module_folderpath();
std::string cmdLineStr(cmdline);
std::wstring cmdLineWStr(cmdLineStr.begin(), cmdLineStr.end());
@@ -57,7 +57,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
}
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
std::string cmdLineStr(cmdline);
auto cmdArgs = split(cmdLineStr, " ");
if (cmdArgs.size() < 1)
@@ -66,7 +66,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
std::wstring id(cmdArgs[0].begin(), cmdArgs[0].end());
if (id.empty())
{
@@ -94,45 +94,103 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
WorkspacesData::WorkspacesProject projectToLaunch{};
if (invokePoint == InvokePoint::LaunchAndEdit)
{
// check the temp file in case the project is just created and not saved to the workspaces.json yet
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
// check the temp launch file in case the project is launched from the editor
if (std::filesystem::exists(WorkspacesData::TempLaunchWorkspacesFile()))
{
try
{
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempLaunchWorkspacesFile());
if (savedWorkspacesJson.has_value())
{
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
auto savedWorkspaces = WorkspacesData::WorkspacesListJSON::FromJson(savedWorkspacesJson.value());
if (savedWorkspaces.has_value())
{
projectToLaunch = savedWorkspaces.value();
workspaces = savedWorkspaces.value();
}
else
{
Logger::critical("Incorrect Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
Logger::critical("Incorrect temporal launch Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
}
else
{
Logger::critical("Incorrect Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
Logger::critical("Incorrect temporal launch Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
}
catch (std::exception ex)
{
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
Logger::critical("Exception on reading temporal launch Workspaces file: {}", ex.what());
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
if (workspaces.empty())
{
Logger::warn("Temp Launch Workspaces file is empty");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_EMPTY_FILE), WorkspacesData::TempLaunchWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
for (const auto& proj : workspaces)
{
if (proj.id == id)
{
projectToLaunch = proj;
break;
}
}
}
if (projectToLaunch.id.empty())
{
// check the temp file in case the project is just created and not saved to the workspaces.json yet
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
{
try
{
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
if (savedWorkspacesJson.has_value())
{
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
if (savedWorkspaces.has_value())
{
projectToLaunch = savedWorkspaces.value();
}
else
{
Logger::critical("Incorrect temporal Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
}
else
{
Logger::critical("Incorrect temporal Workspaces file");
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
}
catch (std::exception ex)
{
Logger::critical("Exception on reading temporal Workspaces file: {}", ex.what());
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
return 1;
}
}
}
}
if (projectToLaunch.id.empty())
{
try
@@ -201,7 +259,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
std::vector<std::pair<std::wstring, std::wstring>> launchErrors{};
auto start = std::chrono::high_resolution_clock::now();
bool launchedSuccessfully = Launch(projectToLaunch, monitors, launchErrors);
// update last-launched time
if (invokePoint != InvokePoint::LaunchAndEdit)
{

View File

@@ -3,17 +3,22 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using ManagedCommon;
using Microsoft.VisualBasic.Devices;
using Windows.ApplicationModel;
using Windows.Management.Deployment;
namespace WorkspacesLauncherUI.Models
@@ -29,8 +34,6 @@ namespace WorkspacesLauncherUI.Models
public string AppPath { get; set; }
public bool Loading => LaunchState == "waiting";
private Icon _icon;
public Icon Icon
@@ -69,23 +72,14 @@ namespace WorkspacesLauncherUI.Models
public string LaunchState { get; set; }
public string StateGlyph
public string StateImageSource
{
get => LaunchState switch
{
"launched" => "\U0000F78C",
"failed" => "\U0000EF2C",
_ => "\U0000EF2C",
};
}
public System.Windows.Media.Brush StateColor
{
get => LaunchState switch
{
"launched" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 0, 128, 0)),
"failed" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
"waiting" => "../images/spinner.gif",
"launched" => "../images/checkmark.png",
"failed" => "../images/failed.png",
_ => "../images/spinner.gif",
};
}

View File

@@ -79,7 +79,7 @@ namespace WorkspacesLauncherUI.Properties {
}
/// <summary>
/// Looks up a localized string similar to Your workspace is launching. Waiting on ....
/// Looks up a localized string similar to Your project is launching. Waiting on ....
/// </summary>
public static string LauncherWindowTitle {
get {

View File

@@ -124,6 +124,6 @@
<value>Dismiss</value>
</data>
<data name="LauncherWindowTitle" xml:space="preserve">
<value>Your workspace is launching. Waiting on ...</value>
<value>Your project is launching. Waiting on ...</value>
</data>
</root>

View File

@@ -2,7 +2,6 @@
x:Class="WorkspacesLauncherUI.StatusWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:WorkspacesLauncherUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WorkspacesLauncherUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -22,11 +21,6 @@
Topmost="True"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<converters:BooleanToInvertedVisibilityConverter x:Key="BooleanToInvertedVisibilityConverter" />
</Window.Resources>
<Grid Margin="5" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
@@ -61,28 +55,14 @@
FontWeight="Normal"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Text="{Binding Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<ui:ProgressRing
x:Name="ProgressRing"
<Image
Grid.Column="2"
Width="20"
Height="20"
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="True"
Visibility="{Binding Loading, Mode=OneWay, Converter={StaticResource BoolToVis}, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Width="20"
Height="20"
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
FontSize="20"
Foreground="{Binding StateColor}"
Text="{Binding StateGlyph}"
Visibility="{Binding Loading, Mode=OneWay, Converter={StaticResource BooleanToInvertedVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" />
Source="{Binding StateImageSource}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>

View File

@@ -30,7 +30,10 @@
<AssemblyName>PowerToys.WorkspacesLauncherUI</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Remove="images\checkmark.png" />
<None Remove="images\DefaultIcon.ico" />
<None Remove="images\failed.png" />
<None Remove="images\spinner.gif" />
<None Remove="images\Workspaces.ico" />
</ItemGroup>
<ItemGroup>
@@ -84,6 +87,18 @@
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="images\checkmark.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="images\failed.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="images\spinner.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -22,6 +22,12 @@ namespace WorkspacesData
return settingsFolderPath + L"\\temp-workspaces.json";
}
std::wstring TempLaunchWorkspacesFile()
{
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
return settingsFolderPath + L"\\tempLaunch-workspaces.json";
}
std::wstring LaunchWorkspacesFile()
{
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);

View File

@@ -6,6 +6,7 @@ namespace WorkspacesData
{
std::wstring WorkspacesFile();
std::wstring TempWorkspacesFile();
std::wstring TempLaunchWorkspacesFile();
std::wstring LaunchWorkspacesFile();
struct WorkspacesProject

View File

@@ -75,14 +75,7 @@ public:
virtual void OnHotkeyEx() override
{
if (is_process_running())
{
sendHotkeyEvent();
}
else
{
launch_editor();
}
launch_editor();
}
// Return the configured status for the gpo policy for the module
@@ -164,12 +157,6 @@ public:
m_toggleEditorEvent = nullptr;
}
if (m_hotkeyEvent)
{
CloseHandle(m_hotkeyEvent);
m_hotkeyEvent = nullptr;
}
delete this;
}
@@ -184,12 +171,6 @@ public:
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "Workspaces");
init_settings();
m_hotkeyEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::WORKSPACES_HOTKEY_EVENT);
if (!m_hotkeyEvent)
{
Logger::warn(L"Failed to create hotkey event. {}", get_last_error_or_default(GetLastError()));
}
m_toggleEditorEvent = CreateDefaultEvent(CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT);
if (!m_toggleEditorEvent)
{
@@ -222,12 +203,23 @@ private:
executable_args.append(std::to_wstring(powertoys_pid));
}
void sendHotkeyEvent()
void SendCloseEvent()
{
Logger::trace(L"Signaled hotkey event");
if (!SetEvent(m_hotkeyEvent))
auto exitEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::WORKSPACES_EXIT_EVENT);
if (!exitEvent)
{
Logger::warn(L"Failed to signal hotkey event. {}", get_last_error_or_default(GetLastError()));
Logger::warn(L"Failed to create exitEvent. {}", get_last_error_or_default(GetLastError()));
}
else
{
Logger::trace(L"Signaled exitEvent");
if (!SetEvent(exitEvent))
{
Logger::warn(L"Failed to signal exitEvent. {}", get_last_error_or_default(GetLastError()));
}
ResetEvent(exitEvent);
CloseHandle(exitEvent);
}
}
@@ -245,14 +237,10 @@ private:
ResetEvent(m_toggleEditorEvent);
}
if (m_hotkeyEvent)
{
ResetEvent(m_hotkeyEvent);
}
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
SendCloseEvent();
m_hProcess = nullptr;
}
}
@@ -314,15 +302,15 @@ private:
{
Logger::trace(L"Starting Workspaces Editor");
unsigned long powertoys_pid = GetCurrentProcessId();
/*unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
executable_args.append(std::to_wstring(powertoys_pid));*/
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.lpFile = L"PowerToys.WorkspacesEditor.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
//sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started the Workspaces Editor");
@@ -335,11 +323,6 @@ private:
m_hProcess = sei.hProcess;
}
bool is_process_running() const
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
@@ -350,13 +333,10 @@ private:
// Handle to event used to invoke Workspaces Editor
HANDLE m_toggleEditorEvent;
// Handle to event used when hotkey is invoked
HANDLE m_hotkeyEvent;
// Hotkey to invoke the module
HotkeyEx m_hotkey{
.modifiersMask = MOD_CONTROL | MOD_WIN,
.vkCode = 0xC0, // VK_OEM_3 key; usually `~
.modifiersMask = MOD_SHIFT | MOD_WIN,
.vkCode = 0x4F, // O key;
};
};

View File

@@ -17,6 +17,6 @@ namespace Awake.Core
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
// is representative of the date when the last change was made before
// the pull request is issued.
internal const string BuildId = "VISEGRADRELAY_08152024";
internal const string BuildId = "DAISY023_04102024";
}
}

View File

@@ -5,12 +5,10 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
@@ -64,7 +62,7 @@ namespace Awake.Core
{
Thread monitorThread = new(() =>
{
Thread.CurrentThread.IsBackground = false;
Thread.CurrentThread.IsBackground = true;
while (true)
{
ExecutionState state = _stateQueue.Take();
@@ -128,18 +126,10 @@ namespace Awake.Core
_stateQueue.Add(ExecutionState.ES_CONTINUOUS);
// Next, make sure that any existing background threads are terminated.
if (_tokenSource != null)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
}
else
{
Logger.LogWarning("The token source was null.");
}
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
Logger.LogInfo("Instantiating of new token source and thread token completed.");
}
@@ -147,11 +137,12 @@ namespace Awake.Core
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent());
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update);
CancelExistingThread();
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update);
if (IsUsingPowerToysConfig)
{
try
@@ -426,18 +417,5 @@ namespace Awake.Core
}
}
}
public static Process? GetParentProcess()
{
return GetParentProcess(Process.GetCurrentProcess().Handle);
}
private static Process? GetParentProcess(IntPtr handle)
{
ProcessBasicInformation pbi = default;
int status = Bridge.NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf<ProcessBasicInformation>(), out _);
return status != 0 ? null : Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
}
}

View File

@@ -1,20 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace Awake.Core.Models
{
[StructLayout(LayoutKind.Sequential)]
internal struct ProcessBasicInformation
{
public IntPtr ExitStatus;
public IntPtr PebAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public IntPtr UniquePID;
public IntPtr InheritedFromUniqueProcessId;
}
}

View File

@@ -56,13 +56,13 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
internal static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
internal static extern void PostQuitMessage(int nExitCode);
[DllImport("shell32.dll", SetLastError = true)]
[DllImport("shell32.dll")]
internal static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData pnid);
[DllImport("user32.dll", SetLastError = true)]
@@ -83,14 +83,14 @@ namespace Awake.Core.Native
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", SetLastError = true)]
@@ -100,10 +100,7 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetMenuInfo(IntPtr hMenu, ref MenuInfo lpcmi);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll")]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("ntdll.dll")]
internal static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength);
}
}

View File

@@ -43,6 +43,11 @@ namespace Awake.Core
HiddenWindowHandle = IntPtr.Zero;
}
public static void InitializeTray(string text, Icon icon)
{
CreateHiddenWindow(icon, text);
}
private static void ShowContextMenu(IntPtr hWnd)
{
if (TrayMenu != IntPtr.Zero)
@@ -83,7 +88,7 @@ namespace Awake.Core
}
}
public static void InitializeTray(Icon icon, string text)
private static void CreateHiddenWindow(Icon icon, string text)
{
IntPtr hWnd = IntPtr.Zero;
@@ -138,13 +143,7 @@ namespace Awake.Core
Bridge.UpdateWindow(hWnd);
SetShellIcon(hWnd, text, icon);
});
}).Wait();
Task.Run(() =>
{
RunOnMainThread(() =>
{
RunMessageLoop();
});
});
@@ -152,8 +151,6 @@ namespace Awake.Core
internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add)
{
Logger.LogInfo($"Setting the shell icon.\nText: {text}\nAction: {action}");
int message = Native.Constants.NIM_ADD;
switch (action)
@@ -171,8 +168,6 @@ namespace Awake.Core
if (action == TrayIconAction.Add || action == TrayIconAction.Update)
{
Logger.LogInfo($"Adding or updating tray icon. HIcon handle is {icon?.Handle}\nHWnd: {hWnd}");
_notifyIconData = new NotifyIconData
{
CbSize = Marshal.SizeOf(typeof(NotifyIconData)),

View File

@@ -44,7 +44,6 @@ namespace Awake
internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"];
internal static readonly string[] AliasesPidOption = ["--pid", "-p"];
internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"];
internal static readonly string[] AliasesParentPidOption = ["--use-parent-pid", "-u"];
private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico"));
@@ -117,12 +116,6 @@ namespace Awake
IsRequired = false,
};
var parentPidOption = new Option<bool>(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
RootCommand? rootCommand =
[
configOption,
@@ -130,11 +123,10 @@ namespace Awake
timeOption,
pidOption,
expireAtOption,
parentPidOption,
];
rootCommand.Description = Core.Constants.AppName;
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption);
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption);
return rootCommand.InvokeAsync(args).Result;
}
@@ -161,9 +153,9 @@ namespace Awake
Manager.CompleteExit(exitCode);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid)
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt)
{
if (pid == 0 && !useParentPid)
if (pid == 0)
{
Logger.LogInfo("No PID specified. Allocating console...");
Manager.AllocateConsole();
@@ -183,12 +175,11 @@ namespace Awake
Logger.LogInfo($"The value for --time-limit is: {timeLimit}");
Logger.LogInfo($"The value for --pid is: {pid}");
Logger.LogInfo($"The value for --expire-at is: {expireAt}");
Logger.LogInfo($"The value for --use-parent-pid is: {useParentPid}");
// Start the monitor thread that will be used to track the current state.
Manager.StartMonitor();
TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName);
TrayHelper.InitializeTray(Core.Constants.FullAppName, _defaultAwakeIcon);
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent());
new Thread(() =>
@@ -218,47 +209,12 @@ namespace Awake
}
ScaffoldConfiguration(settingsPath);
if (pid != 0)
{
Logger.LogInfo($"Bound to target process while also using PowerToys settings: {pid}");
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
}
catch (Exception ex)
{
Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}");
}
}
else if (pid != 0 || useParentPid)
{
// Second, we snap to process-based execution. Because this is something that
// is snapped to a running entity, we only want to enable the ability to set
// indefinite keep-awake with the display settings that the user wants to set.
int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0;
if (targetPid != 0)
{
Logger.LogInfo($"Bound to target process: {targetPid}");
Manager.SetIndefiniteKeepAwake(displayOn);
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
else
{
Logger.LogError("Not binding to any process.");
}
}
else
{
// Date-based binding takes precedence over timed configuration, so we want to
@@ -291,6 +247,15 @@ namespace Awake
}
}
}
if (pid != 0)
{
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
}
private static void ScaffoldConfiguration(string settingsPath)

View File

@@ -114,15 +114,6 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Uses the parent process as the bound target - once the process terminates, Awake stops..
/// </summary>
internal static string AWAKE_CMD_PARENT_PID_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_PARENT_PID_OPTION", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exit.
/// </summary>

View File

@@ -205,7 +205,4 @@
<value>s</value>
<comment>Used to display number of seconds in the system tray tooltip.</comment>
</data>
<data name="AWAKE_CMD_PARENT_PID_OPTION" xml:space="preserve">
<value>Uses the parent process as the bound target - once the process terminates, Awake stops.</value>
</data>
</root>

View File

@@ -15,7 +15,6 @@
namespace NonLocalizable
{
const wchar_t PowerToysAppFZEditor[] = L"POWERTOYS.FANCYZONESEDITOR.EXE";
const wchar_t PowerToysWorkspacesEditor[] = L"POWERTOYS.WORKSPACESEDITOR.EXE";
const char SplashClassName[] = "MsoSplash";
const wchar_t CoreWindow[] = L"Windows.UI.Core.CoreWindow";
const wchar_t SearchUI[] = L"SearchUI.exe";
@@ -201,7 +200,7 @@ bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, const std::wst
return true;
}
static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::PowerToysWorkspacesEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI };
static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI };
return (check_excluded_app(hwnd, processPath, defaultExcludedApps));
}

View File

@@ -247,14 +247,6 @@ namespace KeyboardEventHandlers
bool isMatchOnChordEnd = false;
bool isMatchOnChordStart = false;
static bool isAltRightKeyInvoked = false;
// Check if the right Alt key (AltGr) is pressed.
if (data->lParam->vkCode == VK_RMENU && ii.GetVirtualKeyState(VK_LCONTROL))
{
isAltRightKeyInvoked = true;
}
// If the shortcut has been pressed down
if (!it->second.isShortcutInvoked && it->first.CheckModifiersKeyboardState(ii))
{
@@ -530,13 +522,6 @@ namespace KeyboardEventHandlers
// 5. The user presses any key apart from the action key or a modifier key in the original shortcut - revert the keyboard state to just the original modifiers being held down along with the current key press
// 6. The user releases any key apart from original modifier or original action key - This can't happen since the key down would have to happen first, which is handled above
// Prevents the unintended release of the Ctrl part when AltGr is pressed. AltGr acts as both Ctrl and Alt being pressed.
// After triggering a shortcut involving AltGr, the system might attempt to release the Ctrl part. This code ensures Ctrl remains pressed, maintaining the AltGr state correctly.
if (isAltRightKeyInvoked && data->lParam->vkCode == VK_LCONTROL && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
{
break;
}
// Get the common keys between the two shortcuts
int commonKeys = (remapToShortcut && !isRunProgram) ? it->first.GetCommonModifiersCount(std::get<Shortcut>(it->second.targetShortcut)) : 0;
@@ -558,15 +543,8 @@ namespace KeyboardEventHandlers
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, 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 it's 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);
}
else
{
isAltRightKeyInvoked = false;
}
// 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 it's 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);
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->Ctrl+V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@@ -581,16 +559,8 @@ namespace KeyboardEventHandlers
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
// Ensures that after releasing both the action key and AltGr, Ctrl does not remain falsely pressed.
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 it's own key message
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
}
else
{
isAltRightKeyInvoked = false;
}
// 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 it's own key message
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@@ -702,22 +672,16 @@ namespace KeyboardEventHandlers
// Release new key state
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
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);
}
// Set original shortcut key down state except the action key
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, 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
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
if (!isAltRightKeyInvoked)
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
}
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
@@ -777,10 +741,7 @@ namespace KeyboardEventHandlers
if (newRemapping.RemapToKey())
{
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.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
{
// If the action key from the last shortcut is still being pressed, release it.
@@ -791,19 +752,13 @@ namespace KeyboardEventHandlers
else
{
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.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
{
// If the action key from the last shortcut is still being pressed, release it.
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(from.actionKey), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
if (!isAltRightKeyInvoked)
{
Helpers::SetModifierKeyEvents(to, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, from);
}
Helpers::SetModifierKeyEvents(to, it->second.winKeyInvoked, 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;
}
@@ -828,13 +783,10 @@ namespace KeyboardEventHandlers
{
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
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.winKeyInvoked, 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));
}
// 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));
// key down for original shortcut action key with shortcut flag so that we don't invoke the same shortcut remap again
if (isActionKeyPressed)
@@ -848,13 +800,10 @@ namespace KeyboardEventHandlers
// Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after shortcut to shortcut is released to open start menu
}
if (!isAltRightKeyInvoked)
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
}
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp)
@@ -899,11 +848,8 @@ namespace KeyboardEventHandlers
{
std::vector<INPUT> keyEventList;
if (!isAltRightKeyInvoked)
{
// Set original shortcut key down state
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
// Set original shortcut key down state
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, 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
if (isRemapToDisable && isOriginalActionKeyPressed)
@@ -917,13 +863,10 @@ namespace KeyboardEventHandlers
// Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after another shortcut to key remap is released to open start menu
if (!isAltRightKeyInvoked)
{
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
}
// Reset the remap state
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
it->second.isOriginalActionKeyPressed = false;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)

View File

@@ -50,6 +50,7 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter
return new List<Result>();
}
// Convert
return UnitHandler.Convert(convertModel)
.Select(x => GetResult(x))
.ToList();

View File

@@ -593,7 +593,8 @@ namespace PowerLauncher.ViewModel
_updateSource?.Dispose();
var currentUpdateSource = new CancellationTokenSource();
_updateSource = currentUpdateSource;
_updateToken = _updateSource.Token;
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;
var queryText = QueryText.Trim();
var pluginQueryPairs = QueryBuilder.Build(queryText);
@@ -630,7 +631,7 @@ namespace PowerLauncher.ViewModel
query.SelectedItems = _userSelectedRecord.GetGenericHistory();
var results = PluginManager.QueryForPlugin(plugin, query);
resultPluginPair[plugin.Metadata] = results;
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
@@ -641,7 +642,7 @@ namespace PowerLauncher.ViewModel
}
else
{
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
// To execute a query corresponding to each plugin
foreach (KeyValuePair<PluginPair, Query> pluginQueryItem in pluginQueryPairs)
@@ -651,7 +652,7 @@ namespace PowerLauncher.ViewModel
query.SelectedItems = _userSelectedRecord.GetGenericHistory();
var results = PluginManager.QueryForPlugin(plugin, query);
resultPluginPair[plugin.Metadata] = results;
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
}
}
@@ -663,11 +664,11 @@ namespace PowerLauncher.ViewModel
Results.Clear();
foreach (var p in resultPluginPair)
{
UpdateResultView(p.Value, queryText, _updateToken);
_updateToken.ThrowIfCancellationRequested();
UpdateResultView(p.Value, queryText, currentCancellationToken);
currentCancellationToken.ThrowIfCancellationRequested();
}
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
numResults = Results.Results.Count;
if (!doFinalSort)
{
@@ -676,7 +677,7 @@ namespace PowerLauncher.ViewModel
}
}
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
if (!doFinalSort)
{
UpdateResultsListViewAfterQuery(queryText);
@@ -688,7 +689,7 @@ namespace PowerLauncher.ViewModel
if (!delayedExecution.HasValue || delayedExecution.Value)
{
// Run the slower query of the DelayedExecution plugins
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
Parallel.ForEach(plugins, (plugin) =>
{
try
@@ -696,7 +697,7 @@ namespace PowerLauncher.ViewModel
Query query;
pluginQueryPairs.TryGetValue(plugin, out query);
var results = PluginManager.QueryForPlugin(plugin, query, true);
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
if ((results?.Count ?? 0) != 0)
{
lock (_addResultsLock)
@@ -704,16 +705,16 @@ namespace PowerLauncher.ViewModel
// Using CurrentCultureIgnoreCase since this is user facing
if (queryText.Equals(_currentQuery, StringComparison.CurrentCultureIgnoreCase))
{
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
// Remove the original results from the plugin
Results.Results.RemoveAll(r => r.Result.PluginID == plugin.Metadata.ID);
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
// Add the new results from the plugin
UpdateResultView(results, queryText, _updateToken);
UpdateResultView(results, queryText, currentCancellationToken);
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
numResults = Results.Results.Count;
if (!doFinalSort)
{
@@ -721,7 +722,7 @@ namespace PowerLauncher.ViewModel
}
}
_updateToken.ThrowIfCancellationRequested();
currentCancellationToken.ThrowIfCancellationRequested();
if (!doFinalSort)
{
UpdateResultsListViewAfterQuery(queryText, noInitialResults, true);
@@ -750,7 +751,7 @@ namespace PowerLauncher.ViewModel
};
PowerToysTelemetry.Log.WriteEvent(queryEvent);
},
_updateToken);
currentCancellationToken);
if (doFinalSort)
{
@@ -762,7 +763,7 @@ namespace PowerLauncher.ViewModel
Results.SelectedItem = Results.Results.FirstOrDefault();
UpdateResultsListViewAfterQuery(queryText, false, false);
},
_updateToken);
currentCancellationToken);
}
}
}

View File

@@ -20,7 +20,6 @@
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>

View File

@@ -157,16 +157,14 @@ namespace PowerAccent.Core
{
return letter switch
{
LetterKey.VK_0 => new[] { "₀", "⁰", "↉" },
LetterKey.VK_1 => new[] { "₁", "¹", "½", "⅓", "¼", "⅕", "⅙", "⅐", "⅛", "⅑", "⅒" },
LetterKey.VK_2 => new[] { "₂", "²", "⅔", "⅖" },
LetterKey.VK_3 => new[] { "₃", "³", "¾", "⅗", "⅜" },
LetterKey.VK_4 => new[] { "₄", "⁴", "⅘" },
LetterKey.VK_5 => new[] { "₅", "⁵", "⅚", "⅝" },
LetterKey.VK_6 => new[] { "₆", "⁶" },
LetterKey.VK_7 => new[] { "₇", "⁷", "⅞" },
LetterKey.VK_8 => new[] { "₈", "⁸", "∞" },
LetterKey.VK_9 => new[] { "₉", "⁹" },
LetterKey.VK_0 => new[] { "↉" },
LetterKey.VK_1 => new[] { "½", "⅓", "¼", "⅕", "⅙", "⅐", "⅛", "⅑", "⅒" },
LetterKey.VK_2 => new[] { "⅔", "⅖" },
LetterKey.VK_3 => new[] { "¾", "⅗", "⅜" },
LetterKey.VK_4 => new[] { "⅘" },
LetterKey.VK_5 => new[] { "⅚", "⅝" },
LetterKey.VK_7 => new[] { "" },
LetterKey.VK_8 => new[] { "" },
LetterKey.VK_A => new[] { "ȧ", "ǽ", "∀" },
LetterKey.VK_B => new[] { "ḃ" },
LetterKey.VK_C => new[] { "ċ", "°C", "©", "", "∁" },
@@ -480,6 +478,16 @@ namespace PowerAccent.Core
{
return letter switch
{
LetterKey.VK_0 => new[] { "₀", "⁰" },
LetterKey.VK_1 => new[] { "₁", "¹" },
LetterKey.VK_2 => new[] { "₂", "²" },
LetterKey.VK_3 => new[] { "₃", "³" },
LetterKey.VK_4 => new[] { "₄", "⁴" },
LetterKey.VK_5 => new[] { "₅", "⁵" },
LetterKey.VK_6 => new[] { "₆", "⁶" },
LetterKey.VK_7 => new[] { "₇", "⁷" },
LetterKey.VK_8 => new[] { "₈", "⁸" },
LetterKey.VK_9 => new[] { "₉", "⁹" },
LetterKey.VK_A => new[] { "á", "à", "â", "ã", "ª" },
LetterKey.VK_C => new[] { "ç" },
LetterKey.VK_E => new[] { "é", "ê", "€" },

View File

@@ -1,183 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library;
public sealed class AdvancedPasteCustomAction : INotifyPropertyChanged, ICloneable
{
private int _id;
private string _name = string.Empty;
private string _prompt = string.Empty;
private HotkeySettings _shortcut = new();
private bool _isShown;
private bool _canMoveUp;
private bool _canMoveDown;
private bool _isValid;
[JsonPropertyName("id")]
public int Id
{
get => _id;
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged();
}
}
}
[JsonPropertyName("name")]
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
UpdateIsValid();
}
}
}
[JsonPropertyName("prompt")]
public string Prompt
{
get => _prompt;
set
{
if (_prompt != value)
{
_prompt = value;
OnPropertyChanged();
UpdateIsValid();
}
}
}
[JsonPropertyName("shortcut")]
public HotkeySettings Shortcut
{
get => _shortcut;
set
{
if (_shortcut != value)
{
// We null-coalesce here rather than outside this branch as we want to raise PropertyChanged when the setter is called
// with null; the ShortcutControl depends on this.
_shortcut = value ?? new();
OnPropertyChanged();
}
}
}
[JsonPropertyName("isShown")]
public bool IsShown
{
get => _isShown;
set
{
if (_isShown != value)
{
_isShown = value;
OnPropertyChanged();
}
}
}
[JsonIgnore]
public bool CanMoveUp
{
get => _canMoveUp;
set
{
if (_canMoveUp != value)
{
_canMoveUp = value;
OnPropertyChanged();
}
}
}
[JsonIgnore]
public bool CanMoveDown
{
get => _canMoveDown;
set
{
if (_canMoveDown != value)
{
_canMoveDown = value;
OnPropertyChanged();
}
}
}
[JsonIgnore]
public bool IsValid
{
get => _isValid;
private set
{
if (_isValid != value)
{
_isValid = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string ToJsonString() => JsonSerializer.Serialize(this);
public object Clone()
{
AdvancedPasteCustomAction clone = new();
clone.Update(this);
return clone;
}
public void Update(AdvancedPasteCustomAction other)
{
Id = other.Id;
Name = other.Name;
Prompt = other.Prompt;
Shortcut = other.GetShortcutClone();
IsShown = other.IsShown;
CanMoveUp = other.CanMoveUp;
CanMoveDown = other.CanMoveDown;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private HotkeySettings GetShortcutClone()
{
object shortcut = null;
if (Shortcut.TryToCmdRepresentable(out string shortcutString))
{
_ = HotkeySettings.TryParseFromCmd(shortcutString, out shortcut);
}
return (shortcut as HotkeySettings) ?? new HotkeySettings();
}
private void UpdateIsValid()
{
IsValid = !string.IsNullOrWhiteSpace(Name) && !string.IsNullOrWhiteSpace(Prompt);
}
}

View File

@@ -1,26 +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 System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library;
public sealed class AdvancedPasteCustomActions
{
private static readonly JsonSerializerOptions _serializerOptions = new()
{
WriteIndented = true,
};
[JsonPropertyName("value")]
public ObservableCollection<AdvancedPasteCustomAction> Value { get; set; } = [];
public AdvancedPasteCustomActions()
{
}
public string ToJsonString() => JsonSerializer.Serialize(this, _serializerOptions);
}

View File

@@ -20,7 +20,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
PasteAsPlainTextShortcut = DefaultPasteAsPlainTextShortcut;
PasteAsMarkdownShortcut = new();
PasteAsJsonShortcut = new();
CustomActions = new();
ShowCustomPreview = true;
SendPasteKeyCombination = true;
CloseAfterLosingFocus = false;
@@ -48,10 +47,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("paste-as-json-hotkey")]
public HotkeySettings PasteAsJsonShortcut { get; set; }
[JsonPropertyName("custom-actions")]
[CmdConfigureIgnoreAttribute]
public AdvancedPasteCustomActions CustomActions { get; set; }
public override string ToString()
=> JsonSerializer.Serialize(this);
}

View File

@@ -16,38 +16,38 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
Id = id;
Name = string.Empty;
Fit = ResizeFit.Fit;
Fit = (int)ResizeFit.Fit;
Width = 0;
Height = 0;
Unit = ResizeUnit.Pixel;
Unit = (int)ResizeUnit.Pixel;
}
public ImageSize()
{
Id = 0;
Name = string.Empty;
Fit = ResizeFit.Fit;
Fit = (int)ResizeFit.Fit;
Width = 0;
Height = 0;
Unit = ResizeUnit.Pixel;
Unit = (int)ResizeUnit.Pixel;
}
public ImageSize(int id, string name, ResizeFit fit, double width, double height, ResizeUnit unit)
{
Id = id;
Name = name;
Fit = fit;
Fit = (int)fit;
Width = width;
Height = height;
Unit = unit;
Unit = (int)unit;
}
private int _id;
private string _name;
private ResizeFit _fit;
private int _fit;
private double _height;
private double _width;
private ResizeUnit _unit;
private int _unit;
public int Id
{
@@ -70,7 +70,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
get
{
if (Unit == ResizeUnit.Percent && Fit != ResizeFit.Stretch)
if (Unit == 2 && Fit != 2)
{
return 0;
}
@@ -85,7 +85,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
get
{
if (Unit == ResizeUnit.Percent && Fit != ResizeFit.Stretch)
if (Unit == 2 && Fit != 2)
{
return false;
}
@@ -115,7 +115,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
[JsonPropertyName("fit")]
public ResizeFit Fit
public int Fit
{
get
{
@@ -193,7 +193,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
[JsonPropertyName("unit")]
public ResizeUnit Unit
public int Unit
{
get
{

View File

@@ -3,11 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
@@ -31,25 +28,18 @@ public sealed class SetAdditionalSettingsCommandLineCommand
private struct AdditionalPropertyInfo
{
// A path to the property starting from the root module Settings object in the following format: "RootPropertyA.NestedPropertyB[...]"
public string PropertyPath;
// Property Type hint so we know how to handle it
public JsonValueKind PropertyType;
public string RootPropertyName;
public JsonValueKind RootObjectType;
}
private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { PropertyPath = "Plugins", PropertyType = JsonValueKind.Array } }, { "ImageResizer", new AdditionalPropertyInfo { PropertyPath = "Properties.ImageresizerSizes.Value", PropertyType = JsonValueKind.Array } } };
private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { RootPropertyName = "Plugins", RootObjectType = JsonValueKind.Array } } };
private static IEnumerable<object> ExecuteRootArray(IEnumerable<JsonElement> properties, IEnumerable<object> currentPropertyValuesArray)
private static void ExecuteRootArray(JsonElement.ArrayEnumerator properties, IEnumerable<object> currentPropertyValuesArray)
{
// In case it's an array of objects -> combine the existing values with the provided
var result = currentPropertyValuesArray;
// In case it's an array of object -> combine the existing values with the provided
var currentPropertyValueType = currentPropertyValuesArray.FirstOrDefault()?.GetType();
var currentPropertyValueType = GetUnderlyingTypeOfCollection(currentPropertyValuesArray);
object matchedElement = null;
object newKeyPropertyValue = null;
foreach (var arrayElement in properties)
{
var newElementPropertyValues = new Dictionary<string, object>();
@@ -57,16 +47,15 @@ public sealed class SetAdditionalSettingsCommandLineCommand
{
var elementPropertyName = elementProperty.Name;
var elementPropertyType = currentPropertyValueType.GetProperty(elementPropertyName).PropertyType;
var elementNewPropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString());
var elemePropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString());
if (elementPropertyName == KeyPropertyName)
{
newKeyPropertyValue = elementNewPropertyValue;
foreach (var currentElementValue in currentPropertyValuesArray)
{
var currentElementType = currentElementValue.GetType();
var keyPropertyNameInfo = currentElementType.GetProperty(KeyPropertyName);
var currentKeyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue);
if (string.Equals(currentKeyPropertyValue, elementNewPropertyValue))
var keyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue);
if (string.Equals(keyPropertyValue, elemePropertyValue))
{
matchedElement = currentElementValue;
break;
@@ -75,18 +64,7 @@ public sealed class SetAdditionalSettingsCommandLineCommand
}
else
{
newElementPropertyValues.Add(elementPropertyName, elementNewPropertyValue);
}
}
// Appending a new element -> create it first using a default ctor with 0 args and append it to the result
if (matchedElement == null)
{
newElementPropertyValues.Add(KeyPropertyName, newKeyPropertyValue);
matchedElement = Activator.CreateInstance(currentPropertyValueType);
if (matchedElement != null)
{
result = result.Append(matchedElement);
newElementPropertyValues.Add(elementPropertyName, elemePropertyValue);
}
}
@@ -98,148 +76,6 @@ public sealed class SetAdditionalSettingsCommandLineCommand
propertyInfo.SetValue(matchedElement, overriddenProperty.Value);
}
}
matchedElement = null;
}
return result;
}
private static object GetNestedPropertyValue(object obj, string propertyPath)
{
if (obj == null || string.IsNullOrWhiteSpace(propertyPath))
{
return null;
}
var properties = propertyPath.Split('.');
object currentObject = obj;
PropertyInfo currentProperty = null;
foreach (var property in properties)
{
if (currentObject == null)
{
return null;
}
currentProperty = currentObject.GetType().GetProperty(property);
if (currentProperty == null)
{
return null;
}
currentObject = currentProperty.GetValue(currentObject);
}
return currentObject;
}
// To apply changes to a generic collection, we must recreate it and assign it to the property
private static object CreateCompatibleCollection(Type collectionType, Type elementType, IEnumerable<object> newValues)
{
if (typeof(IList<>).MakeGenericType(elementType).IsAssignableFrom(collectionType) ||
typeof(ObservableCollection<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
var concreteType = typeof(List<>).MakeGenericType(elementType);
if (typeof(ObservableCollection<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
concreteType = typeof(ObservableCollection<>).MakeGenericType(elementType);
}
else if (collectionType.IsInterface || collectionType.IsAbstract)
{
concreteType = typeof(List<>).MakeGenericType(elementType);
}
var list = (IList)Activator.CreateInstance(concreteType);
foreach (var newValue in newValues)
{
list.Add(Convert.ChangeType(newValue, elementType, CultureInfo.InvariantCulture));
}
return list;
}
else if (typeof(IEnumerable<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
var listType = typeof(List<>).MakeGenericType(elementType);
var list = (IList)Activator.CreateInstance(listType);
foreach (var newValue in newValues)
{
list.Add(Convert.ChangeType(newValue, elementType, CultureInfo.InvariantCulture));
}
return list;
}
return null;
}
private static void SetNestedPropertyValue(object obj, string propertyPath, IEnumerable<object> newValues)
{
if (obj == null || string.IsNullOrWhiteSpace(propertyPath))
{
return;
}
var properties = propertyPath.Split('.');
object currentObject = obj;
PropertyInfo currentProperty = null;
for (int i = 0; i < properties.Length - 1; i++)
{
if (currentObject == null)
{
return;
}
currentProperty = currentObject.GetType().GetProperty(properties[i]);
if (currentProperty == null)
{
return;
}
currentObject = currentProperty.GetValue(currentObject);
}
if (currentObject == null)
{
return;
}
currentProperty = currentObject.GetType().GetProperty(properties.Last());
if (currentProperty == null)
{
return;
}
var propertyType = currentProperty.PropertyType;
var elementType = propertyType.GetGenericArguments()[0];
var newCollection = CreateCompatibleCollection(propertyType, elementType, newValues);
if (newCollection != null)
{
currentProperty.SetValue(currentObject, newCollection);
}
}
private static Type GetUnderlyingTypeOfCollection(IEnumerable<object> currentPropertyValuesArray)
{
Type collectionType = currentPropertyValuesArray.GetType();
if (!collectionType.IsGenericType)
{
throw new ArgumentException("Invalid json data supplied");
}
Type[] genericArguments = collectionType.GetGenericArguments();
if (genericArguments.Length > 0)
{
return genericArguments[0];
}
else
{
throw new ArgumentException("Invalid json data supplied");
}
}
@@ -255,39 +91,14 @@ public sealed class SetAdditionalSettingsCommandLineCommand
return;
}
var currentPropertyValue = GetNestedPropertyValue(settingsConfig, additionalPropertiesInfo.PropertyPath);
var propertyValueInfo = settingsConfigType.GetProperty(additionalPropertiesInfo.RootPropertyName);
var currentPropertyValue = propertyValueInfo.GetValue(settingsConfig);
// For now, only a certain data shapes are supported
switch (additionalPropertiesInfo.PropertyType)
switch (additionalPropertiesInfo.RootObjectType)
{
case JsonValueKind.Array:
if (currentPropertyValue == null)
{
currentPropertyValue = new JsonArray();
}
IEnumerable<JsonElement> propertiesToSet = null;
// Powershell ConvertTo-Json call omits wrapping a single value in an array, so we must do it here
if (settings.RootElement.ValueKind == JsonValueKind.Object)
{
var wrapperArray = new JsonArray();
wrapperArray.Add(settings.RootElement);
propertiesToSet = (IEnumerable<JsonElement>)wrapperArray.GetEnumerator();
}
else if (settings.RootElement.ValueKind == JsonValueKind.Array)
{
propertiesToSet = settings.RootElement.EnumerateArray().AsEnumerable();
}
else
{
throw new ArgumentException("Invalid json data supplied");
}
var newPropertyValue = ExecuteRootArray(propertiesToSet, currentPropertyValue as IEnumerable<object>);
SetNestedPropertyValue(settingsConfig, additionalPropertiesInfo.PropertyPath, newPropertyValue);
ExecuteRootArray(settings.RootElement.EnumerateArray(), currentPropertyValue as IEnumerable<object>);
break;
default:
throw new NotImplementedException();

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
Name,
}
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0xC0);
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, false, false, true, 0x4F);
public WorkspacesProperties()
{

View File

@@ -233,10 +233,10 @@ namespace ViewModelTests
// Assert
ImageSize newTestSize = viewModel.Sizes.First(x => x.Id == 0);
Assert.AreEqual(newTestSize.Name, "New size 1");
Assert.AreEqual(newTestSize.Fit, ResizeFit.Fit);
Assert.AreEqual(newTestSize.Fit, (int)ResizeFit.Fit);
Assert.AreEqual(newTestSize.Width, 854);
Assert.AreEqual(newTestSize.Height, 480);
Assert.AreEqual(newTestSize.Unit, ResizeUnit.Pixel);
Assert.AreEqual(newTestSize.Unit, (int)ResizeUnit.Pixel);
}
[TestMethod]
@@ -287,10 +287,10 @@ namespace ViewModelTests
{
Id = 0,
Name = "Test",
Fit = ResizeFit.Fit,
Fit = (int)ResizeFit.Fit,
Width = 30,
Height = 30,
Unit = ResizeUnit.Pixel,
Unit = (int)ResizeUnit.Pixel,
};
double negativeWidth = -2.0;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,32 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public sealed class ImageResizerFitToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ResizeFit)
{
return (int)value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return (ResizeFit)intValue;
}
return ResizeFit.Fill;
}
}

View File

@@ -1,32 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public sealed class ImageResizerUnitToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ResizeUnit)
{
return (int)value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return (ResizeUnit)intValue;
}
return ResizeUnit.Centimeter;
}
}

View File

@@ -115,10 +115,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
internalSettings = new HotkeySettings();
this.Unloaded += ShortcutControl_Unloaded;
this.Loaded += ShortcutControl_Loaded;
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
if (App.GetSettingsWindow() != null)
{
App.GetSettingsWindow().Activated += ShortcutDialog_SettingsWindow_Activated;
}
// We create the Dialog in C# because doing it in XAML is giving WinUI/XAML Island bugs when using dark theme.
shortcutDialog = new ContentDialog
{
@@ -130,9 +134,11 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
CloseButtonText = resourceLoader.GetString("Activation_Shortcut_Cancel"),
DefaultButton = ContentDialogButton.Primary,
};
shortcutDialog.PrimaryButtonClick += ShortcutDialog_PrimaryButtonClick;
shortcutDialog.SecondaryButtonClick += ShortcutDialog_Reset;
shortcutDialog.RightTapped += ShortcutDialog_Disable;
shortcutDialog.Opened += ShortcutDialog_Opened;
shortcutDialog.Closing += ShortcutDialog_Closing;
AutomationProperties.SetName(EditButton, resourceLoader.GetString("Activation_Shortcut_Title"));
OnAllowDisableChanged(this, null);
@@ -150,28 +156,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
// Dispose the HotkeySettingsControlHook object to terminate the hook threads when the textbox is unloaded
hook?.Dispose();
if (hook != null)
{
hook.Dispose();
}
hook = null;
}
private void ShortcutControl_Loaded(object sender, RoutedEventArgs e)
{
// These all belong here; because of virtualization in e.g. a ListView, the control can go through several Loaded / Unloaded cycles.
hook?.Dispose();
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
shortcutDialog.PrimaryButtonClick += ShortcutDialog_PrimaryButtonClick;
shortcutDialog.Opened += ShortcutDialog_Opened;
shortcutDialog.Closing += ShortcutDialog_Closing;
if (App.GetSettingsWindow() != null)
{
App.GetSettingsWindow().Activated += ShortcutDialog_SettingsWindow_Activated;
}
}
private void KeyEventHandler(int key, bool matchValue, int matchValueCode)
{
VirtualKey virtualKey = (VirtualKey)key;

View File

@@ -65,47 +65,45 @@
</Button>
</Grid>
<Grid Grid.Row="1">
<ScrollViewer>
<ItemsControl
Margin="12,26,12,24"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
TabNavigation="Local">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel HorizontalAlignment="Stretch" VerticalSpacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:FlyoutMenuItem">
<controls:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Label}"
Click="ModuleButton_Click"
Tag="{x:Bind Tag}"
Visibility="{x:Bind Visible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:FlyoutMenuButton.Content>
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Label}"
TextAlignment="Center"
TextWrapping="Wrap" />
</controls:FlyoutMenuButton.Content>
<controls:FlyoutMenuButton.Icon>
<Image>
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</Image.Source>
</Image>
</controls:FlyoutMenuButton.Icon>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ToolTip}" Visibility="{x:Bind ToolTip, Converter={StaticResource StringVisibilityConverter}}" />
</ToolTipService.ToolTip>
</controls:FlyoutMenuButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<ItemsControl
Margin="12,26,12,24"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
TabNavigation="Local">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel HorizontalAlignment="Stretch" VerticalSpacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:FlyoutMenuItem">
<controls:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Label}"
Click="ModuleButton_Click"
Tag="{x:Bind Tag}"
Visibility="{x:Bind Visible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:FlyoutMenuButton.Content>
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Label}"
TextAlignment="Center"
TextWrapping="Wrap" />
</controls:FlyoutMenuButton.Content>
<controls:FlyoutMenuButton.Icon>
<Image>
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</Image.Source>
</Image>
</controls:FlyoutMenuButton.Icon>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ToolTip}" Visibility="{x:Bind ToolTip, Converter={StaticResource StringVisibilityConverter}}" />
</ToolTipService.ToolTip>
</controls:FlyoutMenuButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
<Grid Grid.Row="2">

View File

@@ -14,7 +14,6 @@
<TextBlock x:Uid="Oobe_HowToUse" Style="{ThemeResource OobeSubtitleStyle}" />
<tk7controls:MarkdownTextBlock x:Uid="Oobe_Workspaces_HowToUse" Background="Transparent" />
<controls:ShortcutWithTextLabelControl x:Name="HotkeyControl" x:Uid="Oobe_Workspaces_HowToUse_Shortcut" />
<TextBlock x:Uid="Oobe_TipsAndTricks" Style="{ThemeResource OobeSubtitleStyle}" />

View File

@@ -2,7 +2,6 @@
// 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.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -38,7 +37,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
HotkeyControl.Keys = SettingsRepository<WorkspacesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)

View File

@@ -5,10 +5,8 @@
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:Microsoft.PowerToys.Settings.UI.Library"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
x:Name="RootPage"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<Page.Resources>
@@ -102,95 +100,33 @@
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Actions" HeaderIcon="{ui:FontIcon Glyph=&#xE792;}">
<Button
x:Uid="AdvancedPasteUI_AddCustomActionButton"
Click="AddCustomActionButton_Click"
Style="{ThemeResource AccentButtonStyle}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Shortcut" HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<tkcontrols:SettingsExpander
x:Uid="AdvancedPasteUI_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsExpanded="True">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.AdvancedPasteUIShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsPlainText_Shortcut">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.PasteAsPlainTextShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsMarkdown_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsMarkdownShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsJson_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsJsonShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<ItemsControl
x:Name="CustomActions"
x:Uid="CustomActions"
HorizontalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{x:Bind ViewModel.CustomActions, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:AdvancedPasteCustomAction">
<tkcontrols:SettingsCard
Margin="0,0,0,2"
Click="EditCustomActionButton_Click"
Description="{x:Bind Prompt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Header="{x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsActionIconVisible="False"
IsClickEnabled="True">
<tkcontrols:SettingsCard.Resources>
<x:Double x:Key="SettingsCardActionButtonWidth">0</x:Double>
</tkcontrols:SettingsCard.Resources>
<StackPanel Orientation="Horizontal" Spacing="4">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=Shortcut, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="Enable_CustomAction"
AutomationProperties.HelpText="{x:Bind Name}"
IsOn="{x:Bind IsShown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
OffContent=""
OnContent="" />
<Button
x:Uid="More_Options_Button"
Grid.Column="1"
VerticalAlignment="Center"
Content="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Style="{StaticResource SubtleButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="MoveUp"
Click="ReorderButtonUp_Click"
Icon="{ui:FontIcon Glyph=&#xE74A;}"
IsEnabled="{x:Bind CanMoveUp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<MenuFlyoutItem
x:Uid="MoveDown"
Click="ReorderButtonDown_Click"
Icon="{ui:FontIcon Glyph=&#xE74B;}"
IsEnabled="{x:Bind CanMoveDown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="RemoveItem"
Click="DeleteCustomActionButton_Click"
Icon="{ui:FontIcon Glyph=&#xE74D;}"
IsEnabled="true" />
</MenuFlyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<TextBlock x:Uid="More_Options_ButtonTooltip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</tkcontrols:SettingsCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Visibility="Collapsed">
<!-- There's a bug that makes it so that the first shortcut control inside an expander doesn't work. We add this dummy one so the other entries aren't affected. -->
<TextBox />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsPlainText_Shortcut">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.PasteAsPlainTextShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsMarkdown_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsMarkdownShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsJson_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsJsonShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<InfoBar
x:Uid="AdvancedPaste_ShortcutWarning"
IsClosable="False"
@@ -266,31 +202,5 @@
</Grid>
</Grid>
</ContentDialog>
<ContentDialog
x:Name="CustomActionDialog"
x:Uid="CustomActionDialog"
Closed="CustomActionDialog_Closed"
IsPrimaryButtonEnabled="{Binding IsValid, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsSecondaryButtonEnabled="True"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
<ContentDialog.DataContext>
<models:AdvancedPasteCustomAction />
</ContentDialog.DataContext>
<StackPanel Spacing="16">
<TextBox
x:Uid="AdvancedPasteUI_CustomAction_Name"
Width="340"
HorizontalAlignment="Left"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox
x:Uid="AdvancedPasteUI_CustomAction_Prompt"
Width="340"
Height="280"
HorizontalAlignment="Left"
AcceptsReturn="true"
Text="{Binding Prompt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</StackPanel>
</ContentDialog>
</Grid>
</Page>

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