Compare commits

...

189 Commits

Author SHA1 Message Date
Mike Griese
8428f666bf oh this SO could have been one commit 2023-03-02 16:15:19 -06:00
Mike Griese
a35c23f667 it's really this easy? 2023-03-02 16:07:09 -06:00
Mike Griese
8c5bfb5756 Merge branch 'dev/migrie/oop/3/akallabeth' into dev/migrie/oop/3/an-unexpected-party 2023-03-02 13:31:25 -06:00
Mike Griese
59d6ff29a8 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-03-02 13:31:04 -06:00
Mike Griese
7591b0f404 Fix a UiaEngine race that would absolutely block self-host
(cherry picked from commit fda16631cc)
2023-03-02 13:24:03 -06:00
Mike Griese
2c15f97ee8 Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-03-02 13:23:51 -06:00
Mike Griese
48aa555428 Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-03-02 13:23:22 -06:00
Mike Griese
b983c69c8e Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-03-02 13:22:01 -06:00
Mike Griese
fda16631cc Fix a UiaEngine race that would absolutely block self-host 2023-03-02 13:20:58 -06:00
Mike Griese
88033246f1 spelling is hard 2023-03-02 11:18:02 -06:00
Mike Griese
08f6a53be5 This is gross but reduces the number of redundant copies of these brushes we need 2023-03-02 11:05:16 -06:00
Mike Griese
8e2c7a7bdd some cleanup 2023-03-02 10:20:59 -06:00
Mike Griese
1e331a037c Merge branch 'dev/migrie/oop/3/ainulindale' of https://github.com/microsoft/terminal into dev/migrie/oop/3/ainulindale 2023-03-02 09:54:09 -06:00
Mike Griese
5c852c3c4c some smaller nits 2023-03-02 09:54:04 -06:00
Mike Griese
7e2eb0d8e5 Apply suggestions from code review
Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
2023-03-02 09:53:51 -06:00
Mike Griese
17dbc7b5ce That's it. Ship the PR 2023-02-28 14:41:56 -06:00
Mike Griese
08acc90e5f I'm amazing. Say it. I'm amazing 2023-02-28 14:26:00 -06:00
Mike Griese
4f8d64ec6f non-client borders accounted for: [X] 2023-02-28 13:58:10 -06:00
Mike Griese
5164bffa78 hey that's a good point carlos 2023-02-28 12:04:24 -06:00
Mike Griese
ada3f427a0 well that is an underwhelming codeformat commit. Thanks VS 2023-02-28 11:56:37 -06:00
Mike Griese
5e23a72beb Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-28 11:55:17 -06:00
Mike Griese
0199aba286 the last nits, I think 2023-02-28 11:29:53 -06:00
Mike Griese
b589d092e9 Lots of PR nits 2023-02-28 11:22:29 -06:00
Mike Griese
74260ce6e1 some gross dead code 2023-02-28 10:09:46 -06:00
Mike Griese
01388c95b0 some todo notes 2023-02-28 10:05:29 -06:00
Mike Griese
fa1140dc07 better scaling 2023-02-27 17:08:46 -06:00
Mike Griese
e1472095e2 events aren't really scaled correctly, but it does work 2023-02-27 16:54:17 -06:00
Mike Griese
b9dc8c3f5b Really, I did it on the first try 2023-02-27 14:25:29 -06:00
Mike Griese
055cdfedd0 moveTab to a nonexistent window creates that window.
This was actually easy enough that I'm tempted to include it in the prior branches
2023-02-27 14:03:25 -06:00
Mike Griese
257fe33269 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-27 10:22:43 -06:00
Mike Griese
34bc66f8bf Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-27 10:20:55 -06:00
Mike Griese
6934bc8d05 Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-02-27 10:19:35 -06:00
Mike Griese
cacb822cf6 Dustin wanted a setting to opt-in to isolated mode, and this seems to work 2023-02-27 10:16:22 -06:00
Mike Griese
b75fb24512 Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-27 09:34:59 -06:00
Mike Griese
c79f27c3da std::vectors are better 2023-02-27 08:32:18 -06:00
Mike Griese
273f2a84ef Merge remote-tracking branch 'origin/main' into dev/migrie/oop/3/foreword 2023-02-23 16:56:15 -06:00
Mike Griese
d2a5a4aab7 [silmarillion] Disable the UIA renderer before detaching
(cherry picked from commit b1a867db6c)
2023-02-23 16:53:59 -06:00
Mike Griese
9a4739659b [ainulindale] The Terminal shouldn't crash on a WM_SETTINGCHANGE
(cherry picked from commit 7649725150)
2023-02-23 16:53:07 -06:00
Mike Griese
7649725150 [ainulindale] The Terminal shouldn't crash on a WM_SETTINGCHANGE 2023-02-23 16:51:16 -06:00
Mike Griese
b1a867db6c [silmarillion] Disable the UIA renderer before detaching 2023-02-23 12:26:05 -06:00
Mike Griese
614ec58c31 spel? I think that's it? 2023-02-23 10:25:50 -06:00
Mike Griese
70450afe7e 0 TODOs left 2023-02-23 10:24:22 -06:00
Mike Griese
9ec9da3da2 ACTUALLY put the tab in the right place after dropping
1 TODOs left
2023-02-23 09:38:55 -06:00
Mike Griese
b5dbae9244 OMEGA BODGE 2023-02-23 09:36:57 -06:00
Mike Griese
d67815bf0c Try to drag/drop to the rigt place in the tab row, only to discover that we ALWAYS drag the 0th tab now. Peculiar. 2023-02-23 08:27:35 -06:00
Mike Griese
6586c4a816 Use the correct tab when dragging ; check the PID too
4 TODOs left
2023-02-22 14:42:30 -06:00
Mike Griese
0cf13d81b1 Notes, cleanup. 6 TODOs remain 2023-02-22 11:24:30 -06:00
Mike Griese
2537342ffb On drop, ask the sender to send content to us.
The incredibly dumb solution I considered.

    As previously outlined in https://github.com/microsoft/terminal/issues/5000#issuecomment-1435328038
2023-02-22 11:11:06 -06:00
Mike Griese
c6cce0fd6b Revert "Everything-is-awful.wav"
This reverts commit b41ae7115c.
2023-02-21 12:41:31 -06:00
Mike Griese
b41ae7115c Everything-is-awful.wav 2023-02-21 12:41:21 -06:00
Mike Griese
c2a2caf029 Fixes for a bad merge 2023-02-21 12:41:01 -06:00
Mike Griese
951ece1f3e Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-17 11:23:22 -06:00
Mike Griese
1f8766e972 shockingly few spel 2023-02-17 11:06:13 -06:00
Mike Griese
091660e96d cleanup and comments before the PR 2023-02-17 11:02:53 -06:00
Mike Griese
0e0d8572a3 Select the right tab before spliting it; movePane with a single pane works again 2023-02-17 10:21:18 -06:00
Mike Griese
9da1192993 Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-16 12:00:55 -06:00
Mike Griese
f9caf191a9 Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-02-16 12:00:38 -06:00
Mike Griese
13257dafa0 0 TODOs remain 2023-02-16 11:11:09 -06:00
Mike Griese
ecab57fba1 that was quite silly 2023-02-16 11:10:06 -06:00
Mike Griese
091f32c575 a comment to go with the parent commit 2023-02-15 15:43:20 -06:00
Mike Griese
62a4ee058b https://media.tenor.com/knFWHfkiwoUAAAAC/im-fine-hades.gif 2023-02-15 15:39:23 -06:00
Mike Griese
2bc578bc6c Revert "I'm committing everything just in case, but I'm gonna revert and just do the part that fixed it"
This reverts commit 667b6586d2.
2023-02-15 15:33:49 -06:00
Mike Griese
667b6586d2 I'm committing everything just in case, but I'm gonna revert and just do the part that fixed it 2023-02-15 15:33:36 -06:00
Mike Griese
bc80943fd9 the tests all...pass? 2023-02-14 17:07:59 -06:00
Mike Griese
aa8b0c564d Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-14 16:47:11 -06:00
Mike Griese
118bffa6cd fix the tests 2023-02-14 15:51:18 -06:00
Mike Griese
f10dfac20f some reliability when moving tabs that currently have output across the boundary 2023-02-14 12:45:35 -06:00
Mike Griese
e5f98807e2 cleanup to get ready for review 2023-02-14 11:34:00 -06:00
Mike Griese
8cac90fc4d Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-14 09:49:54 -06:00
Mike Griese
d06ad8b5dc spel 2023-02-14 09:48:19 -06:00
Mike Griese
4b22963f9d notes 2023-02-14 09:18:48 -06:00
Mike Griese
855a79dbe0 Add auto_revokers to all TermControl ev handlers, but that wasn't what ended up fixing this bug.
All we actually needed was to do a _FontSizeChangedHandlers() to set up the font size and the markers in the TermControl.
2023-02-14 09:08:23 -06:00
Mike Griese
28735112d2 Don't add multiple event handlers for the core
Basically, because we were projecting the event straight trhough the control, from core->page, basically when we would do a PasteRequested, we'd call the handler registered on the first control, then also the handler added by the second.
2023-02-14 09:00:40 -06:00
Mike Griese
83da057a70 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-14 08:48:32 -06:00
Mike Griese
3988a1c4e5 [silmarillion] Fix moving a tab with panes. Now, the non-first panes don't rebuild themselves on move
(cherry picked from commit 7d903dea4a)
2023-02-14 08:48:12 -06:00
Mike Griese
2262f5574e Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-14 08:47:57 -06:00
Mike Griese
77346000df [valaquenta] Lifetime manage controls
(cherry picked from commit 4eba01f919)
2023-02-14 08:39:25 -06:00
Mike Griese
7d903dea4a [silmarillion] Fix moving a tab with panes. Now, the non-first panes don't rebuild themselves on move 2023-02-13 17:13:59 -06:00
Mike Griese
4eba01f919 [valaquenta] Lifetime manage controls 2023-02-13 16:08:35 -06:00
Mike Griese
f0802618e8 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-13 14:27:34 -06:00
Mike Griese
68fad338cc Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-13 14:27:15 -06:00
Mike Griese
0f0316f322 Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-02-13 14:25:56 -06:00
Mike Griese
3f9decaa82 Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-13 14:25:36 -06:00
Mike Griese
d9d4d2e62d get the tests to build, at least 2023-02-13 12:08:54 -06:00
Mike Griese
82224bc87a this was usnused 2023-02-13 11:48:32 -06:00
Mike Griese
a9ac218c4d runformat 2023-02-13 10:57:19 -06:00
Mike Griese
603a2ce53a spel 2023-02-13 10:56:31 -06:00
Mike Griese
2621519f20 cleanup for review 2023-02-13 10:46:37 -06:00
Mike Griese
1ec8c0de2e Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-13 09:30:23 -06:00
Mike Griese
a4f19a9dff spel 2023-02-13 09:28:13 -06:00
Mike Griese
33685d9e9d bwahahahahaha 2023-02-13 09:26:51 -06:00
Mike Griese
65084c84ec Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-13 09:23:25 -06:00
Mike Griese
0ce6309c11 Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-13 08:51:44 -06:00
Mike Griese
c0658975b2 Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-02-11 07:05:46 -06:00
Mike Griese
a76993365e merges are hard some times 2023-02-10 17:21:55 -06:00
Mike Griese
dffb41601b Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale 2023-02-10 17:09:29 -06:00
Mike Griese
e40575b18b let's do it 2023-02-10 16:50:58 -06:00
Mike Griese
cfa61088fe Merge remote-tracking branch 'origin/main' into dev/migrie/oop/3/foreword 2023-02-10 13:24:49 -06:00
Mike Griese
0ad5b596bd I think it's ready for review 2023-02-10 13:24:15 -06:00
Mike Griese
7660937139 and with that, we're ready to cleanup for review.
2 TODOs left
2023-02-10 13:05:57 -06:00
Mike Griese
45487294a0 Some additional cleanup of the WindowManager code
3 TODOs left
2023-02-10 11:16:37 -06:00
Mike Griese
f904e5d22d I give up and am moving on to the next TODOs. 8 TODOs remain 2023-02-10 09:48:08 -06:00
Mike Griese
0395dc40a3 Revert "This actually seems to make the lifetime management worse - we just dtor the WindowEmperor randomly now, which is no good"
This reverts commit 7e91bdb289.
2023-02-09 16:39:15 -06:00
Mike Griese
7e91bdb289 This actually seems to make the lifetime management worse - we just dtor the WindowEmperor randomly now, which is no good 2023-02-09 16:39:07 -06:00
Mike Griese
c69f0bc444 Quake logic seems to work again.
Down to just 10 TODOs left
2023-02-09 14:16:27 -06:00
Mike Griese
e214624e1f Re-add support for the notification icon.
14 TODOs left
2023-02-09 13:52:44 -06:00
Mike Griese
b0726c2057 Handle Quit actions
16 TODOs
2023-02-09 12:54:23 -06:00
Mike Griese
055da357b1 Readd ShouldImmediatelyHandoffToElevated
17 TODOs remain
2023-02-09 12:33:43 -06:00
Mike Griese
950ce6c4fc make pane brushes a member variable, which is good enough
(cherry picked from commit 7bad8c9642)
2023-02-09 12:27:19 -06:00
Mike Griese
0b79e81ae9 Summoning works, except for making new windows, but DANG 2023-02-09 11:03:10 -06:00
Mike Griese
a7379ca8e9 It so SO works 2023-02-09 10:51:50 -06:00
Mike Griese
4d5f6d27fb cleanup. 18 TODOs remain 2023-02-08 17:26:33 -06:00
Mike Griese
3fb8e8cac3 Hey this worked!
The trick is, that if you do this while debugging, the AppState will get persisted as you're stepping through, which can make debugging this really tricky
2023-02-08 17:13:15 -06:00
Mike Griese
6e6d14e0dc We're getting closer to loading up the previous state, but it doesn't always work as expected
20 TODOs left
2023-02-08 15:29:04 -06:00
Mike Griese
a5a9930354 Hey look I brought two windows back into existence!...
They weren't from the persisted JSON, but they did come back

  21 TODOs left
2023-02-08 13:25:41 -06:00
Mike Griese
0f4c4d8eef It persists, but it doesn't restore yet.
21 TODOs left
2023-02-08 12:56:22 -06:00
Mike Griese
8bb839113a move more window persist code around.
It doesn't save or restore, but it does seem to not crash.

  24 TODOs left
2023-02-08 12:42:12 -06:00
Mike Griese
dc1ae9a895 Starting to move these things to the right places 2023-02-08 12:08:28 -06:00
Mike Griese
84e228f1fe Start to move window restoration into the Emperor
I think I have a vision for it now. 25 TODOs left.
2023-02-08 11:05:43 -06:00
Mike Griese
3026922e59 Fix the window name not updating as it changed. Added some notes, so, 26 TODOs left 2023-02-08 09:57:45 -06:00
Mike Griese
64257d830a This straight up isn't even used in main anymore, 26 TODOs left 2023-02-08 09:31:58 -06:00
Mike Griese
4db381e2cb not totally happy with this, but I understand why. 29 TODOs left 2023-02-08 09:10:09 -06:00
Mike Griese
40fdbc1536 Hey this comment was TODOne, 29 TODOs left 2023-02-08 09:02:32 -06:00
Mike Griese
07ff4183e4 Plumb initial load result into the window on startup
30 TODOs left
2023-02-08 09:01:48 -06:00
Mike Griese
d5396d1104 UWP is dead
32 TODOs
2023-02-08 08:45:05 -06:00
Mike Griese
93e9dc505d I guess we need to just hardcode these sizes. Sad, but okay.
33 TODOs
2023-02-08 08:44:49 -06:00
Mike Griese
761bd6a6ab This one was dead code, 34 TODOs left 2023-02-08 08:07:38 -06:00
Mike Griese
23c4d4c0f8 Move the initialization of the showHide Throttler after we init the dispatcher.
35 TODOs left
2023-02-08 08:05:46 -06:00
Mike Griese
4e7da2ec13 I guess this is just dead now
36 TODOs
2023-02-08 06:51:44 -06:00
Mike Griese
2332f0c0e4 Don't try to snap on create if we're not prepared to snap
37 TODOs left
2023-02-08 06:51:29 -06:00
Mike Griese
337b7b5699 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth 2023-02-07 17:07:31 -06:00
Mike Griese
a7353ac347 Merge branch 'dev/migrie/oop/3/valaquenta' into dev/migrie/oop/3/quenta-silmarillion 2023-02-07 17:07:09 -06:00
Mike Griese
f27db5997e Merge branch 'dev/migrie/oop/3/ainulindale' into dev/migrie/oop/3/valaquenta 2023-02-07 17:05:48 -06:00
Mike Griese
2822c36507 [ainulindale] Expand commands in the AppLogic, not on each page
TerminalPage is the thing that ends up expanding iterable Command. It does
  this largely with copies - it makes a new map, a new vector, copies the
  Commands over, and does the work there before setting up the cmdpal.

  Except, it's not making a copy of the Commands, it's making a copy of the
  vector, with winrt objects all pointing at the Command objects that are
  ultimately owned by CascadiaSettings.

  This doesn't matter if there's only one TerminalPage - we'll only ever do that once.

  If there's many, on different threads, then one tpage will end up expanding
  the subcommands of one Command while another tpage is ALSO iterating on those
  subcommands. Hence why I'm getting `hresult_changed_state`s

(cherry picked from commit 2122eec186)
2023-02-07 17:04:25 -06:00
Mike Griese
f06e484324 [ainulindale] This I'm confident fixes some of the x-thread errors, but this doesn't fix the crash
(cherry picked from commit 700aadcb15)
2023-02-07 17:03:41 -06:00
Mike Griese
9924e23dec [ainulindale] "fix" hot reload
Doesn't work with multiple windows open, but doesn't do _nothing_

(cherry picked from commit 427a4a51c5)
2023-02-07 17:02:25 -06:00
Mike Griese
47336c0982 [ainulindale] Clean tear down the App when the process exits
(cherry picked from commit 57d1dd4358)
2023-02-07 17:00:28 -06:00
Mike Griese
2122eec186 [ainulindale] Expand commands in the AppLogic, not on each page
TerminalPage is the thing that ends up expanding iterable Command. It does
  this largely with copies - it makes a new map, a new vector, copies the
  Commands over, and does the work there before setting up the cmdpal.

  Except, it's not making a copy of the Commands, it's making a copy of the
  vector, with winrt objects all pointing at the Command objects that are
  ultimately owned by CascadiaSettings.

  This doesn't matter if there's only one TerminalPage - we'll only ever do that once.

  If there's many, on different threads, then one tpage will end up expanding
  the subcommands of one Command while another tpage is ALSO iterating on those
  subcommands. Hence why I'm getting `hresult_changed_state`s
2023-02-07 16:57:23 -06:00
Mike Griese
81524ea71e Revert "This might all be wrong"
This reverts commit e3fa46ff94.
2023-02-07 15:07:15 -06:00
Mike Griese
e3fa46ff94 This might all be wrong 2023-02-07 15:07:03 -06:00
Mike Griese
38b28e5bdb [foreward]? This is literally never used. This can go in main. 2023-02-07 14:49:09 -06:00
Mike Griese
700aadcb15 [ainulindale] This I'm confident fixes some of the x-thread errors, but this doesn't fix the crash 2023-02-07 14:48:20 -06:00
Mike Griese
7bad8c9642 make pane brushes a member variable, which is good enough 2023-02-07 13:06:06 -06:00
Mike Griese
eea6a7bcca Add auto_revokers to all TermControl ev handlers, but that wasn't what ended up fixing this bug.
All we actually needed was to do a _FontSizeChangedHandlers() to set up the font size and the markers in the TermControl.
2023-02-07 12:55:44 -06:00
Mike Griese
1cef94e696 Don't add multiple event handlers for the core
Basically, because we were projecting the event straight trhough the control, from core->page, basically when we would do a PasteRequested, we'd call the handler registered on the first control, then also the handler added by the second.
2023-02-07 11:43:10 -06:00
Mike Griese
c9ed8dc042 When we detach a tab, Close()ing the control shouldn't close the content. 2023-02-07 11:20:43 -06:00
Mike Griese
e2882055ee Interestingly, the order matters 2023-02-07 11:19:55 -06:00
Mike Griese
ee63aff320 this fixes the selection crash. This makes sense - the original TermControl was kept alive by listeners to events on the Core, who's now on another thread 2023-02-06 14:19:49 -06:00
Mike Griese
ad1f331154 Merge branch 'dev/migrie/oop/3/quenta-silmarillion' into dev/migrie/oop/3/akallabeth
# Conflicts:
#	src/cascadia/TerminalApp/TerminalPage.h
2023-02-06 14:04:34 -06:00
Mike Griese
c82051ffdd [silmarillion] Pull DetachTab into a method
(cherry picked from commit ba7b5aec79)
2023-02-06 14:03:56 -06:00
Mike Griese
11f6561153 [silmarillion] Detach on moveTab, but not on drag
It remains to be seen if this worked for movePane, but one step at a time

(cherry picked from commit c282797cac)
2023-02-06 14:02:55 -06:00
Mike Griese
380f0295ca [silmarillion] Update the validation for the moveTab action
(cherry picked from commit a20201fff2)
2023-02-06 14:02:37 -06:00
Mike Griese
828695ee8a Detaching during tearout too 2023-02-06 14:00:00 -06:00
Mike Griese
ba7b5aec79 [silmarillion] Pull DetachTab into a method 2023-02-06 13:59:47 -06:00
Mike Griese
c282797cac [silmarillion] Detach on moveTab, but not on drag
It remains to be seen if this worked for movePane, but one step at a time
2023-02-06 13:48:52 -06:00
Mike Griese
a20201fff2 [silmarillion] Update the validation for the moveTab action 2023-02-06 13:42:54 -06:00
Mike Griese
40046c38dc one shot one opportunity
Cherry-picked from 4b4e057a7
2023-02-03 13:40:37 -06:00
Mike Griese
427a4a51c5 [ainulindale] "fix" hot reload
Doesn't work with multiple windows open, but doesn't do _nothing_
2023-02-03 13:28:30 -06:00
Mike Griese
17057d147d (maybe valaquenta) well, the keybindings stuff is... better? 2023-02-03 10:45:20 -06:00
Mike Griese
631f690e42 remove the old control when the content gets moved 2023-02-03 09:41:16 -06:00
Mike Griese
57d1dd4358 [ainulindale] Clean tear down the App when the process exits 2023-02-03 09:15:53 -06:00
Mike Griese
713ea71725 Revert "this is important for moving, yknow, the panes"
This reverts commit 0a6c21fed0.
2023-02-03 09:10:17 -06:00
Mike Griese
0f33913423 actually start drawing to the new control when we move it 2023-02-03 09:10:04 -06:00
Mike Griese
1f56807fa0 move to int IDs is okay too 2023-02-02 12:28:46 -06:00
Mike Griese
0a6c21fed0 this is important for moving, yknow, the panes 2023-02-02 12:09:25 -06:00
Mike Griese
ebe5fd8163 can I get a whoop whoop (Adds suuport for moving tabs to another window)
cherry-picked from 4acbffb0e
2023-02-02 11:58:05 -06:00
Mike Griese
d143f312d3 Add comments
This is basically the comment from aaefdf4408. The other comment wasn't relevant anymore
2023-02-02 11:30:01 -06:00
Mike Griese
3bd6957a6d This works to open a new pane in the new window...
but it doesn't render, and the old pane keeps on choochin.

  And it only works with a window name, not and ID

  And the `Dispatcher` definitely needs to get re-wired for the new thread, cause dragging it across a DPI boundary (aka, resize) crashes the window
2023-02-02 11:22:36 -06:00
Mike Griese
6a2239d487 Pane officially opened via the serialized actions, via across the process boundary
cherry-picked from 1f2bb760e
2023-02-02 10:52:45 -06:00
Mike Griese
4dcc67b4d7 Serialize the pane to a Content string, instead of just the guid.
There's a lot of renaming, signature changes here. But StartupActions is a good mechanism for passing panes around, more or less.

cherry-picked from cfe879da9
2023-02-02 08:40:45 -06:00
Mike Griese
37a8fde29c Holy shit all the plumbing worked on the first try
cherry-picked from 9870eb1d39
2023-02-02 08:16:37 -06:00
Mike Griese
1b4f1efb09 I literally think this is the whole thing 2023-02-01 17:08:37 -06:00
Mike Griese
e623299ac6 notes 2023-02-01 14:12:31 -06:00
Mike Griese
274d62d8a8 it's working 2023-02-01 13:56:40 -06:00
Mike Griese
f655296c1e This does successfully get a window on the screen, which is pretty impressive
It exits itself after 30s, but hey it worked
2023-02-01 13:15:44 -06:00
Mike Griese
7a3e2e098d Start rewriting WindowManager to facilitate the new process model
At this point, I determined that I would need to make some big changes to
  AppHost and decided that it was time to commit before moving on.
2023-02-01 11:48:43 -06:00
Mike Griese
2c4613a494 wow it just... worked 2023-02-01 08:07:29 -06:00
Mike Griese
f5b030c28c it runs! 2023-01-31 15:21:29 -06:00
Mike Griese
d456210f37 Use the single AppLogic for all windows. Remove the _app references. It launches and crashes immediately. We'll keep shuffling code. 2023-01-31 12:59:30 -06:00
Mike Griese
af14c2b751 [TO PARENT] Move the page ctor call, so that it can happen after the XAML island is started.
I think this can work in the parent at least
2023-01-31 12:58:39 -06:00
Mike Griese
ef7e2edfa5 Merge branch 'dev/migrie/oop/3/foreword' into dev/migrie/oop/3/ainulindale
# Conflicts:
#	src/cascadia/WindowsTerminal/AppHost.h
2023-01-31 11:11:35 -06:00
Mike Griese
5116ca1e77 I think the todo's that are left, we can move on without them for now. 2023-01-31 10:47:46 -06:00
Mike Griese
2195515937 it launches 2023-01-31 10:44:38 -06:00
Mike Griese
99bc280207 It doesn't crash on launch. That's something. There's no startupActions though, so it immediately exits 2023-01-31 09:34:02 -06:00
Mike Griese
439b21f879 this is dangerously close to compiling 2023-01-30 17:06:31 -06:00
Mike Griese
936c01f948 Start splitting AppLogic into AppLogic and Window logic
We'll need this for #5000, for ainulindale. This refactoring will be annoying
  enough as it is so we may as well do it as a first, separate PR.
2023-01-30 14:52:19 -06:00
Mike Griese
e6220b7fe7 Revert "I don't think I want any of these"
This reverts commit a5255ba8ed.
2023-01-30 12:37:35 -06:00
Mike Griese
a5255ba8ed I don't think I want any of these 2023-01-30 12:37:26 -06:00
Mike Griese
581acd40d9 I definitely want all of this. But I started down a path for refactoring AppLogic that I hate so I'm gonna start over 2023-01-30 12:37:12 -06:00
88 changed files with 5335 additions and 3388 deletions

View File

@@ -160,6 +160,7 @@ rcx
REGCLS
RETURNCMD
rfind
RLO
ROOTOWNER
roundf
RSHIFT

View File

@@ -6,7 +6,7 @@
"C_Cpp.loggingLevel": "None",
"files.associations": {
"xstring": "cpp",
"*.idl": "cpp",
"*.idl": "midl3",
"array": "cpp",
"future": "cpp",
"istream": "cpp",
@@ -106,4 +106,4 @@
"**/packages/**": true,
"**/Generated Files/**": true
}
}
}

View File

@@ -1822,7 +1822,7 @@
"name": {
"type": "string",
"description": "The name of the theme. This will be displayed in the settings UI.",
"not": {
"not": {
"enum": [ "light", "dark", "system" ]
}
},
@@ -2092,6 +2092,11 @@
"description": "When set to true, the terminal will focus the pane on mouse hover.",
"type": "boolean"
},
"compatibility.isolatedMode": {
"default": false,
"description": "When set to true, Terminal windows will not be able to interact with each other (including global hotkeys, tab drag/drop, running commandlines in existing windows, etc.). This is a compatibility escape hatch for users who are running into certain windowing issues.",
"type": "boolean"
},
"copyFormatting": {
"default": true,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",

View File

@@ -164,8 +164,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
@@ -287,8 +288,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
@@ -412,8 +414,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
}
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
@@ -527,8 +530,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
@@ -621,8 +625,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
@@ -744,8 +749,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
@@ -880,8 +886,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
@@ -982,8 +989,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
@@ -1205,8 +1213,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile());
}
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
_logCommandNames(expandedCommands.GetView());
settings.ExpandCommands();
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
_logCommandNames(expandedCommands);
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());

View File

@@ -42,6 +42,17 @@ namespace TerminalAppLocalTests
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
struct WindowProperties : winrt::implements<WindowProperties, winrt::TerminalApp::IWindowProperties>
{
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(uint64_t, WindowId);
WINRT_PROPERTY(winrt::hstring, WindowNameForDisplay);
WINRT_PROPERTY(winrt::hstring, WindowIdForDisplay);
public:
bool IsQuakeWindow() { return _WindowName == L"_quake"; };
};
class TabTests
{
// For this set of tests, we need to activate some XAML content. For
@@ -110,6 +121,7 @@ namespace TerminalAppLocalTests
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
CascadiaSettings initialSettings);
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
winrt::com_ptr<WindowProperties> _windowProperties;
};
template<typename TFunction>
@@ -239,11 +251,15 @@ namespace TerminalAppLocalTests
// it's weird.
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
_windowProperties = winrt::make_self<WindowProperties>();
winrt::TerminalApp::IWindowProperties iProps{ *_windowProperties };
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
auto result = RunOnUIThread([&projectedPage, &page, initialSettings, iProps]() {
projectedPage = winrt::TerminalApp::TerminalPage();
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
page->_settings = initialSettings;
page->WindowProperties(iProps);
});
VERIFY_SUCCEEDED(result);
@@ -1242,10 +1258,13 @@ namespace TerminalAppLocalTests
END_TEST_METHOD_PROPERTIES()
auto page = _commonSetup();
page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
page->RenameWindowRequested([&page, this](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
// In the real terminal, this would bounce up to the monarch and
// come back down. Instead, immediately call back and set the name.
page->WindowName(args.ProposedName());
//
// This replicates how TerminalWindow works
_windowProperties->WindowName(args.ProposedName());
page->WindowNameChanged();
});
auto windowNameChanged = false;
@@ -1260,7 +1279,7 @@ namespace TerminalAppLocalTests
page->_RequestWindowRename(winrt::hstring{ L"Foo" });
});
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(L"Foo", page->_WindowName);
VERIFY_ARE_EQUAL(L"Foo", page->WindowName());
VERIFY_IS_TRUE(windowNameChanged,
L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed");
});

View File

@@ -10,6 +10,7 @@
#include "ProposeCommandlineResult.h"
#include "Monarch.g.cpp"
#include "WindowRequestedArgs.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
@@ -687,6 +688,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
case WindowingBehaviorUseName:
windowID = _lookupPeasantIdForName(targetWindowName);
break;
case WindowingBehaviorUseNone:
// This should be impossible. The if statement above should have
// prevented WindowingBehaviorUseNone from falling in here.
// Explode, because this is a programming error.
THROW_HR(E_UNEXPECTED);
default:
windowID = ::base::saturated_cast<uint64_t>(targetWindow);
break;
@@ -724,6 +730,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
result->WindowName(targetWindowName);
result->ShouldCreateWindow(true);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
// If this fails, it'll be logged in the following
// TraceLoggingWrite statement, with succeeded=false
}
@@ -759,9 +767,20 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
result->Id(windowID);
result->WindowName(targetWindowName);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
return *result;
}
}
else if (targetWindow == WindowingBehaviorUseNone)
{
// In this case, the targetWindow was UseNone, which means that we
// want to make a message box, but otherwise not make a Terminal
// window.
auto result = winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(false);
return *result;
}
// If we get here, we couldn't find an existing window. Make a new one.
TraceLoggingWrite(g_hRemotingProvider,
@@ -773,6 +792,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// In this case, no usable ID was provided. Return { true, nullopt }
auto result = winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true);
result->WindowName(targetWindowName);
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
return *result;
}
@@ -1034,4 +1056,95 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return winrt::single_threaded_vector(std::move(vec));
}
void Monarch::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex,
Windows::Foundation::IReference<Windows::Foundation::Rect> windowBounds)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Requested",
TraceLoggingWideString(window.c_str(), "window", "The name of the window we tried to move to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
uint64_t windowId = _lookupPeasantIdForName(window);
if (windowId == 0)
{
// Try the name as an integer ID
uint32_t temp;
if (!Utils::StringToUint(window.c_str(), temp))
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_FailedToParseId",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
windowId = temp;
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
auto request = winrt::make_self<implementation::AttachRequest>(content, tabIndex);
targetPeasant.AttachContentToWindow(*request);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Completed",
TraceLoggingInt64(windowId, "windowId", "The ID of the peasant which we sent the content to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_NoWindow",
TraceLoggingInt64(windowId, "windowId", "We could not find a peasant with this ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// In the case where window couldn't be found, then create a window
// for that name / ID.
//
// Don't let the window literally be named "-1", because that's silly
auto request = winrt::make_self<implementation::WindowRequestedArgs>(window == L"-1" ? L"" : window,
content,
windowBounds);
_RequestNewWindowHandlers(*this, *request);
}
}
// Very similar to the above. Someone came and told us that they were the target of a drag/drop, and they know who started it.
// We will go tell the person who started it that they should send that target the content which was dragged.
void Monarch::RequestSendContent(Remoting::RequestReceiveContentArgs args)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Requested",
TraceLoggingUInt64(args.SourceWindow(), "source", "The window which started the drag"),
TraceLoggingUInt64(args.TargetWindow(), "target", "The window which was the target of the drop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
if (auto senderPeasant{ _getPeasant(args.SourceWindow()) })
{
senderPeasant.SendContent(args);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Completed",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
// We couldn't find the peasant that started the drag. Well that
// sure is weird, but that would indicate that the sender closed
// after starting the drag. No matter. We can just do nothing.
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_NoWindow",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
}

View File

@@ -6,6 +6,7 @@
#include "Monarch.g.h"
#include "Peasant.h"
#include "WindowActivatedArgs.h"
#include "WindowRequestedArgs.g.h"
#include <atomic>
// We sure different GUIDs here depending on whether we're running a Release,
@@ -38,6 +39,36 @@ namespace RemotingUnitTests
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct WindowRequestedArgs : public WindowRequestedArgsT<WindowRequestedArgs>
{
public:
WindowRequestedArgs(const Remoting::ProposeCommandlineResult& result, const Remoting::CommandlineArgs& command) :
_Id{ result.Id() },
_WindowName{ result.WindowName() },
_args{ command.Commandline() },
_CurrentDirectory{ command.CurrentDirectory() } {};
WindowRequestedArgs(const winrt::hstring& window, const winrt::hstring& content, Windows::Foundation::IReference<Windows::Foundation::Rect> bounds) :
_Id{ nullptr },
_WindowName{ window },
_args{},
_CurrentDirectory{},
_Content{ content },
_InitialBounds{ bounds } {};
void Commandline(const winrt::array_view<const winrt::hstring>& value) { _args = { value.begin(), value.end() }; };
winrt::com_array<winrt::hstring> Commandline() { return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() }; }
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, Id);
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, CurrentDirectory);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
private:
winrt::com_array<winrt::hstring> _args;
};
struct Monarch : public MonarchT<Monarch>
{
Monarch();
@@ -60,6 +91,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
void RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, Windows::Foundation::IReference<Windows::Foundation::Rect> windowBounds);
void RequestSendContent(Remoting::RequestReceiveContentArgs args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@@ -67,6 +101,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs);
private:
uint64_t _ourPID;
@@ -191,4 +227,5 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(Monarch);
BASIC_FACTORY(WindowRequestedArgs);
}

View File

@@ -18,6 +18,19 @@ namespace Microsoft.Terminal.Remoting
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
}
[default_interface] runtimeclass WindowRequestedArgs {
WindowRequestedArgs(ProposeCommandlineResult result, CommandlineArgs command);
Windows.Foundation.IReference<UInt64> Id { get; };
String WindowName { get; };
String[] Commandline { get; };
String CurrentDirectory { get; };
String Content { get; };
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
}
[default_interface] runtimeclass SummonWindowSelectionArgs {
SummonWindowSelectionArgs();
SummonWindowSelectionArgs(String windowName);
@@ -31,8 +44,7 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.IReference<UInt64> WindowID;
}
[default_interface] runtimeclass QuitAllRequestedArgs
{
[default_interface] runtimeclass QuitAllRequestedArgs {
QuitAllRequestedArgs();
Windows.Foundation.IAsyncAction BeforeQuitAllAction;
}
@@ -60,12 +72,17 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
void RequestMoveContent(String window, String content, UInt32 tabIndex, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
void RequestSendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
};
runtimeclass Monarch : [default] IMonarch

View File

@@ -8,6 +8,8 @@
#include "GetWindowLayoutArgs.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
#include "AttachRequest.g.cpp"
#include "RequestReceiveContentArgs.g.cpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
@@ -275,6 +277,22 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::AttachContentToWindow(Remoting::AttachRequest request)
{
try
{
_AttachRequestedHandlers(*this, request);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_AttachContentToWindow",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::Quit()
{
try
@@ -310,4 +328,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return args->WindowLayoutJson();
}
void Peasant::SendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args)
{
_SendContentRequestedHandlers(*this, args);
}
}

View File

@@ -5,6 +5,8 @@
#include "Peasant.g.h"
#include "RenameRequestArgs.h"
#include "AttachRequest.g.h"
#include "RequestReceiveContentArgs.g.h"
namespace RemotingUnitTests
{
@@ -12,6 +14,31 @@ namespace RemotingUnitTests
};
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct AttachRequest : public AttachRequestT<AttachRequest>
{
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
AttachRequest(winrt::hstring content,
uint32_t tabIndex) :
_Content{ content },
_TabIndex{ tabIndex } {};
};
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
{
WINRT_PROPERTY(uint64_t, SourceWindow);
WINRT_PROPERTY(uint64_t, TargetWindow);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
_SourceWindow{ src },
_TargetWindow{ tgt },
_TabIndex{ tabIndex } {};
};
struct Peasant : public PeasantT<Peasant>
{
Peasant();
@@ -32,11 +59,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestQuitAll();
void Quit();
void AttachContentToWindow(Remoting::AttachRequest request);
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
winrt::hstring GetWindowLayout();
void SendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args);
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
@@ -47,12 +77,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
TYPED_EVENT(SendContentRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs);
private:
Peasant(const uint64_t testPID);
uint64_t _ourPID;
@@ -69,4 +103,5 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(Peasant);
BASIC_FACTORY(RequestReceiveContentArgs);
}

View File

@@ -43,7 +43,6 @@ namespace Microsoft.Terminal.Remoting
ToMouse,
};
[default_interface] runtimeclass SummonWindowBehavior {
SummonWindowBehavior();
Boolean MoveToCurrentDesktop;
@@ -52,6 +51,18 @@ namespace Microsoft.Terminal.Remoting
MonitorBehavior ToMonitor;
}
[default_interface] runtimeclass AttachRequest {
String Content { get; };
UInt32 TabIndex { get; };
}
[default_interface] runtimeclass RequestReceiveContentArgs {
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
UInt64 SourceWindow { get; };
UInt64 TargetWindow { get; };
UInt32 TabIndex { get; };
};
interface IPeasant
{
CommandlineArgs InitialArgs { get; };
@@ -70,23 +81,31 @@ namespace Microsoft.Terminal.Remoting
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);
void RequestShowNotificationIcon();
void RequestHideNotificationIcon();
void RequestQuitAll();
void Quit();
String GetWindowLayout();
void AttachContentToWindow(AttachRequest request);
void SendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, AttachRequest> AttachRequested;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> SendContentRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@@ -2,10 +2,13 @@
// Licensed under the MIT license.
#include "pch.h"
#include "WindowManager.h"
#include "MonarchFactory.h"
#include "CommandlineArgs.h"
#include "../inc/WindowingBehavior.h"
#include "MonarchFactory.h"
#include "CommandlineArgs.h"
#include "FindTargetWindowArgs.h"
#include "ProposeCommandlineResult.h"
@@ -21,32 +24,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
WindowManager::WindowManager()
{
_monarchWaitInterrupt.create();
// Register with COM as a server for the Monarch class
_registerAsMonarch();
// Instantiate an instance of the Monarch. This may or may not be in-proc!
auto foundMonarch = false;
while (!foundMonarch)
{
try
{
_createMonarchAndCallbacks();
// _createMonarchAndCallbacks will initialize _isKing
foundMonarch = true;
}
catch (...)
{
// If we fail to find the monarch,
// stay in this jail until we do.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ExceptionInCtor",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
}
WindowManager::~WindowManager()
{
// IMPORTANT! Tear down the registration as soon as we exit. If we're not a
@@ -55,32 +33,185 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch!
CoRevokeClassObject(_registrationHostClass);
_registrationHostClass = 0;
SignalClose();
_monarchWaitInterrupt.SetEvent();
// A thread is joinable once it's been started. Basically this just
// makes sure that the thread isn't just default-constructed.
if (_electionThread.joinable())
{
_electionThread.join();
}
}
void WindowManager::SignalClose()
void WindowManager::_createMonarch()
{
// Heads up! This only works because we're using
// "metadata-based-marshalling" for our WinRT types. That means the OS is
// using the .winmd file we generate to figure out the proxy/stub
// definitions for our types automatically. This only works in the following
// cases:
//
// * If we're running unpackaged: the .winmd must be a sibling of the .exe
// * If we're running packaged: the .winmd must be in the package root
_monarch = try_create_instance<Remoting::IMonarch>(Monarch_clsid,
CLSCTX_LOCAL_SERVER);
}
// Check if we became the king, and if we are, wire up callbacks.
void WindowManager::_createCallbacks()
{
assert(_monarch);
// Here, we're the king!
//
// This is where you should do any additional setup that might need to be
// done when we become the king. This will be called both for the first
// window, and when the current monarch dies.
_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.QuitAllRequested({ get_weak(), &WindowManager::_QuitAllRequestedHandlers });
_monarch.RequestNewWindow({ get_weak(), &WindowManager::_raiseRequestNewWindow });
}
void WindowManager::_registerAsMonarch()
{
winrt::check_hresult(CoRegisterClassObject(Monarch_clsid,
winrt::make<::MonarchFactory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&_registrationHostClass));
}
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
{
_FindTargetWindowRequestedHandlers(sender, args);
}
void WindowManager::_raiseRequestNewWindow(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args)
{
_RequestNewWindowHandlers(sender, args);
}
Remoting::ProposeCommandlineResult WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args, const bool isolatedMode)
{
bool shouldCreateWindow = false;
if (!isolatedMode)
{
// _createMonarch always attempts to connect an existing monarch. In
// isolated mode, we don't want to do that.
_createMonarch();
}
if (_monarch)
{
try
// We connected to a monarch instance, not us though. This won't hit
// in isolated mode.
shouldCreateWindow = false;
// Send the commandline over to the monarch process
if (_proposeToMonarch(args))
{
_monarch.SignalClose(_peasant.GetID());
// If that succeeded, then we don't need to make a new window.
// Our job is done. Either the monarch is going to run the
// commandline in an existing window, or a new one, but either way,
// this process doesn't need to make a new window.
return winrt::make<ProposeCommandlineResult>(shouldCreateWindow);
}
// Otherwise, we'll try to handle this ourselves.
}
// Theoretically, this condition is always true here:
//
// if (_monarch == nullptr)
//
// If we do still have a _monarch at this point, then we must have
// successfully proposed to it in _proposeToMonarch, so we can't get
// here with a monarch.
{
// No preexisting instance.
// Raise an event, to ask how to handle this commandline. We can't ask
// the app ourselves - we exist isolated from that knowledge (and
// dependency hell). The WindowManager will raise this up to the app
// host, which will then ask the AppLogic, who will then parse the
// commandline and determine the provided ID of the window.
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
// This is handled by some handler in-proc
_FindTargetWindowRequestedHandlers(*this, *findWindowArgs);
// After the event was handled, ResultTargetWindow() will be filled with
// the parsed result.
const auto targetWindow = findWindowArgs->ResultTargetWindow();
const auto targetWindowName = findWindowArgs->ResultTargetWindowName();
if (targetWindow == WindowingBehaviorUseNone)
{
// This commandline doesn't deserve a window. Don't make a monarch
// either.
shouldCreateWindow = false;
return winrt::make<ProposeCommandlineResult>(shouldCreateWindow);
}
else
{
// This commandline _does_ want a window, which means we do want
// to create a window, and a monarch.
//
// Congrats! This is now THE PROCESS. It's the only one that's
// getting any windows.
// In isolated mode, we don't want to register as the monarch,
// we just want to make a local one. So we'll skip this step.
// The condition below it will handle making the unregistered
// local monarch.
if (!isolatedMode)
{
_registerAsMonarch();
_createMonarch();
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_IntentionallyIsolated",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
if (!_monarch)
{
// Something catastrophically bad happened here OR we were
// intentionally in isolated mode. We don't want to just
// exit immediately. Instead, we'll just instantiate a local
// Monarch instance, without registering it. We're firmly in
// the realm of undefined behavior, but better to have some
// window than not.
_monarch = winrt::make<Monarch>();
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_FailedToCoCreate",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
_createCallbacks();
// So, we wanted a new peasant. Cool!
//
// We need to fill in args.ResultTargetWindow,
// args.ResultTargetWindowName so that we can create the new
// window with those values. Otherwise, the very first window
// won't obey the given name / ID.
//
// So let's just ask the monarch (ourselves) to get those values.
return _monarch.ProposeCommandline(args);
}
CATCH_LOG()
}
}
void WindowManager::_proposeToMonarch(const Remoting::CommandlineArgs& args,
std::optional<uint64_t>& givenID,
winrt::hstring& givenName)
// Method Description:
// - Helper attempting to call to the monarch multiple times. If the monarch
// fails to respond, or we encounter any sort of error, we'll try again
// until we find one, or decisively determine there isn't one.
bool WindowManager::_proposeToMonarch(const Remoting::CommandlineArgs& args)
{
// these two errors are Win32 errors, convert them to HRESULTS so we can actually compare below.
static constexpr auto RPC_SERVER_UNAVAILABLE_HR = HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE);
@@ -114,10 +245,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// dies between now and the inspection of
// `result.ShouldCreateWindow` below, we don't want to explode
// (since _proposeToMonarch is not try/caught).
auto outOfProcResult = _monarch.ProposeCommandline(args);
result = winrt::make<implementation::ProposeCommandlineResult>(outOfProcResult);
proposedCommandline = true;
_monarch.ProposeCommandline(args);
return true;
}
catch (...)
{
@@ -154,560 +284,74 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_monarch = winrt::make<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
_createCallbacks();
// Set the monarch to null, so that we'll create a new one
// (or just generally check if we need to even make a window
// for this commandline.)
_monarch = nullptr;
return false;
}
else
{
// We failed to ask the monarch. It must have died. Try and
// find the real monarch. Don't perform an election, that
// assumes we have a peasant, which we don't yet.
_createMonarchAndCallbacks();
// _createMonarchAndCallbacks will initialize _isKing
}
if (_isKing)
{
// We became the king. We don't need to ProposeCommandline to ourself, we're just
// going to do it.
//
// Return early, because there's nothing else for us to do here.
// find another monarch.
_createMonarch();
if (!_monarch)
{
// We failed to create a monarch. That means there
// aren't any other windows, and we can become the monarch.
return false;
}
// Go back around the loop.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_proposeToMonarch_becameKing",
"WindowManager_proposeToMonarch_tryAgain",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// In WindowManager::ProposeCommandline, had we been the
// king originally, we would have started by setting
// this to true. We became the monarch here, so set it
// here as well.
_shouldCreateWindow = true;
return;
}
// Here, we created the new monarch, it wasn't us, so we're
// gonna go through the while loop again and ask the new
// king.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_proposeToMonarch_tryAgain",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
// Here, the monarch (not us) has replied to the message. Get the
// valuables out of the response:
_shouldCreateWindow = result.ShouldCreateWindow();
if (result.Id())
{
givenID = result.Id().Value();
}
givenName = result.WindowName();
// TraceLogging doesn't have a good solution for logging an
// optional. So we have to repeat the calls here:
if (givenID)
{
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ProposeCommandline",
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ProposeCommandline",
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
TraceLoggingPointer(nullptr, "Id", "No ID provided"),
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
{
// If we're the king, we _definitely_ want to process the arguments, we were
// launched with them!
//
// Otherwise, the King will tell us if we should make a new window
_shouldCreateWindow = _isKing;
std::optional<uint64_t> givenID;
winrt::hstring givenName{};
if (!_isKing)
{
_proposeToMonarch(args, givenID, givenName);
}
// During _proposeToMonarch, it's possible that we found that the king was dead, and we're the new king. Cool! Do this now.
if (_isKing)
{
// We're the monarch, we don't need to propose anything. We're just
// going to do it.
//
// However, we _do_ need to ask what our name should be. It's
// possible someone started the _first_ wt with something like `wt
// -w king` as the commandline - we want to make sure we set our
// name to "king".
//
// The FindTargetWindow event is the WindowManager's way of saying
// "I do not know how to figure out how to turn this list of args
// into a window ID/name. Whoever's listening to this event does, so
// I'll ask them". It's a convoluted way of hooking the
// WindowManager up to AppLogic without actually telling it anything
// about TerminalApp (or even WindowsTerminal)
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
_raiseFindTargetWindowRequested(nullptr, *findWindowArgs);
const auto responseId = findWindowArgs->ResultTargetWindow();
if (responseId > 0)
{
givenID = ::base::saturated_cast<uint64_t>(responseId);
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ProposeCommandline_AsMonarch",
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else if (responseId == WindowingBehaviorUseName)
{
givenName = findWindowArgs->ResultTargetWindowName();
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ProposeCommandline_AsMonarch",
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ProposeCommandline_AsMonarch",
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
TraceLoggingWideString(L"", "Name", "The name we should assign this window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
if (_shouldCreateWindow)
{
// If we should create a new window, then instantiate our Peasant
// instance, and tell that peasant to handle that commandline.
_createOurPeasant({ givenID }, givenName);
// Spawn a thread to wait on the monarch, and handle the election
if (!_isKing)
{
_createPeasantThread();
}
// This right here will just tell us to stash the args away for the
// future. The AppHost hasn't yet set up the callbacks, and the rest
// of the app hasn't started at all. We'll note them and come back
// later.
_peasant.ExecuteCommandline(args);
}
// Otherwise, we'll do _nothing_.
// I don't think we can ever get here, but the compiler doesn't know
return false;
}
bool WindowManager::ShouldCreateWindow()
{
return _shouldCreateWindow;
}
void WindowManager::_registerAsMonarch()
{
winrt::check_hresult(CoRegisterClassObject(Monarch_clsid,
winrt::make<::MonarchFactory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&_registrationHostClass));
}
void WindowManager::_createMonarch()
{
// Heads up! This only works because we're using
// "metadata-based-marshalling" for our WinRT types. That means the OS is
// using the .winmd file we generate to figure out the proxy/stub
// definitions for our types automatically. This only works in the following
// cases:
//
// * If we're running unpackaged: the .winmd must be a sibling of the .exe
// * If we're running packaged: the .winmd must be in the package root
_monarch = create_instance<Remoting::IMonarch>(Monarch_clsid,
CLSCTX_LOCAL_SERVER);
}
// Tries to instantiate a monarch, tries again, and eventually either throws
// (so that the caller will try again) or falls back to the isolated
// monarch.
void WindowManager::_redundantCreateMonarch()
{
_createMonarch();
if (_monarch == nullptr)
{
// See MSFT:38540483, GH#12774 for details.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_NullMonarchTryAgain",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// Here we're gonna just give it a quick second try.Probably not
// definitive, but might help.
_createMonarch();
}
if (_monarch == nullptr)
{
// See MSFT:38540483, GH#12774 for details.
if constexpr (Feature_IsolatedMonarchMode::IsEnabled())
{
// Fall back to having a in proc monarch. Were now isolated from
// other windows. This is a pretty torn state, but at least we
// didn't just explode.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_NullMonarchIsolateMode",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_monarch = winrt::make<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
}
else
{
// The monarch is null. We're hoping that we can find another,
// hopefully us. We're gonna go back around the loop again and
// see what happens. If this is really an infinite loop (where
// the OS won't even give us back US as the monarch), then I
// suppose we'll find out soon enough.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_NullMonarchTryAgain",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
winrt::hresult_error(E_UNEXPECTED, L"Did not expect the Monarch to ever be null");
}
}
}
// NOTE: This can throw! Callers include:
// - the constructor, who performs this in a loop until it successfully
// find a a monarch
// - the performElection method, which is called in the waitOnMonarch
// thread. All the calls in that thread are wrapped in try/catch's
// already.
// - _createOurPeasant, who might do this in a loop to establish us with the
// monarch.
void WindowManager::_createMonarchAndCallbacks()
{
_redundantCreateMonarch();
// We're pretty confident that we have a Monarch here.
_createCallbacks();
}
// Check if we became the king, and if we are, wire up callbacks.
void WindowManager::_createCallbacks()
{
// Save the result of checking if we're the king. We want to avoid
// unnecessary calls back and forth if we can.
_isKing = _areWeTheKing();
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ConnectedToMonarch",
TraceLoggingUInt64(_monarch.GetPID(), "monarchPID", "The PID of the new Monarch"),
TraceLoggingBoolean(_isKing, "isKing", "true if we are the new monarch"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
if (_peasant)
{
if (const auto& lastActivated{ _peasant.GetLastActivatedArgs() })
{
// Inform the monarch of the time we were last activated
_monarch.HandleActivatePeasant(lastActivated);
}
}
if (!_isKing)
{
return;
}
// Here, we're the king!
//
// This is where you should do any additional setup that might need to be
// done when we become the king. This will be called both for the first
// window, and when the current monarch dies.
_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.QuitAllRequested({ get_weak(), &WindowManager::_QuitAllRequestedHandlers });
_BecameMonarchHandlers(*this, nullptr);
}
bool WindowManager::_areWeTheKing()
{
const auto ourPID{ GetCurrentProcessId() };
const auto kingPID{ _monarch.GetPID() };
return (ourPID == kingPID);
}
Remoting::IPeasant WindowManager::_createOurPeasant(std::optional<uint64_t> givenID,
const winrt::hstring& givenName)
Remoting::Peasant WindowManager::CreatePeasant(Remoting::WindowRequestedArgs args)
{
auto p = winrt::make_self<Remoting::implementation::Peasant>();
if (givenID)
if (args.Id())
{
p->AssignID(givenID.value());
p->AssignID(args.Id().Value());
}
// If the name wasn't specified, this will be an empty string.
p->WindowName(givenName);
_peasant = *p;
p->WindowName(args.WindowName());
// Try to add us to the monarch. If that fails, try to find a monarch
// again, until we find one (we will eventually find us)
while (true)
{
try
{
_monarch.AddPeasant(_peasant);
break;
}
catch (...)
{
try
{
// Wrap this in its own try/catch, because this can throw.
_createMonarchAndCallbacks();
}
catch (...)
{
}
}
}
p->ExecuteCommandline(*winrt::make_self<CommandlineArgs>(args.Commandline(), args.CurrentDirectory()));
_peasant.GetWindowLayoutRequested({ get_weak(), &WindowManager::_GetWindowLayoutRequestedHandlers });
_monarch.AddPeasant(*p);
p->GetWindowLayoutRequested({ get_weak(), &WindowManager::_GetWindowLayoutRequestedHandlers });
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_CreateOurPeasant",
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
TraceLoggingUInt64(p->GetID(), "peasantID", "The ID of our new peasant"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// If the peasant asks us to quit we should not try to act in future elections.
_peasant.QuitRequested([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto wm = weakThis.get())
{
wm->_monarchWaitInterrupt.SetEvent();
}
});
return _peasant;
return *p;
}
// Method Description:
// - Attempt to connect to the monarch process. This might be us!
// - For the new monarch, add us to their list of peasants.
// Arguments:
// - <none>
// Return Value:
// - true iff we're the new monarch process.
// NOTE: This can throw!
bool WindowManager::_performElection()
void WindowManager::SignalClose(Remoting::Peasant peasant)
{
_createMonarchAndCallbacks();
// Tell the new monarch who we are. We might be that monarch!
_monarch.AddPeasant(_peasant);
// This method is only called when a _new_ monarch is elected. So
// don't do anything here that needs to be done for all monarch
// windows. This should only be for work that's done when a window
// _becomes_ a monarch, after the death of the previous monarch.
return _isKing;
}
void WindowManager::_createPeasantThread()
{
// If we catch an exception trying to get at the monarch ever, we can
// set the _monarchWaitInterrupt, and use that to trigger a new
// election. Though, we wouldn't be able to retry the function that
// caused the exception in the first place...
_electionThread = std::thread([this] {
_waitOnMonarchThread();
});
}
void WindowManager::_waitOnMonarchThread()
{
// This is the array of HANDLEs that we're going to wait on in
// WaitForMultipleObjects below.
// * waits[0] will be the handle to the monarch process. It gets
// signalled when the process exits / dies.
// * waits[1] is the handle to our _monarchWaitInterrupt event. Another
// thread can use that to manually break this loop. We'll do that when
// we're getting torn down.
HANDLE waits[2];
waits[1] = _monarchWaitInterrupt.get();
const auto peasantID = _peasant.GetID(); // safe: _peasant is in-proc.
auto exitThreadRequested = false;
while (!exitThreadRequested)
if (_monarch)
{
// At any point in all this, the current monarch might die. If it
// does, we'll go straight to a new election, in the "jail"
// try/catch below. Worst case, eventually, we'll become the new
// monarch.
try
{
// This might fail to even ask the monarch for its PID.
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(_monarch.GetPID())) };
// If we fail to open the monarch, then they don't exist
// anymore! Go straight to an election.
if (hMonarch.get() == nullptr)
{
const auto gle = GetLastError();
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_FailedToOpenMonarch",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
exitThreadRequested = _performElection();
continue;
}
waits[0] = hMonarch.get();
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
switch (waitResult)
{
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the monarch process
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_MonarchDied",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// Connect to the new monarch, which might be us!
// If we become the monarch, then we'll return true and exit this thread.
exitThreadRequested = _performElection();
break;
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_MonarchWaitInterrupted",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
exitThreadRequested = true;
break;
case WAIT_TIMEOUT:
// This should be impossible.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_MonarchWaitTimeout",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
exitThreadRequested = true;
break;
default:
{
// Returning any other value is invalid. Just die.
const auto gle = GetLastError();
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_WaitFailed",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
ExitProcess(0);
}
}
}
catch (...)
{
// Theoretically, if window[1] dies when we're trying to get
// its PID we'll get here. If we just try to do the election
// once here, it's possible we might elect window[2], but have
// it die before we add ourselves as a peasant. That
// _performElection call will throw, and we wouldn't catch it
// here, and we'd die.
// Instead, we're going to have a resilient election process.
// We're going to keep trying an election, until one _doesn't_
// throw an exception. That might mean burning through all the
// other dying monarchs until we find us as the monarch. But if
// this process is alive, then there's _someone_ in the line of
// succession.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ExceptionInWaitThread",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
auto foundNewMonarch = false;
while (!foundNewMonarch)
{
try
{
exitThreadRequested = _performElection();
// It doesn't matter if we're the monarch, or someone
// else is, but if we complete the election, then we've
// registered with a new one. We can escape this jail
// and re-enter society.
foundNewMonarch = true;
}
catch (...)
{
// If we fail to acknowledge the results of the election,
// stay in this jail until we do.
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_ExceptionInNestedWaitThread",
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
_monarch.SignalClose(peasant.GetID());
}
CATCH_LOG()
}
}
Remoting::Peasant WindowManager::CurrentWindow()
{
return _peasant;
}
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
{
_FindTargetWindowRequestedHandlers(sender, args);
}
bool WindowManager::IsMonarch()
{
return _isKing;
}
void WindowManager::SummonWindow(const Remoting::SummonWindowSelectionArgs& args)
{
// We should only ever get called when we are the monarch, because only
@@ -747,10 +391,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestShowNotificationIcon()
winrt::fire_and_forget WindowManager::RequestShowNotificationIcon(Remoting::Peasant peasant)
{
co_await winrt::resume_background();
_peasant.RequestShowNotificationIcon();
peasant.RequestShowNotificationIcon();
}
// Method Description:
@@ -759,11 +403,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestHideNotificationIcon()
winrt::fire_and_forget WindowManager::RequestHideNotificationIcon(Remoting::Peasant peasant)
{
auto strongThis{ get_strong() };
co_await winrt::resume_background();
_peasant.RequestHideNotificationIcon();
peasant.RequestHideNotificationIcon();
}
// Method Description:
@@ -772,11 +416,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestQuitAll()
winrt::fire_and_forget WindowManager::RequestQuitAll(Remoting::Peasant peasant)
{
auto strongThis{ get_strong() };
co_await winrt::resume_background();
_peasant.RequestQuitAll();
peasant.RequestQuitAll();
}
bool WindowManager::DoesQuakeWindowExist()
@@ -784,9 +428,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return _monarch.DoesQuakeWindowExist();
}
void WindowManager::UpdateActiveTabTitle(winrt::hstring title)
void WindowManager::UpdateActiveTabTitle(winrt::hstring title, Remoting::Peasant peasant)
{
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
winrt::get_self<implementation::Peasant>(peasant)->ActiveTabTitle(title);
}
Windows::Foundation::Collections::IVector<winrt::hstring> WindowManager::GetAllWindowLayouts()
@@ -801,4 +445,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return nullptr;
}
winrt::fire_and_forget WindowManager::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex,
Windows::Foundation::IReference<Windows::Foundation::Rect> windowBounds)
{
co_await winrt::resume_background();
_monarch.RequestMoveContent(window, content, tabIndex, windowBounds);
}
winrt::fire_and_forget WindowManager::RequestSendContent(Remoting::RequestReceiveContentArgs args)
{
co_await winrt::resume_background();
_monarch.RequestSendContent(args);
}
}

View File

@@ -1,10 +1,8 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- WindowManager.h
Abstract:
- The Window Manager takes care of coordinating the monarch and peasant for this
process.
@@ -16,9 +14,7 @@ Abstract:
- When the monarch needs to ask the TerminalApp about how to parse a
commandline, it'll ask by raising an event that we'll bubble up to the
AppHost.
--*/
#pragma once
#include "WindowManager.g.h"
@@ -29,30 +25,31 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct WindowManager : public WindowManagerT<WindowManager>
{
public:
WindowManager();
~WindowManager();
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args, const bool isolatedMode);
Remoting::Peasant CreatePeasant(Remoting::WindowRequestedArgs args);
void ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
bool ShouldCreateWindow();
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
bool IsMonarch();
void SignalClose(Remoting::Peasant peasant);
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SignalClose();
void SummonAllWindows();
uint64_t GetNumberOfPeasants();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
winrt::fire_and_forget RequestShowNotificationIcon();
winrt::fire_and_forget RequestHideNotificationIcon();
winrt::fire_and_forget RequestQuitAll();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
uint64_t GetNumberOfPeasants();
winrt::fire_and_forget RequestShowNotificationIcon(Remoting::Peasant peasant);
winrt::fire_and_forget RequestHideNotificationIcon(Remoting::Peasant peasant);
winrt::fire_and_forget RequestQuitAll(Remoting::Peasant peasant);
void UpdateActiveTabTitle(winrt::hstring title, Remoting::Peasant peasant);
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
bool DoesQuakeWindowExist();
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, Windows::Foundation::IReference<Windows::Foundation::Rect> windowBounds);
winrt::fire_and_forget RequestSendContent(Remoting::RequestReceiveContentArgs args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@@ -60,34 +57,22 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs);
private:
bool _shouldCreateWindow{ false };
bool _isKing{ false };
DWORD _registrationHostClass{ 0 };
winrt::Microsoft::Terminal::Remoting::IMonarch _monarch{ nullptr };
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
wil::unique_event _monarchWaitInterrupt;
std::thread _electionThread;
void _registerAsMonarch();
void _createMonarch();
void _redundantCreateMonarch();
void _createMonarchAndCallbacks();
void _createCallbacks();
bool _areWeTheKing();
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant(std::optional<uint64_t> givenID,
const winrt::hstring& givenName);
void _registerAsMonarch();
bool _performElection();
void _createPeasantThread();
void _waitOnMonarchThread();
bool _proposeToMonarch(const Remoting::CommandlineArgs& args);
void _createCallbacks();
void _raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
void _proposeToMonarch(const Remoting::CommandlineArgs& args,
std::optional<uint64_t>& givenID,
winrt::hstring& givenName);
void _raiseRequestNewWindow(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args);
};
}

View File

@@ -7,29 +7,39 @@ namespace Microsoft.Terminal.Remoting
[default_interface] runtimeclass WindowManager
{
WindowManager();
void ProposeCommandline(CommandlineArgs args);
void SignalClose();
Boolean ShouldCreateWindow { get; };
IPeasant CurrentWindow();
Boolean IsMonarch { get; };
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args, Boolean isolatedMode);
Peasant CreatePeasant(WindowRequestedArgs args);
void SignalClose(Peasant p);
void RequestShowNotificationIcon(Peasant p);
void RequestHideNotificationIcon(Peasant p);
void UpdateActiveTabTitle(String title, Peasant p);
void RequestQuitAll(Peasant p);
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowNotificationIcon();
void RequestHideNotificationIcon();
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
UInt64 GetNumberOfPeasants();
void RequestQuitAll();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
void RequestMoveContent(String window, String content, UInt32 tabIndex, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
void RequestSendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
};
}

View File

@@ -24,27 +24,5 @@ namespace winrt::TerminalApp::implementation
Name(command.Name());
KeyChordText(command.KeyChordText());
Icon(command.IconPath());
_commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) {
auto item{ weakThis.get() };
auto senderCommand{ sender.try_as<Microsoft::Terminal::Settings::Model::Command>() };
if (item && senderCommand)
{
auto changedProperty = e.PropertyName();
if (changedProperty == L"Name")
{
item->Name(senderCommand.Name());
}
else if (changedProperty == L"KeyChordText")
{
item->KeyChordText(senderCommand.KeyChordText());
}
else if (changedProperty == L"IconPath")
{
item->Icon(senderCommand.IconPath());
}
}
});
}
}

View File

@@ -77,22 +77,7 @@ namespace winrt::TerminalApp::implementation
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(const LaunchActivatedEventArgs& /*e*/)
{
// if this is a UWP... it means its our problem to hook up the content to the window here.
if (_isUwp)
{
auto content = Window::Current().Content();
if (content == nullptr)
{
auto logic = Logic();
logic.RunAsUwp(); // Must set UWP status first, settings might change based on it.
logic.ReloadSettings();
logic.Create();
auto page = logic.GetRoot().as<TerminalPage>();
Window::Current().Content(page);
Window::Current().Activate();
}
}
// We used to support a pure UWP version of the Terminal. This method
// was only ever used to do UWP-specific setup of our App.
}
}

View File

@@ -209,7 +209,7 @@ namespace winrt::TerminalApp::implementation
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
auto moved = _MovePane(realArgs);
args.Handled(moved);
}
}
@@ -789,17 +789,8 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = actionArgs.ActionArgs().try_as<MoveTabArgs>())
{
auto direction = realArgs.Direction();
if (direction != MoveTabDirection::None)
{
if (auto focusedTabIndex = _GetFocusedTabIndex())
{
auto currentTabIndex = focusedTabIndex.value();
auto delta = direction == MoveTabDirection::Forward ? 1 : -1;
_TryMoveTab(currentTabIndex, currentTabIndex + delta);
}
}
actionArgs.Handled(true);
auto moved = _MoveTab(realArgs);
actionArgs.Handled(moved);
}
}

View File

@@ -340,7 +340,7 @@ void AppCommandlineArgs::_buildMovePaneParser()
if (_movePaneTabIndex >= 0)
{
movePaneAction.Action(ShortcutAction::MovePane);
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex) };
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex), L"" };
movePaneAction.Args(args);
_startupActions.push_back(movePaneAction);
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,12 @@
#include "AppLogic.g.h"
#include "FindTargetWindowResult.g.h"
#include "SystemMenuChangeArgs.g.h"
#include "Jumplist.h"
#include "LanguageProfileNotifier.h"
#include "TerminalPage.h"
#include "AppCommandlineArgs.h"
#include "TerminalWindow.h"
#include "ContentManager.h"
#include <inc/cppwinrt_utils.h>
#include <ThrottledFunc.h>
@@ -36,18 +38,7 @@ namespace winrt::TerminalApp::implementation
FindTargetWindowResult(id, L""){};
};
struct SystemMenuChangeArgs : SystemMenuChangeArgsT<SystemMenuChangeArgs>
{
WINRT_PROPERTY(winrt::hstring, Name, L"");
WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add);
WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr);
public:
SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) :
_Name{ name }, _Action{ action }, _Handler{ handler } {};
};
struct AppLogic : AppLogicT<AppLogic, IInitializeWithWindow>
struct AppLogic : AppLogicT<AppLogic>
{
public:
static AppLogic* Current() noexcept;
@@ -56,172 +47,70 @@ namespace winrt::TerminalApp::implementation
AppLogic();
~AppLogic() = default;
STDMETHODIMP Initialize(HWND hwnd);
void Create();
bool IsUwp() const noexcept;
void RunAsUwp();
bool IsElevated() const noexcept;
void ReloadSettings();
bool HasSettingsStartupActions() const noexcept;
bool ShouldUsePersistedLayout() const;
void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector<hstring>& layouts);
[[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept;
void Quit();
bool HasCommandlineArguments() const noexcept;
bool HasSettingsStartupActions() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
TerminalApp::FindTargetWindowResult FindTargetWindow(array_view<const winrt::hstring> actions);
winrt::hstring ParseCommandlineMessage();
bool ShouldExitEarly();
bool FocusMode() const;
bool Fullscreen() const;
void Maximized(bool newMaximized);
bool AlwaysOnTop() const;
bool AutoHideWindow();
bool ShouldUsePersistedLayout();
bool ShouldImmediatelyHandoffToElevated();
void HandoffToElevated();
hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector<hstring>& layouts);
void IdentifyWindow();
void RenameFailed();
winrt::hstring WindowName();
void WindowName(const winrt::hstring& name);
uint64_t WindowId();
void WindowId(const uint64_t& id);
void SetPersistedLayoutIdx(const uint32_t idx);
void SetNumberOfOpenWindows(const uint64_t num);
bool IsQuakeWindow() const noexcept;
void RequestExitFullscreen();
Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
bool CenterOnLaunch();
TerminalApp::InitialPosition GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
Microsoft::Terminal::Settings::Model::LaunchMode GetLaunchMode();
bool GetShowTabsInTitlebar();
bool GetInitialAlwaysOnTop();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
Windows::UI::Xaml::UIElement GetRoot() noexcept;
void SetInboundListener();
hstring Title();
void TitlebarClicked();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void WindowVisibilityChanged(const bool showOrHide);
winrt::TerminalApp::TaskbarState TaskbarState();
winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush();
void WindowActivated(const bool activated);
bool GetMinimizeToNotificationArea();
bool GetAlwaysShowNotificationIcon();
bool GetShowTitleInTitlebar();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
void DismissDialog();
Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> GlobalHotkeys();
Microsoft::Terminal::Settings::Model::Theme Theme();
bool IsolatedMode();
bool AllowHeadless();
// -------------------------------- WinRT Events ---------------------------------
// PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
// Usually we'd just do
// WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
//
// But what we're doing here is exposing the Page's PropertyChanged _as
// our own event_. It's a FORWARDED_CALLBACK, essentially.
winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return _root->PropertyChanged(handler); }
void PropertyChanged(winrt::event_token const& token) { _root->PropertyChanged(token); }
TerminalApp::TerminalWindow CreateNewWindow();
TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Settings::Model::Theme);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs);
winrt::TerminalApp::ContentManager ContentManager();
TerminalApp::ParseCommandlineResult GetParseCommandlineMessage(array_view<const winrt::hstring> args);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
private:
bool _isUwp{ false };
bool _isElevated{ false };
// If you add controls here, but forget to null them either here or in
// the ctor, you're going to have a bad time. It'll mysteriously fail to
// activate the AppLogic.
// ALSO: If you add any UIElements as roots here, make sure they're
// updated in _ApplyTheme. The root currently is _root.
winrt::com_ptr<TerminalPage> _root{ nullptr };
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
winrt::hstring _settingsLoadExceptionText;
HRESULT _settingsLoadedResult = S_OK;
bool _loadedInitialSettings = false;
uint64_t _numOpenWindows{ 0 };
std::shared_mutex _dialogLock;
winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog;
::TerminalApp::AppCommandlineArgs _appArgs;
bool _hasSettingsStartupActions{ false };
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};
// These fields invoke _reloadSettings and must be destroyed before _reloadSettings.
// (C++ destroys members in reverse-declaration-order.)
winrt::com_ptr<LanguageProfileNotifier> _languageProfileNotifier;
wil::unique_folder_change_reader_nothrow _reader;
TerminalApp::ContentManager _contentManager{ *winrt::make_self<implementation::ContentManager>() };
static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view<const hstring> args,
const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior);
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
void _ShowLoadWarningsDialog();
bool _IsKeyboardServiceEnabled();
void _ApplyLanguageSettingChange() noexcept;
void _RefreshThemeRoutine();
fire_and_forget _ApplyStartupTaskStateChange();
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
[[nodiscard]] HRESULT _TryLoadSettings() noexcept;
void _ProcessLazySettingsChanges();
void _RegisterSettingsChange();
fire_and_forget _DispatchReloadSettings();
void _OpenSettingsUI();
bool _hasCommandLineArguments{ false };
bool _hasSettingsStartupActions{ false };
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings;
// These are events that are handled by the TerminalPage, but are
// exposed through the AppLogic. This macro is used to forward the event
// directly to them.
FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent);
FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged);
FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed);
FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged);
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);
FORWARDED_TYPED_EVENT(ChangeMaximizeRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, ChangeMaximizeRequested);
FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged);
FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell);
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested);
FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested);
FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged);
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested);
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);
FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@@ -1,41 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "TerminalPage.idl";
import "ShortcutActionDispatch.idl";
import "IDirectKeyListener.idl";
import "TerminalWindow.idl";
namespace TerminalApp
{
struct InitialPosition
{
Int64 X;
Int64 Y;
};
[default_interface] runtimeclass FindTargetWindowResult
{
Int32 WindowId { get; };
String WindowName { get; };
};
delegate void SystemMenuItemHandler();
enum SystemMenuChangeAction
struct ParseCommandlineResult
{
Add = 0,
Remove = 1
String Message;
Int32 ExitCode;
};
[default_interface] runtimeclass SystemMenuChangeArgs {
String Name { get; };
SystemMenuChangeAction Action { get; };
SystemMenuItemHandler Handler { get; };
};
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
[default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
[default_interface] runtimeclass AppLogic
{
AppLogic();
@@ -50,94 +34,27 @@ namespace TerminalApp
void RunAsUwp();
Boolean IsElevated();
Boolean HasCommandlineArguments();
ContentManager ContentManager { get; };
Boolean HasSettingsStartupActions();
Int32 SetStartupCommandline(String[] commands);
Int32 ExecuteCommandline(String[] commands, String cwd);
String ParseCommandlineMessage { get; };
Boolean ShouldExitEarly { get; };
void Quit();
void ReloadSettings();
Windows.UI.Xaml.UIElement GetRoot();
void SetInboundListener();
String Title { get; };
Boolean FocusMode { get; };
Boolean Fullscreen { get; };
void Maximized(Boolean newMaximized);
Boolean AlwaysOnTop { get; };
Boolean AutoHideWindow { get; };
void IdentifyWindow();
String WindowName;
UInt64 WindowId;
void SetPersistedLayoutIdx(UInt32 idx);
void SetNumberOfOpenWindows(UInt64 num);
void RenameFailed();
void RequestExitFullscreen();
Boolean IsQuakeWindow();
Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
Boolean CenterOnLaunch { get; };
InitialPosition GetInitialPosition(Int64 defaultInitialX, Int64 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
Microsoft.Terminal.Settings.Model.LaunchMode GetLaunchMode();
Boolean GetShowTabsInTitlebar();
Boolean GetInitialAlwaysOnTop();
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
void TitlebarClicked();
void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position);
void WindowVisibilityChanged(Boolean showOrHide);
TaskbarState TaskbarState{ get; };
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
Boolean ShouldUsePersistedLayout();
Boolean ShouldImmediatelyHandoffToElevated();
void HandoffToElevated();
String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position);
void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector<String> layouts);
Boolean GetMinimizeToNotificationArea();
Boolean GetAlwaysShowNotificationIcon();
Boolean GetShowTitleInTitlebar();
void ReloadSettings();
Microsoft.Terminal.Settings.Model.Theme Theme { get; };
Boolean IsolatedMode { get; };
Boolean AllowHeadless { get; };
FindTargetWindowResult FindTargetWindow(String[] args);
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
TerminalWindow CreateNewWindow();
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
void DismissDialog();
ParseCommandlineResult GetParseCommandlineMessage(String[] args);
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Theme> RequestedThemeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ChangeMaximizeRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RaiseVisualBell;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.SystemMenuChangeArgs> SystemMenuChangeRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ContentManager.h"
#include "ContentManager.g.cpp"
#include <wil/token_helpers.h>
#include "../../types/inc/utils.hpp"
using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
using IInspectable = Windows::Foundation::IInspectable;
}
namespace winrt::TerminalApp::implementation
{
ControlInteractivity ContentManager::CreateCore(Microsoft::Terminal::Control::IControlSettings settings,
IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection)
{
auto content = ControlInteractivity{ settings, unfocusedAppearance, connection };
content.Closed({ this, &ContentManager::_closedHandler });
const auto& contentGuid{ content.Id() };
_content.Insert(contentGuid, content);
return content;
}
ControlInteractivity ContentManager::LookupCore(winrt::guid id)
{
return _content.TryLookup(id);
}
void ContentManager::Detach(const Microsoft::Terminal::Control::TermControl& control)
{
const auto contentGuid{ control.ContentGuid() };
if (const auto& content{ LookupCore(contentGuid) })
{
control.Detach();
content.Attached({ get_weak(), &ContentManager::_finalizeDetach });
_recentlyDetachedContent.Insert(contentGuid, content);
}
}
void ContentManager::_finalizeDetach(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e)
{
if (const auto& content{ sender.try_as<winrt::Microsoft::Terminal::Control::ControlInteractivity>() })
{
_recentlyDetachedContent.TryRemove(content.Id());
}
}
void ContentManager::_closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e)
{
if (const auto& content{ sender.try_as<winrt::Microsoft::Terminal::Control::ControlInteractivity>() })
{
const auto& contentGuid{ content.Id() };
_content.TryRemove(contentGuid);
_recentlyDetachedContent.TryRemove(contentGuid);
}
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ContentManager.g.h"
#include <inc/cppwinrt_utils.h>
namespace winrt::TerminalApp::implementation
{
struct ContentManager : ContentManagerT<ContentManager>
{
public:
ContentManager() = default;
Microsoft::Terminal::Control::ControlInteractivity CreateCore(Microsoft::Terminal::Control::IControlSettings settings,
Microsoft::Terminal::Control::IControlAppearance unfocusedAppearance,
Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
Microsoft::Terminal::Control::ControlInteractivity LookupCore(winrt::guid id);
void Detach(const Microsoft::Terminal::Control::TermControl& control);
private:
Windows::Foundation::Collections::IMap<winrt::guid, Microsoft::Terminal::Control::ControlInteractivity> _content{
winrt::multi_threaded_map<winrt::guid, Microsoft::Terminal::Control::ControlInteractivity>()
};
Windows::Foundation::Collections::IMap<winrt::guid, Microsoft::Terminal::Control::ControlInteractivity> _recentlyDetachedContent{
winrt::multi_threaded_map<winrt::guid, Microsoft::Terminal::Control::ControlInteractivity>()
};
void _finalizeDetach(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e);
void _closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e);
};
}

View File

@@ -33,9 +33,6 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
static const int AnimationDurationInMilliseconds = 200;
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr };
Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) :
_control{ control },
_lastActive{ lastFocused },
@@ -83,7 +80,7 @@ Pane::Pane(std::shared_ptr<Pane> first,
// Use the unfocused border color as the pane background, so an actual color
// appears behind panes as we animate them sliding in.
_root.Background(s_unfocusedBorderBrush);
_root.Background(_themeResources.unfocusedBorderBrush);
_root.Children().Append(_borderFirst);
_root.Children().Append(_borderSecond);
@@ -111,10 +108,12 @@ Pane::Pane(std::shared_ptr<Pane> first,
// - Extract the terminal settings from the current (leaf) pane's control
// to be used to create an equivalent control
// Arguments:
// - <none>
// - asContent: when true, we're trying to serialize this pane for moving across
// windows. In that case, we'll need to fill in the content guid for our new
// terminal args.
// Return Value:
// - Arguments appropriate for a SplitPane or NewTab action
NewTerminalArgs Pane::GetTerminalArgsForPane() const
NewTerminalArgs Pane::GetTerminalArgsForPane(const bool asContent) const
{
// Leaves are the only things that have controls
assert(_IsLeaf());
@@ -159,6 +158,12 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
// object. That would work for schemes set by the Terminal, but not ones set
// by VT, but that seems good enough.
// Only fill in the ContentGuid if absolutely needed.
if (asContent)
{
args.ContentGuid(_control.ContentGuid());
}
return args;
}
@@ -170,36 +175,62 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
// Arguments:
// - currentId: the id to use for the current/first pane
// - nextId: the id to use for a new pane if we split
// - asContent: We're serializing this set of actions as content actions for
// moving to other windows, so we need to make sure to include ContentGuid's
// in the final actions.
// - asMovePane: only used with asContent. When this is true, we're building
// these actions as a part of moving the pane to another window, but without
// the context of the hosting tab. In that case, we'll want to build a
// splitPane action even if we're just a single leaf, because there's no other
// parent to try and build an action for us.
// Return Value:
// - The state from building the startup actions, includes a vector of commands,
// the original root pane, the id of the focused pane, and the number of panes
// created.
Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t nextId)
Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId,
uint32_t nextId,
const bool asContent,
const bool asMovePane)
{
// if we are a leaf then all there is to do is defer to the parent.
if (_IsLeaf())
// Normally, if we're a leaf, return an empt set of actions, because the
// parent pane will build the SplitPane action for us. If we're building
// actions for a movePane action though, we'll still need to include
// ourselves.
if (!asMovePane && _IsLeaf())
{
if (_lastActive)
{
return { {}, shared_from_this(), currentId, 0 };
// empty args, this is the first pane, currentId is
return { .args = {}, .firstPane = shared_from_this(), .focusedPaneId = currentId, .panesCreated = 0 };
}
return { {}, shared_from_this(), std::nullopt, 0 };
return { .args = {}, .firstPane = shared_from_this(), .focusedPaneId = std::nullopt, .panesCreated = 0 };
}
auto buildSplitPane = [&](auto newPane) {
ActionAndArgs actionAndArgs;
actionAndArgs.Action(ShortcutAction::SplitPane);
const auto terminalArgs{ newPane->GetTerminalArgsForPane() };
const auto terminalArgs{ newPane->GetTerminalArgsForPane(asContent) };
// When creating a pane the split size is the size of the new pane
// and not position.
const auto splitDirection = _splitState == SplitState::Horizontal ? SplitDirection::Down : SplitDirection::Right;
SplitPaneArgs args{ SplitType::Manual, splitDirection, 1. - _desiredSplitPosition, terminalArgs };
const auto splitSize = (asContent && _IsLeaf() ? .5 : 1. - _desiredSplitPosition);
SplitPaneArgs args{ SplitType::Manual, splitDirection, splitSize, terminalArgs };
actionAndArgs.Args(args);
return actionAndArgs;
};
if (asContent && _IsLeaf())
{
return {
.args = { buildSplitPane(shared_from_this()) },
.firstPane = shared_from_this(),
.focusedPaneId = currentId,
.panesCreated = 1
};
}
auto buildMoveFocus = [](auto direction) {
MoveFocusArgs args{ direction };
@@ -226,7 +257,12 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
focusedPaneId = nextId;
}
return { { actionAndArgs }, _firstChild, focusedPaneId, 1 };
return {
.args = { actionAndArgs },
.firstPane = _firstChild,
.focusedPaneId = focusedPaneId,
.panesCreated = 1
};
}
// We now need to execute the commands for each side of the tree
@@ -263,7 +299,12 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
// mutually exclusive.
const auto focusedPaneId = firstState.focusedPaneId.has_value() ? firstState.focusedPaneId : secondState.focusedPaneId;
return { actions, firstState.firstPane, focusedPaneId, firstState.panesCreated + secondState.panesCreated + 1 };
return {
.args = { actions },
.firstPane = firstState.firstPane,
.focusedPaneId = focusedPaneId,
.panesCreated = firstState.panesCreated + secondState.panesCreated + 1
};
}
// Method Description:
@@ -1396,8 +1437,8 @@ void Pane::UpdateVisuals()
{
_UpdateBorders();
}
_borderFirst.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush);
_borderSecond.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush);
_borderFirst.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
_borderSecond.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
}
// Method Description:
@@ -1849,7 +1890,7 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
Controls::Grid dummyGrid;
// GH#603 - we can safely add a BG here, as the control is gone right
// away, to fill the space as the rest of the pane expands.
dummyGrid.Background(s_unfocusedBorderBrush);
dummyGrid.Background(_themeResources.unfocusedBorderBrush);
// It should be the size of the closed pane.
dummyGrid.Width(removedOriginalSize.Width);
dummyGrid.Height(removedOriginalSize.Height);
@@ -2127,7 +2168,7 @@ void Pane::_SetupEntranceAnimation()
// * If we give the parent (us) root BG a color, then a transparent pane
// will flash opaque during the animation, then back to transparent, which
// looks bad.
_secondChild->_root.Background(s_unfocusedBorderBrush);
_secondChild->_root.Background(_themeResources.unfocusedBorderBrush);
const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast<float>(totalSize));
@@ -3092,51 +3133,20 @@ float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedV
return std::clamp(requestedValue, minSplitPosition, maxSplitPosition);
}
// Function Description:
// - Attempts to load some XAML resources that the Pane will need. This includes:
// * The Color we'll use for active Panes's borders - SystemAccentColor
// * The Brush we'll use for inactive Panes - TabViewBackground (to match the
// color of the titlebar)
// Arguments:
// - requestedTheme: this should be the currently active Theme for the app
// Return Value:
// - <none>
void Pane::SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
// Method Description:
// - Update our stored brushes for the current theme. This will also recursively
// update all our children.
// - TerminalPage creates these brushes and ultimately owns them. Effectively,
// we're just storing a smart pointer to the page's brushes.
void Pane::UpdateResources(const PaneResources& resources)
{
const auto res = Application::Current().Resources();
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
if (res.HasKey(accentColorKey))
{
const auto colorFromResources = ThemeLookup(res, requestedTheme, accentColorKey);
// If SystemAccentColor is _not_ a Color for some reason, use
// Transparent as the color, so we don't do this process again on
// the next pane (by leaving s_focusedBorderBrush nullptr)
auto actualColor = winrt::unbox_value_or<Color>(colorFromResources, Colors::Black());
s_focusedBorderBrush = SolidColorBrush(actualColor);
}
else
{
// DON'T use Transparent here - if it's "Transparent", then it won't
// be able to hittest for clicks, and then clicking on the border
// will eat focus.
s_focusedBorderBrush = SolidColorBrush{ Colors::Black() };
}
_themeResources = resources;
UpdateVisuals();
const auto unfocusedBorderBrushKey = winrt::box_value(L"UnfocusedBorderBrush");
if (res.HasKey(unfocusedBorderBrushKey))
if (!_IsLeaf())
{
// MAKE SURE TO USE ThemeLookup, so that we get the correct resource for
// the requestedTheme, not just the value from the resources (which
// might not respect the settings' requested theme)
auto obj = ThemeLookup(res, requestedTheme, unfocusedBorderBrushKey);
s_unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
}
else
{
// DON'T use Transparent here - if it's "Transparent", then it won't
// be able to hittest for clicks, and then clicking on the border
// will eat focus.
s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() };
_firstChild->UpdateResources(resources);
_secondChild->UpdateResources(resources);
}
}

View File

@@ -51,6 +51,12 @@ enum class SplitState : int
Vertical = 2
};
struct PaneResources
{
winrt::Windows::UI::Xaml::Media::SolidColorBrush focusedBorderBrush{ nullptr };
winrt::Windows::UI::Xaml::Media::SolidColorBrush unfocusedBorderBrush{ nullptr };
};
class Pane : public std::enable_shared_from_this<Pane>
{
public:
@@ -91,8 +97,8 @@ public:
std::optional<uint32_t> focusedPaneId;
uint32_t panesCreated;
};
BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId);
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const;
BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId, const bool asContent = false, const bool asMovePane = false);
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane(const bool asContent = false) const;
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
@@ -136,7 +142,7 @@ public:
bool ContainsReadOnly() const;
static void SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
void UpdateResources(const PaneResources& resources);
// Method Description:
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
@@ -217,8 +223,8 @@ private:
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush;
PaneResources _themeResources;
#pragma region Properties that need to be transferred between child / parent panes upon splitting / closing
std::shared_ptr<Pane> _firstChild{ nullptr };

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "SettingsLoadEventArgs.g.h"
#include <inc/cppwinrt_utils.h>
namespace winrt::TerminalApp::implementation
{
struct SettingsLoadEventArgs : SettingsLoadEventArgsT<SettingsLoadEventArgs>
{
WINRT_PROPERTY(bool, Reload, false);
WINRT_PROPERTY(uint64_t, Result, S_OK);
WINRT_PROPERTY(winrt::hstring, ExceptionText, L"");
WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings>, Warnings, nullptr);
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::CascadiaSettings, NewSettings, nullptr);
public:
SettingsLoadEventArgs(bool reload,
uint64_t result,
const winrt::hstring& exceptionText,
const winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings>& warnings,
const Microsoft::Terminal::Settings::Model::CascadiaSettings& newSettings) :
_Reload{ reload },
_Result{ result },
_ExceptionText{ exceptionText },
_Warnings{ warnings },
_NewSettings{ newSettings } {};
};
}

View File

@@ -50,7 +50,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - The list of actions.
std::vector<ActionAndArgs> SettingsTab::BuildStartupActions() const
std::vector<ActionAndArgs> SettingsTab::BuildStartupActions(const bool /*asContent*/) const
{
ActionAndArgs action;
action.Action(ShortcutAction::OpenSettings);

View File

@@ -30,7 +30,7 @@ namespace winrt::TerminalApp::implementation
void UpdateSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings);
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const override;
private:
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;

View File

@@ -264,6 +264,19 @@ namespace winrt::TerminalApp::implementation
tab->_RequestFocusActiveControlHandlers();
}
});
// BODGY: When the tab is drag/dropped, the TabView gets a
// TabDragStarting. However, the way it is implemented[^1], the
// TabViewItem needs either an Item or a Content for the event to
// include the correct TabViewItem. Otherwise, it will just return the
// first TabViewItem in the TabView with the same Content as the dragged
// tab (which, if the Content is null, will be the _first_ tab).
//
// So here, we'll stick an empty border in, just so that every tab has a
// Content which is not equal to the others.
//
// [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758
TabViewItem().Content(winrt::WUX::Controls::Border{});
}
std::optional<winrt::Windows::UI::Color> TabBase::GetTabColor()

View File

@@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
virtual std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const = 0;
virtual std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const = 0;
virtual std::optional<winrt::Windows::UI::Color> GetTabColor();
void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused,

View File

@@ -512,6 +512,11 @@ namespace winrt::TerminalApp::implementation
_mruTabs.RemoveAt(mruIndex);
}
if (_stashedDraggedTab && *_stashedDraggedTab == tab)
{
_stashedDraggedTab = nullptr;
}
_tabs.RemoveAt(tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
_UpdateTabIndices();
@@ -523,7 +528,7 @@ namespace winrt::TerminalApp::implementation
// if the user manually closed all tabs.
// Do this only if we are the last window; the monarch will notice
// we are missing and remove us that way otherwise.
if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1)
if (!_maintainStateOnTabClose && _settings.GlobalSettings().ShouldUsePersistedLayout() && _numOpenWindows == 1)
{
auto state = ApplicationState::SharedInstance();
state.PersistedWindowLayouts(nullptr);
@@ -709,7 +714,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TabBase TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept
{
uint32_t tabIndexFromControl{};
if (_tabView.TabItems().IndexOf(tabViewItem, tabIndexFromControl))
const auto items{ _tabView.TabItems() };
if (items.IndexOf(tabViewItem, tabIndexFromControl))
{
// If IndexOf returns true, we've actually got an index
return _tabs.GetAt(tabIndexFromControl);

View File

@@ -138,6 +138,15 @@
<ClInclude Include="AppLogic.h">
<DependentUpon>AppLogic.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ContentManager.h">
<DependentUpon>TerminalPage.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalWindow.h">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SettingsLoadEventArgs.h">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
@@ -231,6 +240,12 @@
<ClCompile Include="AppLogic.cpp">
<DependentUpon>AppLogic.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ContentManager.cpp">
<DependentUpon>TerminalPage.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalWindow.cpp">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
</ItemGroup>
@@ -252,6 +267,7 @@
<Midl Include="ShortcutActionDispatch.idl" />
<Midl Include="AppKeyBindings.idl" />
<Midl Include="AppLogic.idl" />
<Midl Include="TerminalWindow.idl" />
<Midl Include="MinMaxCloseControl.idl">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
<SubType>Code</SubType>

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "RequestReceiveContentArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
@@ -50,24 +52,48 @@ namespace winrt::TerminalApp::implementation
_ProposedName{ name } {};
};
struct RequestMoveContentArgs : RequestMoveContentArgsT<RequestMoveContentArgs>
{
WINRT_PROPERTY(winrt::hstring, Window);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(uint32_t, TabIndex);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Point>, WindowPosition);
public:
RequestMoveContentArgs(const winrt::hstring window, const winrt::hstring content, uint32_t tabIndex) :
_Window{ window },
_Content{ content },
_TabIndex{ tabIndex } {};
};
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
{
WINRT_PROPERTY(uint64_t, SourceWindow);
WINRT_PROPERTY(uint64_t, TargetWindow);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
_SourceWindow{ src },
_TargetWindow{ tgt },
_TabIndex{ tabIndex } {};
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
TerminalPage();
TerminalPage(const TerminalApp::ContentManager& manager);
// This implements shobjidl's IInitializeWithWindow, but due to a XAML Compiler bug we cannot
// put it in our inheritance graph. https://github.com/microsoft/microsoft-ui-xaml/issues/3331
STDMETHODIMP Initialize(HWND hwnd);
void SetSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings, bool needRefreshUI);
winrt::fire_and_forget SetSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings, bool needRefreshUI);
void Create();
bool ShouldUsePersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
bool ShouldImmediatelyHandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
std::optional<uint32_t> LoadPersistedLayoutIdx(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
@@ -117,20 +143,14 @@ namespace winrt::TerminalApp::implementation
const bool initial,
const winrt::hstring cwd = L"");
// Normally, WindowName and WindowId would be
// WINRT_OBSERVABLE_PROPERTY's, but we want them to raise
// WindowNameForDisplay and WindowIdForDisplay instead
winrt::hstring WindowName() const noexcept;
winrt::fire_and_forget WindowName(const winrt::hstring& value);
uint64_t WindowId() const noexcept;
void WindowId(const uint64_t& value);
// For the sake of XAML binding:
winrt::hstring WindowName() const noexcept { return _WindowProperties.WindowName(); };
uint64_t WindowId() const noexcept { return _WindowProperties.WindowId(); };
winrt::hstring WindowIdForDisplay() const noexcept { return _WindowProperties.WindowIdForDisplay(); };
winrt::hstring WindowNameForDisplay() const noexcept { return _WindowProperties.WindowNameForDisplay(); };
void SetNumberOfOpenWindows(const uint64_t value);
void SetPersistedLayoutIdx(const uint32_t value);
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
bool IsElevated() const noexcept;
void OpenSettingsUI();
@@ -138,6 +158,13 @@ namespace winrt::TerminalApp::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
TerminalApp::IWindowProperties WindowProperties();
void WindowProperties(const TerminalApp::IWindowProperties& props);
winrt::fire_and_forget WindowNameChanged();
winrt::fire_and_forget AttachContent(winrt::hstring content, uint32_t tabIndex);
winrt::fire_and_forget SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
@@ -153,13 +180,16 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable);
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable);
TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable);
TYPED_EVENT(CloseRequested, IInspectable, IInspectable);
TYPED_EVENT(OpenSystemMenu, IInspectable, IInspectable);
TYPED_EVENT(QuitRequested, IInspectable, IInspectable);
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs);
TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
private:
@@ -192,8 +222,7 @@ namespace winrt::TerminalApp::implementation
bool _isFullscreen{ false };
bool _isMaximized{ false };
bool _isAlwaysOnTop{ false };
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
std::optional<uint32_t> _loadFromPersistedLayoutIdx{};
uint64_t _numOpenWindows{ 0 };
@@ -230,7 +259,17 @@ namespace winrt::TerminalApp::implementation
int _renamerLayoutCount{ 0 };
bool _renamerPressedEnter{ false };
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
TerminalApp::IWindowProperties _WindowProperties{ nullptr };
PaneResources _paneResources;
TerminalApp::ContentManager _manager{ nullptr };
winrt::com_ptr<winrt::TerminalApp::implementation::TabBase> _stashedDraggedTab{ nullptr };
til::point _dragOffset{ 0, 0 };
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult>
_ShowDialogHelper(const std::wstring_view& name);
void _ShowAboutDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowQuitDialog();
@@ -273,10 +312,6 @@ namespace winrt::TerminalApp::implementation
void _UpdateCommandsForPalette();
void _SetBackgroundImage(const winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig& newAppearance);
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> _ExpandCommands(Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> commandsToExpand,
Windows::Foundation::Collections::IVectorView<Microsoft::Terminal::Settings::Model::Profile> profiles,
Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::ColorScheme> schemes);
void _DuplicateFocusedTab();
void _DuplicateTab(const TerminalTab& tab);
@@ -301,7 +336,8 @@ namespace winrt::TerminalApp::implementation
bool _SelectTab(uint32_t tabIndex);
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
bool _MoveTab(const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
template<typename F>
bool _ApplyToActiveControls(F f)
@@ -392,6 +428,8 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Control::TermControl& term);
winrt::Microsoft::Terminal::Control::TermControl _InitControlFromContent(const winrt::guid& contentGuid);
std::shared_ptr<Pane> _MakePane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr,
const winrt::TerminalApp::TabBase& sourceTab = nullptr,
@@ -459,9 +497,22 @@ namespace winrt::TerminalApp::implementation
void _updateThemeColors();
void _updateTabCloseButton(const winrt::Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
void _onTabDragStarting(winrt::Microsoft::UI::Xaml::Controls::TabView sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs e);
void _onTabStripDragOver(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e);
winrt::fire_and_forget _onTabStripDrop(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e);
winrt::fire_and_forget _onTabDroppedOutside(winrt::Windows::Foundation::IInspectable sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDroppedOutsideEventArgs e);
void _DetachPaneFromWindow(std::shared_ptr<Pane> pane);
void _DetachTabFromWindow(const winrt::com_ptr<TabBase>& terminalTab);
void _MoveContent(std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions,
const winrt::hstring& windowName,
const uint32_t tabIndex,
Windows::Foundation::IReference<Windows::Foundation::Point> dragPoint = nullptr);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
@@ -477,4 +528,5 @@ namespace winrt::TerminalApp::implementation
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalPage);
BASIC_FACTORY(RequestReceiveContentArgs);
}

View File

@@ -5,21 +5,56 @@ import "IDirectKeyListener.idl";
namespace TerminalApp
{
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
[default_interface] runtimeclass ContentManager
{
Microsoft.Terminal.Control.ControlInteractivity CreateCore(Microsoft.Terminal.Control.IControlSettings settings,
Microsoft.Terminal.Control.IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
Microsoft.Terminal.Control.ControlInteractivity LookupCore(Guid id);
void Detach(Microsoft.Terminal.Control.TermControl control);
}
delegate void LastTabClosedEventArgs();
[default_interface] runtimeclass RenameWindowRequestedArgs
{
String ProposedName { get; };
};
[default_interface] runtimeclass RequestMoveContentArgs
{
String Window { get; };
String Content { get; };
UInt32 TabIndex { get; };
Windows.Foundation.IReference<Windows.Foundation.Point> WindowPosition { get; };
};
[default_interface] runtimeclass RequestReceiveContentArgs {
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
UInt64 SourceWindow { get; };
UInt64 TargetWindow { get; };
UInt32 TabIndex { get; };
};
interface IDialogPresenter
{
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
};
interface IWindowProperties
{
String WindowName { get; };
UInt64 WindowId { get; };
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
Boolean IsQuakeWindow();
};
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
{
TerminalPage();
TerminalPage(ContentManager manager);
// XAML bound properties
String ApplicationDisplayName { get; };
@@ -29,13 +64,9 @@ namespace TerminalApp
Boolean Fullscreen { get; };
Boolean AlwaysOnTop { get; };
IWindowProperties WindowProperties { get; };
void IdentifyWindow();
String WindowName;
UInt64 WindowId;
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
void RenameFailed();
Boolean IsQuakeWindow();
// We cannot use the default XAML APIs because we want to make sure
// that there's only one application-global dialog visible at a time,
@@ -45,9 +76,16 @@ namespace TerminalApp
String KeyboardServiceDisabledText { get; };
TaskbarState TaskbarState{ get; };
void AttachContent(String content, UInt32 tabIndex);
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
void SendContentToOther(RequestReceiveContentArgs args);
String WindowName { get; };
UInt64 WindowId { get; };
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
@@ -59,10 +97,13 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
}
}

View File

@@ -438,16 +438,16 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - A vector of commands
std::vector<ActionAndArgs> TerminalTab::BuildStartupActions() const
std::vector<ActionAndArgs> TerminalTab::BuildStartupActions(const bool asContent) const
{
// Give initial ids (0 for the child created with this tab,
// 1 for the child after the first split.
auto state = _rootPane->BuildStartupActions(0, 1);
auto state = _rootPane->BuildStartupActions(0, 1, asContent);
{
ActionAndArgs newTabAction{};
newTabAction.Action(ShortcutAction::NewTab);
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() };
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane(asContent) };
newTabAction.Args(newTabArgs);
state.args.emplace(state.args.begin(), std::move(newTabAction));
@@ -783,6 +783,8 @@ namespace winrt::TerminalApp::implementation
bool TerminalTab::FocusPane(const uint32_t id)
{
if (_rootPane == nullptr)
return false;
_changingActivePane = true;
const auto res = _rootPane->FocusPane(id);
_changingActivePane = false;

View File

@@ -82,7 +82,7 @@ namespace winrt::TerminalApp::implementation
void EnterZoom();
void ExitZoom();
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const override;
int GetLeafPaneCount() const noexcept;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "TerminalWindow.g.h"
#include "SystemMenuChangeArgs.g.h"
#include "SettingsLoadEventArgs.h"
#include "TerminalPage.h"
#include "SettingsLoadEventArgs.h"
#include <inc/cppwinrt_utils.h>
#include <ThrottledFunc.h>
#ifdef UNIT_TESTING
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class CommandlineTest;
};
#endif
namespace winrt::TerminalApp::implementation
{
struct SystemMenuChangeArgs : SystemMenuChangeArgsT<SystemMenuChangeArgs>
{
WINRT_PROPERTY(winrt::hstring, Name, L"");
WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add);
WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr);
public:
SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) :
_Name{ name }, _Action{ action }, _Handler{ handler } {};
};
struct TerminalWindow : TerminalWindowT<TerminalWindow, IInitializeWithWindow>
{
public:
TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult, const TerminalApp::ContentManager& manager);
~TerminalWindow() = default;
STDMETHODIMP Initialize(HWND hwnd);
void Create();
bool IsUwp() const noexcept;
bool IsElevated() const noexcept;
void Quit();
winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
bool HasCommandlineArguments() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
void SetStartupContent(winrt::hstring content, Windows::Foundation::IReference<Windows::Foundation::Rect> contentBounds);
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
winrt::hstring ParseCommandlineMessage();
bool ShouldExitEarly();
bool ShouldImmediatelyHandoffToElevated();
void HandoffToElevated();
bool FocusMode() const;
bool Fullscreen() const;
void Maximized(bool newMaximized);
bool AlwaysOnTop() const;
bool AutoHideWindow();
hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void IdentifyWindow();
void RenameFailed();
std::optional<uint32_t> LoadPersistedLayoutIdx() const;
winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout() const;
void SetPersistedLayoutIdx(const uint32_t idx);
void SetNumberOfOpenWindows(const uint64_t num);
bool ShouldUsePersistedLayout() const;
void RequestExitFullscreen();
Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
bool CenterOnLaunch();
TerminalApp::InitialPosition GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
Microsoft::Terminal::Settings::Model::LaunchMode GetLaunchMode();
bool GetShowTabsInTitlebar();
bool GetInitialAlwaysOnTop();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
Windows::UI::Xaml::UIElement GetRoot() noexcept;
hstring Title();
void TitlebarClicked();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void WindowVisibilityChanged(const bool showOrHide);
winrt::TerminalApp::TaskbarState TaskbarState();
winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush();
void WindowActivated(const bool activated);
bool GetMinimizeToNotificationArea();
bool GetAlwaysShowNotificationIcon();
bool RequestsTrayIcon();
bool GetShowTitleInTitlebar();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
void DismissDialog();
Microsoft::Terminal::Settings::Model::Theme Theme();
void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::SettingsLoadEventArgs& arg);
// Normally, WindowName and WindowId would be
// WINRT_OBSERVABLE_PROPERTY's, but we want them to raise
// WindowNameForDisplay and WindowIdForDisplay instead
winrt::hstring WindowName() const noexcept;
void WindowName(const winrt::hstring& value);
uint64_t WindowId() const noexcept;
void WindowId(const uint64_t& value);
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
void AttachContent(winrt::hstring content, uint32_t tabIndex);
void SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
// -------------------------------- WinRT Events ---------------------------------
// PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
// Usually we'd just do
// WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
//
// But what we're doing here is exposing the Page's PropertyChanged _as
// our own event_. It's a FORWARDED_CALLBACK, essentially.
winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return _root->PropertyChanged(handler); }
void PropertyChanged(winrt::event_token const& token) { _root->PropertyChanged(token); }
TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Settings::Model::Theme);
private:
// If you add controls here, but forget to null them either here or in
// the ctor, you're going to have a bad time. It'll mysteriously fail to
// activate the AppLogic.
// ALSO: If you add any UIElements as roots here, make sure they're
// updated in _ApplyTheme. The root currently is _root.
winrt::com_ptr<TerminalPage> _root{ nullptr };
winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog{ nullptr };
std::shared_mutex _dialogLock;
bool _hasCommandLineArguments{ false };
::TerminalApp::AppCommandlineArgs _appArgs;
bool _gotSettingsStartupActions{ false };
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _settingsStartupArgs{};
Windows::Foundation::IReference<Windows::Foundation::Rect> _contentBounds{ nullptr };
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
uint64_t _numOpenWindows{ 1 };
std::optional<uint32_t> _loadFromPersistedLayoutIdx{};
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
TerminalApp::SettingsLoadEventArgs _initialLoadResult{ nullptr };
TerminalApp::ContentManager _manager{ nullptr };
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _initialContentArgs;
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey,
const winrt::hstring& contentKey,
HRESULT settingsLoadedResult,
const winrt::hstring& exceptionText);
void _ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings>& warnings);
bool _IsKeyboardServiceEnabled();
void _RefreshThemeRoutine();
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _OpenSettingsUI();
// These are events that are handled by the TerminalPage, but are
// exposed through the AppLogic. This macro is used to forward the event
// directly to them.
FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent);
FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged);
FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed);
FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged);
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);
FORWARDED_TYPED_EVENT(ChangeMaximizeRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, ChangeMaximizeRequested);
FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged);
FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell);
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested);
FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested);
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested);
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);
FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged);
TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable);
TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
FORWARDED_TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs, _root, RequestMoveContent);
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;
#endif
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalWindow);
}

View File

@@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "TerminalPage.idl";
import "ShortcutActionDispatch.idl";
import "IDirectKeyListener.idl";
namespace TerminalApp
{
struct InitialPosition
{
Int64 X;
Int64 Y;
};
delegate void SystemMenuItemHandler();
enum SystemMenuChangeAction
{
Add = 0,
Remove = 1
};
[default_interface] runtimeclass SystemMenuChangeArgs {
String Name { get; };
SystemMenuChangeAction Action { get; };
SystemMenuItemHandler Handler { get; };
};
[default_interface] runtimeclass SettingsLoadEventArgs
{
Boolean Reload { get; };
UInt64 Result { get; };
IVector<Microsoft.Terminal.Settings.Model.SettingsLoadWarnings> Warnings { get; };
String ExceptionText { get; };
Microsoft.Terminal.Settings.Model.CascadiaSettings NewSettings { get; };
};
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
[default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, IWindowProperties, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TerminalWindow(SettingsLoadEventArgs result, ContentManager manager);
// For your own sanity, it's better to do setup outside the ctor.
// If you do any setup in the ctor that ends up throwing an exception,
// then it might look like TermApp just failed to activate, which will
// cause you to chase down the rabbit hole of "why is TermApp not
// registered?" when it definitely is.
void Create();
Boolean IsElevated();
Boolean HasCommandlineArguments();
Int32 SetStartupCommandline(String[] commands);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
Int32 ExecuteCommandline(String[] commands, String cwd);
String ParseCommandlineMessage { get; };
Boolean ShouldExitEarly { get; };
Boolean ShouldImmediatelyHandoffToElevated();
void HandoffToElevated();
void Quit();
Windows.UI.Xaml.UIElement GetRoot();
String Title { get; };
Boolean FocusMode { get; };
Boolean Fullscreen { get; };
void Maximized(Boolean newMaximized);
Boolean AlwaysOnTop { get; };
Boolean AutoHideWindow { get; };
void IdentifyWindow();
void SetPersistedLayoutIdx(UInt32 idx);
void SetNumberOfOpenWindows(UInt64 num);
void RenameFailed();
void RequestExitFullscreen();
Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
Boolean CenterOnLaunch { get; };
InitialPosition GetInitialPosition(Int64 defaultInitialX, Int64 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
Microsoft.Terminal.Settings.Model.LaunchMode GetLaunchMode();
Boolean GetShowTabsInTitlebar();
Boolean GetInitialAlwaysOnTop();
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
void TitlebarClicked();
void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position);
void WindowVisibilityChanged(Boolean showOrHide);
TaskbarState TaskbarState{ get; };
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position);
Boolean GetMinimizeToNotificationArea();
Boolean GetAlwaysShowNotificationIcon();
Boolean RequestsTrayIcon();
Boolean GetShowTitleInTitlebar();
// These already have accessors as a part of IWindowProperties, but we
// also want to be able to set them.
String WindowName { set; };
UInt64 WindowId { set; };
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
void DismissDialog();
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Theme> RequestedThemeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ChangeMaximizeRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RaiseVisualBell;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.SystemMenuChangeArgs> SystemMenuChangeRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
void AttachContent(String content, UInt32 tabIndex);
void SendContentToOther(RequestReceiveContentArgs args);
}
}

View File

@@ -154,7 +154,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
}
_setupDispatcherAndCallbacks();
UpdateSettings(settings, unfocusedAppearance);
}
void ControlCore::_setupDispatcherAndCallbacks()
{
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
// us the same dispatcher as TermControl::Dispatcher(). If we're out of
// proc, this'll return null. We'll need to instead make a new
@@ -211,8 +217,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
core->_ScrollPositionChangedHandlers(*core, update);
}
});
UpdateSettings(settings, unfocusedAppearance);
}
ControlCore::~ControlCore()
@@ -225,6 +229,30 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::Detach()
{
// Disable the renderer, so that it doesn't try to start any new frames
// for our engines while we're not attached to anything.
_renderer->WaitForPaintCompletionAndDisable(INFINITE);
_tsfTryRedrawCanvas.reset();
_updatePatternLocations.reset();
_updateScrollBar.reset();
}
void ControlCore::Reparent(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_settings->KeyBindings(keyBindings);
_setupDispatcherAndCallbacks();
const auto actualNewSize = _actualFont.GetSize();
// Bubble this up, so our new control knows how big we want the font.
_FontSizeChangedHandlers(actualNewSize.width, actualNewSize.height, true);
// Turn the rendering back on now that we're ready to go.
_renderer->EnablePainting();
_AttachedHandlers(*this, nullptr);
}
bool ControlCore::Initialize(const double actualWidth,
const double actualHeight,
const double compositionScale)
@@ -574,7 +602,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// itself - it was initiated by the mouse wheel, or the scrollbar.
_terminal->UserScrollViewport(viewTop);
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
void ControlCore::AdjustOpacity(const double adjustment)
@@ -971,18 +1002,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::SizeChanged(const double width,
const double height)
{
// _refreshSizeUnderLock redraws the entire terminal.
// Don't call it if we don't have to.
if (_panelWidth == width && _panelHeight == height)
{
return;
}
_panelWidth = width;
_panelHeight = height;
auto lock = _terminal->LockForWriting();
_refreshSizeUnderLock();
SizeOrScaleChanged(width, height, _compositionScale);
}
void ControlCore::ScaleChanged(const double scale)
@@ -991,19 +1011,31 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return;
}
SizeOrScaleChanged(_panelWidth, _panelHeight, scale);
}
void ControlCore::SizeOrScaleChanged(const double width,
const double height,
const double scale)
{
// _refreshSizeUnderLock redraws the entire terminal.
// Don't call it if we don't have to.
if (_compositionScale == scale)
if (_panelWidth == width && _panelHeight == height && _compositionScale == scale)
{
return;
}
const auto oldScale = _compositionScale;
_panelWidth = width;
_panelHeight = height;
_compositionScale = scale;
auto lock = _terminal->LockForWriting();
// _updateFont relies on the new _compositionScale set above
_updateFont();
if (oldScale != scale)
{
// _updateFont relies on the new _compositionScale set above
_updateFont();
}
_refreshSizeUnderLock();
}
@@ -1364,7 +1396,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests)
if (!_inUnitTests && _updateScrollBar)
{
_updateScrollBar->Run(update);
}
@@ -1374,14 +1406,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
// Additionally, start the throttled update of where our links are.
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
void ControlCore::_terminalCursorPositionChanged()
{
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
_tsfTryRedrawCanvas->Run();
if (_tsfTryRedrawCanvas)
{
_tsfTryRedrawCanvas->Run();
}
}
void ControlCore::_terminalTaskbarProgressChanged()
@@ -1409,7 +1448,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// The UI thread might try to acquire the console lock from time to time.
// --> Unlock it, so the UI doesn't hang while we're busy.
const auto suspension = _terminal->SuspendLock();
// This call will block for the duration, unless shutdown early.
_midiAudio.PlayNote(reinterpret_cast<HWND>(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
}
@@ -1660,6 +1698,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// _renderer will always exist since it's introduced in the ctor
_renderer->AddRenderEngine(pEngine);
}
void ControlCore::DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
{
_renderer->RemoveRenderEngine(pEngine);
}
bool ControlCore::IsInReadOnlyMode() const
{
@@ -1683,7 +1725,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->Write(hstr);
// Start the throttled update of where our hyperlinks are.
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
catch (...)
{
@@ -1692,6 +1737,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
uint64_t ControlCore::SwapChainHandle() const
{
// This is called by:
// * TermControl::RenderEngineSwapChainChanged, who is only registered
// after Core::Initialize() is called.
// * TermControl::_InitializeTerminal, after the call to Initialize, for
// _AttachDxgiSwapChainToXaml.
// In both cases, we'll have a _renderEngine by then.
return _renderEngine ? reinterpret_cast<uint64_t>(_renderEngine->GetSwapChainHandle()) : 0u;
}
// Method Description:
// - Clear the contents of the buffer. The region cleared is given by
// clearType:

View File

@@ -63,6 +63,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const double compositionScale);
void EnablePainting();
void Detach();
void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance);
void ApplyAppearance(const bool& focused);
Control::IControlSettings Settings() { return *_settings; };
@@ -73,8 +75,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme);
uint64_t SwapChainHandle() const;
void Reparent(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
void SizeOrScaleChanged(const double width, const double height, const double scale);
void AdjustFontSize(float fontSizeDelta);
void ResetFontSize();
@@ -190,6 +196,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool& selectionNeedsToBeCopied);
void AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
void DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
bool IsInReadOnlyMode() const;
void ToggleReadOnlyMode();
@@ -233,6 +240,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
TYPED_EVENT(Attached, IInspectable, IInspectable);
// clang-format on
private:
@@ -285,6 +294,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
void _setupDispatcherAndCallbacks();
bool _setFontSizeUnderLock(float fontSize);
void _updateFont(const bool initialUpdate = false);
void _refreshSizeUnderLock();

View File

@@ -76,6 +76,8 @@ namespace Microsoft.Terminal.Control
IControlAppearance UnfocusedAppearance { get; };
Boolean HasUnfocusedAppearance();
UInt64 SwapChainHandle { get; };
Windows.Foundation.Size FontSize { get; };
String FontFaceName { get; };
UInt16 FontWeight { get; };
@@ -108,6 +110,7 @@ namespace Microsoft.Terminal.Control
void AdjustFontSize(Single fontSizeDelta);
void SizeChanged(Double width, Double height);
void ScaleChanged(Double scale);
void SizeOrScaleChanged(Double width, Double height, Double scale);
void ToggleShaderEffects();
void ToggleReadOnlyMode();
@@ -126,7 +129,6 @@ namespace Microsoft.Terminal.Control
String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
void Close();
void BlinkCursor();
Boolean IsInReadOnlyMode { get; };
Boolean CursorOn;
@@ -162,5 +164,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
};
}

View File

@@ -44,7 +44,48 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_lastMouseClickPos{},
_selectionNeedsToBeCopied{ false }
{
_guid = ::Microsoft::Console::Utils::CreateGuid();
_core = winrt::make_self<ControlCore>(settings, unfocusedAppearance, connection);
_core->Attached([weakThis = get_weak()](auto&&, auto&&) {
if (auto self{ weakThis.get() })
{
self->_AttachedHandlers(*self, nullptr);
}
});
}
winrt::guid ControlInteractivity::Id()
{
return _guid;
}
void ControlInteractivity::Detach()
{
if (_uiaEngine)
{
// There's a potential race here where we've removed the TermControl
// from the UI tree, but the UIA engine is in the middle of a paint,
// and the UIA engine will try to dispatch to the
// TermControlAutomationPeer, which (is now)/(will very soon be) gone.
//
// To alleviate, make sure to disable the UIA engine and remove it,
// and ALSO disable the renderer. Core.Detach will take care of the
// WaitForPaintCompletionAndDisable (which will stop the renderer
// after all current engines are done painting).
//
// Simply disabling the UIA engine is not enough, because it's
// possible that it had already started presenting here.
LOG_IF_FAILED(_uiaEngine->Disable());
_core->DetachUiaEngine(_uiaEngine.get());
}
_core->Detach();
}
void ControlInteractivity::Reparent(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_core->Reparent(keyBindings);
}
// Method Description:
@@ -73,6 +114,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return *_core;
}
void ControlInteractivity::Close()
{
_ClosedHandlers(*this, nullptr);
if (_core)
{
_core->Close();
}
}
// Method Description:
// - Returns the number of clicks that occurred (double and triple click support).
// Every call to this function registers a click.
@@ -649,7 +699,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
try
{
const auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
if (_uiaEngine)
{
_core->DetachUiaEngine(_uiaEngine.get());
}
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
_core->AttachUiaEngine(_uiaEngine.get());
return *autoPeer;

View File

@@ -44,6 +44,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void Initialize();
Control::ControlCore Core();
void Close();
void Detach();
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Render::IRenderData* GetRenderData() const;
@@ -85,10 +88,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SetEndSelectionPoint(const Core::Point pixelPosition);
bool ManglePathsForWsl();
winrt::guid Id();
void Reparent(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
TYPED_EVENT(Attached, IInspectable, IInspectable);
TYPED_EVENT(Closed, IInspectable, IInspectable);
private:
// NOTE: _uiaEngine must be ordered before _core.
//
@@ -129,6 +138,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval{ std::nullopt };
winrt::guid _guid;
unsigned int _numberOfClicks(Core::Point clickPos, Timestamp clickTime);
void _updateSystemParameterSettings() noexcept;

View File

@@ -23,6 +23,12 @@ namespace Microsoft.Terminal.Control
void GotFocus();
void LostFocus();
Guid Id { get; };
void Reparent(Microsoft.Terminal.Control.IKeyBindings keyBindings);
void Detach();
void Close();
InteractivityAutomationPeer OnCreateAutomationPeer();
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
@@ -65,6 +71,8 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> Closed;
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
};
}

View File

@@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TermControl::TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection) :
TermControl{ winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, connection) }
{
}
TermControl::TermControl(Control::ControlInteractivity content) :
_interactivity{ content },
_isInternalScrollBarUpdate{ false },
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
@@ -61,32 +67,35 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
InitializeComponent();
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, connection);
_core = _interactivity.Core();
// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
_core.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
_core.WarningBell({ this, &TermControl::_coreWarningBell });
_core.CursorPositionChanged({ this, &TermControl::_CursorPositionChanged });
// This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here.
_core.RendererEnteredErrorState({ get_weak(), &TermControl::_RendererEnteredErrorState });
_revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState });
// These callbacks can only really be triggered by UI interactions. So
// they don't need weak refs - they can't be triggered unless we're
// alive.
_core.BackgroundColorChanged({ this, &TermControl::_coreBackgroundColorChanged });
_core.FontSizeChanged({ this, &TermControl::_coreFontSizeChanged });
_core.TransparencyChanged({ this, &TermControl::_coreTransparencyChanged });
_core.RaiseNotice({ this, &TermControl::_coreRaisedNotice });
_core.HoveredHyperlinkChanged({ this, &TermControl::_hoveredHyperlinkChanged });
_core.FoundMatch({ this, &TermControl::_coreFoundMatch });
_core.UpdateSelectionMarkers({ this, &TermControl::_updateSelectionMarkers });
_core.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
_revokers.BackgroundColorChanged = _core.BackgroundColorChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreBackgroundColorChanged });
_revokers.FontSizeChanged = _core.FontSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreFontSizeChanged });
_revokers.TransparencyChanged = _core.TransparencyChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreTransparencyChanged });
_revokers.RaiseNotice = _core.RaiseNotice(winrt::auto_revoke, { get_weak(), &TermControl::_coreRaisedNotice });
_revokers.HoveredHyperlinkChanged = _core.HoveredHyperlinkChanged(winrt::auto_revoke, { get_weak(), &TermControl::_hoveredHyperlinkChanged });
_revokers.FoundMatch = _core.FoundMatch(winrt::auto_revoke, { get_weak(), &TermControl::_coreFoundMatch });
_revokers.UpdateSelectionMarkers = _core.UpdateSelectionMarkers(winrt::auto_revoke, { get_weak(), &TermControl::_updateSelectionMarkers });
_revokers.coreOpenHyperlink = _core.OpenHyperlink(winrt::auto_revoke, { get_weak(), &TermControl::_HyperlinkHandler });
_revokers.interactivityOpenHyperlink = _interactivity.OpenHyperlink(winrt::auto_revoke, { get_weak(), &TermControl::_HyperlinkHandler });
_revokers.interactivityScrollPositionChanged = _interactivity.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged });
// "Bubbled" events - ones we want to handle, by raising our own event.
_revokers.CopyToClipboard = _core.CopyToClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCopyToClipboard });
_revokers.TitleChanged = _core.TitleChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleTitleChanged });
_revokers.TabColorChanged = _core.TabColorChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleTabColorChanged });
_revokers.TaskbarProgressChanged = _core.TaskbarProgressChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSetTaskbarProgress });
_revokers.ConnectionStateChanged = _core.ConnectionStateChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleConnectionStateChanged });
_revokers.ShowWindowChanged = _core.ShowWindowChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleShowWindowChanged });
_revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested });
_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
@@ -95,8 +104,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
// Any earlier than the last layout update and we may not know the terminal's starting size.
if (_InitializeTerminal())
if (_InitializeTerminal(false))
{
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
@@ -130,13 +138,52 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
//
// NOTE: _ScrollPositionChanged has to be registered after we set up the
// _updateScrollBar func. Otherwise, we could get a callback from an
// attached content before we set up the throttled func, and that'll A/V
_revokers.coreScrollPositionChanged = _core.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged });
_revokers.WarningBell = _core.WarningBell(winrt::auto_revoke, { get_weak(), &TermControl::_coreWarningBell });
_revokers.CursorPositionChanged = _core.CursorPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_CursorPositionChanged });
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
_ApplyUISettings();
}
Control::TermControl TermControl::AttachContent(Control::ControlInteractivity content, const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
const auto term{ winrt::make_self<TermControl>(content) };
term->_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(term->_core.SwapChainHandle()));
content.Reparent(keyBindings);
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
auto r = term->SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [term](auto /*s*/, auto /*e*/) {
// Replace the normal initialize routine with one that will allow up
// to complete initialization even though the Core was already
// initialized.
if (term->_InitializeTerminal(true))
{
// Only let this succeed once.
term->_layoutUpdatedRevoker.revoke();
}
});
term->_layoutUpdatedRevoker.swap(r);
return *term;
}
winrt::guid TermControl::ContentGuid() const
{
return _interactivity.Id();
}
void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update)
{
// Assumptions:
@@ -808,7 +855,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
nativePanel->SetSwapChainHandle(swapChainHandle);
}
bool TermControl::_InitializeTerminal()
bool TermControl::_InitializeTerminal(const bool reattach)
{
if (_initializedTerminal)
{
@@ -832,18 +879,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// after Enable, then it'll be possible to paint the frame once
// _before_ the warning handler is set up, and then warnings from
// the first paint will be ignored!
_core.RendererWarning({ get_weak(), &TermControl::_RendererWarning });
_revokers.RendererWarning = _core.RendererWarning(winrt::auto_revoke, { get_weak(), &TermControl::_RendererWarning });
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
panelScaleX);
if (!coreInitialized)
// If we're re-attaching an existing content, then we want to proceed even though the Terminal was already initialized.
if (!reattach)
{
return false;
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
panelScaleX);
if (!coreInitialized)
{
return false;
}
_interactivity.Initialize();
}
else
{
_core.SizeOrScaleChanged(panelWidth, panelHeight, panelScaleX);
}
_interactivity.Initialize();
_core.SwapChainChanged({ get_weak(), &TermControl::RenderEngineSwapChainChanged });
_revokers.SwapChainChanged = _core.SwapChainChanged(winrt::auto_revoke, { get_weak(), &TermControl::RenderEngineSwapChainChanged });
_core.EnablePainting();
auto bufferHeight = _core.BufferHeight();
@@ -1144,12 +1199,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return true;
}
// TODO: GH#5000
// The Core owning the keybindings is weird. That's for sure. In the
// future, we may want to pass the keybindings into the control
// separately, so the control can have a pointer to an in-proc
// Keybindings object, rather than routing through the ControlCore.
// (see GH#5000)
auto bindings = _core.Settings().KeyBindings();
if (!bindings)
{
@@ -1998,13 +2047,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_RestorePointerCursorHandlers(*this, nullptr);
_revokers = {};
// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();
_core.Close();
if (!_detached)
{
_interactivity.Close();
}
}
}
void TermControl::Detach()
{
_revokers = {};
_interactivity.Detach();
_detached = true;
}
// Method Description:
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly
@@ -2741,18 +2801,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
IControlSettings TermControl::Settings() const
{
// TODO: GH#5000
// We still need this in a couple places:
// - Pane.cpp uses this for parsing out the StartingTitle, Commandline,
// etc for Pane::GetTerminalArgsForPane.
// - TerminalTab::_CreateToolTipTitle uses the ProfileName for the
// tooltip for the tab.
//
// These both happen on the UI thread right now. In the future, when we
// have to hop across the process boundary to get at the core settings,
// it may make sense to cache these values inside the TermControl
// itself, so it can do the hop once when it's first setup, rather than
// when it's needed by the UI thread.
return _core.Settings();
}
@@ -2939,9 +2987,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Ensure the marker is oriented properly
// (i.e. if start is at the beginning of the buffer, it should be flipped)
auto transform{ marker.RenderTransform().as<Windows::UI::Xaml::Media::ScaleTransform>() };
transform.ScaleX(std::abs(transform.ScaleX()) * (flipMarker ? -1.0 : 1.0));
marker.RenderTransform(transform);
//
// Note - This RenderTransform might not be a
// ScaleTransform, if we haven't had a _coreFontSizeChanged
// handled yet, because that's the first place we set the
// RenderTransform
if (const auto& transform{ marker.RenderTransform().try_as<Windows::UI::Xaml::Media::ScaleTransform>() })
{
transform.ScaleX(std::abs(transform.ScaleX()) * (flipMarker ? -1.0 : 1.0));
marker.RenderTransform(transform);
}
// Compute the location of the top left corner of the cell in DIPS
auto terminalPos{ targetEnd ? markerData.EndPos : markerData.StartPos };

View File

@@ -25,14 +25,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
struct TermControl : TermControlT<TermControl>
{
TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection);
TermControl(Control::ControlInteractivity content);
TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection);
static Control::TermControl AttachContent(Control::ControlInteractivity content, const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings);
winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance);
IControlSettings Settings() const;
winrt::guid ContentGuid() const;
hstring GetProfileName() const;
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
@@ -136,21 +140,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
void Detach();
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
PROJECTED_FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs, _core, CopyToClipboard);
PROJECTED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
PROJECTED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs, _core, ShowWindowChanged);
PROJECTED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable, _core, CloseTerminalRequested);
// UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE
BUBBLED_FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
@@ -218,6 +225,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _showMarksInScrollbar{ false };
bool _isBackgroundLight{ false };
bool _detached{ false };
inline bool _IsClosing() const noexcept
{
@@ -243,7 +251,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
static bool _isColorLight(til::color bg) noexcept;
void _changeBackgroundOpacity();
bool _InitializeTerminal();
bool _InitializeTerminal(const bool reattach);
void _SetFontSize(int fontSize);
void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e);
void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
@@ -315,6 +323,36 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::point _toPosInDips(const Core::Point terminalCellPos);
void _throttledUpdateScrollbar(const ScrollBarUpdate& update);
struct Revokers
{
Control::ControlCore::ScrollPositionChanged_revoker coreScrollPositionChanged;
Control::ControlCore::WarningBell_revoker WarningBell;
Control::ControlCore::CursorPositionChanged_revoker CursorPositionChanged;
Control::ControlCore::RendererEnteredErrorState_revoker RendererEnteredErrorState;
Control::ControlCore::BackgroundColorChanged_revoker BackgroundColorChanged;
Control::ControlCore::FontSizeChanged_revoker FontSizeChanged;
Control::ControlCore::TransparencyChanged_revoker TransparencyChanged;
Control::ControlCore::RaiseNotice_revoker RaiseNotice;
Control::ControlCore::HoveredHyperlinkChanged_revoker HoveredHyperlinkChanged;
Control::ControlCore::FoundMatch_revoker FoundMatch;
Control::ControlCore::UpdateSelectionMarkers_revoker UpdateSelectionMarkers;
Control::ControlCore::OpenHyperlink_revoker coreOpenHyperlink;
Control::ControlCore::CopyToClipboard_revoker CopyToClipboard;
Control::ControlCore::TitleChanged_revoker TitleChanged;
Control::ControlCore::TabColorChanged_revoker TabColorChanged;
Control::ControlCore::TaskbarProgressChanged_revoker TaskbarProgressChanged;
Control::ControlCore::ConnectionStateChanged_revoker ConnectionStateChanged;
Control::ControlCore::ShowWindowChanged_revoker ShowWindowChanged;
Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested;
// These are set up in _InitializeTerminal
Control::ControlCore::RendererWarning_revoker RendererWarning;
Control::ControlCore::SwapChainChanged_revoker SwapChainChanged;
Control::ControlInteractivity::OpenHyperlink_revoker interactivityOpenHyperlink;
Control::ControlInteractivity::ScrollPositionChanged_revoker interactivityScrollPositionChanged;
Control::ControlInteractivity::PasteFromClipboard_revoker PasteFromClipboard;
} _revokers{};
};
}

View File

@@ -3,6 +3,7 @@
import "IMouseWheelListener.idl";
import "IControlSettings.idl";
import "ControlInteractivity.idl";
import "IDirectKeyListener.idl";
import "EventArgs.idl";
import "ICoreState.idl";
@@ -17,10 +18,14 @@ namespace Microsoft.Terminal.Control
ICoreState,
Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TermControl(ControlInteractivity content);
TermControl(IControlSettings settings,
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
static TermControl AttachContent(ControlInteractivity content, Microsoft.Terminal.Control.IKeyBindings keyBindings);
static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings,
UInt32 dpi,
Int32 commandlineCols,
@@ -29,6 +34,8 @@ namespace Microsoft.Terminal.Control
void UpdateControlSettings(IControlSettings settings);
void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance);
Guid ContentGuid{ get; };
Microsoft.Terminal.Control.IControlSettings Settings { get; };
event FontSizeChangedEventArgs FontSizeChanged;
@@ -100,5 +107,7 @@ namespace Microsoft.Terminal.Control
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
void Detach();
}
}

View File

@@ -423,4 +423,30 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
winrt::hstring ActionAndArgs::Serialize(winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> args)
{
Json::Value json{ Json::objectValue };
JsonUtils::SetValueForKey(json, "actions", args);
Json::StreamWriterBuilder wbuilder;
auto str = Json::writeString(wbuilder, json);
return winrt::to_hstring(str);
}
winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> ActionAndArgs::Deserialize(winrt::hstring content)
{
auto data = winrt::to_string(content);
std::string errs;
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> result{ nullptr };
JsonUtils::GetValueForKey(root, "actions", result);
return result;
}
}

View File

@@ -16,6 +16,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::vector<SettingsLoadWarnings>& warnings);
static Json::Value ToJson(const Model::ActionAndArgs& val);
static winrt::hstring Serialize(winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> args);
static winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> Deserialize(winrt::hstring content);
ActionAndArgs() = default;
ActionAndArgs(ShortcutAction action);
ActionAndArgs(ShortcutAction action, IActionArgs args) :

View File

@@ -13,6 +13,7 @@
#include "ResizePaneArgs.g.cpp"
#include "MoveFocusArgs.g.cpp"
#include "MovePaneArgs.g.cpp"
#include "MoveTabArgs.g.cpp"
#include "SwapPaneArgs.g.cpp"
#include "AdjustFontSizeArgs.g.cpp"
#include "SendInputArgs.g.cpp"
@@ -28,7 +29,6 @@
#include "CloseOtherTabsArgs.g.cpp"
#include "CloseTabsAfterArgs.g.cpp"
#include "CloseTabArgs.g.cpp"
#include "MoveTabArgs.g.cpp"
#include "ScrollToMarkArgs.g.cpp"
#include "AddMarkArgs.g.cpp"
#include "FindMatchArgs.g.cpp"
@@ -245,6 +245,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring MovePaneArgs::GenerateName() const
{
if (!Window().empty())
{
return winrt::hstring{
fmt::format(L"{}, window:{}, tab index:{}", RS_(L"MovePaneCommandKey"), Window(), TabIndex())
};
}
return winrt::hstring{
fmt::format(L"{}, tab index:{}", RS_(L"MovePaneCommandKey"), TabIndex())
};
@@ -648,6 +654,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring MoveTabArgs::GenerateName() const
{
if (!Window().empty())
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"MoveTabToWindowCommandKey")),
Window())
};
}
winrt::hstring directionString;
switch (Direction())
{

View File

@@ -96,8 +96,9 @@ private:
X(Windows::Foundation::IReference<Control::CopyFormat>, CopyFormatting, "copyFormatting", false, nullptr)
////////////////////////////////////////////////////////////////////////////////
#define MOVE_PANE_ARGS(X) \
X(uint32_t, TabIndex, "index", false, 0)
#define MOVE_PANE_ARGS(X) \
X(uint32_t, TabIndex, "index", false, 0) \
X(winrt::hstring, Window, "window", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define SWITCH_TO_TAB_ARGS(X) \
@@ -172,8 +173,15 @@ private:
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, nullptr)
////////////////////////////////////////////////////////////////////////////////
#define MOVE_TAB_ARGS(X) \
X(MoveTabDirection, Direction, "direction", args->Direction() == MoveTabDirection::None, MoveTabDirection::None)
// Interestingly, the order MATTERS here. Window has to be BEFORE Direction,
// because otherwise we won't have parsed the Window yet when we validate the
// Direction.
#define MOVE_TAB_ARGS(X) \
X(winrt::hstring, Window, "window", false, L"") \
X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), MoveTabDirection::None)
// Other ideas:
// X(uint32_t, TabIndex, "index", false, 0) \ // target? source?
////////////////////////////////////////////////////////////////////////////////
#define SCROLL_UP_ARGS(X) \
@@ -276,6 +284,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARG(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, nullptr);
ACTION_ARG(winrt::hstring, ColorScheme);
ACTION_ARG(Windows::Foundation::IReference<bool>, Elevate, nullptr);
ACTION_ARG(winrt::guid, ContentGuid);
static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
@@ -286,6 +295,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
static constexpr std::string_view ElevateKey{ "elevate" };
static constexpr std::string_view ContentKey{ "__content" };
public:
hstring GenerateName() const;
@@ -304,7 +314,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
otherAsUs->_Profile == _Profile &&
otherAsUs->_SuppressApplicationTitle == _SuppressApplicationTitle &&
otherAsUs->_ColorScheme == _ColorScheme &&
otherAsUs->_Elevate == _Elevate;
otherAsUs->_Elevate == _Elevate &&
otherAsUs->_ContentGuid == _ContentGuid;
}
return false;
};
@@ -321,6 +332,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle);
JsonUtils::GetValueForKey(json, ColorSchemeKey, args->_ColorScheme);
JsonUtils::GetValueForKey(json, ElevateKey, args->_Elevate);
JsonUtils::GetValueForKey(json, ContentKey, args->_ContentGuid);
return *args;
}
static Json::Value ToJson(const Model::NewTerminalArgs& val)
@@ -340,6 +352,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
JsonUtils::SetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle);
JsonUtils::SetValueForKey(json, ColorSchemeKey, args->_ColorScheme);
JsonUtils::SetValueForKey(json, ElevateKey, args->_Elevate);
JsonUtils::SetValueForKey(json, ContentKey, args->_ContentGuid);
return json;
}
Model::NewTerminalArgs Copy() const
@@ -354,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_SuppressApplicationTitle = _SuppressApplicationTitle;
copy->_ColorScheme = _ColorScheme;
copy->_Elevate = _Elevate;
copy->_ContentGuid = _ContentGuid;
return *copy;
}
size_t Hash() const
@@ -373,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(SuppressApplicationTitle());
h.write(ColorScheme());
h.write(Elevate());
h.write(ContentGuid());
}
};
}

View File

@@ -132,6 +132,8 @@ namespace Microsoft.Terminal.Settings.Model
// not modify whatever the profile's value is (either true or false)
Windows.Foundation.IReference<Boolean> Elevate;
Guid ContentGuid{ get; set; };
Boolean Equals(NewTerminalArgs other);
String GenerateName();
String ToCommandline();
@@ -158,8 +160,9 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass MovePaneArgs : IActionArgs
{
MovePaneArgs(UInt32 tabIndex);
MovePaneArgs(UInt32 tabIndex, String Window);
UInt32 TabIndex;
String Window;
};
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs
@@ -276,8 +279,9 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass MoveTabArgs : IActionArgs
{
MoveTabArgs(MoveTabDirection direction);
MoveTabArgs(String window, MoveTabDirection direction);
MoveTabDirection Direction { get; };
String Window { get; };
};
[default_interface] runtimeclass ScrollUpArgs : IActionArgs

View File

@@ -10,6 +10,7 @@
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Windows::Foundation::Collections;
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
@@ -118,7 +119,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// Method Description:
// - Retrieves a map of actions that can be bound to a key
Windows::Foundation::Collections::IMapView<hstring, Model::ActionAndArgs> ActionMap::AvailableActions()
IMapView<hstring, Model::ActionAndArgs> ActionMap::AvailableActions()
{
if (!_AvailableActionsCache)
{
@@ -172,7 +173,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// - Retrieves a map of command names to the commands themselves
// - These commands should not be modified directly because they may result in
// an invalid state for the `ActionMap`
Windows::Foundation::Collections::IMapView<hstring, Model::Command> ActionMap::NameMap()
IMapView<hstring, Model::Command> ActionMap::NameMap()
{
if (!_NameMapCache)
{
@@ -283,7 +284,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return cumulativeActions;
}
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> ActionMap::GlobalHotkeys()
IMapView<Control::KeyChord, Model::Command> ActionMap::GlobalHotkeys()
{
if (!_GlobalHotkeysCache)
{
@@ -292,7 +293,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _GlobalHotkeysCache.GetView();
}
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> ActionMap::KeyBindings()
IMapView<Control::KeyChord, Model::Command> ActionMap::KeyBindings()
{
if (!_KeyBindingMapCache)
{
@@ -854,4 +855,79 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
cmd->ActionAndArgs(action);
AddAction(*cmd);
}
void ActionMap::_recursiveUpdateCommandKeybindingLabels()
{
const auto& commands{ _ExpandedMapCache };
for (const auto& nameAndCmd : commands)
{
const auto& command = nameAndCmd.Value();
if (command.HasNestedCommands())
{
_recursiveUpdateCommandKeybindingLabels();
}
else
{
// If there's a keybinding that's bound to exactly this command,
// then get the keychord and display it as a
// part of the command in the UI.
// We specifically need to do this for nested commands.
const auto keyChord{ GetKeyBindingForAction(command.ActionAndArgs().Action(),
command.ActionAndArgs().Args()) };
command.RegisterKey(keyChord);
}
}
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
{
std::wstring leftName{ lhs.Name() };
std::wstring rightName{ rhs.Name() };
return leftName.compare(rightName) < 0;
}
void ActionMap::ExpandCommands(const IVectorView<Model::Profile>& profiles,
const IMapView<winrt::hstring, Model::ColorScheme>& schemes)
{
// TODO in review - It's a little weird to stash the expanded commands
// into a separate map. Is it possible to just replace the name map with
// the post-expanded commands?
//
// WHILE also making sure that upon re-saving the commands, we don't
// actually serialize the results of the expansion. I don't think it is.
auto warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() };
std::vector<Model::ColorScheme> sortedSchemes;
sortedSchemes.reserve(schemes.Size());
for (const auto& nameAndScheme : schemes)
{
sortedSchemes.push_back(nameAndScheme.Value());
}
std::sort(sortedSchemes.begin(),
sortedSchemes.end(),
_compareSchemeNames);
auto copyOfCommands = winrt::single_threaded_map<winrt::hstring, Model::Command>();
const auto& commandsToExpand{ NameMap() };
for (const auto& nameAndCommand : commandsToExpand)
{
copyOfCommands.Insert(nameAndCommand.Key(), nameAndCommand.Value());
}
Command::ExpandCommands(copyOfCommands,
profiles,
winrt::param::vector_view<Model::ColorScheme>{ sortedSchemes },
warnings);
_ExpandedMapCache = copyOfCommands;
}
IMapView<hstring, Model::Command> ActionMap::ExpandedCommands()
{
return _ExpandedMapCache.GetView();
}
}

View File

@@ -75,6 +75,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void DeleteKeyBinding(const Control::KeyChord& keys);
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
Windows::Foundation::Collections::IMapView<hstring, Model::Command> ExpandedCommands();
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);
private:
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
@@ -90,11 +94,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
void _recursiveUpdateCommandKeybindingLabels();
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _KeyBindingMapCache{ nullptr };
Windows::Foundation::Collections::IMap<hstring, Model::Command> _ExpandedMapCache{ nullptr };
std::unordered_map<winrt::hstring, Model::Command> _NestedCommands;
std::vector<Model::Command> _IterableCommands;
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;

View File

@@ -20,6 +20,8 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.Collections.IMapView<String, Command> NameMap { get; };
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> KeyBindings { get; };
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> GlobalHotkeys { get; };
Windows.Foundation.Collections.IMapView<String, Command> ExpandedCommands { get; };
};
[default_interface] runtimeclass ActionMap : IActionMapView

View File

@@ -1214,3 +1214,8 @@ void CascadiaSettings::_validateThemeExists()
}
}
}
void CascadiaSettings::ExpandCommands()
{
_globals->ExpandCommands(ActiveProfiles().GetView(), GlobalSettings().ColorSchemes());
}

View File

@@ -143,6 +143,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Model::DefaultTerminal CurrentDefaultTerminal() noexcept;
void CurrentDefaultTerminal(const Model::DefaultTerminal& terminal);
void ExpandCommands();
private:
static const std::filesystem::path& _settingsPath();
static const std::filesystem::path& _releaseSettingsPath();

View File

@@ -53,5 +53,7 @@ namespace Microsoft.Terminal.Settings.Model
static Boolean IsDefaultTerminalSet { get; };
IObservableVector<DefaultTerminal> DefaultTerminals { get; };
DefaultTerminal CurrentDefaultTerminal;
void ExpandCommands();
}
}

View File

@@ -67,9 +67,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
hstring IconPath() const noexcept;
void IconPath(const hstring& val);
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);

View File

@@ -24,11 +24,14 @@ namespace Microsoft.Terminal.Settings.Model
ActionAndArgs();
ActionAndArgs(ShortcutAction action, IActionArgs args);
static String Serialize(IVector<ActionAndArgs> args);
static IVector<ActionAndArgs> Deserialize(String content);
IActionArgs Args;
ShortcutAction Action;
};
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
[default_interface] runtimeclass Command
{
Command();

View File

@@ -239,3 +239,14 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
{
return _themes.GetView();
}
void GlobalAppSettings::ExpandCommands(const winrt::Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
const winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes)
{
_actionMap->ExpandCommands(profiles, schemes);
}
bool GlobalAppSettings::ShouldUsePersistedLayout() const
{
return FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && !IsolatedMode();
}

View File

@@ -62,6 +62,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::Collections::IMapView<hstring, Model::Theme> Themes() noexcept;
void AddTheme(const Model::Theme& theme);
Model::Theme CurrentTheme() noexcept;
bool ShouldUsePersistedLayout() const;
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");

View File

@@ -97,6 +97,8 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, ShowAdminShield);
INHERITABLE_SETTING(IVector<NewTabMenuEntry>, NewTabMenu);
INHERITABLE_SETTING(Boolean, EnableColorSelection);
INHERITABLE_SETTING(Boolean, IsolatedMode);
INHERITABLE_SETTING(Boolean, AllowHeadless);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);
@@ -108,5 +110,7 @@ namespace Microsoft.Terminal.Settings.Model
void AddTheme(Theme theme);
INHERITABLE_SETTING(ThemePair, Theme);
Theme CurrentTheme { get; };
Boolean ShouldUsePersistedLayout();
}
}

View File

@@ -18,51 +18,53 @@ Author(s):
// Macro format (defaultArgs are optional):
// (type, name, jsonKey, defaultArgs)
#define MTSM_GLOBAL_SETTINGS(X) \
X(int32_t, InitialRows, "initialRows", 30) \
X(int32_t, InitialCols, "initialCols", 80) \
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
X(bool, CopyOnSelect, "copyOnSelect", false) \
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
X(bool, DetectURLs, "experimental.detectURLs", true) \
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
X(Model::ThemePair, Theme, "theme") \
X(hstring, Language, "language") \
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
X(bool, InputServiceWarning, "inputServiceWarning", true) \
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
X(bool, AutoHideWindow, "autoHideWindow", false) \
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
X(bool, DisableAnimations, "disableAnimations", false) \
X(hstring, StartupActions, "startupActions", L"") \
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }))
#define MTSM_GLOBAL_SETTINGS(X) \
X(int32_t, InitialRows, "initialRows", 30) \
X(int32_t, InitialCols, "initialCols", 80) \
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
X(bool, CopyOnSelect, "copyOnSelect", false) \
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
X(bool, DetectURLs, "experimental.detectURLs", true) \
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
X(Model::ThemePair, Theme, "theme") \
X(hstring, Language, "language") \
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
X(bool, InputServiceWarning, "inputServiceWarning", true) \
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
X(bool, AutoHideWindow, "autoHideWindow", false) \
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
X(bool, DisableAnimations, "disableAnimations", false) \
X(hstring, StartupActions, "startupActions", L"") \
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
X(bool, AllowHeadless, "compatibility.allowHeadless", true) \
X(bool, IsolatedMode, "compatibility.isolatedMode", false)
#define MTSM_PROFILE_SETTINGS(X) \
X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \

View File

@@ -166,6 +166,10 @@
<value>Move tab {0}</value>
<comment>{0} will be replaced with a "forward" / "backward"</comment>
</data>
<data name="MoveTabToWindowCommandKey" xml:space="preserve">
<value>Move tab to window {0}</value>
<comment>{0} will be replaced with a user-specified name of a window</comment>
</data>
<data name="MoveTabDirectionForward" xml:space="preserve">
<value>forward</value>
</data>

View File

@@ -121,6 +121,7 @@ namespace RemotingUnitTests
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, Remoting::QuitAllRequestedArgs);
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, Remoting::WindowRequestedArgs);
};
class RemotingTests

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,10 @@
class AppHost
{
public:
AppHost() noexcept;
AppHost(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
virtual ~AppHost();
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
@@ -19,25 +22,31 @@ public:
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
bool HasWindow();
winrt::TerminalApp::TerminalWindow Logic();
static void s_DisplayMessageBox(const winrt::TerminalApp::ParseCommandlineResult& message);
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
private:
std::unique_ptr<IslandWindow> _window;
winrt::TerminalApp::App _app;
winrt::TerminalApp::AppLogic _logic;
winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr };
std::vector<winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs> _hotkeys;
winrt::TerminalApp::AppLogic _appLogic;
winrt::TerminalApp::TerminalWindow _windowLogic;
winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr };
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
winrt::com_ptr<IVirtualDesktopManager> _desktopManager{ nullptr };
bool _shouldCreateWindow{ false };
bool _useNonClientArea{ false };
std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
std::shared_ptr<ThrottledFuncTrailing<bool>> _showHideWindowThrottler;
winrt::Windows::Foundation::IAsyncAction _SaveWindowLayouts();
winrt::fire_and_forget _SaveWindowLayoutsRepeat();
void _HandleCommandlineArgs();
void _preInit();
void _HandleCommandlineArgs(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args);
winrt::Microsoft::Terminal::Settings::Model::LaunchPosition _GetWindowLaunchPosition();
void _HandleCreateWindow(const HWND hwnd, til::rect proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
@@ -64,12 +73,6 @@ private:
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> _GetWindowLayoutAsync();
void _FindTargetWindow(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
void _BecomeMonarch(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _GlobalHotkeyPressed(const long hotkeyIndex);
void _HandleSummon(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior& args);
@@ -84,11 +87,8 @@ private:
bool _LazyLoadDesktopManager();
void _listenForInboundConnections();
winrt::fire_and_forget _setupGlobalHotkeys();
winrt::fire_and_forget _createNewTerminalWindow(winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs args);
void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
const winrt::TerminalApp::SettingsLoadEventArgs& args);
void _IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
@@ -110,19 +110,9 @@ private:
void _CloseRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _QuitAllRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args);
void _ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args);
void _CreateNotificationIcon();
void _DestroyNotificationIcon();
void _ShowNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _HideNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _updateTheme();
void _PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender,
@@ -130,14 +120,22 @@ private:
void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
std::unique_ptr<NotificationIcon> _notificationIcon;
winrt::event_token _ReAddNotificationIconToken;
winrt::event_token _NotificationIconPressedToken;
winrt::event_token _ShowNotificationIconContextMenuToken;
winrt::event_token _NotificationIconMenuItemSelectedToken;
void _handleMoveContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::RequestMoveContentArgs args);
void _handleAttach(const winrt::Windows::Foundation::IInspectable& sender,
winrt::Microsoft::Terminal::Remoting::AttachRequest args);
void _requestUpdateSettings();
// Page -> us -> monarch
void _handleReceiveContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::RequestReceiveContentArgs args);
// monarch -> us -> Page
void _handleSendContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args);
winrt::event_token _GetWindowLayoutRequestedToken;
winrt::event_token _WindowCreatedToken;
winrt::event_token _WindowClosedToken;
// Helper struct. By putting these all into one struct, we can revoke them
// all at once, by assigning _revokers to a fresh Revokers instance. That'll
@@ -146,33 +144,38 @@ private:
struct Revokers
{
// Event handlers to revoke in ~AppHost, before calling App.Close
winrt::Microsoft::Terminal::Remoting::WindowManager::BecameMonarch_revoker BecameMonarch;
winrt::Microsoft::Terminal::Remoting::Peasant::ExecuteCommandlineRequested_revoker peasantExecuteCommandlineRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::SummonRequested_revoker peasantSummonRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::DisplayWindowIdRequested_revoker peasantDisplayWindowIdRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::QuitRequested_revoker peasantQuitRequested;
winrt::TerminalApp::AppLogic::CloseRequested_revoker CloseRequested;
winrt::TerminalApp::AppLogic::RequestedThemeChanged_revoker RequestedThemeChanged;
winrt::TerminalApp::AppLogic::FullscreenChanged_revoker FullscreenChanged;
winrt::TerminalApp::AppLogic::FocusModeChanged_revoker FocusModeChanged;
winrt::TerminalApp::AppLogic::AlwaysOnTopChanged_revoker AlwaysOnTopChanged;
winrt::TerminalApp::AppLogic::RaiseVisualBell_revoker RaiseVisualBell;
winrt::TerminalApp::AppLogic::SystemMenuChangeRequested_revoker SystemMenuChangeRequested;
winrt::TerminalApp::AppLogic::ChangeMaximizeRequested_revoker ChangeMaximizeRequested;
winrt::TerminalApp::AppLogic::TitleChanged_revoker TitleChanged;
winrt::TerminalApp::AppLogic::LastTabClosed_revoker LastTabClosed;
winrt::TerminalApp::AppLogic::SetTaskbarProgress_revoker SetTaskbarProgress;
winrt::TerminalApp::AppLogic::IdentifyWindowsRequested_revoker IdentifyWindowsRequested;
winrt::TerminalApp::AppLogic::RenameWindowRequested_revoker RenameWindowRequested;
winrt::TerminalApp::AppLogic::SettingsChanged_revoker SettingsChanged;
winrt::TerminalApp::AppLogic::IsQuakeWindowChanged_revoker IsQuakeWindowChanged;
winrt::TerminalApp::AppLogic::SummonWindowRequested_revoker SummonWindowRequested;
winrt::TerminalApp::AppLogic::OpenSystemMenu_revoker OpenSystemMenu;
winrt::TerminalApp::AppLogic::QuitRequested_revoker QuitRequested;
winrt::TerminalApp::AppLogic::ShowWindowChanged_revoker ShowWindowChanged;
winrt::Microsoft::Terminal::Remoting::Peasant::AttachRequested_revoker AttachRequested;
winrt::TerminalApp::TerminalWindow::CloseRequested_revoker CloseRequested;
winrt::TerminalApp::TerminalWindow::RequestedThemeChanged_revoker RequestedThemeChanged;
winrt::TerminalApp::TerminalWindow::FullscreenChanged_revoker FullscreenChanged;
winrt::TerminalApp::TerminalWindow::FocusModeChanged_revoker FocusModeChanged;
winrt::TerminalApp::TerminalWindow::AlwaysOnTopChanged_revoker AlwaysOnTopChanged;
winrt::TerminalApp::TerminalWindow::RaiseVisualBell_revoker RaiseVisualBell;
winrt::TerminalApp::TerminalWindow::SystemMenuChangeRequested_revoker SystemMenuChangeRequested;
winrt::TerminalApp::TerminalWindow::ChangeMaximizeRequested_revoker ChangeMaximizeRequested;
winrt::TerminalApp::TerminalWindow::TitleChanged_revoker TitleChanged;
winrt::TerminalApp::TerminalWindow::LastTabClosed_revoker LastTabClosed;
winrt::TerminalApp::TerminalWindow::SetTaskbarProgress_revoker SetTaskbarProgress;
winrt::TerminalApp::TerminalWindow::IdentifyWindowsRequested_revoker IdentifyWindowsRequested;
winrt::TerminalApp::TerminalWindow::RenameWindowRequested_revoker RenameWindowRequested;
winrt::TerminalApp::TerminalWindow::IsQuakeWindowChanged_revoker IsQuakeWindowChanged;
winrt::TerminalApp::TerminalWindow::SummonWindowRequested_revoker SummonWindowRequested;
winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu;
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged;
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
winrt::TerminalApp::AppLogic::PropertyChanged_revoker PropertyChanged;
winrt::Microsoft::Terminal::Remoting::Peasant::SendContentRequested_revoker SendContentRequested;
} _revokers{};
};

View File

@@ -27,8 +27,6 @@ using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
#define IDM_SYSTEM_MENU_BEGIN 0x1000
const UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
IslandWindow::IslandWindow() noexcept :
_interopWindowHandle{ nullptr },
_rootGrid{ nullptr },
@@ -405,18 +403,19 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
// - The total dimension
long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize, const long nonClientSize)
{
return gsl::narrow_cast<int>(_pfnSnapDimensionCallback(isWidth, gsl::narrow_cast<float>(clientSize)) + nonClientSize);
if (_pfnSnapDimensionCallback)
{
return gsl::narrow_cast<int>(_pfnSnapDimensionCallback(isWidth, gsl::narrow_cast<float>(clientSize)) + nonClientSize);
}
// We might have been called in WM_CREATE, before we've initialized XAML or
// our page. That's okay.
return clientSize + nonClientSize;
}
[[nodiscard]] LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
switch (message)
{
case WM_HOTKEY:
{
_HotkeyPressedHandlers(static_cast<long>(wparam));
return 0;
}
case WM_GETMINMAXINFO:
{
_OnGetMinMaxInfo(wparam, lparam);
@@ -632,30 +631,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
}
break;
}
case CM_NOTIFY_FROM_NOTIFICATION_AREA:
{
switch (LOWORD(lparam))
{
case NIN_SELECT:
case NIN_KEYSELECT:
{
_NotifyNotificationIconPressedHandlers();
return 0;
}
case WM_CONTEXTMENU:
{
const til::point eventPoint{ GET_X_LPARAM(wparam), GET_Y_LPARAM(wparam) };
_NotifyShowNotificationIconContextMenuHandlers(eventPoint);
return 0;
}
}
break;
}
case WM_MENUCOMMAND:
{
_NotifyNotificationIconMenuItemSelectedHandlers((HMENU)lparam, (UINT)wparam);
return 0;
}
case WM_SYSCOMMAND:
{
// the low 4 bits contain additional information (that we don't care about)
@@ -732,16 +707,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
_AutomaticShutdownRequestedHandlers();
return true;
}
default:
// We'll want to receive this message when explorer.exe restarts
// so that we can re-add our icon to the notification area.
// This unfortunately isn't a switch case because we register the
// message at runtime.
if (message == WM_TASKBARCREATED)
{
_NotifyReAddNotificationIconHandlers();
return 0;
}
}
// TODO: handle messages here...
@@ -1258,65 +1223,6 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
}
}
// Method Description:
// - Call UnregisterHotKey once for each previously registered hotkey.
// Return Value:
// - <none>
void IslandWindow::UnregisterHotKey(const int index) noexcept
{
TraceLoggingWrite(
g_hWindowsTerminalProvider,
"UnregisterHotKey",
TraceLoggingDescription("Emitted when clearing previously set hotkeys"),
TraceLoggingInt64(index, "index", "the index of the hotkey to remove"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
LOG_IF_WIN32_BOOL_FALSE(::UnregisterHotKey(_window.get(), index));
}
// Method Description:
// - Call RegisterHotKey to attempt to register that keybinding as a global hotkey.
// - When these keys are pressed, we'll get a WM_HOTKEY message with the payload
// containing the index we registered here.
// - Call UnregisterHotKey() before registering your hotkeys.
// See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey#remarks
// Arguments:
// - hotkey: The key-combination to register.
// Return Value:
// - <none>
bool IslandWindow::RegisterHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept
{
const auto vkey = hotkey.Vkey();
auto hotkeyFlags = MOD_NOREPEAT;
{
const auto modifiers = hotkey.Modifiers();
WI_SetFlagIf(hotkeyFlags, MOD_WIN, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows));
WI_SetFlagIf(hotkeyFlags, MOD_ALT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu));
WI_SetFlagIf(hotkeyFlags, MOD_CONTROL, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control));
WI_SetFlagIf(hotkeyFlags, MOD_SHIFT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift));
}
// TODO GH#8888: We should display a warning of some kind if this fails.
// This can fail if something else already bound this hotkey.
const auto result = ::RegisterHotKey(_window.get(), index, hotkeyFlags, vkey);
TraceLoggingWrite(g_hWindowsTerminalProvider,
"RegisterHotKey",
TraceLoggingDescription("Emitted when setting hotkeys"),
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
TraceLoggingUInt64(vkey, "vkey", "the key"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_WIN), "win", "is WIN in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_ALT), "alt", "is ALT in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_CONTROL), "control", "is CONTROL in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_SHIFT), "shift", "is SHIFT in the modifiers"),
TraceLoggingBool(result, "succeeded", "true if we succeeded"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return result;
}
// Method Description:
// - Summon the window, or possibly dismiss it. If toggleVisibility is true,
// then we'll dismiss (minimize) the window if it's currently active.

View File

@@ -3,7 +3,6 @@
#include "pch.h"
#include "BaseWindow.h"
#include <winrt/TerminalApp.h>
void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept;
@@ -51,9 +50,6 @@ public:
void FlashTaskbar();
void SetTaskbarProgress(const size_t state, const size_t progress);
void UnregisterHotKey(const int index) noexcept;
bool RegisterHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
winrt::fire_and_forget SummonWindow(winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior args);
bool IsQuakeWindow() const noexcept;
@@ -74,7 +70,6 @@ public:
WINRT_CALLBACK(WindowCloseButtonClicked, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
WINRT_CALLBACK(WindowActivated, winrt::delegate<void(bool)>);
WINRT_CALLBACK(HotkeyPressed, winrt::delegate<void(long)>);
WINRT_CALLBACK(NotifyNotificationIconPressed, winrt::delegate<void()>);
WINRT_CALLBACK(NotifyWindowHidden, winrt::delegate<void()>);
WINRT_CALLBACK(NotifyShowNotificationIconContextMenu, winrt::delegate<void(til::point)>);

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#pragma once
// This enumerates all the possible actions
// that our notification icon context menu could do.
enum class NotificationIconMenuItemAction

View File

@@ -0,0 +1,692 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "WindowEmperor.h"
#include "../inc/WindowingBehavior.h"
#include "../../types/inc/utils.hpp"
#include "../WinRTUtils/inc/WtExeUtils.h"
#include "resource.h"
#include "NotificationIcon.h"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
using namespace std::chrono_literals;
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
#define TERMINAL_MESSAGE_CLASS_NAME L"TERMINAL_MESSAGE_CLASS"
extern "C" IMAGE_DOS_HEADER __ImageBase;
const UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
WindowEmperor::WindowEmperor() noexcept :
_app{}
{
_manager.FindTargetWindowRequested([this](const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& findWindowArgs) {
{
const auto targetWindow = _app.Logic().FindTargetWindow(findWindowArgs.Args().Commandline());
findWindowArgs.ResultTargetWindow(targetWindow.WindowId());
findWindowArgs.ResultTargetWindowName(targetWindow.WindowName());
}
});
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
}
WindowEmperor::~WindowEmperor()
{
_app.Close();
_app = nullptr;
}
void _buildArgsFromCommandline(std::vector<winrt::hstring>& args)
{
if (auto commandline{ GetCommandLineW() })
{
auto argc = 0;
// Get the argv, and turn them into a hstring array to pass to the app.
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
if (argv)
{
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
}
}
if (args.empty())
{
args.emplace_back(L"wt.exe");
}
}
bool WindowEmperor::HandleCommandlineArgs()
{
std::vector<winrt::hstring> args;
_buildArgsFromCommandline(args);
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
Remoting::CommandlineArgs eventArgs{ { args }, { cwd } };
const auto isolatedMode{ _app.Logic().IsolatedMode() };
const auto result = _manager.ProposeCommandline(eventArgs, isolatedMode);
if (result.ShouldCreateWindow())
{
CreateNewWindowThread(Remoting::WindowRequestedArgs{ result, eventArgs }, true);
_manager.RequestNewWindow([this](auto&&, const Remoting::WindowRequestedArgs& args) {
CreateNewWindowThread(args, false);
});
_becomeMonarch();
}
else
{
const auto res = _app.Logic().GetParseCommandlineMessage(eventArgs.Commandline());
if (!res.Message.empty())
{
AppHost::s_DisplayMessageBox(res);
ExitThread(res.ExitCode);
}
}
return result.ShouldCreateWindow();
}
void WindowEmperor::WaitForWindows()
{
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
}
void WindowEmperor::CreateNewWindowThread(Remoting::WindowRequestedArgs args, const bool /*firstWindow*/)
{
Remoting::Peasant peasant{ _manager.CreatePeasant(args) };
auto window{ std::make_shared<WindowThread>(_app.Logic(), args, _manager, peasant) };
window->Started([this, sender = window]() -> winrt::fire_and_forget {
// Add a callback to the window's logic to let us know when the window's
// quake mode state changes. We'll use this to check if we need to add
// or remove the notification icon.
sender->Logic().IsQuakeWindowChanged([this](auto&&, auto &&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(this->_dispatcher);
this->_checkWindowsForNotificationIcon();
});
sender->UpdateSettingsRequested([this]() -> winrt::fire_and_forget {
// We MUST be on the main thread to update the settings. We will crash when trying to enumerate fragment extensions otherwise.
co_await wil::resume_foreground(this->_dispatcher);
_app.Logic().ReloadSettings();
});
// These come in on the sender's thread. Move back to our thread.
co_await wil::resume_foreground(_dispatcher);
_windows.push_back(std::move(sender));
});
window->Exited([this](uint64_t senderID) -> winrt::fire_and_forget {
// These come in on the sender's thread. Move back to our thread.
co_await wil::resume_foreground(_dispatcher);
// find the window in _windows who's peasant's Id matches the peasant's Id
// and remove it
_windows.erase(std::remove_if(_windows.begin(), _windows.end(), [&](const auto& w) {
return w->Peasant().GetID() == senderID;
}),
_windows.end());
// When we run out of windows, exit our process if and only if:
// * We're not allowed to run headless OR
// * we've explicitly been told to "quit", which should fully exit the Terminal.
if (_windows.size() == 0 &&
(_quitting || !_app.Logic().AllowHeadless()))
{
_close();
}
});
window->Start();
}
// Method Description:
// - Set up all sorts of handlers now that we've determined that we're a process
// that will end up hosting the windows. These include:
// - Setting up a message window to handle hotkeys and notification icon
// invokes.
// - Setting up the global hotkeys.
// - Setting up the notification icon.
// - Setting up callbacks for when the settings change.
// - Setting up callbacks for when the number of windows changes.
// - Setting up the throttled func for layout persistence. Arguments:
// - <none>
void WindowEmperor::_becomeMonarch()
{
_createMessageWindow();
_setupGlobalHotkeys();
// When the settings change, we'll want to update our global hotkeys and our
// notification icon based on the new settings.
_app.Logic().SettingsChanged([this](auto&&, const TerminalApp::SettingsLoadEventArgs& args) {
if (SUCCEEDED(args.Result()))
{
_setupGlobalHotkeys();
_checkWindowsForNotificationIcon();
}
});
// On startup, immediately check if we need to show the notification icon.
_checkWindowsForNotificationIcon();
// Set the number of open windows (so we know if we are the last window)
// and subscribe for updates if there are any changes to that number.
_WindowCreatedToken = _manager.WindowCreated({ this, &WindowEmperor::_numberOfWindowsChanged });
_WindowClosedToken = _manager.WindowClosed({ this, &WindowEmperor::_numberOfWindowsChanged });
// If the monarch receives a QuitAll event it will signal this event to be
// ran before each peasant is closed.
_revokers.QuitAllRequested = _manager.QuitAllRequested(winrt::auto_revoke, { this, &WindowEmperor::_quitAllRequested });
// The monarch should be monitoring if it should save the window layout.
// We want at least some delay to prevent the first save from overwriting
_getWindowLayoutThrottler.emplace(std::move(std::chrono::seconds(10)), std::move([this]() { _saveWindowLayoutsRepeat(); }));
_getWindowLayoutThrottler.value()();
}
// sender and args are always nullptr
void WindowEmperor::_numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::Foundation::IInspectable&)
{
if (_getWindowLayoutThrottler)
{
_getWindowLayoutThrottler.value()();
}
const auto& numWindows{ _manager.GetNumberOfPeasants() };
for (const auto& _windowThread : _windows)
{
_windowThread->Logic().SetNumberOfOpenWindows(numWindows);
}
// If we closed out the quake window, and don't otherwise need the tray
// icon, let's get rid of it.
_checkWindowsForNotificationIcon();
}
// Raised from our windowManager (on behalf of the monarch). We respond by
// giving the monarch an async function that the manager should wait on before
// completing the quit.
void WindowEmperor::_quitAllRequested(const winrt::Windows::Foundation::IInspectable&,
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args)
{
_quitting = true;
// Make sure that the current timer is destroyed so that it doesn't attempt
// to run while we are in the middle of quitting.
if (_getWindowLayoutThrottler.has_value())
{
_getWindowLayoutThrottler.reset();
}
// Tell the monarch to wait for the window layouts to save before
// everyone quits.
args.BeforeQuitAllAction(_saveWindowLayouts());
}
#pragma region LayoutPersistence
winrt::Windows::Foundation::IAsyncAction WindowEmperor::_saveWindowLayouts()
{
// Make sure we run on a background thread to not block anything.
co_await winrt::resume_background();
if (_app.Logic().ShouldUsePersistedLayout())
{
try
{
TraceLoggingWrite(g_hWindowsTerminalProvider,
"AppHost_SaveWindowLayouts_Collect",
TraceLoggingDescription("Logged when collecting window state"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
const auto layoutJsons = _manager.GetAllWindowLayouts();
TraceLoggingWrite(g_hWindowsTerminalProvider,
"AppHost_SaveWindowLayouts_Save",
TraceLoggingDescription("Logged when writing window state"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_app.Logic().SaveWindowLayoutJsons(layoutJsons);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
TraceLoggingWrite(g_hWindowsTerminalProvider,
"AppHost_SaveWindowLayouts_Failed",
TraceLoggingDescription("An error occurred when collecting or writing window state"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
co_return;
}
winrt::fire_and_forget WindowEmperor::_saveWindowLayoutsRepeat()
{
// Make sure we run on a background thread to not block anything.
co_await winrt::resume_background();
co_await _saveWindowLayouts();
// Don't need to save too frequently.
co_await winrt::resume_after(30s);
// As long as we are supposed to keep saving, request another save.
// This will be delayed by the throttler so that at most one save happens
// per 10 seconds, if a save is requested by another source simultaneously.
if (_getWindowLayoutThrottler.has_value())
{
TraceLoggingWrite(g_hWindowsTerminalProvider,
"AppHost_requestGetLayout",
TraceLoggingDescription("Logged when triggering a throttled write of the window state"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_getWindowLayoutThrottler.value()();
}
}
#pragma endregion
#pragma region WindowProc
static WindowEmperor* GetThisFromHandle(HWND const window) noexcept
{
const auto data = GetWindowLongPtr(window, GWLP_USERDATA);
return reinterpret_cast<WindowEmperor*>(data);
}
[[nodiscard]] static LRESULT __stdcall MessageWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
WINRT_ASSERT(window);
if (WM_NCCREATE == message)
{
auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
WindowEmperor* that = static_cast<WindowEmperor*>(cs->lpCreateParams);
WINRT_ASSERT(that);
WINRT_ASSERT(!that->_window);
that->_window = wil::unique_hwnd(window);
SetWindowLongPtr(that->_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
}
else if (WindowEmperor* that = GetThisFromHandle(window))
{
return that->MessageHandler(message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
void WindowEmperor::_createMessageWindow()
{
WNDCLASS wc{};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
wc.lpszClassName = TERMINAL_MESSAGE_CLASS_NAME;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MessageWndProc;
wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON));
RegisterClass(&wc);
WINRT_ASSERT(!_window);
WINRT_VERIFY(CreateWindow(wc.lpszClassName,
L"Windows Terminal",
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_MESSAGE,
nullptr,
wc.hInstance,
this));
}
LRESULT WindowEmperor::MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept
{
switch (message)
{
case WM_HOTKEY:
{
_hotkeyPressed(static_cast<long>(wParam));
return 0;
}
case CM_NOTIFY_FROM_NOTIFICATION_AREA:
{
switch (LOWORD(lParam))
{
case NIN_SELECT:
case NIN_KEYSELECT:
{
_notificationIcon->NotificationIconPressed();
return 0;
}
case WM_CONTEXTMENU:
{
const til::point eventPoint{ GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam) };
_notificationIcon->ShowContextMenu(eventPoint, _manager.GetPeasantInfos());
return 0;
}
}
break;
}
case WM_MENUCOMMAND:
{
_notificationIcon->MenuItemSelected((HMENU)lParam, (UINT)wParam);
return 0;
}
default:
{
// We'll want to receive this message when explorer.exe restarts
// so that we can re-add our icon to the notification area.
// This unfortunately isn't a switch case because we register the
// message at runtime.
if (message == WM_TASKBARCREATED)
{
_notificationIcon->ReAddNotificationIcon();
return 0;
}
}
}
return DefWindowProc(_window.get(), message, wParam, lParam);
}
winrt::fire_and_forget WindowEmperor::_close()
{
// Important! Switch back to the main thread for the emperor. That way, the
// quit will go to the emperor's message pump.
co_await wil::resume_foreground(_dispatcher);
PostQuitMessage(0);
}
#pragma endregion
#pragma region GlobalHotkeys
// Method Description:
// - Called when the monarch failed to summon a window for a given set of
// SummonWindowSelectionArgs. In this case, we should create the specified
// window ourselves.
// - This is to support the scenario like `globalSummon(Name="_quake")` being
// used to summon the window if it already exists, or create it if it doesn't.
// Arguments:
// - args: Contains information on how we should name the window
// Return Value:
// - <none>
static winrt::fire_and_forget _createNewTerminalWindow(Settings::Model::GlobalSummonArgs args)
{
// Hop to the BG thread
co_await winrt::resume_background();
// This will get us the correct exe for dev/preview/release. If you
// don't stick this in a local, it'll get mangled by ShellExecute. I
// have no idea why.
const auto exePath{ GetWtExePath() };
// If we weren't given a name, then just use new to force the window to be
// unnamed.
winrt::hstring cmdline{
fmt::format(L"-w {}",
args.Name().empty() ? L"new" :
args.Name())
};
SHELLEXECUTEINFOW seInfo{ 0 };
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_NOASYNC;
seInfo.lpVerb = L"open";
seInfo.lpFile = exePath.c_str();
seInfo.lpParameters = cmdline.c_str();
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
co_return;
}
void WindowEmperor::_hotkeyPressed(const long hotkeyIndex)
{
if (hotkeyIndex < 0 || static_cast<size_t>(hotkeyIndex) > _hotkeys.size())
{
return;
}
const auto& summonArgs = til::at(_hotkeys, hotkeyIndex);
Remoting::SummonWindowSelectionArgs args{ summonArgs.Name() };
// desktop:any - MoveToCurrentDesktop=false, OnCurrentDesktop=false
// desktop:toCurrent - MoveToCurrentDesktop=true, OnCurrentDesktop=false
// desktop:onCurrent - MoveToCurrentDesktop=false, OnCurrentDesktop=true
args.OnCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::OnCurrent);
args.SummonBehavior().MoveToCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::ToCurrent);
args.SummonBehavior().ToggleVisibility(summonArgs.ToggleVisibility());
args.SummonBehavior().DropdownDuration(summonArgs.DropdownDuration());
switch (summonArgs.Monitor())
{
case Settings::Model::MonitorBehavior::Any:
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
break;
case Settings::Model::MonitorBehavior::ToCurrent:
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToCurrent);
break;
case Settings::Model::MonitorBehavior::ToMouse:
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToMouse);
break;
}
_manager.SummonWindow(args);
if (args.FoundMatch())
{
// Excellent, the window was found. We have nothing else to do here.
}
else
{
// We should make the window ourselves.
_createNewTerminalWindow(summonArgs);
}
}
bool WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept
{
const auto vkey = hotkey.Vkey();
auto hotkeyFlags = MOD_NOREPEAT;
{
const auto modifiers = hotkey.Modifiers();
WI_SetFlagIf(hotkeyFlags, MOD_WIN, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows));
WI_SetFlagIf(hotkeyFlags, MOD_ALT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu));
WI_SetFlagIf(hotkeyFlags, MOD_CONTROL, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control));
WI_SetFlagIf(hotkeyFlags, MOD_SHIFT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift));
}
// TODO GH#8888: We should display a warning of some kind if this fails.
// This can fail if something else already bound this hotkey.
const auto result = ::RegisterHotKey(_window.get(), index, hotkeyFlags, vkey);
LOG_LAST_ERROR_IF(!result);
TraceLoggingWrite(g_hWindowsTerminalProvider,
"RegisterHotKey",
TraceLoggingDescription("Emitted when setting hotkeys"),
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
TraceLoggingUInt64(vkey, "vkey", "the key"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_WIN), "win", "is WIN in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_ALT), "alt", "is ALT in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_CONTROL), "control", "is CONTROL in the modifiers"),
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_SHIFT), "shift", "is SHIFT in the modifiers"),
TraceLoggingBool(result, "succeeded", "true if we succeeded"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return result;
}
// Method Description:
// - Call UnregisterHotKey once for each previously registered hotkey.
// Return Value:
// - <none>
void WindowEmperor::_unregisterHotKey(const int index) noexcept
{
TraceLoggingWrite(
g_hWindowsTerminalProvider,
"UnregisterHotKey",
TraceLoggingDescription("Emitted when clearing previously set hotkeys"),
TraceLoggingInt64(index, "index", "the index of the hotkey to remove"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
LOG_IF_WIN32_BOOL_FALSE(::UnregisterHotKey(_window.get(), index));
}
winrt::fire_and_forget WindowEmperor::_setupGlobalHotkeys()
{
// The hotkey MUST be registered on the main thread. It will fail otherwise!
co_await wil::resume_foreground(_dispatcher);
if (!_window)
{
// MSFT:36797001 There's a surprising number of hits of this callback
// getting triggered during teardown. As a best practice, we really
// should make sure _window exists before accessing it on any coroutine.
// We might be getting called back after the app already began getting
// cleaned up.
co_return;
}
// Unregister all previously registered hotkeys.
//
// RegisterHotKey(), will not unregister hotkeys automatically.
// If a hotkey with a given HWND and ID combination already exists
// then a duplicate one will be added, which we don't want.
// (Additionally we want to remove hotkeys that were removed from the settings.)
for (auto i = 0, count = gsl::narrow_cast<int>(_hotkeys.size()); i < count; ++i)
{
_unregisterHotKey(i);
}
_hotkeys.clear();
// Re-register all current hotkeys.
for (const auto& [keyChord, cmd] : _app.Logic().GlobalHotkeys())
{
if (auto summonArgs = cmd.ActionAndArgs().Args().try_as<Settings::Model::GlobalSummonArgs>())
{
auto index = gsl::narrow_cast<int>(_hotkeys.size());
const auto succeeded = _registerHotKey(index, keyChord);
TraceLoggingWrite(g_hWindowsTerminalProvider,
"AppHost_setupGlobalHotkey",
TraceLoggingDescription("Emitted when setting a single hotkey"),
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
TraceLoggingWideString(cmd.Name().c_str(), "name", "the name of the command"),
TraceLoggingBoolean(succeeded, "succeeded", "true if we succeeded"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_hotkeys.emplace_back(summonArgs);
}
}
}
#pragma endregion
////////////////////////////////////////////////////////////////////////////////
#pragma region NotificationIcon
// Method Description:
// - Creates a Notification Icon and hooks up its handlers
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowEmperor::_createNotificationIcon()
{
_notificationIcon = std::make_unique<NotificationIcon>(_window.get());
_notificationIcon->SummonWindowRequested([this](auto& args) { _manager.SummonWindow(args); });
}
// Method Description:
// - Deletes our notification icon if we have one.
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowEmperor::_destroyNotificationIcon()
{
_notificationIcon->RemoveIconFromNotificationArea();
_notificationIcon = nullptr;
}
void WindowEmperor::_checkWindowsForNotificationIcon()
{
// We need to check some conditions to show the notification icon.
//
// * If there's a Quake window somewhere, we'll want to keep the
// notification icon.
// * There's two settings - MinimizeToNotificationArea and
// AlwaysShowNotificationIcon. If either one of them are true, we want to
// make sure there's a notification icon.
//
// If both are false, we want to remove our icon from the notification area.
// When we remove our icon from the notification area, we'll also want to
// re-summon any hidden windows, but right now we're not keeping track of
// who's hidden, so just summon them all. Tracking the work to do a "summon
// all minimized" in GH#10448
bool needsIcon = false;
for (const auto& _windowThread : _windows)
{
needsIcon |= _windowThread->Logic().RequestsTrayIcon();
}
if (needsIcon)
{
_showNotificationIconRequested();
}
else
{
_hideNotificationIconRequested();
}
}
void WindowEmperor::_showNotificationIconRequested()
{
if (!_notificationIcon)
{
_createNotificationIcon();
}
}
void WindowEmperor::_hideNotificationIconRequested()
{
// Destroy it only if our settings allow it
if (_notificationIcon)
{
// If we no longer want the tray icon, but we did have one, then quick
// re-summon all our windows, so they don't get lost when the icon
// disappears forever.
_manager.SummonAllWindows();
_destroyNotificationIcon();
}
}
#pragma endregion

View File

@@ -0,0 +1,83 @@
/*++
Copyright (c) Microsoft Corporation Licensed under the MIT license.
Class Name:
- WindowEmperor.h
Abstract:
- The WindowEmperor is our class for managing the single Terminal process
with all our windows. It will be responsible for handling the commandline
arguments. It will initially try to find another terminal process to
communicate with. If it does, it'll hand off to the existing process.
- If it determines that it should create a window, it will set up a new thread
for that window, and a message loop on the main thread for handling global
state, such as hotkeys and the notification icon.
--*/
#pragma once
#include "pch.h"
#include "WindowThread.h"
class WindowEmperor
{
public:
WindowEmperor() noexcept;
~WindowEmperor();
void WaitForWindows();
bool HandleCommandlineArgs();
void CreateNewWindowThread(winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, const bool firstWindow);
LRESULT MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept;
wil::unique_hwnd _window;
private:
winrt::TerminalApp::App _app;
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
winrt::Microsoft::Terminal::Remoting::WindowManager _manager;
std::vector<std::shared_ptr<WindowThread>> _windows;
std::vector<std::thread> _threads;
std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
winrt::event_token _WindowCreatedToken;
winrt::event_token _WindowClosedToken;
std::vector<winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs> _hotkeys;
std::unique_ptr<NotificationIcon> _notificationIcon;
bool _quitting{ false };
void _becomeMonarch();
void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
void _quitAllRequested(const winrt::Windows::Foundation::IInspectable&,
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs&);
winrt::Windows::Foundation::IAsyncAction _saveWindowLayouts();
winrt::fire_and_forget _saveWindowLayoutsRepeat();
void _createMessageWindow();
void _hotkeyPressed(const long hotkeyIndex);
bool _registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
void _unregisterHotKey(const int index) noexcept;
winrt::fire_and_forget _setupGlobalHotkeys();
winrt::fire_and_forget _close();
void _createNotificationIcon();
void _destroyNotificationIcon();
void _checkWindowsForNotificationIcon();
void _showNotificationIconRequested();
void _hideNotificationIconRequested();
struct Revokers
{
winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
} _revokers{};
};

View File

@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "WindowThread.h"
WindowThread::WindowThread(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
winrt::Microsoft::Terminal::Remoting::WindowManager manager,
winrt::Microsoft::Terminal::Remoting::Peasant peasant) :
_peasant{ peasant },
_appLogic{ logic },
_args{ args },
_manager{ manager }
{
// DO NOT start the AppHost here in the ctor, as that will start XAML on the wrong thread!
}
void WindowThread::Start()
{
_thread = std::thread([this]() {
// Start the AppHost HERE, on the actual thread we want XAML to run on
_host = std::make_unique<::AppHost>(_appLogic,
_args,
_manager,
_peasant);
_host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); });
// Enter the main window loop.
const auto exitCode = WindowProc();
_host = nullptr;
// !! LOAD BEARING !!
//
// Make sure to finish pumping all the messages for our thread here. We
// may think we're all done, but we're not quite. XAML needs more time
// to pump the remaining events through, even at the point we're
// exiting. So do that now. If you don't, then the last tab to close
// will never actually destruct the last tab / TermControl / ControlCore
// / renderer.
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
_ExitedHandlers(_peasant.GetID());
return exitCode;
});
LOG_IF_FAILED(SetThreadDescription(_thread.native_handle(), L"Window Thread"));
}
winrt::TerminalApp::TerminalWindow WindowThread::Logic()
{
return _host->Logic();
}
static bool _messageIsF7Keypress(const MSG& message)
{
return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7;
}
static bool _messageIsAltKeyup(const MSG& message)
{
return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU;
}
static bool _messageIsAltSpaceKeypress(const MSG& message)
{
return message.message == WM_SYSKEYDOWN && message.wParam == VK_SPACE;
}
int WindowThread::WindowProc()
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
_host->Initialize();
// Inform the emperor that we're ready to go. We need to do this after
// Initialize, so that the windowLogic is ready to be used
_StartedHandlers();
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
// dialog experience triggered when you press F7. Official recommendation from the Xaml
// team is to catch F7 before we hand it off.
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
// implementing a custom IF7Listener interface.
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
// been handled we can discard the message before we even translate it.
if (_messageIsF7Keypress(message))
{
if (_host->OnDirectKeyEvent(VK_F7, LOBYTE(HIWORD(message.lParam)), true))
{
// The application consumed the F7. Don't let Xaml get it.
continue;
}
}
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
// here, and plumb it through.
if (_messageIsAltKeyup(message))
{
// Let's pass <Alt> to the application
if (_host->OnDirectKeyEvent(VK_MENU, LOBYTE(HIWORD(message.lParam)), false))
{
// The application consumed the Alt. Don't let Xaml get it.
continue;
}
}
// GH#7125 = System XAML will show a system dialog on Alt Space. We want to
// explicitly prevent that because we handle that ourselves. So similar to
// above, we steal the event and hand it off to the host.
if (_messageIsAltSpaceKeypress(message))
{
_host->OnDirectKeyEvent(VK_SPACE, LOBYTE(HIWORD(message.lParam)), true);
continue;
}
TranslateMessage(&message);
DispatchMessage(&message);
}
return 0;
}
winrt::Microsoft::Terminal::Remoting::Peasant WindowThread::Peasant()
{
return _peasant;
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "pch.h"
#include "AppHost.h"
class WindowThread
{
public:
WindowThread(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
winrt::Microsoft::Terminal::Remoting::WindowManager manager,
winrt::Microsoft::Terminal::Remoting::Peasant peasant);
int WindowProc();
winrt::TerminalApp::TerminalWindow Logic();
void Start();
winrt::Microsoft::Terminal::Remoting::Peasant Peasant();
WINRT_CALLBACK(Started, winrt::delegate<>);
WINRT_CALLBACK(Exited, winrt::delegate<uint64_t>);
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
private:
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
winrt::TerminalApp::AppLogic _appLogic{ nullptr };
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs _args{ nullptr };
winrt::Microsoft::Terminal::Remoting::WindowManager _manager{ nullptr };
std::thread _thread;
std::unique_ptr<::AppHost> _host{ nullptr };
};

View File

@@ -56,6 +56,8 @@
<ClInclude Include="NonClientIslandWindow.h" />
<ClInclude Include="NotificationIcon.h" />
<ClInclude Include="VirtualDesktopUtils.h" />
<ClInclude Include="WindowEmperor.h" />
<ClInclude Include="WindowThread.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -67,6 +69,8 @@
<ClCompile Include="NonClientIslandWindow.cpp" />
<ClCompile Include="NotificationIcon.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" />
<ClCompile Include="WindowEmperor.cpp" />
<ClCompile Include="WindowThread.cpp" />
<ClCompile Include="icon.cpp" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
#include "pch.h"
#include "AppHost.h"
#include "WindowEmperor.h"
#include "resource.h"
#include "../types/inc/User32Utils.hpp"
#include <WilErrorReporting.h>
@@ -83,19 +83,6 @@ static void EnsureNativeArchitecture()
}
}
static bool _messageIsF7Keypress(const MSG& message)
{
return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7;
}
static bool _messageIsAltKeyup(const MSG& message)
{
return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU;
}
static bool _messageIsAltSpaceKeypress(const MSG& message)
{
return message.message == WM_SYSKEYDOWN && message.wParam == VK_SPACE;
}
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
TraceLoggingRegister(g_hWindowsTerminalProvider);
@@ -127,68 +114,9 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// doing that, we can safely init as STA before any WinRT dispatches.
winrt::init_apartment(winrt::apartment_type::single_threaded);
// Create the AppHost object, which will create both the window and the
// Terminal App. This MUST BE constructed before the Xaml manager as TermApp
// provides an implementation of Windows.UI.Xaml.Application.
AppHost host;
if (!host.HasWindow())
::WindowEmperor emperor{};
if (emperor.HandleCommandlineArgs())
{
// If we were told to not have a window, exit early. Make sure to use
// ExitProcess to die here. If you try just `return 0`, then
// the XAML app host will crash during teardown. ExitProcess avoids
// that.
ExitProcess(0);
emperor.WaitForWindows();
}
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
host.Initialize();
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
// dialog experience triggered when you press F7. Official recommendation from the Xaml
// team is to catch F7 before we hand it off.
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
// implementing a custom IF7Listener interface.
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
// been handled we can discard the message before we even translate it.
if (_messageIsF7Keypress(message))
{
if (host.OnDirectKeyEvent(VK_F7, LOBYTE(HIWORD(message.lParam)), true))
{
// The application consumed the F7. Don't let Xaml get it.
continue;
}
}
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
// here, and plumb it through.
if (_messageIsAltKeyup(message))
{
// Let's pass <Alt> to the application
if (host.OnDirectKeyEvent(VK_MENU, LOBYTE(HIWORD(message.lParam)), false))
{
// The application consumed the Alt. Don't let Xaml get it.
continue;
}
}
// GH#7125 = System XAML will show a system dialog on Alt Space. We want to
// explicitly prevent that because we handle that ourselves. So similar to
// above, we steal the event and hand it off to the host.
if (_messageIsAltSpaceKeypress(message))
{
host.OnDirectKeyEvent(VK_SPACE, LOBYTE(HIWORD(message.lParam)), true);
continue;
}
TranslateMessage(&message);
DispatchMessage(&message);
}
return 0;
}

View File

@@ -9,5 +9,6 @@ constexpr int32_t WindowingBehaviorUseNew{ -1 };
constexpr int32_t WindowingBehaviorUseExisting{ -2 };
constexpr int32_t WindowingBehaviorUseAnyExisting{ -3 };
constexpr int32_t WindowingBehaviorUseName{ -4 };
constexpr int32_t WindowingBehaviorUseNone{ -5 };
static constexpr std::wstring_view QuakeWindowName{ L"_quake" };

View File

@@ -102,6 +102,27 @@ public:
winrt::event_token name(const Windows::Foundation::TypedEventHandler<sender, args>& h) { return handler.handlerName(h); } \
void name(const winrt::event_token& token) noexcept { handler.handlerName(token); }
// This is a bit like *FORWARDED_TYPED_EVENT. When you use a forwarded event,
// the handler gets added to the object that's raising the event. For example,
// the TerminalPage might be the handler for the TermControl's
// BackgroundColorChanged event, which is actually implemented by the
// ControlCore. So when Core raises an event, it immediately calls the handler
// on the Page.
//
// Instead, the BUBBLED event introduces an indirection layer. In the above
// example, the Core would raise the event, but now the Control would handle it,
// and raise an event with each of its own handlers.
//
// This allows us to detach the core from the control safely, without needing to
// re-wire all the event handlers from page->control again.
//
// Implement like:
//
// _core.TitleChanged({ get_weak(), &TermControl::_bubbleTitleChanged });
#define BUBBLED_FORWARDED_TYPED_EVENT(name, sender, args) \
TYPED_EVENT(name, sender, args) \
void _bubble##name(const sender& s, const args& a) { _##name##Handlers(s, a); }
// Use this macro to quick implement both the getter and setter for a property.
// This should only be used for simple types where there's no logic in the
// getter/setter beyond just accessing/updating the value.

View File

@@ -341,6 +341,11 @@ void AtlasEngine::SetCallback(std::function<void(HANDLE)> pfn) noexcept
_api.swapChainChangedCallback = std::move(pfn);
}
HANDLE AtlasEngine::GetSwapChainHandle() noexcept
{
return _api.swapChainHandle.get();
}
void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
{
if (_api.enableTransparentBackground != isTransparent)

View File

@@ -68,6 +68,7 @@ namespace Microsoft::Console::Render
// DxRenderer - setter
void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
void SetCallback(std::function<void(HANDLE)> pfn) noexcept override;
HANDLE GetSwapChainHandle() noexcept override;
void EnableTransparentBackground(const bool isTransparent) noexcept override;
void SetForceFullRepaintRendering(bool enable) noexcept override;
[[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;

View File

@@ -1306,6 +1306,20 @@ void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
THROW_HR_MSG(E_UNEXPECTED, "engines array is full");
}
void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine)
{
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
for (auto& p : _engines)
{
if (p == pEngine)
{
p = nullptr;
return;
}
}
}
// Method Description:
// - Registers a callback for when the background color is changed
// Arguments:

View File

@@ -82,6 +82,7 @@ namespace Microsoft::Console::Render
void WaitUntilCanRender();
void AddRenderEngine(_In_ IRenderEngine* const pEngine);
void RemoveRenderEngine(_In_ IRenderEngine* const pEngine);
void SetBackgroundColorChangedCallback(std::function<void()> pfn);
void SetFrameColorChangedCallback(std::function<void()> pfn);

View File

@@ -987,6 +987,10 @@ void DxEngine::SetCallback(std::function<void(const HANDLE)> pfn) noexcept
{
_pfn = std::move(pfn);
}
HANDLE DxEngine::GetSwapChainHandle() noexcept
{
return _swapChainHandle.get();
}
void DxEngine::SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept
{

View File

@@ -58,6 +58,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept override;
void SetCallback(std::function<void(const HANDLE)> pfn) noexcept override;
HANDLE GetSwapChainHandle() noexcept override;
void SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept override;
bool GetRetroTerminalEffect() const noexcept override;

View File

@@ -100,6 +100,7 @@ namespace Microsoft::Console::Render
// DxRenderer - setter
virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {}
virtual void SetCallback(std::function<void(HANDLE)> pfn) noexcept {}
virtual HANDLE GetSwapChainHandle() noexcept { return nullptr; }
virtual void EnableTransparentBackground(const bool isTransparent) noexcept {}
virtual void SetForceFullRepaintRendering(bool enable) noexcept {}
[[nodiscard]] virtual HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; }