Compare commits

..

155 Commits

Author SHA1 Message Date
Dustin L. Howett
0e301b010d Migrate spelling-0.0.21 changes from main 2022-07-15 14:28:10 -05:00
Mike Griese
27887603b4 tab drag/drop is actually kinda just... done 2022-07-15 14:28:10 -05:00
Mike Griese
87c840b381 Merge branch 'dev/migrie/oop/2/wandavision' into dev/migrie/oop/2/loki 2022-07-15 12:34:34 -05:00
Mike Griese
91616885db Actually, movePane is a lot harder, and I honestly think we can punt that 2022-07-15 12:33:23 -05:00
Mike Griese
089c015347 more notes to go with the parent commit 2022-07-15 11:57:45 -05:00
Mike Griese
dea94d51ad this and 83649075a should probably go to the parent branch 2022-07-15 11:49:16 -05:00
Mike Griese
83649075a7 the final_release thing Dustin was talking about. I think I still need to synchronously close the connection though 2022-07-15 11:11:31 -05:00
Mike Griese
e9375d67f1 Remove the tab from the previous window, as well.
When we move a tab to a new window, we'll hang on to a reference to all the
  content procs, and then remove the whole tab from the UI tree. We'll hang on
  to those refs until the new tab gets attached, and the controls take the next
  ContentProcess reference.
2022-07-15 10:35:02 -05:00
Mike Griese
4b4e057a75 one shot one opportunity 2022-07-15 06:20:19 -05:00
Mike Griese
35c82d37f0 this is important for moving, yknow, the panes 2022-07-14 16:47:29 -05:00
Mike Griese
4acbffb0e3 can I get a whoop whoop 2022-07-14 16:40:39 -05:00
Mike Griese
aaefdf4408 Add comments 2022-07-14 15:44:42 -05:00
Mike Griese
1f2bb760eb Pane officially opened via the serialized actions, via across the process boundary 2022-07-14 15:38:37 -05:00
Mike Griese
5cb4ab1a9e notes 2022-07-14 13:05:04 -05:00
Mike Griese
cfe879da96 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.
2022-07-14 13:01:00 -05:00
Mike Griese
ac62de78a2 IT ABSOLUTELY WORKED 2022-07-13 15:49:04 -05:00
Mike Griese
9870eb1d39 Holy shit all the plumbing worked on the first try 2022-07-13 15:11:26 -05:00
Mike Griese
e06ce39811 more cleanup 2022-07-13 13:08:56 -05:00
Mike Griese
997212f9c5 cleanup 2022-07-13 13:03:40 -05:00
Mike Griese
2ab0a50a11 thank god that horror is cleaned up 2022-07-13 12:40:52 -05:00
Mike Griese
376939cef8 Now panes run out of proc too 2022-07-13 12:25:33 -05:00
Mike Griese
15a7d49f4d Start using PreparedContent everywhere ; add _asyncSplitPane* methods
There were entirely too many places initializing a ControlSettings and Profile with evaluateSettings it and that was a chore. A PreparedContent is a handly little bundle of that, with the IAsyncOperation for starting the content.

We need that to pass to the pane operations, which aren't yet hooked up
2022-07-13 11:53:37 -05:00
Mike Griese
ef595b497a Convert duplicateTab to be out-of-proc 2022-07-13 09:38:12 -05:00
Mike Griese
97676cedd8 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/endgame 2022-07-13 06:28:55 -05:00
Mike Griese
91bf709ca6 Fix a bad null deref (#13493)
Yep this is my bad. I was trying to fix a bug where only having one of `background` or `unfocusedBackground` would cause the other to never get set to the default. In attempting to force all the logic into an if statement, I messed up the order of operations. My bad 😢 

Regressed in #13456
2022-07-12 19:53:57 +00:00
Carlos Zamora
7abed9f6fb Add keyboard navigation to hyperlinks (#13405)
## Summary of the Pull Request
Adds support to navigate to clickable hyperlinks using only the keyboard. When in mark mode, the user can press [shift+]tab to go the previous/next hyperlink in the text buffer. Once a hyperlink is selected, the user can press <kbd>Ctrl+Enter</kbd> to open the hyperlink.

## References
#4993 

## PR Checklist
* [x] Closes #6649
* [x] Documentation updated at https://github.com/MicrosoftDocs/terminal/pull/558

## Detailed Description of the Pull Request / Additional comments
- Main change
   - The `OpenHyperlink` event needs to be piped down to `ControlCore` now so that we can open a hyperlink at that layer.
   - `SelectHyperlink(dir)` searches the buffer in a given direction and finds the first hyperlink, then selects it.
   - "finding the hyperlink" is the tough part because the pattern tree takes in buffer coordinates, searches through the buffer in that given space, then stores everything relative to the defined space. Normally, the given buffer coordinates would align with the viewport's start and end. However, we're now trying to search outside of the viewport (sometimes), so we need to manage two coordinate systems at the same time.
   - `convertToSearchArea()` lambda was used to convert a given coordinate into the search area coordinate system. So if the search area is the visible viewport, we spit out a viewport position. If the search area is the _next_ viewport, we spit out a position relative to that.
   - `extractResultFromList()` lambda takes the list of patterns from the pattern tree and spits out the hyperlink we want. If we're searching forwards, we get the next one. Otherwise, we get the previous one. We explicitly ignore the one we're already on. If we don't find any, we return `nullopt`.
   - Now that we have all these cool tools, we use them to progressively search through the buffer to find the next/previous hyperlink. Start by searching the visible viewport _after_ (or _before_) the current selection. If we can't find anything, go to the next "page" (viewport scrolled up/down). Repeat this process until something comes up.
   - If we can't find anything, nothing happens. We don't wrap around.
- Other relevant changes
   - the `copy` action is no longer bound to `Enter`. Instead, we manually handle it in `ControlCore.cpp`. This also lets us handle <kbd>Shift+Enter</kbd> appropriately without having to take another key binding.
   - `_ScrollToPoint` was added. It's a simple function that just scrolls the viewport such that the provided buffer position is in view. This was used to de-duplicate code.
   - `_ScrollToPoint` was added into the `ToggleMarkMode()` function. Turns out, we don't scroll to the new selection when we enter mark mode (whoops!). We _should_ do that and we should backport this part of the change too. I'll handle that.
   - add some clarity when some functions are using the viewport position vs the buffer position. This is important because the pattern interval tree works in the viewport space.

## Validation Steps Performed
- case: all hyperlinks are in the view
   -  get next link
   -  get prev link
-  case: need to scroll down for next link
-  case: need to scroll up for next link
2022-07-12 19:51:37 +00:00
Mike Griese
b4a52c847f Send focus events even in ReadOnly mode (#13483)
Does what it says on the tin. When we get focused, temporarily turn off readonly mode, as to not pop the dialog when the focus sequence is eventually sent to the connection. 

* closes #13461
2022-07-12 19:48:34 +00:00
Mike Griese
3f21996589 A pile of Theming nits fixes (#13465)
The main fix here is for the caption button colors. If you had a dark OS/app theme, and a light titlebar, we'd end up with light glyphs, so the caption buttons would be impossible to find.

There's also a pile of nits from #12992 in here. Probably enough to close #13456 out, but I'll let Dustin be the judge. 

Filing today, to get in for 1.16 selfhost (@DHowett)
2022-07-12 12:30:21 +00:00
Leonard Hecker
fd74d59c6c Extend test coverage of TestDbcsWriteRead (#13316)
`TestDbcsWriteRead` failed to properly test all text input scenarios.
In order to write wide characters with `CHAR_INFO` structs using the `W` APIs
one must still repeat the `CHAR_INFO` twice, once with `COMMON_LVB_LEADING_BYTE`
and once with `COMMON_LVB_TRAILING_BYTE` set in the attributes. This is because
`CHAR_INFO` APIs are column oriented. `TestDbcsWriteRead` on the other hand
only tested the scenario of sending wide characters without those flags.

This commit fixes the problem by introducing a `UnicodeDoubled` mode and
increases the number of test patterns from 14 to 17. Due to the existing
code having been written with the false assumption in mind, a simpler
modification resulted in +1100 LOC. As such I opted to rewrite the code instead
and replaced the potentially buggy output generators with static arrays.

Code page 437 tests were removed, as it contains no DBCS characters anyways.

This is preliminary work for #8000.
2022-07-11 22:03:01 +00:00
James Holderness
04478d1df0 Add line breaks in the debug tap output (#13475)
## Summary of the Pull Request

When the debug tap converts control characters into visible glyphs, it
ends up losing the structure of the output, and that can sometimes make
things difficult to read. This PR attempts to alleviate that problem by
reinjecting an actual line break in the debug stream whenever an `LF`
control is received.

## PR Checklist
* [x] Closes #12312
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number
where discussion took place: #12312

## Validation Steps Performed

I've tested the updated debug tab with a number of different shells, and
also a couple of different apps. When there aren't many linefeeds in the
output, it's obviously not going to make much of a difference, but when
there are, I think it definitely improves the readability.
2022-07-11 19:09:38 +00:00
Davide Giacometti
589286a357 Don't set full screen to quake window (#13473)
If launch mode is set to full screen quake window is opened in full
screen.

## Validation Steps Performed
- Set startup > launch mode > full screen
- Launch quake window
- Quake window shouldn't be opened in full screen

Closes #12894
2022-07-11 18:49:01 +00:00
Leonard Hecker
d74b66a0a7 AtlasEngine: Improve glyph generation performance (#13477)
#13458 added the ability to reuse tiles from our glyph atlas texture
so that we stop running out of GPU memory for complex Unicode.
This however can result in our glyph generation being a performance issue in
edge cases, to the point that the application may feel outright unuseable.

CJK glyphs for instance can easily exceed the maximum atlas texture size
(twice the window size), but take a significant amount of CPU and GPU time to
rasterize and draw, which results in "jelly scrolling" down to ~1 FPS.
This PR improves the situation of the latter half by directly drawing
glyphs into the texture atlas without an intermediate scratchpad texture.

This reduces GPU usage by 96% on my system (33% -> 2%) which improves general
render performance by ~100% (15 -> 30 FPS). CPU usage remains the same however,
but that's not really something we can do anything about at this time.
The atlas texture is already our primary means to reduce the CPU cost after all.

## Validation Steps Performed
* Disable V-Sync for OpenConsole in NVIDIA Control Panel
* Enable `debugGlyphGenerationPerformance`
* Print the entire CJK block U+4E00..U+9FFF
* Measure the above GPU usage and FPS improvements 
  (Alternatively: Just scroll around and judge the "jellyness".)
2022-07-11 18:31:21 +00:00
Mike Griese
ae5b04f7b2 Merge branch 'dev/migrie/oop/2/infinity-war' into dev/migrie/oop/2/endgame 2022-07-11 13:01:04 -05:00
Mike Griese
3434208947 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-07-11 13:00:38 -05:00
Mike Griese
32379c29f0 Wrap the tooltips on the new tab button (#13463)
In non-en-us locales, these tooltips can get really long and get clipped.

Closes MSFT:39603031
See Also #9913
2022-07-11 17:13:55 +00:00
James Holderness
cd2166aedf Allow leading spaces to bypass console aliases (#13476)
## Summary of the Pull Request

When you create a console alias that overrides an existing command, it
should still be possible to execute the original command by prefixing it
with a space. However, at some point in the past, there was an attempt
to improve the usability by trimming leading spaces, and that ended up
breaking this functionality. This PR reverts that change, so leading
spaces can once again be used to bypass an alias.

## PR Checklist
* [x] Closes #4189
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number
where discussion took place: #4189

## Validation Steps Performed

I've updated the existing alias unit test for leading spaces to match
the new behavior, i.e. it now confirms that a command with leading
spaces will not match the alias.

I've also manually confirmed that the `doskey` test case reported in
issue #4189 is now working as expected.
2022-07-11 12:36:08 +00:00
Leonard Hecker
66f4f9d9ea AtlasEngine: Implement LRU invalidation for glyph tiles (#13458)
So far AtlasEngine would only grow the backing texture atlas once it gets full,
without the ability to reuse tiles once it gets full. This commit adds LRU
capabilities to the glyph-to-tile hashmap, allowing us to reuse the least
recently used tiles for new ones once the atlas texture is full.
This commit uses a quadratic growth factor with power-of-2 textures,
resulting in a backing atlas of 1x to 2x the size of the window.
While AtlasEngine is still incapable of shrinking the texture, it'll now at
least not grow to 128MB or result in weird glitches under most circumstances.

## Validation Steps Performed
* Print `utf8_sequence_0-0x2ffff_assigned_printable_unseparated.txt`
  from https://github.com/bits/UTF-8-Unicode-Test-Documents
* Scroll back up to the top
* PowerShell input line is still there rendering as ASCII. 
2022-07-11 12:23:48 +00:00
PankajBhojwani
bbc570d107 Implement MVVM for the Rendering page (#13391)
## Summary of the Pull Request
Implements a `RenderingViewModel` for the Rendering page in the SUI

## PR Checklist
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Rendering page still works
2022-07-08 22:50:38 +00:00
PankajBhojwani
238b5c2e22 Implement MVVM for the Interaction page (#13378)
## Summary of the Pull Request
Implements an `InteractionViewModel` for the Interaction page in the SUI

## PR Checklist
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Interaction page still works
2022-07-08 22:13:15 +00:00
Sergey
55dd4af9cb Add ability to select where new tab will appear (#13421)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Adds ability to select where new tab will appear: at the end or after currently selected tab.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #12955 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
PR adds setting "NewTabPosition" in global appearances with dropdown list and uses it then creating new tabs. This setting does not affect settings tab position (should it?).
There should also be a documentation update, I'm just waiting to see if this change even acceptable.
<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2022-07-08 21:46:01 +00:00
PankajBhojwani
2970017243 Implement MVVM for the GlobalAppearance page (#13390)
## Summary of the Pull Request
Implements a `GlobalAppearanceViewModel` for the GlobalAppearance page in the SUI

## PR Checklist
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Global appearance page still works
2022-07-08 20:49:03 +00:00
PankajBhojwani
1faf67fba3 Implement MVVM for the Launch page (#13377)
## Summary of the Pull Request
Implements a `LaunchViewModel` for the Launch page in the SUI

## PR Checklist
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA

## Validation Steps Performed
Launch page still works
2022-07-08 11:55:37 +00:00
Mike Griese
024b9fc0f4 Add a setting for the unfocused tabRow background color (#13049)
Adds `tabRow.unfocusedBackground` to the theme properties.

When provided, the window will use this ThemeColor as the color of the tab row when the window is inactive. 

When omitted, the window will fall back to the default tab row color, `{"key": "TabViewBackground"}` from our App.xaml.

* [ ] tests added.
* [x] Closes #4862
* [ ] Needs a whole pile of docs updates, which we'll do at the end here.


This actually helped validate #12992 quite a bit. I found a bunch of bugs concerning null colors, null objects. Json parsing is hard 😛
2022-07-07 17:57:48 +00:00
Mike Griese
aa4e9f5414 Also hide the scroll marks when hiding the scrollbar (#13454)
This one's pretty obvious. I added another scrollbar sized grid to the terminal,
but I forgot to collapse it when the user requests `"scrollbarState": "hidden"`.

* [x] Closes #13446
* [x] I work here
2022-07-07 17:30:50 +00:00
Mike Griese
76b00e3b31 Add support for tab.background to themes (#13178)
## Summary of the Pull Request

Adds support for the `tab.background` property to themes. This is also a ThemeColor, so it accepts, colors, `accent`, and `terminalBackground`, just like everything else.

## References
* See #3327 
* ⚠️ targets #12992 ⚠️


## PR Checklist
* [x] Closes #702
* [x] I work here
* [ ] Tests added/passed
* [n/a] Requires documentation to be updated - YUP

## Detailed Description of the Pull Request / Additional comments

I apparently left behing an optional color in TerminalTab for theme colors some time ago, just never used it. Crazy, huh?

## Validation Steps Performed

gifs below
2022-07-07 08:26:17 -05:00
Mike Griese
07d58a800c Initial Theme support (#12992)
##### ⚠️ targeting 1.15

## Summary of the Pull Request

Adds support for Themes, a new type of customization for the Terminal. Themes allow the user to customize elements of the Terminal window itself. In this first iteration, this PR adds support for two main properties:
* enabling Mica as the window backdrop
* changing the tab row color (read: changing the titelbar color)

These represent the most important asks of theming in the Terminal. The properties added in this PR are:

* Theme color variants:
    - `"#rrggbb"` or `"#aarrggbb"`
    - `"accent"`
    - `"terminalBackground"`
* Properties (_listed here in dot notation, but implemented as sub-objects_)
    - `tabRow.background`: accepts a ThemeColor (above)
    - `window.applicationTheme`: accepts one of `{"system", "light", "dark"}`
    - `window.useMica`: accepts a boolean, defaults to false.

## References
* As first described in #3327
* spec'd in #12530

## PR Checklist
* [x] Sorta enables #10509, but doesn't close it. That'll need more comprehensive changes to the titlebar code.
  * **update**: I totally disabled mica, but left the serialization code. It just seems silly without #10509. 
* [x] Closes #1963
* [x] Closes #3774 
* [x] Closes #12939
* [x] Does the bulk of the #3327 work, but I'm going to leave that open since that's become my megathread for everything related to theming.
* [x] I work here
* [x] Tests added/passed
* [ ] Requires documentation to be updated - **SURE DOES**

## Detailed Description of the Pull Request / Additional comments

### --> GO READ #12530 <--

Seriously. 

These themes aren't customizable in the SUI currently. You can change the active theme, and the UI will show all of the defined themes, but they're not editable there. 

They don't layer. You'll need to define your own themes.

Thay can't come from fragments. This is a really cool future idea, but not implemented in this v0.

The sub objects have some gnarly macros to generate a lot of the serialization code for you. 

### TODOs

* [x] I still have yet to establish what the accent color algorithm is. This might be proprietary and require a ThemeHelpers workaround.
* [x] Make sure `terminalBackground` & the SUI result in something sensible
* [x] Make sure runtime BG changes work with `terminalBackground`. One time, they didn't. `printf "\x1b]11;rgb:ff/00/ff\x07"`
* [x] Acrylic Terminal BG's look weird, like, the opacity is always 50% or something. And the tab row looks all wrong then.

## Validation Steps Performed

This is the blob I've been testing with:
<details>

```jsonc
    // "useAcrylicInTabRow": true,
    "theme": "my dark",
    // "theme": "Edge",
    "theme": "orangey",
    "theme": "WHITE",
    // "theme": "terminal",
    "themes": [
        {
            "name": "my dark",
            "window": {
                "applicationTheme": "dark",
                "useMica": true,
            },
            "tabRow": {
                "background": "#00000000",
            },
        },
        {
            "name": "Edge",
            "tabRow": { "background": "accent" },
            "window": { "applicationTheme": "system" }
        },
        {
            "name": "orangey",

            "window": {
                "applicationTheme": "light",
                "useMica": true,
            },
            "tabRow": {
                "background": "#ff8800",
            },
        },
        {
            "name": "WHITE",
            "window": {
                "applicationTheme": "dark",
                "useMica": true,
            },
            "tabRow": {
                "background": "#FFFFFF",
            },
        },
        {
            "name": "terminal",

            "window": {
                "applicationTheme": "dark",
                "useMica": false,
            },
            "tabRow": {
                "background": "terminalBackground",
            },
        },
    ]
```
    
</details>
2022-07-07 06:54:54 -05:00
Mike Griese
f893f7516a Merge branch 'dev/migrie/oop/2/infinity-war' into dev/migrie/oop/2/endgame 2022-07-06 13:14:37 -05:00
Mike Griese
702e15ce5a Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-07-06 13:14:17 -05:00
Mike Griese
ff23726b7c Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/endgame 2022-07-06 07:04:33 -05:00
Mike Griese
7675354b2d notes 2022-07-06 07:04:22 -05:00
Dustin L. Howett
16028dee8b RelEng: Teach ServicingPipeline to infer the version if it's null (#13439)
```
% .\tools\ReleaseEngineering\ServicingPipeline.ps1
Inferred servicing version 1.14
PICK f025c53dba: Remove the fallback to wsl.exe when HKCU\...\Lxss doesn't exist (#13436)
 OK
```
2022-07-06 06:11:53 -05:00
dansmor7
c6b67aad4b Update tab hover colors (#13434)
The hover tab color used to be generated from the selected tab color, which would end up lighter or darker, and white-gray colors would end up pink.
It is now simply the selected tab color with 60% opacity. This is also how brushes are created for accent buttons and color buttons (although with different opacity levels).
2022-07-05 16:56:01 -05:00
Leonard Hecker
bd403dc8e7 Switch to VS17 and C++20 (#13368)
C++20 goodies include:
* 3-way comparison operator `<=>` and `operator==() = default`
* designated initializers
* constraints and concepts
* abbreviated function templates (`void foo(auto bar)`)
* pack-expansions in lambda init-captures
* heterogeneous lookups in `unordered_set` / `unordered_map`
* `consteval` / `constinit`
* `starts_with` / `ends_with` for `string` / `string_view`
* `midpoint`, `lerp`
* `<bit>`
* `<ranges>`
* `<span>`
* `<barrier>`
* `<latch>`
* `<semaphore>`
2022-07-05 21:06:05 +00:00
Dustin L. Howett
f025c53dba Remove the fallback to wsl.exe when HKCU\...\Lxss doesn't exist (#13436)
The main result of this fallback is that we attempt to launch wsl.exe
when the user hasn't installed or interacted with WSL. On our test
machines, that results in the creation of a wsl.exe process that tells
us precisely nothing; on WDAC managed machines it results in an Event
Log entry about spawning another (possibly blocked) process.

The registry is more reliable, and if the "API" it provides changes we
can just rev terminal.

Closes #11716
2022-07-05 20:47:20 +00:00
Carlos Zamora
66ecb0bd63 Update renderer on SwitchSelectionEndpoint (#13435)
## Summary of the Pull Request
In #13370, we should be notifying the renderer that the selection changed. Minor oversight and simple fix.

## References
#4993
#13370 
Closes #13413
2022-07-05 20:38:22 +00:00
Leonard Hecker
95a19624a4 Fix a race condition in CTerminalHandoff::s_StopListening (#13410)
This commit fixes a minor race condition covered as part of #13368.
The member `_pfnHandoff` was read without the mutex `_mtx` being locked first.
The issue was solved by acquiring the lock early and running the entire
`s_StopListening` function with that lock held.
2022-07-05 20:25:14 +00:00
Mike Griese
86dfefa690 Fix the bellsound schema, for real (#13433)
As noted in https://github.com/microsoft/terminal/pull/11511#issuecomment-1173541384.

Missed in #13035
2022-07-05 14:33:00 -05:00
PankajBhojwani
478c2c3613 Update schema with scroll marks actions and settings (#13414)
Update schema with the settings/actions added in #12948 

## PR Checklist
* [x] Closes #13404
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Schema updated.
2022-07-01 14:24:34 -07:00
Dustin Howett
a579566a1d version: bump to 1.16 on main 2022-06-30 23:02:26 -05:00
dependabot[bot]
f1b6b17c58 Bump Newtonsoft.Json from 12.0.3 to 13.0.1 (#13363)
* Bump Newtonsoft.Json from 12.0.3 to 13.0.1 in /dep/nuget

Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Complete the Newtonsoft.Json upgrade to 13.0.1

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dustin Howett <duhowett@microsoft.com>
2022-06-30 22:31:40 -05:00
Carlos Zamora
7b8a53c030 Introduce switchSelectionEndpoint action (#13370)
## Summary of the Pull Request
Introduces the `switchSelectionEndpoint` action which switches whichever selection endpoint is targeted when a selection is present. For example, if you are targeting "start", `switchSelectionEndpoint` makes it so that now you are targeting "end". This also updates the selection markers appropriately.

## References
Spec - #5804 
#13358
Closes #3663

## Detailed Description of the Pull Request / Additional comments
Most of the code is just standard code of adding a new action. Other than that, we have...
- if there is no selection, the action fails and the keybinding is passed through (similar to `copy()`)
- when we update the selection endpoint, we need to also update the "pivot". This ensures that future calls of `UpdateSelection()` respect this swap.
- [Corner Case] if the cursor is being moved, we make it so that you basically "anchored" an endpoint and you don't have to hold shift anymore.
2022-07-01 01:44:39 +00:00
Dustin Howett
d6a9e9ffb6 Merge remote-tracking branch 'openconsole/inbox' 2022-06-30 20:17:07 -05:00
Dustin Howett
eb1d6b599e OS PR 7534836: HOTFIX MSFT-40226902 by leaking the onecore renderers on exit
## Why is this change being made?
Our conhost OneCore backend isn't as thoroughly tested as our Win32 one and fell victim to the general "bugginess" of our shutdown handling centered around `ServiceLocator::RundownAndExit`. In the past we simply leaked all resources, but changed it so that a cleanup on exit occurs, so that we can track resource leaks for instance. This broke OneCore which has a more delicate shutdown than Win32.

## What changed?
This commit reverts changes being made in 9d7a46f64c and after.

## How was the change tested?
This change was tested in a OneCore VM by repeatedly spawning subprocesses and ensuring they exit in a timely manner and without unexpected crashes.

Related work items: MSFT-40226902, MSFT-22128499

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 0683758a0846aefbbe50730c3cd336623328ddeb
2022-07-01 01:11:47 +00:00
Carlos Zamora
2cd2351deb Miscellaneous bug fixes for Mark Mode (#13358)
## Summary of the Pull Request
1. [copy on select] when manually copying text (i.e. kbd or right-click) while in mark/quick-edit mode, we now dismiss the selection.
2. `Enter` is now bound to copy by default. 
    - This works very well with mark mode and provides a more consistent behavior with conhost's selection experience overall.
    - "why not hardcode `Enter` as a way to copy when in mark mode?"
        - In an effort to make this as configurable as possible, I decided to make it a configurable keybinding, but am open to suggestions.
3. selection markers
   a. we now hide the selection markers when multi-clicking the terminal.
   b. selection markers are now properly shown when a single cell selection exists
      - Prior to this PR, any single cell selection would display both markers. Now, we actually track which endpoint we're moving and display the appropriate one.
4. ensures that when you use keyboard selection to move past the top/bottom of the scroll area, we clamp it to the origin/bottom-right respectively. The fix is also better here in that it provides consistent behavior across all of the `_MoveByX` functions.
5. adds `toggleBlockSelection` to the schema

## References
#13053 

## Validation Steps Performed
Did a whole flowchart of expected behavior with copy on select:
- enable `copyOnSelect`
   - make a selection with the mouse
      -  right-click should copy the text --> clear the selection --> paste
   - use keyboard selection to quick-edit the existing selection
      -  `copy` action should clear the selection
      -  right-click should copy the text --> clear the selection --> paste

Played with selection markers a bit in mark mode and quick edit mode. Markers are updating appropriately.
2022-06-30 20:07:07 -05:00
msftbot[bot]
843e61aa8a Migrate FabricBot Tasks to Config-as-Code (#13397)
FabricBot is now a [config-as-code-only] platform. As a result, while
you can still use the [FabricBot Configuration Portal] to modify your
FabricBot configuration, you can no longer save the changes. The only
way to save changes to your configuration at the moment is to _export
configuration_ from the portal and upload the exported configuration to
`.github/fabricbot.json` in your repository. In this pull request, we
are adding your FabricBot configuration to your repository at
`.github/fabricbot.json` so that you can make changes to it going
forward.

While the [FabricBot Configuration Portal] is the *only way* to modify
your FabricBot configuration at the moment, we have a feature on our
backlog to publish the JSON schema defining the structure of the
FabricBot configuration file. With the JSON schema, you can (1) use a
plaintext editor of your choice to modify the FabricBot configuration
file and use the schema to validate the file after editing or (2)
[configure VS Code] to use the schema when editing FabricBot
configuration file to take advantage of convenience features such as
automatic code completion and field description on mouseover.

[config-as-code-only]: https://eng.ms/docs/products/1es-data-insights/merlinbot/extensions/bot-config-as-code
[FabricBot Configuration Portal]: https://portal.fabricbot.ms/bot/?repo=microsoft/terminal
[configure VS Code]: https://code.visualstudio.com/Docs/languages/json#_json-schemas-and-settings

Co-authored-by: msftbot[bot] <48340428+msftbot[bot]@users.noreply.github.com>
Co-authored-by: Dustin Howett <duhowett@microsoft.com>
2022-06-30 19:59:51 -05:00
Carlos Zamora
f785168aac Remove most uses of CompareInBounds (#13244)
## Summary of the Pull Request
Replaces most uses of `Viewport::CompareInBounds()` with `til::point`'s `<` and `>` operators. `CompareInBounds` has been the cause of a bunch of UIA crashes over the years. Replacing them entirely ensures that the `FAILFAST_IF` isn't ever touched.

Unfortunately, we still need `IncrementInBounds` and `DecrementInBounds` to have support for that exclusive end.

## References
#13183
2022-07-01 00:06:33 +00:00
Leonard Hecker
8fba292bd7 Fix coordSizeUnscaled calculation in AtlasEngine (#13384)
This commit fixes a bug causing the OpenConsole to get increasingly larger
every time the font is changed when the AtlasEngine is active.
The only impact this bug had on Windows Terminal is that the
`font-size` in HTML and RTF selection copies are too large.

## Validation Steps Performed
* OpenConsole window size doesn't change when
  switching between main and alt buffer 
2022-06-30 21:16:19 +00:00
Dustin Howett
9e8427d81e Merge remote-tracking branch 'openconsole/inbox' into main 2022-06-24 17:51:35 -05:00
Dustin Howett
059b53bf94 Migrate OSS up to 8962f88f9 2022-06-24 17:39:59 -05:00
Dustin Howett
4d48f305a1 OS PR 7517171: Fix a race condition in ServiceLocator::RundownAndExit
[Git2Git] Merged PR 7517171: Fix a race condition in ServiceLocator::RundownAndExit

The whole premise of RundownAndExit is that one thread enters it, runs
down the console and terminates it. One thread enters, 0 threads leave.

After some recent console locking changes, we found that on OneCore
devices it was possible for two threads (the I/O thread and the coniosrv
Input thread) to try to rundown and exit at the same time.

This SRWLOCK prevents that from happening.

Fixes #40146639

Related work items: #40146639 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev ff235c6e49e065bccc937dee91f43ff310b5743c

Related work items: #40146639
2022-06-24 22:20:36 +00:00
Dustin Howett
06e2317012 OS PR 7517072: Force the double-click tests to have a high click timeout
[Git2Git] Merged PR 7517072: conhost: Force the double-click tests to have a high click timeout

The conhost tests might run somewhere that does not have a double click time.
We should hardcode a value for the purposes of testing.

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev 3c2620dab577e16e8672b369e4dfcde7c02881a1

Related work items: MSFT-40150725
2022-06-24 21:52:13 +00:00
Dustin Howett
4071df73ba OS build fixes on top of 24a53d496
This introduces the build rules for midi.lib to the OS!

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev fb066544f112ae491bb1d1f73578bc47fa167b41
2022-06-24 20:58:03 +00:00
Mike Griese
19605bf75b A tab should be able to be created without a pane, and this works for that 2022-06-23 15:27:01 -05:00
Dustin L. Howett
8962f88f90 Disable the VT color quirk for pwsh and modern inbox powershell (#13352)
In #6810, we introduced a "quirk" for all known versions of PowerShell
that suppressed their requests for black background/gray foreground.
This was done to avoid an [issue in PSReadline] where it would paint
black bars all over the screen if the default background color wasn't
the same as the ANSI black color.

Years have passed since that quirk was introduced. The underlying bug
was fixed, and the fix was released broadly long ago. It's time for us
to remove the quirk... almost.

Terminal still runs on versions of Windows that ship a broken version of
PSReadline. We must maintain the quirk there -- the user can't do
anything about it, and we would make their experience worse if we
removed the quirk entirely.

PowerShell 7.0 also ships a broken version of PSReadline. It is still in
support for another 6 months, but updates have been available for some
time. We can encourage users to update.

Therefore, we only need the quirk for Windows PowerShell, and then only
for specific versions of Windows.

_Inside Windows_, we don't even need that: we're guaranteed to be built
alongside a fixed version of PowerShell!

Closes #6807

[issue in PSReadline]: https://github.com/PowerShell/PSReadLine/issues/830#issuecomment-650508857
2022-06-23 15:40:27 +00:00
Mike Griese
eb4b2a7534 The first tab now opens out-of-proc too
We're gonna definitely run into a lot of pain here. Like, the initialization
  finishes now bbefore the tabs are created, so the "no tabs got opened" check
  needs to be changed.

  I don't think I need both resume_bg's in CreateContent and also in AsyncCreateTab
2022-06-23 09:05:00 -05:00
Carlos Zamora
72a4e936cc Fix moving selection past scroll area (#13318)
## Summary of the Pull Request
Introduced in #10824, this fixes a bug where you could use keyboard selection to move below the scroll area. Instead, we now clamp movement to the mutable viewport (aka the scrollable area). Specifically, we clamp to the corners (i.e. 0,0 or bottom right cell of scroll area).

## Validation Steps Performed
 (no output) try to move past bottom of viewport
 (with output, at bottom of scroll area) try to move past viewport
 (with output, NOT at bottom of scroll area) try to move past viewport
 try to move past top of viewport
2022-06-22 23:16:24 +00:00
Mike Griese
1f083cbd89 differentiate these two, for clarity 2022-06-22 16:45:38 -05:00
Dustin Howett
1ac7fe16ae Migrate OSS up to 24a53d496 2022-06-22 15:35:12 -05:00
Dustin Howett
ff7a632f0f Import OS build fixes on top of 7dbe741e1 2022-06-22 20:29:49 +00:00
Mike Griese
797ebae6e1 guess what, this totally isn't used! 2022-06-22 14:45:44 -05:00
Mike Griese
4f7e99123a Merge branch 'dev/migrie/oop/2/infinity-war' into dev/migrie/oop/2/endgame 2022-06-22 11:20:16 -05:00
Mike Griese
b8cd2b239f that was vestigial anyways 2022-06-22 11:19:24 -05:00
Leonard Hecker
24a53d4968 Dismiss Terminal-by-default banner on handoff (#13344)
It's not useful to notify users that WT can be made the default if it's already
clearly being used for handoff. This commit will suppresses the banner then.

## PR Checklist
* [x] Closes #13314
* [x] I work here

## Validation Steps Performed
* Modify `TerminalPage::ShowSetAsDefaultInfoBar` to not check for
  `CascadiaSettings::IsDefaultTerminalSet()`
* Set Terminal Dev as the default
* Set incoming connections to open in the latest Terminal window
* Delete `state.json` after every test below
* Launching Terminal Dev shows the banner 
  Launching `cmd.exe` dismisses the banner in the current Terminal 
* Launching `cmd.exe` launches Terminal Dev without banner 
2022-06-21 23:02:29 +00:00
Mike Griese
023eb75550 this was definitely a bug 2022-06-21 16:52:45 -05:00
Dustin L. Howett
0b97c7b5ca Strip XAML files from the MSIX payload (#13351)
These files are vestigial, because we are also shipping (either as loose
files or embedded in resources.pri) precompiled xbf/xaml binary format
files.

This saves us almost 500kb on disk.

Fixes #11687

Validation
----------
I ran a local build and saw that it produced a working Terminal, packaged
and unpackaged.
2022-06-21 13:37:42 -05:00
Mike Griese
f7d93849a8 this was LOAD BEARING 2022-06-21 13:15:21 -05:00
Mike Griese
7707716b08 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-06-21 11:44:43 -05:00
Carlos Zamora
6436e712e7 Add selection marker overlays for keyboard selection (#10865)
## Summary of the Pull Request
This introduces a selection marker overlay that tells the user which endpoint is currently being moved by the keyboard. The selection markers are respect font size changes and `cursor` color.

## References
#715 - Keyboard Selection
#2840 - Keyboard Selection Spec
#5804 - Mark Mode Spec

## Detailed Description of the Pull Request / Additional comments
- `TermControl` layer:
   - Use a canvas (similar to the one used for hyperlinks) to be able to draw the selection markers.
   - If we are notified that the selection changed, update the selection markers appropriately.
   - `UpdateSelectionMarkersEventArgs` lets us distinguish between mouse and keyboard selections.  `ClearMarkers` is set to true in the following cases...
      1.  Mouse selection, via SetEndSelectionPoint
      2. `LeftClickOnTerminal`, pretty self-explanatory
      3. a selection created from searching for text
- `ControlCore` layer:
   - Responsible for notifying `TermControl` to update the selection markers when a selection has changed.
   - Transfers info (the selection endpoint positions and which endpoint we're moving) from the terminal core to the term control.
- `TerminalCore` layer:
   - Provides the viewport position of the selection endpoints.


## Validation Steps Performed
- mouse selection (w/ and w/out shift) --> no markers
- keyboard selection --> markers
- markers update appropriately when we pivot the selection
- markers scroll when you hit a boundary
- markers take the color of the cursor color setting
- markers are resized when the font size changes
2022-06-20 17:47:53 -05:00
James Holderness
08c2f350e6 Make sure conpty is flushed before clearing scrollback (#13324)
## Summary of the Pull Request

When you execute a `cls` in the cmd shell, or `Clear-Host` in
PowerShell, we have a pair of shims that attempt to detect those
operations and forward an `ED3` sequence to conpty to clear the
scrollback.

If there was a linefeed at the bottom of the viewport immediately 
prior to those functions being called, that event might still be
pending, and only forwarded to conpty after the `ED3`. The result
then is a line pushed into the scrollback that shouldn't be there.

This PR tries to avoid that situation by forcing the renderer to
flush before the `ED3` sequence is sent.

## References

The `cls` and `Clear-Host` shims were originally added in PR #5627.

## PR Checklist
* [x] Closes #5770
* [x] Closes #13320
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not
checked, I'm ready to accept this work might be rejected in favor of a
different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed

I've manually tested in PowerShell with `echo Hello; Clear-Host` (this
is the only way I could reliably reproduce the original problem), and in
the cmd shell with `cls`. Both cases are now working as expected.
2022-06-20 17:47:35 -05:00
Ofek Lev
a5fb91dd4c Fix typos in dev build script (#13333) 2022-06-20 11:38:46 -05:00
Mike Griese
848314ef17 Fix a deadlock in ShowWindow (#13309)
When we send this ShowWindow message, if we send it to it's
going to need to get processed by the window message thread before
returning. We're handling this message under lock. However, the first
thing the conhost message thread does is lock the console. That'll
deadlock us. So unlock here, first, to let the message thread deal with
this message, then re-lock so later on this thread can unlock again
safely.

* [x] Closes #13301 
* [x] Tested conhost
* [x] Tested terminal
2022-06-16 22:10:00 +00:00
Dustin L. Howett
deffbbc7f5 Regenerate CodepointWidthDetector from Unicode 14.0 (#13292) 2022-06-16 21:56:01 +00:00
Mike Griese
f12ee745ef Update the scrollbar postiton on scrollToMark (#13291)
When I moved this into ControlCore, I forgot that UserScrollViewport is usually triggered by the scrollbar updating, so it doesn't ask the UI to update. Since this logic is in ControlCore, it's sorta in a weird place where it needs to communicate both up and down:
* update the `Terminal`'s viewport position
* update the `TermControl`'s scrollbar position

Checklist:

* [x] Closes a bug bash bug
* [x] Missed in #12948 
* See also #11000
2022-06-16 21:55:17 +00:00
Dustin L. Howett
9eb191d545 Fix an issue preventing the compliance pipeline from running (#13289) 2022-06-13 15:48:34 -05:00
Dustin Howett
bfd910c4ca Migrate OSS up to 7dbe741e1 2022-06-07 17:21:20 -05:00
Leonard Hecker
715844b01c Use feature markers during terminal-by-default handoff (#13160)
If we want to make Windows Terminal the default terminal under Windows,
we'll have to make conhost "handoff" incoming connections by default.
But this poses a problem: How can the seldomly updated conhost know
whether the routinely updated Windows Terminal version is actually willing
to accept such handoffs by default (it might be unwilling due to bugs, etc.)?

This commit solves the issue by introducing:
* A marker interface (`IDefaultTerminalMarker`): If it exists,
  Windows Terminal indicates its willingness to accept the handoff.
* Turning the all-0 GUID from being synonymous for conhost,
  to being synonymous for "Let Windows decide". Without this we wouldn't
  be able to differentiate between users who consciously chose conhost
  as their default terminal, vs. users who want the standard behavior.

Testing fallback behavior:
* Install "Terminal" 1.13
* Delete the 2 keys below `HKCU\Console\%%Startup`
* Enable `Feature_AttemptHandoff` in `features.xml`
  Return `true` from `DefaultApp::CheckShouldTerminalBeDefault`
* Replace `conhost.exe` and `console.dll` with `sfpcopy` after building
* Launching `cmd.exe` launches as a conhost window 
  (because "Terminal" 1.13 lacks the marker interface)
* Open properties page in `conhost.exe`
  "Let Windows decide" is select by default 
* Changing the selection writes the new value 

Testing the new behavior:
* Delete the 2 keys below `HKCU\Console\%%Startup`
* Enable `Feature_AttemptHandoff` in `features.xml`
  Return `true` from `DefaultApp::CheckShouldTerminalBeDefault`
* Use `CLSID_WindowsTerminalConsoleDev` and `CLSID_WindowsTerminalTerminalDev`
  for the initialization of `TerminalDelegationPair`
* Replace `conhost.exe` and `console.dll` with `sfpcopy` after building
* Deploy the "Terminal Dev" package
* Launching `cmd.exe` launches "Terminal Dev" 
  (because "Terminal Dev" has the marker interface)
* Open the settings tab
  "Let Windows decide" is select by default 
* Changing the selection and saving writes the new value 

(cherry picked from commit 1b81c6540f)
Service-Card-Id: 82925080
Service-Version: Inbox
2022-06-07 17:00:04 -05:00
Mike Griese
dfd345405a holy nuts it worked
LAWD this is dirty code. But POC, it does work for new tabs from the dropdown. Trick is that MakePane can't be used like that anymore, so gotta find a new way.
2022-05-25 12:36:35 -05:00
Mike Griese
daa2ef139f This is when I realized that asyncmakepane was never going to work 2022-05-24 16:36:40 -05:00
Mike Griese
44ac527f31 more nits, more a11y stuff 2022-05-24 12:09:50 -05:00
Mike Griese
52f7664d01 some nits 2022-05-24 10:47:45 -05:00
Mike Griese
223e270778 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-05-24 10:23:43 -05:00
Mike Griese
eac5cebbc6 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-05-05 09:21:49 -05:00
Mike Griese
7d4ee45e4e Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-04-28 05:56:45 -05:00
Mike Griese
02fa24cb12 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-04-20 06:36:40 -05:00
Mike Griese
f898855c82 yes I know this doesn't return that's the whole point 2022-04-20 06:36:31 -05:00
Mike Griese
a38fe5f1a6 woah typos 2022-04-19 16:10:15 -05:00
Mike Griese
bbe32f80e5 Reduce the diff size trivially 2022-04-19 14:37:05 -05:00
Mike Griese
446d07e79f add logging 2022-04-19 12:30:02 -05:00
Mike Griese
a74b45aa2c hey look this assert() doesn't hit anymore 2022-04-19 12:12:45 -05:00
Mike Griese
c588a9c75d cleanup from fixed a11y code 2022-04-19 11:18:11 -05:00
Mike Griese
082c63bde5 this seems to work with narrator. I think this is what I was trying to do 2022-04-18 17:03:39 -05:00
Mike Griese
91977bec5a For this commit, I went with what was in #11501. That seems to have been wrong. I get all sorts of errors. I should to the opposite.
(cherry picked from commit bf0f516634)
2022-04-18 16:17:30 -05:00
Mike Griese
07a1a07e47 When trying to use Narrator, this gets hit and throws an exception. Plumb it through instead 2022-04-18 16:12:01 -05:00
Mike Griese
ce8f8fb618 Make the window smaller because the default is HUUEG 2022-04-18 16:11:09 -05:00
Dustin L. Howett
538285cc45 git2git: remove some more things from the OS build (#12931)
This will result in the deletion of the following directories from the OS tree, under `onecore/windows/core/console/open`:

* doc/
* src/tools/MonarchPeasantPackage/
* src/api-ms-win-core-synch-l1-2-0/
* src/tools/ansi-color/
* src/tools/ColorTool/

We have gotten some PoliCheck flags on `doc/` (thanks to Niksa.md), but also the OS build just doesn't need these folders 😄

(cherry picked from commit 27b63ad02a)
Service-Card-Id: 80783789
Service-Version: Inbox
2022-04-18 14:37:21 -05:00
Mike Griese
b1ee82ae18 Revert "start thinking about moving the timers to being threadpooltimers in the core. I think every x-proc call in TermControl logs that message, so _crap_"
This reverts commit 76d8fa7b7e.
2022-04-18 12:17:33 -05:00
Mike Griese
358edd520a Revert "For this commit, I went with what was in #11501. That seems to have been wrong. I get all sorts of errors. I should to the opposite."
This reverts commit bf0f516634.
2022-04-15 14:05:45 -05:00
Mike Griese
bf0f516634 For this commit, I went with what was in #11501. That seems to have been wrong. I get all sorts of errors. I should to the opposite. 2022-04-15 14:05:38 -05:00
Mike Griese
99308b43e2 This actually works for not loading the resources 2022-04-15 14:04:41 -05:00
Mike Griese
2a9eefc2d9 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/2/infinity-war 2022-04-15 12:02:16 -05:00
Mike Griese
33b1ab9e79 own the handle 2022-04-13 06:47:11 -05:00
Mike Griese
7b3ca83329 this reconnects the new window to the existing content process 2022-04-13 06:08:07 -05:00
Mike Griese
76d8fa7b7e start thinking about moving the timers to being threadpooltimers in the core. I think every x-proc call in TermControl logs that message, so _crap_ 2022-04-12 10:53:52 -05:00
Mike Griese
9141f0419a tried to make these blindli fire_and_forgets. Didn't work 2022-04-12 10:23:27 -05:00
Mike Griese
857fb399e1 add localization back 2022-04-12 09:42:37 -05:00
Mike Griese
b3129192ad derp 2022-04-12 09:42:22 -05:00
Mike Griese
b253440cf5 add a velocity flag for this 2022-04-12 09:34:36 -05:00
Mike Griese
5429fca422 The sample works again 2022-04-12 06:53:45 -05:00
Mike Griese
46e299d2b6 Definitely needed those 2022-04-11 14:47:51 -05:00
Mike Griese
34601f7e05 more cleanup 2022-04-11 13:28:46 -05:00
Mike Griese
cf70797083 BODGY, don't raise an event on destruction, that would be too... 2022-04-11 13:28:46 -05:00
Mike Griese
07ba33b893 simplify the interface here a bit 2022-04-11 13:28:46 -05:00
Mike Griese
f818885636 move the content process main to another file as well 2022-04-11 13:28:44 -05:00
Mike Griese
738d1910da Move the content process handling to a separate file in TermControl project 2022-04-11 13:28:06 -05:00
Mike Griese
651efe248b add a dialog internally to the bounds of the control, not outside of the control 2022-04-11 13:28:04 -05:00
Mike Griese
768d4c0df3 some cleanup 2022-04-11 13:27:13 -05:00
Mike Griese
8ceb9baf9a This doesn't immediately crash, but it does crash when you start asking for the actual text ranges. That's not what you want. 2022-04-11 13:26:11 -05:00
Mike Griese
35ca313b48 Revert "This too didn't work. Creating the XAML thing not on the XAML thing isn't going to work"
This reverts commit fd364db727.
2022-04-11 13:21:55 -05:00
Mike Griese
1a78557b61 This too didn't work. Creating the XAML thing not on the XAML thing isn't going to work 2022-04-11 13:21:08 -05:00
Mike Griese
dafb627278 Revert "this was the part where I realized I dun goofed"
This reverts commit 64533c838a.
2022-04-11 13:19:22 -05:00
Mike Griese
eeb01a139d this was the part where I realized I dun goofed 2022-04-11 13:19:22 -05:00
Mike Griese
4a8f0e9562 The sample app has a hard time loading TermControl resources so we're just going to disable this for now 2022-04-11 13:19:22 -05:00
Mike Griese
ea9d3cb5f7 a fix for a crash when closing 2022-04-11 13:19:22 -05:00
Mike Griese
3d15b097b7 This works to kill the content and have the app live 2022-04-11 13:19:10 -05:00
Mike Griese
5a07282d19 Add a kill button for manually killing the content 2022-04-11 13:18:41 -05:00
Mike Griese
7e792b2b5e You know, there's 0% chance that this is the right pattern for this, but it _works_ 2022-04-11 13:18:40 -05:00
Mike Griese
c3a94454e0 some short-circuits for these inits, to make them more stable 2022-04-11 13:18:40 -05:00
Mike Griese
2f23e1fc0c A close button, and more logging 2022-04-11 13:18:40 -05:00
Mike Griese
996c71a933 Add a signal that the content can use to tell the window it's ready 2022-04-11 13:18:40 -05:00
Mike Griese
7731f5943a Some comments because everything is hard 2022-04-11 13:18:40 -05:00
Mike Griese
bb49d5086c I believe this merges the buisness of connection-factory, though there are many issues. 2022-04-11 13:18:38 -05:00
Mike Griese
e168413e9f I think this merges the-whole-thing into this branch. The remote control doesn't render right, but I think that's because the actual HEAD of all this work is in connection-factory 2022-04-11 13:17:47 -05:00
Leonard Hecker
a59e3d0c57 Fix DBCS attribute corruption during reflow (#12853)
855e136 contains a regression which breaks buffer reflow if wide surrogate
characters are present. This happens because we made use of the
`TextBufferCellIterator` whose increment operator skips 2 cells for wide
characters. This created a "misalignment" in the reflow logic which was written
for cell-wise iteration. This commit fixes the issue, by reverting back to the
previous algorithm without iterators.

Closes #12837
Closes MSFT-38904421

## Validation Steps Performed
* Run ``pwsh -noprofile -command echo "`u{D83D}`u{DE43}"``
* Resizing conhost preserves all contents 
* Resizing Windows Terminal doesn't crash it 
* Added a test covering this issue 

(cherry picked from commit 10b9044120)
Service-Card-Id: 80340091
Service-Version: Inbox
2022-04-08 12:27:16 -05:00
Mike Griese
f73da456fa Fix the OS build (#12790)
The `cascadia/` directory straight up isn't checked into the OS. So adding a test dependency on code in there was a BAD IDEA.

(cherry picked from commit 4e61be9cd7)
Service-Card-Id: 79863821
Service-Version: Inbox
2022-03-29 17:18:29 -05:00
Mike Griese
9ccd6ecd74 Manually copy trailing attributes on a resize (#12637)
## THE WHITE WHALE

This is a fairly naive fix for this bug. It's not terribly performant,
but neither is resize in the first place.

When the buffer gets resized, typically we only copy the text up to the
`MeasureRight` point, the last printable char in the row. Then we'd just
use the last char's attributes to fill the remainder of the row.

Instead, this PR changes how reflow behaves when it gets to the end of
the row. After we finish copying text, then manually walk through the
attributes at the end of the row, and copy them over. This ensures that
cells that just have a colored space in them get copied into the new
buffer as well, and we don't just blat the last character's attributes
into the rest of the row. We'll do a similar thing once we get to the
last printable char in the buffer, copying the remaining attributes.

This could DEFINITELY be more performant. I think this current
implementation walks the attrs _on every cell_, then appends the new
attrs to the new ATTR_ROW. That could be optimized by just using the
actual iterator. The copy after the last printable char bit is also
especially bad in this regard. That could likely be a blind copy - I
just wanted to get this into the world.

Finally, we now copy the final attributes to the correct buffer: the new
one.  We used to copy them to the _old_ buffer, which we were about to
destroy.

## Validation

I'll add more gifs in the morning, not enough time to finish spinning a
release Terminal build with this tonight.

Closes #32 🎉🎉🎉🎉🎉🎉🎉🎉🎉
Closes #12567

(cherry picked from commit 855e1360c0)
2022-03-28 13:33:54 -05:00
211 changed files with 10174 additions and 2859 deletions

3156
.github/fabricbot.json vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,9 @@
trigger: none
pr: none
pool:
pool:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
parameters:
- name: branding
@@ -195,7 +195,6 @@ jobs:
condition: true
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /t:Terminal\CascadiaPackage /p:WindowsTerminalReleaseBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -222,7 +221,6 @@ jobs:
displayName: Build solution **\OpenConsole.sln for PublicTerminalCore
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -231,7 +229,6 @@ jobs:
displayName: Build solution **\OpenConsole.sln for ConPTY
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /p:WindowsTerminalReleaseBuild=true /t:Conhost\Host_EXE;Conhost\winconpty_DLL
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -327,7 +324,7 @@ jobs:
- ${{ if eq(parameters.runCompliance, true) }}:
- template: ./templates/build-console-compliance-job.yml
- ${{ if eq(parameters.buildTerminal, true) }}:
- job: BundleAndSign
strategy:
@@ -546,7 +543,6 @@ jobs:
displayName: Build solution **\OpenConsole.sln for WPF Control
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalReleaseBuild=$(UseReleaseBranding);Version=$(XES_PACKAGEVERSIONNUMBER) /t:Pack
platform: Any CPU
configuration: $(BuildConfiguration)

View File

@@ -13,7 +13,7 @@ jobs:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
steps:
- checkout: self
@@ -27,7 +27,6 @@ jobs:
displayName: 'Build solution **\OpenConsole.sln'
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}

View File

@@ -12,12 +12,12 @@ jobs:
BuildPlatform: ${{ parameters.platform }}
WindowsTerminalBranding: ${{ parameters.branding }}
EnableRichCodeNavigation: true
pool:
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
steps:
- template: build-console-steps.yml

View File

@@ -30,7 +30,7 @@ jobs:
If ($Arch -Eq "x86") { $Arch = "Win32" }
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
- steps: restore-nuget-steps.yml
- template: restore-nuget-steps.yml
- task: UniversalPackages@0
displayName: Download terminal-internal Universal Package
inputs:

View File

@@ -9,12 +9,12 @@ jobs:
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
pool:
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
steps:
- checkout: self
@@ -32,12 +32,11 @@ jobs:
echo VCToolsInstallDir = %VCToolsInstallDir%
echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir%
displayName: 'Retrieve VC tools directory'
- task: VSBuild@1
displayName: 'Build solution **\OpenConsole.sln'
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: "${{ parameters.additionalBuildArguments }}"
@@ -88,4 +87,4 @@ jobs:
displayName: 'Publish All Build Artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'fuzzingBuildOutput'
ArtifactName: 'fuzzingBuildOutput'

View File

@@ -12,12 +12,12 @@ jobs:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
PGOBuildMode: 'Instrument'
pool:
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
steps:
- template: build-console-steps.yml
@@ -34,7 +34,7 @@ jobs:
configuration: ${{ parameters.configuration }}
platform: ${{ parameters.platform }}
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
- template: helix-processtestresults-job.yml
parameters:
name: 'ProcessTestResults'

View File

@@ -28,7 +28,6 @@ steps:
displayName: 'Build solution **\OpenConsole.sln'
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: "${{ parameters.additionalBuildArguments }} /p:PGOBuildMode=$(PGOBuildMode) /bl:$(Build.SourcesDirectory)\\msbuild.binlog"

View File

@@ -11,12 +11,12 @@ jobs:
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
pool:
pool:
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
demands: ImageOverride -equals WinDevVS17-latest
steps:
- checkout: self

View File

@@ -17,7 +17,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2022</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>15</VersionMinor>
<VersionMinor>16</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -16,7 +16,7 @@
<!-- Managed packages -->
<package id="Appium.WebDriver" version="3.0.0.2" targetFramework="net45" />
<package id="Castle.Core" version="4.1.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
<package id="Selenium.Support" version="3.5.0" targetFramework="net45" />
<package id="Selenium.WebDriver" version="3.5.0" targetFramework="net45" />
</packages>

View File

@@ -110,7 +110,7 @@ This takes quite some time, and only generates an `msix`. It does not install th
```powershell
# If you haven't already:
Import-Module tools\OpenConsole.psm1;
Import-Module .\tools\OpenConsole.psm1;
Set-MsBuildDevEnvironment;
# The Set-MsBuildDevEnvironment call is needed for finding the path to
@@ -121,7 +121,7 @@ if ((Get-AppxPackage -Name 'WindowsTerminalDev*') -ne $null) {
Remove-AppxPackage 'WindowsTerminalDev_0.0.1.0_x64__8wekyb3d8bbwe'
};
New-Item ..\loose -Type Directory -Force;
makeappx unpack /v /o /p .\CascadiaPackage_0.0.1.0_x64_Debug.msix /d ..\Loose\;
makeappx unpack /v /o /p .\CascadiaPackage_0.0.1.0_x64_Debug.msix /d ..\loose\;
Add-AppxPackage -Path ..\loose\AppxManifest.xml -Register -ForceUpdateFromAnyVersion -ForceApplicationShutdown
```

View File

@@ -350,9 +350,11 @@
"switchToTab",
"tabSearch",
"toggleAlwaysOnTop",
"toggleBlockSelection",
"toggleFocusMode",
"selectAll",
"setFocusMode",
"switchSelectionEndpoint",
"toggleFullscreen",
"setFullScreen",
"setMaximized",
@@ -364,6 +366,10 @@
"quit",
"adjustOpacity",
"restoreLastClosed",
"addMark",
"scrollToMark",
"clearMark",
"clearAllMarks",
"unbound"
],
"type": "string"
@@ -383,6 +389,15 @@
],
"type": "string"
},
"ScrollToMarkDirection": {
"enum": [
"previous",
"next",
"first",
"last"
],
"type": "string"
},
"ResizeDirection": {
"enum": [
"left",
@@ -732,6 +747,30 @@
"direction"
]
},
"ScrollToMarkAction": {
"description": "Arguments corresponding to a Scroll to Mark Action",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"const": "scrollToMark"
},
"direction": {
"$ref": "#/$defs/ScrollToMarkDirection",
"default": "previous",
"description": "The direction to scroll to a mark."
}
}
}
],
"required": [
"direction"
]
},
"SendInputAction": {
"description": "Arguments corresponding to a Send Input Action",
"allOf": [
@@ -839,6 +878,27 @@
}
]
},
"AddMarkAction": {
"description": "Arguments corresponding to an Add Mark Action",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"const": "addMark"
},
"color": {
"$ref": "#/$defs/Color",
"default": null,
"description": "If provided, will set the mark's color to the given value."
}
}
}
]
},
"SetColorSchemeAction": {
"description": "Arguments corresponding to a Set Color Scheme Action",
"allOf": [
@@ -1667,6 +1727,16 @@
"description": "When set to true, URLs will be detected by the Terminal. This will cause URLs to underline on hover and be clickable by pressing Ctrl.",
"type": "boolean"
},
"experimental.autoMarkPrompts": {
"default": false,
"description": "When set to true, prompts will automatically be marked.",
"type": "boolean"
},
"experimental.showMarksOnScrollbar": {
"default": false,
"description": "When set to true, marks added to the buffer via the addMark action will appear on the scrollbar.",
"type": "boolean"
},
"disableAnimations": {
"default": false,
"description": "When set to `true`, visual animations will be disabled across the application.",
@@ -2011,6 +2081,10 @@
"description": "Controls what happens when the application emits a BEL character. When set to \"all\", the Terminal will play a sound, flash the taskbar icon (if the terminal window is not in focus) and flash the window. An array of specific behaviors can also be used. Supported array values include `audible`, `window` and `taskbar`. When set to \"none\", nothing will happen.",
"$ref": "#/$defs/BellStyle"
},
"bellSound": {
"description": "Sets the sound played when the application emits a BEL. When set to an array, the terminal will pick one of those sounds at random.",
"$ref": "#/$defs/BellSound"
},
"closeOnExit": {
"default": "graceful",
"description": "Sets how the profile reacts to termination or failure to launch. Possible values:\n -\"graceful\" (close when exit is typed or the process exits normally)\n -\"always\" (always close)\n -\"never\" (never close).\ntrue and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",

View File

@@ -3,9 +3,11 @@
#include "pch.h"
#include "MyPage.h"
#include "MySettings.h"
#include <LibraryResources.h>
#include "MyPage.g.cpp"
#include "MySettings.h"
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
#include "..\..\..\src\types\inc\utils.hpp"
using namespace std::chrono_literals;
using namespace winrt::Microsoft::Terminal;
@@ -26,7 +28,7 @@ namespace winrt::SampleApp::implementation
void MyPage::Create()
{
auto settings = winrt::make_self<implementation::MySettings>();
auto settings = winrt::make_self<MySettings>();
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
@@ -44,6 +46,216 @@ namespace winrt::SampleApp::implementation
Control::TermControl control{ *settings, *settings, conn };
InProcContent().Children().Append(control);
// Once the control loads (and not before that), write some text for debugging:
control.Initialized([conn](auto&&, auto&&) {
conn.WriteInput(L"This TermControl is hosted in-proc...");
});
}
static wil::unique_process_information _createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(g) };
// Create an event that the content process will use to signal it is
// ready to go. We won't need the event after this function, so the
// unique_event will clean up our handle when we leave this scope. The
// ContentProcess is responsible for cleaning up its own handle.
wil::unique_event ev{ CreateEvent(nullptr, true, false, nullptr) };
// Make sure to mark this handle as inheritable! Even with
// bInheritHandles=true, this is only inherited when it's explicitly
// allowed to be.
SetHandleInformation(ev.get(), HANDLE_FLAG_INHERIT, 1);
// god bless, fmt::format will format a HANDLE like `0xa80`
std::wstring commandline{
fmt::format(L"WindowsTerminal.exe --content {} --signal {}", guidStr, ev.get())
};
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
THROW_IF_WIN32_BOOL_FALSE(succeeded);
// Wait for the child process to signal that they're ready.
WaitForSingleObject(ev.get(), INFINITE);
return piOne;
}
winrt::fire_and_forget MyPage::_writeToLog(std::wstring_view str)
{
winrt::hstring copy{ str };
// Switch back to the UI thread.
co_await resume_foreground(Dispatcher());
winrt::WUX::Controls::TextBlock block;
block.Text(copy);
Log().Children().Append(block);
}
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender,
const WUX::Input::TappedRoutedEventArgs& eventArgs)
{
auto guidString = GuidInput().Text();
// Capture calling context.
winrt::apartment_context ui_thread;
auto canConvert = guidString.size() == 38 &&
guidString.front() == '{' &&
guidString.back() == '}';
bool tryingToAttach = false;
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
if (canConvert)
{
GUID result{};
if (SUCCEEDED(IIDFromString(guidString.c_str(), &result)))
{
contentGuid = result;
tryingToAttach = true;
}
}
_writeToLog(tryingToAttach ? L"Attaching to existing content process" : L"Creating new content process");
co_await winrt::resume_background();
if (!tryingToAttach)
{
// Spawn a wt.exe, with the guid on the commandline
piContentProcess = std::move(_createHostClassProcess(contentGuid));
}
// THIS MUST TAKE PLACE AFTER _createHostClassProcess.
// * If we're creating a new OOP control, _createHostClassProcess will
// spawn the process that will actually host the ContentProcess
// object.
// * If we're attaching, then that process already exists.
Control::ContentProcess content{ nullptr };
try
{
content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
}
catch (winrt::hresult_error hr)
{
_writeToLog(L"CreateInstance the ContentProcess object");
_writeToLog(fmt::format(L" HR ({}): {}", hr.code(), hr.message().c_str()));
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
if (content == nullptr)
{
_writeToLog(L"Failed to connect to the ContentProcess object. It may not have been started fast enough.");
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
TerminalConnection::ConnectionInformation connectInfo{ nullptr };
Control::IControlSettings settings{ *winrt::make_self<implementation::MySettings>() };
// When creating a terminal for the first time, pass it a connection
// info
//
// otherwise, when attaching to an existing one, just pass null, because
// we don't need the connection info.
if (!tryingToAttach)
{
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted out-of-proc...",
winrt::hstring{},
L"",
nullptr,
32,
80,
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
connectInfo = TerminalConnection::ConnectionInformation(myClass, connectionSettings);
if (!content.Initialize(settings, settings, connectInfo))
{
_writeToLog(L"Failed to Initialize the ContentProcess object.");
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
}
else
{
// If we're attaching, we don't really need to do anything special.
}
// Switch back to the UI thread.
co_await ui_thread;
// Create the XAML control that will be attached to the content process.
// We're not passing in a connection, because the contentGuid will be used instead.
Control::TermControl control{ contentGuid, settings, settings, nullptr };
auto weakControl = winrt::make_weak(control);
control.RaiseNotice([this](auto&&, auto& args) {
_writeToLog(L"Content process died, probably.");
_writeToLog(args.Message());
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
});
control.ConnectionStateChanged([this, weakControl](auto&&, auto&) {
if (auto strongControl{ weakControl.get() })
{
const auto newConnectionState = strongControl.ConnectionState();
if (newConnectionState == TerminalConnection::ConnectionState::Closed)
{
_writeToLog(L"Connection was closed");
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
}
}
});
Log().Children().Clear();
OutOfProcContent().Children().Append(control);
if (!tryingToAttach)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(contentGuid) };
GuidInput().Text(guidStr);
}
}
void MyPage::CloseClicked(const IInspectable& /*sender*/,
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
{
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
}
void MyPage::KillClicked(const IInspectable& /*sender*/,
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
{
if (piContentProcess.hProcess)
{
TerminateProcess(piContentProcess.hProcess, (UINT)-1);
piContentProcess.reset();
}
}
// Method Description:

View File

@@ -14,11 +14,18 @@ namespace winrt::SampleApp::implementation
MyPage();
void Create();
hstring Title();
winrt::fire_and_forget CreateClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
void CloseClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
void KillClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
private:
friend struct MyPageT<MyPage>; // for Xaml to bind events
wil::unique_process_information piContentProcess;
winrt::fire_and_forget _writeToLog(std::wstring_view str);
};
}

View File

@@ -23,9 +23,23 @@
<TextBox x:Name="GuidInput"
Width="400"
PlaceholderText="{}{guid here}" />
<Button Grid.Row="0">
<Button x:Name="CreateOutOfProcControl"
Grid.Row="0"
Tapped="CreateClicked">
Create
</Button>
<Button x:Name="CloseOutOfProcControl"
Grid.Row="0"
Margin="4,0,0,0"
Tapped="CloseClicked">
Close
</Button>
<Button x:Name="KillOutOfProcControl"
Grid.Row="0"
Margin="4,0,0,0"
Tapped="KillClicked">
Kill
</Button>
</StackPanel>
@@ -46,14 +60,26 @@
VerticalAlignment="Stretch"
Background="#ff0000" />
<Grid x:Name="OutOfProcContent"
Grid.Column="1"
Padding="16"
<Grid Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#0000ff" />
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel x:Name="Log"
Grid.Row="0"
Orientation="Vertical" />
<Grid x:Name="OutOfProcContent"
Grid.Row="1"
Padding="16"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#0000ff" />
</Grid>
</Grid>

View File

@@ -17,9 +17,15 @@
<DisableEmbeddedXbf>false</DisableEmbeddedXbf>
<XamlComponentResourceLocation>nested</XamlComponentResourceLocation>
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<ItemDefinitionGroup>
<ClCompile>
@@ -147,14 +153,15 @@
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
<!--

View File

@@ -11,9 +11,13 @@
<!-- sets a bunch of Windows Universal properties -->
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<!-- ========================= XAML files ======================== -->
<ItemGroup>
<!-- DON'T PUT XAML FILES HERE! Put them in SampleAppLib.vcxproj -->
@@ -81,13 +85,11 @@
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>
<ItemDefinitionGroup>
@@ -102,4 +104,7 @@
</Link>
</ItemDefinitionGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
</Project>

View File

@@ -57,8 +57,8 @@ void SampleIslandWindow::MakeWindow() noexcept
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
1024,
860,
nullptr,
nullptr,
wc.hInstance,
@@ -104,8 +104,6 @@ void SampleIslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam)
void SampleIslandWindow::Initialize()
{
const bool initialized = (_interopWindowHandle != nullptr);
_source = DesktopWindowXamlSource{};
auto interop = _source.as<IDesktopWindowXamlSourceNative>();

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{b4427499-9fde-4208-b456-5bc580637633}</ProjectGuid>
@@ -16,7 +15,15 @@
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalVCRTForwarders>true</TerminalVCRTForwarders>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
@@ -138,16 +145,11 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.7.1\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets'))" />
</Target>
<!-- Override GetPackagingOutputs to roll up all our dependencies.
@@ -225,13 +227,24 @@
<TargetPath>%(Filename)%(Extension)</TargetPath>
</PackagingOutputs>
</ItemGroup>
<!-- Same thing AGAIN here, with OpenConsole.exe If you forget this, then
the scratch app will use the inbox conpty with a newer conpty lib, causing
us to send the inbox conhost messages that will make it explode. -->
<ItemGroup>
<_OpenConsoleExe Include="$(OpenConsoleCommonOutDir)\OpenConsole.exe" />
<PackagingOutputs Include="@(_OpenConsoleExe)">
<ProjectName>$(ProjectName)</ProjectName>
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
<TargetPath>%(Filename)%(Extension)</TargetPath>
</PackagingOutputs>
</ItemGroup>
</Target>
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
<Import Project="..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" />
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
</Project>

2
src/audio/dirs Normal file
View File

@@ -0,0 +1,2 @@
DIRS=midi \

2
src/audio/midi/dirs Normal file
View File

@@ -0,0 +1,2 @@
DIRS=lib \

View File

@@ -0,0 +1,8 @@
!include ..\sources.inc
# -------------------------------------
# Program Information
# -------------------------------------
TARGETNAME = ConAudioMidi
TARGETTYPE = LIBRARY

View File

@@ -26,5 +26,6 @@ Abstract:
// Windows Header Files:
#include <windows.h>
#include <mmeapi.h>
// clang-format on

View File

@@ -0,0 +1,28 @@
!include ..\..\..\project.inc
# -------------------------------------
# Windows Console
# - Console Audio Functions
# -------------------------------------
# -------------------------------------
# Build System Settings
# -------------------------------------
# Code in the OneCore depot automatically excludes default Win32 libraries.
# -------------------------------------
# Sources, Headers, and Libraries
# -------------------------------------
PRECOMPILED_CXX = 1
PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES = \
..\MidiAudio.cpp \
INCLUDES = \
$(INCLUDES); \
..; \
..\..\..\inc; \
$(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \

View File

@@ -1138,7 +1138,7 @@ til::point TextBuffer::GetWordStart(const til::point target, const std::wstring_
// that it actually points to a space in the buffer
copy = bufferSize.BottomRightInclusive();
}
else if (bufferSize.CompareInBounds(target, limit, true) >= 0)
else if (target >= limit)
{
// if at/past the limit --> clamp to limit
copy = limitOptional.value_or(bufferSize.BottomRightInclusive());
@@ -1254,7 +1254,7 @@ til::point TextBuffer::GetWordEnd(const til::point target, const std::wstring_vi
// Already at/past the limit. Can't move forward.
const auto bufferSize{ GetSize() };
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
if (bufferSize.CompareInBounds(target, limit, true) >= 0)
if (target >= limit)
{
return target;
}
@@ -1282,7 +1282,7 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons
const auto bufferSize{ GetSize() };
auto result{ target };
if (bufferSize.CompareInBounds(target, limit, true) >= 0)
if (target >= limit)
{
// if we're already on/past the last RegularChar,
// clamp result to that position
@@ -1419,7 +1419,7 @@ bool TextBuffer::MoveToNextWord(til::point& pos, const std::wstring_view wordDel
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
const auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, limit) };
if (bufferSize.CompareInBounds(copy, limit, true) >= 0)
if (copy >= limit)
{
return false;
}
@@ -1466,7 +1466,7 @@ til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<til::po
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
// Clamp pos to limit
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
if (resultPos > limit)
{
resultPos = limit;
}
@@ -1494,7 +1494,7 @@ til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode,
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
// Clamp pos to limit
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
if (resultPos > limit)
{
resultPos = limit;
}
@@ -1524,9 +1524,19 @@ til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode,
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::optional<til::point> limitOptional) const
{
const auto bufferSize = GetSize();
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
bool pastEndInclusive;
til::point limit;
{
// if the limit is past the end of the buffer,
// 1) clamp limit to end of buffer
// 2) set pastEndInclusive
const auto endInclusive{ bufferSize.BottomRightInclusive() };
const auto val = limitOptional.value_or(endInclusive);
pastEndInclusive = val > endInclusive;
limit = pastEndInclusive ? endInclusive : val;
}
const auto distanceToLimit{ bufferSize.CompareInBounds(pos, limit, true) };
const auto distanceToLimit{ bufferSize.CompareInBounds(pos, limit) + (pastEndInclusive ? 1 : 0) };
if (distanceToLimit >= 0)
{
// Corner Case: we're on/past the limit
@@ -1569,7 +1579,7 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos, std::optional<til::point>
const auto bufferSize = GetSize();
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
if (bufferSize.CompareInBounds(pos, limit, true) > 0)
if (pos > limit)
{
// we're past the end
// clamp us to the limit
@@ -1611,7 +1621,7 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
// (0,0) is the top-left of the screen
// the physically "higher" coordinate is closer to the top-left
// the physically "lower" coordinate is closer to the bottom-right
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
const auto [higherCoord, lowerCoord] = start <= end ?
std::make_tuple(start, end) :
std::make_tuple(end, start);

View File

@@ -135,6 +135,10 @@
<!-- 16.3.0 - remove non-resources.pri PRI files since we just forced them back in. -->
<AppxPackagePayload Remove="@(AppxPackagePayload)" Condition="'%(Extension)' == '.pri' and '%(Filename)' != 'resources'" />
<AppxUploadPackagePayload Remove="@(AppxUploadPackagePayload)" Condition="'%(Extension)' == '.pri' and '%(Filename)' != 'resources'" />
<!-- Remove all of the xaml files, because we are using embedded xbf payloads (saves about 500kb on disk!) -->
<AppxPackagePayload Remove="@(AppxPackagePayload)" Condition="'%(Extension)' == '.xaml'" />
<AppxUploadPackagePayload Remove="@(AppxUploadPackagePayload)" Condition="'%(Extension)' == '.xaml'" />
</ItemGroup>
</Target>

View File

@@ -41,6 +41,7 @@
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="ThemeTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View File

@@ -0,0 +1,281 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/Theme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../types/inc/colorTable.hpp"
#include "JsonTestClass.h"
#include <defaults.h>
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// 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.
class ThemeTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ThemeTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ParseSimpleTheme);
TEST_METHOD(ParseEmptyTheme);
TEST_METHOD(ParseNoWindowTheme);
TEST_METHOD(ParseNullWindowTheme);
TEST_METHOD(ParseThemeWithNullThemeColor);
TEST_METHOD(InvalidCurrentTheme);
static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept
{
return Core::Color{ r, g, b, 255 };
}
static Core::Color rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
{
return Core::Color{ r, g, b, a };
}
};
void ThemeTests::ParseSimpleTheme()
{
static constexpr std::string_view orangeTheme{ R"({
"name": "orange",
"tabRow":
{
"background": "#FFFF8800",
"unfocusedBackground": "#FF8844"
},
"window":
{
"applicationTheme": "light",
"useMica": true
}
})" };
const auto schemeObject = VerifyParseSucceeded(orangeTheme);
auto theme = Theme::FromJson(schemeObject);
VERIFY_ARE_EQUAL(L"orange", theme->Name());
VERIFY_IS_NOT_NULL(theme->TabRow());
VERIFY_IS_NOT_NULL(theme->TabRow().Background());
VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
VERIFY_ARE_EQUAL(rgba(0xff, 0xff, 0x88, 0x00), theme->TabRow().Background().Color());
VERIFY_ARE_EQUAL(rgba(0xff, 0x88, 0x44, 0xff), theme->TabRow().UnfocusedBackground().Color());
VERIFY_IS_NOT_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme());
VERIFY_ARE_EQUAL(true, theme->Window().UseMica());
}
void ThemeTests::ParseEmptyTheme()
{
Log::Comment(L"This theme doesn't have any elements defined.");
static constexpr std::string_view emptyTheme{ R"({
"name": "empty"
})" };
const auto schemeObject = VerifyParseSucceeded(emptyTheme);
auto theme = Theme::FromJson(schemeObject);
VERIFY_ARE_EQUAL(L"empty", theme->Name());
VERIFY_IS_NULL(theme->TabRow());
VERIFY_IS_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
}
void ThemeTests::ParseNoWindowTheme()
{
Log::Comment(L"This theme doesn't have a window defined.");
static constexpr std::string_view emptyTheme{ R"({
"name": "noWindow",
"tabRow":
{
"background": "#112233",
"unfocusedBackground": "#FF884400"
},
})" };
const auto schemeObject = VerifyParseSucceeded(emptyTheme);
auto theme = Theme::FromJson(schemeObject);
VERIFY_ARE_EQUAL(L"noWindow", theme->Name());
VERIFY_IS_NOT_NULL(theme->TabRow());
VERIFY_IS_NOT_NULL(theme->TabRow().Background());
VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
VERIFY_ARE_EQUAL(rgb(0x11, 0x22, 0x33), theme->TabRow().Background().Color());
VERIFY_IS_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
}
void ThemeTests::ParseNullWindowTheme()
{
Log::Comment(L"This theme doesn't have a window defined.");
static constexpr std::string_view emptyTheme{ R"({
"name": "nullWindow",
"tabRow":
{
"background": "#112233",
"unfocusedBackground": "#FF884400"
},
"window": null
})" };
const auto schemeObject = VerifyParseSucceeded(emptyTheme);
auto theme = Theme::FromJson(schemeObject);
VERIFY_ARE_EQUAL(L"nullWindow", theme->Name());
VERIFY_IS_NOT_NULL(theme->TabRow());
VERIFY_IS_NOT_NULL(theme->TabRow().Background());
VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
VERIFY_ARE_EQUAL(rgb(0x11, 0x22, 0x33), theme->TabRow().Background().Color());
VERIFY_IS_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
}
void ThemeTests::ParseThemeWithNullThemeColor()
{
Log::Comment(L"These themes are all missing a tabRow background. Make sure we don't somehow default-construct one for them");
static constexpr std::string_view settingsString{ R"json({
"themes": [
{
"name": "backgroundEmpty",
"tabRow":
{
},
"window":
{
"applicationTheme": "light",
"useMica": true
}
},
{
"name": "backgroundNull",
"tabRow":
{
"background": null
},
"window":
{
"applicationTheme": "light",
"useMica": true
}
},
{
"name": "backgroundOmittedEntirely",
"window":
{
"applicationTheme": "light",
"useMica": true
}
}
]
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
const auto& themes{ settings->GlobalSettings().Themes() };
{
const auto& backgroundEmpty{ themes.Lookup(L"backgroundEmpty") };
VERIFY_ARE_EQUAL(L"backgroundEmpty", backgroundEmpty.Name());
VERIFY_IS_NOT_NULL(backgroundEmpty.TabRow());
VERIFY_IS_NULL(backgroundEmpty.TabRow().Background());
}
{
const auto& backgroundNull{ themes.Lookup(L"backgroundNull") };
VERIFY_ARE_EQUAL(L"backgroundNull", backgroundNull.Name());
VERIFY_IS_NOT_NULL(backgroundNull.TabRow());
VERIFY_IS_NULL(backgroundNull.TabRow().Background());
}
{
const auto& backgroundOmittedEntirely{ themes.Lookup(L"backgroundOmittedEntirely") };
VERIFY_ARE_EQUAL(L"backgroundOmittedEntirely", backgroundOmittedEntirely.Name());
VERIFY_IS_NULL(backgroundOmittedEntirely.TabRow());
}
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
void ThemeTests::InvalidCurrentTheme()
{
Log::Comment(L"Make sure specifying an invalid theme falls back to a sensible default.");
static constexpr std::string_view settingsString{ R"json({
"theme": "foo",
"themes": [
{
"name": "bar",
"tabRow": {},
"window":
{
"applicationTheme": "light",
"useMica": true
}
}
]
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
VERIFY_ARE_EQUAL(1u, settings->Warnings().Size());
VERIFY_ARE_EQUAL(Settings::Model::SettingsLoadWarnings::UnknownTheme, settings->Warnings().GetAt(0));
const auto& themes{ settings->GlobalSettings().Themes() };
{
const auto& bar{ themes.Lookup(L"bar") };
VERIFY_ARE_EQUAL(L"bar", bar.Name());
VERIFY_IS_NOT_NULL(bar.TabRow());
VERIFY_IS_NULL(bar.TabRow().Background());
}
const auto currentTheme{ settings->GlobalSettings().CurrentTheme() };
VERIFY_IS_NOT_NULL(currentTheme);
VERIFY_ARE_EQUAL(L"system", currentTheme.Name());
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
}

View File

@@ -1036,4 +1036,26 @@ 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)
{
auto windowId = _lookupPeasantIdForName(window);
if (windowId == 0)
{
/* TODO! try the name as an integer ID */
return;
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
auto request = winrt::make_self<implementation::AttachRequest>(content, tabIndex);
targetPeasant.AttachContentToWindow(*request);
}
else
{
/*TODO! log */
}
}
}

View File

@@ -60,6 +60,8 @@ 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);
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);

View File

@@ -60,6 +60,8 @@ 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);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;

View File

@@ -8,6 +8,7 @@
#include "GetWindowLayoutArgs.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
#include "AttachRequest.g.cpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
@@ -275,6 +276,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

View File

@@ -5,6 +5,7 @@
#include "Peasant.g.h"
#include "RenameRequestArgs.h"
#include "AttachRequest.g.h"
namespace RemotingUnitTests
{
@@ -12,6 +13,18 @@ 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 Peasant : public PeasantT<Peasant>
{
Peasant();
@@ -32,6 +45,8 @@ 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();
@@ -47,12 +62,15 @@ 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);
private:
Peasant(const uint64_t testPID);
uint64_t _ourPID;

View File

@@ -52,6 +52,11 @@ namespace Microsoft.Terminal.Remoting
MonitorBehavior ToMonitor;
}
[default_interface] runtimeclass AttachRequest {
String Content { get; };
UInt32 TabIndex { get; };
}
interface IPeasant
{
CommandlineArgs InitialArgs { get; };
@@ -70,23 +75,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);
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;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@@ -788,4 +788,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return nullptr;
}
winrt::fire_and_forget WindowManager::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex)
{
co_await winrt::resume_background();
_monarch.RequestMoveContent(window, content, tabIndex);
}
}

View File

@@ -48,9 +48,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::fire_and_forget RequestHideNotificationIcon();
winrt::fire_and_forget RequestQuitAll();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
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);

View File

@@ -22,7 +22,11 @@ namespace Microsoft.Terminal.Remoting
void RequestQuitAll();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
void RequestMoveContent(String window, String content, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;

View File

@@ -175,7 +175,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);
}
}
@@ -201,10 +201,15 @@ namespace winrt::TerminalApp::implementation
}
}
_SplitPane(realArgs.SplitDirection(),
// This is safe, we're already filtering so the value is (0, 1)
::base::saturated_cast<float>(realArgs.SplitSize()),
_MakePane(realArgs.TerminalArgs(), realArgs.SplitMode() == SplitType::Duplicate));
// _SplitPaneActiveTab(realArgs.SplitDirection(),
// // This is safe, we're already filtering so the value is (0, 1)
// ::base::saturated_cast<float>(realArgs.SplitSize()),
// _MakePane(realArgs.TerminalArgs(), realArgs.SplitMode() == SplitType::Duplicate));
_asyncSplitPaneActiveTab(realArgs.SplitDirection(),
// This is safe, we're already filtering so the value is (0, 1)
::base::saturated_cast<float>(realArgs.SplitSize()),
_prepareContentProc(realArgs.TerminalArgs(), realArgs.SplitMode() == SplitType::Duplicate));
args.Handled(true);
}
}
@@ -751,17 +756,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);
}
}
@@ -1112,4 +1108,14 @@ namespace winrt::TerminalApp::implementation
args.Handled(handled);
}
}
void TerminalPage::_HandleSwitchSelectionEndpoint(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& control{ _GetActiveControl() })
{
const auto handled = control.SwitchSelectionEndpoint();
args.Handled(handled);
}
}
}

View File

@@ -51,6 +51,7 @@ static const std::array settingsLoadWarningsLabels {
USES_RESOURCE(L"InvalidSplitSize"),
USES_RESOURCE(L"FailedToParseStartupActions"),
USES_RESOURCE(L"FailedToParseSubCommands"),
USES_RESOURCE(L"UnknownTheme"),
};
static const std::array settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),
@@ -292,7 +293,7 @@ namespace winrt::TerminalApp::implementation
_root->Maximized(true);
}
if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode))
if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !IsQuakeWindow())
{
_root->SetFullscreen(true);
}
@@ -367,11 +368,12 @@ namespace winrt::TerminalApp::implementation
// details here, but it does have the desired effect.
// It's not enough to set the theme on the dialog alone.
auto themingLambda{ [this](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) {
auto theme{ _settings.GlobalSettings().Theme() };
auto theme{ _settings.GlobalSettings().CurrentTheme() };
auto requestedTheme{ theme.RequestedTheme() };
auto element{ sender.try_as<winrt::Windows::UI::Xaml::FrameworkElement>() };
while (element)
{
element.RequestedTheme(theme);
element.RequestedTheme(requestedTheme);
element = element.Parent().try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
}
} };
@@ -737,13 +739,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::ElementTheme AppLogic::GetRequestedTheme()
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().Theme();
return Theme().RequestedTheme();
}
bool AppLogic::GetShowTabsInTitlebar()
@@ -962,9 +958,16 @@ namespace winrt::TerminalApp::implementation
}
CATCH_LOG()
// Method Description:
// - Update the current theme of the application. This will trigger our
// RequestedThemeChanged event, to have our host change the theme of the
// root of the application.
// Arguments:
// - newTheme: The ElementTheme to apply to our elements.
void AppLogic::_RefreshThemeRoutine()
{
_ApplyTheme(_settings.GlobalSettings().Theme());
// Propagate the event to the host layer, so it can update its own UI
_RequestedThemeChangedHandlers(*this, Theme());
}
// Function Description:
@@ -1073,18 +1076,6 @@ namespace winrt::TerminalApp::implementation
return _settings;
}
// Method Description:
// - Update the current theme of the application. This will trigger our
// RequestedThemeChanged event, to have our host change the theme of the
// root of the application.
// Arguments:
// - newTheme: The ElementTheme to apply to our elements.
void AppLogic::_ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme)
{
// Propagate the event to the host layer, so it can update its own UI
_RequestedThemeChangedHandlers(*this, newTheme);
}
UIElement AppLogic::GetRoot() noexcept
{
return _root.as<winrt::Windows::UI::Xaml::Controls::Control>();
@@ -1219,6 +1210,19 @@ namespace winrt::TerminalApp::implementation
return {};
}
winrt::Windows::UI::Xaml::Media::Brush AppLogic::TitlebarBrush()
{
if (_root)
{
return _root->TitlebarBrush();
}
return { nullptr };
}
void AppLogic::WindowActivated(const bool activated)
{
_root->WindowActivated(activated);
}
bool AppLogic::HasCommandlineArguments() const noexcept
{
return _hasCommandLineArguments;
@@ -1645,4 +1649,22 @@ namespace winrt::TerminalApp::implementation
{
return _settings.GlobalSettings().ShowTitleInTitlebar();
}
Microsoft::Terminal::Settings::Model::Theme AppLogic::Theme()
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().CurrentTheme();
}
void AppLogic::AttachContent(winrt::hstring content, uint32_t tabIndex)
{
if (_root)
{
_root->AttachContent(content, tabIndex);
}
}
}

View File

@@ -117,18 +117,33 @@ namespace winrt::TerminalApp::implementation
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();
void AttachContent(winrt::hstring content, uint32_t tabIndex);
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();
// -------------------------------- WinRT Events ---------------------------------
TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
// 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);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs);
@@ -183,8 +198,6 @@ namespace winrt::TerminalApp::implementation
void _ReloadSettings();
void _OpenSettingsUI();
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
bool _hasCommandLineArguments{ false };
bool _hasSettingsStartupActions{ false };
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings;
@@ -205,11 +218,14 @@ namespace winrt::TerminalApp::implementation
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);
FORWARDED_TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs, _root, RequestMoveContent);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;
#endif

View File

@@ -35,7 +35,7 @@ namespace TerminalApp
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
[default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter
[default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
AppLogic();
@@ -94,6 +94,8 @@ namespace TerminalApp
void WindowVisibilityChanged(Boolean showOrHide);
TaskbarState TaskbarState{ get; };
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
Boolean ShouldUsePersistedLayout();
Boolean ShouldImmediatelyHandoffToElevated();
@@ -105,7 +107,10 @@ namespace TerminalApp
Boolean GetAlwaysShowNotificationIcon();
Boolean GetShowTitleInTitlebar();
Microsoft.Terminal.Settings.Model.Theme Theme { get; };
FindTargetWindowResult FindTargetWindow(String[] args);
void AttachContent(String content, UInt32 tabIndex);
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
@@ -117,7 +122,7 @@ namespace TerminalApp
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, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
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;
@@ -134,5 +139,8 @@ namespace TerminalApp
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, RequestMoveContentArgs> RequestMoveContent;
}
}

View File

@@ -111,7 +111,15 @@ namespace winrt::Microsoft::TerminalApp::implementation
void DebugTapConnection::_OutputHandler(const hstring str)
{
_TerminalOutputHandlers(til::visualize_control_codes(str));
auto output = til::visualize_control_codes(str);
// To make the output easier to read, we introduce a line break whenever
// an LF control is encountered. But at this point, the LF would have
// been converted to U+240A (␊), so that's what we need to search for.
for (size_t lfPos = 0; (lfPos = output.find(L'\u240A', lfPos)) != std::wstring::npos;)
{
output.insert(++lfPos, L"\r\n");
}
_TerminalOutputHandlers(output);
}
// Called by the DebugInputTapConnection to print user input

View File

@@ -3,8 +3,6 @@
#pragma once
#include "winrt/Microsoft.UI.Xaml.Controls.h"
#include "HighlightedTextSegment.g.h"
#include "HighlightedText.g.h"

View File

@@ -119,7 +119,7 @@ Pane::Pane(std::shared_ptr<Pane> first,
// - <none>
// 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());
@@ -164,6 +164,11 @@ 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.
if (asContent)
{
args.ContentGuid(_control.ContentGuid());
}
return args;
}
@@ -175,14 +180,15 @@ 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: TODO!
// 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)
{
// if we are a leaf then all there is to do is defer to the parent.
if (_IsLeaf())
if (/*!asContent && */ _IsLeaf())
{
if (_lastActive)
{
@@ -195,16 +201,29 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
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);
const auto splitSize = (1. - _desiredSplitPosition);
SplitPaneArgs args{ SplitType::Manual, splitDirection, splitSize, terminalArgs };
actionAndArgs.Args(args);
return actionAndArgs;
};
// if (asContent && _IsLeaf())
// {
// // TODO! This probably won't work. We probably do need to ask the parent
// // of this pane to generate the action for us. Consider moving a pane
// // that's 25% of the parent - the pane doesn't know that. Only the
// // parent does. When we recieve it, we can determine if we're putting it
// // into a tab or a pane, and then parse the NewTerminalArgs out of
// // either the splitPane or the newTab action.
// return { { buildSplitPane(shared_from_this()) }, shared_from_this(), currentId, 1 };
// }
auto buildMoveFocus = [](auto direction) {
MoveFocusArgs args{ direction };

View File

@@ -91,8 +91,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);
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);

View File

@@ -242,6 +242,10 @@
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="UnknownTheme" xml:space="preserve">
<value>&#x2022; The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="LegacyGlobalsProperty" xml:space="preserve">
<value>The "globals" property is deprecated - your settings might need updating. </value>
<comment>{Locked="\"globals\""} </comment>

View File

@@ -44,7 +44,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

@@ -29,7 +29,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:
void _MakeTabViewItem() override;

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;
WINRT_CALLBACK(RequestFocusActiveControl, winrt::delegate<void()>);

View File

@@ -62,7 +62,7 @@ namespace winrt::TerminalApp::implementation
// - existingConnection: An optional connection that is already established to a PTY
// for this tab to host instead of creating one.
// If not defined, the tab will create the connection.
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection)
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs)
try
{
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
@@ -72,10 +72,10 @@ namespace winrt::TerminalApp::implementation
{
return S_FALSE;
}
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
const auto controlSettings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
// Try to handle auto-elevation
if (_maybeElevate(newTerminalArgs, settings, profile))
if (_maybeElevate(newTerminalArgs, controlSettings, profile))
{
return S_OK;
}
@@ -83,35 +83,42 @@ namespace winrt::TerminalApp::implementation
// unfortunately. This seems to be due to Centennial quirks. It works
// unpackaged, but not packaged.
//
// This call to _MakePane won't return nullptr, we already checked that
// case above with the _maybeElevate call.
_CreateNewTabFromPane(_MakePane(newTerminalArgs, false, existingConnection));
// // This call to _MakePane won't return nullptr, we already checked that
// // case above with the _maybeElevate call.
// _CreateNewTabFromPane(_MakePane(newTerminalArgs, false, nullptr));
const auto tabCount = _tabs.Size();
const auto usedManualProfile = (newTerminalArgs != nullptr) &&
(newTerminalArgs.ProfileIndex() != nullptr ||
newTerminalArgs.Profile().empty());
// TerminalSettingsCreateResult controlSettings{ nullptr };
// Profile profile{ nullptr };
// _evaluateSettings(newTerminalArgs, false /*duplicate*/, controlSettings, profile);
auto initContentProc = (newTerminalArgs && newTerminalArgs.ContentGuid() != winrt::guid{}) ?
_AttachToContentProcess(newTerminalArgs.ContentGuid()) :
_CreateNewContentProcess(profile, controlSettings);
_createNewTabFromContent(PreparedContent{ initContentProc, controlSettings, profile });
// const auto tabCount = _tabs.Size();
// const auto usedManualProfile = (newTerminalArgs != nullptr) &&
// (newTerminalArgs.ProfileIndex() != nullptr ||
// newTerminalArgs.Profile().empty());
// Lookup the name of the color scheme used by this profile.
const auto scheme = _settings.GetColorSchemeForProfile(profile);
// If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
// that as the empty string.
const auto schemeName = scheme ? scheme.Name() : L"\0";
// // Lookup the name of the color scheme used by this profile.
// const auto scheme = _settings.GetColorSchemeForProfile(profile);
// // If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
// // that as the empty string.
// const auto schemeName = scheme ? scheme.Name() : L"\0";
TraceLoggingWrite(
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
"TabInformation",
TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"),
TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"),
TraceLoggingFloat64(settings.DefaultSettings().Opacity(), "TintOpacity", "Opacity preference from the settings"),
TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"),
TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
// TraceLoggingWrite(
// g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
// "TabInformation",
// TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
// TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
// TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
// TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
// TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"),
// TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"),
// TraceLoggingFloat64(settings.DefaultSettings().Opacity(), "TintOpacity", "Opacity preference from the settings"),
// TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"),
// TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"),
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
return S_OK;
}
@@ -123,10 +130,20 @@ namespace winrt::TerminalApp::implementation
// - newTabImpl: the uninitialized tab.
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl)
{
newTabImpl->Initialize();
// newTabImpl->Initialize();
uint32_t insertPosition = _tabs.Size();
if (_settings.GlobalSettings().NewTabPosition() == NewTabPosition::AfterCurrentTab)
{
auto currentTabIndex = _GetFocusedTabIndex();
if (currentTabIndex.has_value())
{
insertPosition = currentTabIndex.value() + 1;
}
}
// Add the new tab to the list of our tabs.
_tabs.Append(*newTabImpl);
_tabs.InsertAt(insertPosition, *newTabImpl);
_mruTabs.Append(*newTabImpl);
newTabImpl->SetDispatch(*_actionDispatch);
@@ -154,6 +171,8 @@ namespace winrt::TerminalApp::implementation
// Possibly update the icon of the tab.
page->_UpdateTabIcon(*tab);
page->_updateThemeColors();
// Update the taskbar progress as well. We'll raise our own
// SetTaskbarProgress event here, to get tell the hosting
// application to re-query this value from us.
@@ -193,7 +212,7 @@ namespace winrt::TerminalApp::implementation
if (page && tab)
{
page->_SplitTab(*tab);
page->_SplitTab(tab);
}
});
@@ -220,16 +239,20 @@ namespace winrt::TerminalApp::implementation
});
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);
_tabView.TabItems().InsertAt(insertPosition, tabViewItem);
// Set this tab's icon to the icon from the user's profile
if (const auto profile{ newTabImpl->GetFocusedProfile() })
{
if (!profile.Icon().empty())
{
newTabImpl->UpdateIcon(profile.Icon());
}
}
//
// TODO! This doesn't need ot live in TerminalPage like, at all. This
// should get moved inside TerminalTab::AttachRootPane, or
// TerminalTab::Initialize or something.
// if (const auto profile{ newTabImpl->GetFocusedProfile() })
// {
// if (!profile.Icon().empty())
// {
// newTabImpl->UpdateIcon(profile.Icon());
// }
// }
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabClick });
@@ -279,6 +302,41 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::_createNewTabFromContent(PreparedContent preppedContent,
std::function<void(const winrt::com_ptr<TerminalTab>&)> postInitTab /* defaults to nullptr*/)
{
auto newTabImpl = winrt::make_self<TerminalTab>(nullptr);
// TODO! This tab should have a Content that's initialized with a blank
// grid that takes up the whole space, with the BG set to the BG color
// of the TerminalControl. So that the tab has something to show
// initially.
//
// Alternatively, the Control could be initialized with a
// AsyncAction<ContentProcess> and then when _that_ returns, the
// TermControl starts to initialize itself?
//
// That's an idea. We do already have the settings for the control, just not the content. Huh.
_InitializeTab(newTabImpl); // Adds tab to list, tabview
// If the caller requested additional setup for the tab, do that now.
// For example, DuplicateTab uses this to copy the runtime tab text from
// the old tab to the new one.
if (postInitTab)
{
postInitTab(newTabImpl);
}
// TODO! Do we need both this and the resume_background in _CreateNewContentProcess
co_await winrt::resume_background();
auto content = co_await preppedContent.initContentProc;
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::High);
auto pane = _makePaneFromContent(content, preppedContent.controlSettings, preppedContent.profile);
newTabImpl->AttachRootPane(pane);
// TODO! DebugTap
}
// Method Description:
// - Get the icon of the currently focused terminal control, and set its
// tab's icon to that icon.
@@ -348,30 +406,40 @@ namespace winrt::TerminalApp::implementation
// In the future, it may be preferable to just duplicate the
// current control's live settings (which will include changes
// made through VT).
_CreateNewTabFromPane(_MakePane(nullptr, true, nullptr));
const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty())
{
if (auto newTab{ _GetFocusedTabImpl() })
// _CreateNewTabFromPane(_MakePane(nullptr, true, nullptr));
auto initRuntimeTabTitle = [&tab](auto& newTab) {
const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty())
{
newTab->SetTabText(runtimeTabText);
}
}
};
// TerminalSettingsCreateResult controlSettings{ nullptr };
// Profile profile{ nullptr };
// _evaluateSettings(nullptr, true, controlSettings, profile);
// auto initContentProc = _CreateNewContentProcess(profile, controlSettings);
auto preppedContent = _prepareContentProc(nullptr, true);
_createNewTabFromContent(preppedContent, initRuntimeTabTitle);
}
CATCH_LOG();
}
// Method Description:
// - Sets the specified tab as the focused tab and splits its active pane
// - Sets the specified tab as the focused tab and splits its active pane.
// This will duplicate the profile that's currently active in the given
// tab.
// Arguments:
// - tab: tab to split
void TerminalPage::_SplitTab(TerminalTab& tab)
void TerminalPage::_SplitTab(winrt::com_ptr<TerminalTab>& tab)
{
try
{
_SetFocusedTab(tab);
_SplitPane(tab, SplitDirection::Automatic, 0.5f, _MakePane(nullptr, true));
_SetFocusedTab(*tab);
// _SplitPaneOnTab(tab, SplitDirection::Automatic, 0.5f, _MakePane(nullptr, true));
_asyncSplitPaneOnTab(tab, SplitDirection::Automatic, 0.5f, _prepareContentProc(nullptr, true));
}
CATCH_LOG();
}
@@ -925,6 +993,8 @@ namespace winrt::TerminalApp::implementation
_TitleChangedHandlers(*this, tab.Title());
}
_updateThemeColors();
auto tab_impl = _GetTerminalTabImpl(tab);
if (tab_impl)
{

View File

@@ -60,7 +60,8 @@
FontSize="12">
<ToolTipService.ToolTip>
<ToolTip Placement="Mouse">
<TextBlock IsTextSelectionEnabled="False">
<TextBlock IsTextSelectionEnabled="False"
TextWrapping="Wrap">
<Run x:Uid="NewTabRun" /> <LineBreak />
<Run x:Uid="NewPaneRun"
FontStyle="Italic" /> <LineBreak />

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
@@ -50,6 +51,26 @@ 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);
public:
RequestMoveContentArgs(const winrt::hstring window, const winrt::hstring content, uint32_t tabIndex) :
_Window{ window },
_Content{ content },
_TabIndex{ tabIndex } {};
};
struct PreparedContent
{
Windows::Foundation::IAsyncOperation<winrt::Microsoft::Terminal::Control::ContentProcess> initContentProc{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult controlSettings{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::Profile profile{ nullptr };
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
@@ -131,9 +152,13 @@ namespace winrt::TerminalApp::implementation
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
bool IsElevated() const noexcept;
void OpenSettingsUI();
void WindowActivated(const bool activated);
winrt::fire_and_forget AttachContent(winrt::hstring content, uint32_t tabIndex);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@@ -152,11 +177,16 @@ namespace winrt::TerminalApp::implementation
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);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
std::optional<HWND> _hostingHwnd;
@@ -196,6 +226,8 @@ namespace winrt::TerminalApp::implementation
std::optional<int> _rearrangeFrom{};
std::optional<int> _rearrangeTo{};
bool _removing{ false };
bool _activated{ false };
bool _visible{ true };
std::vector<std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>> _previouslyClosedPanesAndTabs{};
@@ -222,6 +254,8 @@ namespace winrt::TerminalApp::implementation
int _renamerLayoutCount{ 0 };
bool _renamerPressedEnter{ false };
std::unordered_map<winrt::guid, winrt::Microsoft::Terminal::Control::ContentProcess> _recentlyDetachedContent{};
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
void _ShowAboutDialog();
@@ -233,13 +267,14 @@ namespace winrt::TerminalApp::implementation
void _CreateNewTabFlyout();
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile,
Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
winrt::fire_and_forget _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
bool _displayingCloseDialog{ false };
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
@@ -267,7 +302,7 @@ namespace winrt::TerminalApp::implementation
void _DuplicateFocusedTab();
void _DuplicateTab(const TerminalTab& tab);
void _SplitTab(TerminalTab& tab);
void _SplitTab(winrt::com_ptr<TerminalTab>& tab);
winrt::fire_and_forget _ExportTab(const TerminalTab& tab, winrt::hstring filepath);
winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab);
@@ -288,7 +323,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)
@@ -324,13 +360,13 @@ namespace winrt::TerminalApp::implementation
void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll);
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
std::shared_ptr<Pane> newPane);
void _SplitPane(TerminalTab& tab,
const Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
std::shared_ptr<Pane> newPane);
void _SplitPaneActiveTab(const Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
std::shared_ptr<Pane> newPane);
void _SplitPaneOnTab(winrt::com_ptr<TerminalTab>& tab,
const Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
std::shared_ptr<Pane> newPane);
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _ToggleSplitOrientation();
@@ -383,8 +419,6 @@ namespace winrt::TerminalApp::implementation
void _RefreshUIForSettingsReload();
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);
void _ClearNonClientAreaColors();
void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor);
void _ClearNewTabButtonColor();
@@ -443,8 +477,48 @@ namespace winrt::TerminalApp::implementation
static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
void _updateThemeColors();
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
PreparedContent _prepareContentProc(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs,
const bool duplicate);
Windows::Foundation::IAsyncOperation<winrt::Microsoft::Terminal::Control::ContentProcess> _CreateNewContentProcess(winrt::Microsoft::Terminal::Settings::Model::Profile profile,
winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings);
Windows::Foundation::IAsyncOperation<winrt::Microsoft::Terminal::Control::ContentProcess> _AttachToContentProcess(const winrt::guid contentGuid);
winrt::fire_and_forget _createNewTabFromContent(PreparedContent preppedContent,
std::function<void(const winrt::com_ptr<TerminalTab>&)> postInitTab = nullptr);
winrt::fire_and_forget _asyncSplitPaneActiveTab(const Microsoft::Terminal::Settings::Model::SplitDirection splitDirection,
const float splitSize,
PreparedContent preppedContent);
winrt::fire_and_forget _asyncSplitPaneOnTab(winrt::com_ptr<TerminalTab> tab,
const Microsoft::Terminal::Settings::Model::SplitDirection splitDirection,
const float splitSize,
PreparedContent preppedContent);
std::shared_ptr<Pane> _makePaneFromContent(winrt::Microsoft::Terminal::Control::ContentProcess initContentProc,
winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult controlSettings,
winrt::Microsoft::Terminal::Settings::Model::Profile profile);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::guid& contentGuid);
void _evaluateSettings(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs,
const bool duplicate,
winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& controlSettings,
winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
winrt::Microsoft::Terminal::TerminalConnection::ConnectionInformation _CreateConnectionInfoFromSettings(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Settings::Model::TerminalSettings& settings);
void _finalizeDetach(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e);
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);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);

View File

@@ -10,6 +10,12 @@ namespace TerminalApp
{
String ProposedName { get; };
};
[default_interface] runtimeclass RequestMoveContentArgs
{
String Window { get; };
String Content { get; };
UInt32 TabIndex { get; };
};
interface IDialogPresenter
{
@@ -44,6 +50,10 @@ 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);
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
@@ -57,8 +67,12 @@ namespace TerminalApp
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;
}
}

View File

@@ -27,6 +27,17 @@ namespace winrt::TerminalApp::implementation
{
TerminalTab::TerminalTab(std::shared_ptr<Pane> rootPane)
{
if (rootPane != nullptr)
{
AttachRootPane(rootPane);
}
_Setup();
}
void TerminalTab::AttachRootPane(std::shared_ptr<Pane> rootPane)
{
assert(_rootPane == nullptr);
_rootPane = rootPane;
_activePane = nullptr;
@@ -60,7 +71,13 @@ namespace winrt::TerminalApp::implementation
_mruPanes.insert(_mruPanes.begin(), id.value());
}
_Setup();
_rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr);
});
Content(_rootPane->GetRootElement());
Initialize();
}
// Method Description:
@@ -71,12 +88,6 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalTab::_Setup()
{
_rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr);
});
Content(_rootPane->GetRootElement());
_MakeTabViewItem();
_CreateContextMenu();
@@ -379,6 +390,10 @@ namespace winrt::TerminalApp::implementation
{
return _runtimeTabText;
}
if (!_activePane)
{
return L"";
}
if (!_activePane->_IsLeaf())
{
return RS_(L"MultiplePanes");
@@ -437,16 +452,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));
@@ -742,6 +757,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;
@@ -864,7 +881,7 @@ namespace winrt::TerminalApp::implementation
}
});
events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
@@ -1069,7 +1086,7 @@ namespace winrt::TerminalApp::implementation
// Add a Closed event handler to the Pane. If the pane closes out from
// underneath us, and it's zoomed, we want to be able to make sure to
// update our state accordingly to un-zoom that pane. See GH#7252.
auto closedToken = pane->Closed([weakThis, weakPane](auto&& /*s*/, auto&& /*e*/) -> winrt::fire_and_forget {
auto closedToken = pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget {
if (auto tab{ weakThis.get() })
{
if (tab->_zoomedPane)
@@ -1343,7 +1360,7 @@ namespace winrt::TerminalApp::implementation
// -------------------- | -- | --
// Runtime Color | _optional_ | Color Picker / `setTabColor` action
// Control Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT
// Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme
// Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme (handled in _RecalculateAndApplyTabColor)
// Tab Default Color | **default** | TabView in XAML
//
// coalesce will get us the first of these values that's
@@ -1352,7 +1369,6 @@ namespace winrt::TerminalApp::implementation
return til::coalesce(_runtimeTabColor,
controlTabColor,
_themeTabColor,
std::optional<Windows::UI::Color>(std::nullopt));
}
@@ -1370,6 +1386,12 @@ namespace winrt::TerminalApp::implementation
_RecalculateAndApplyTabColor();
}
void TerminalTab::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& color)
{
_themeColor = color;
_RecalculateAndApplyTabColor();
}
// Method Description:
// - This function dispatches a function to the UI thread to recalculate
// what this tab's current background color should be. If a color is set,
@@ -1390,11 +1412,37 @@ namespace winrt::TerminalApp::implementation
auto tab{ ptrTab };
// GetTabColor will return the color set by the color picker, or the
// color specified in the profile. If neither of those were set,
// then look to _themeColor to see if there's a value there.
// Otherwise, clear our color, falling back to the TabView defaults.
auto currentColor = tab->GetTabColor();
if (currentColor.has_value())
{
tab->_ApplyTabColor(currentColor.value());
}
else if (tab->_themeColor != nullptr)
{
// One-liner to safely get the active control's brush.
Media::Brush terminalBrush{ nullptr };
if (const auto& c{ tab->GetActiveTerminalControl() })
{
terminalBrush = c.BackgroundBrush();
}
if (const auto themeBrush{ tab->_themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) })
{
// ThemeColor.Evaluate will get us a Brush (because the
// TermControl could have an acrylic BG, for example). Take
// that brush, and get the color out of it. We don't really
// want to have the tab items themselves be acrylic.
tab->_ApplyTabColor(til::color{ ThemeColor::ColorFromBrush(themeBrush) });
}
else
{
tab->_ClearTabBackgroundColor();
}
}
else
{
tab->_ClearTabBackgroundColor();
@@ -1452,18 +1500,16 @@ namespace winrt::TerminalApp::implementation
subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
}
hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color));
selectedTabBrush.Color(color);
// currently if a tab has a custom color, a deselected state is
// signified by using the same color with a bit of transparency
auto deselectedTabColor = color;
deselectedTabColor.A = 64;
deselectedTabBrush.Color(deselectedTabColor);
deselectedTabBrush.Color(color);
deselectedTabBrush.Opacity(0.3);
hoverTabBrush.Color(color);
hoverTabBrush.Opacity(0.6);
// currently if a tab has a custom color, a deselected state is
// signified by using the same color with a bit of transparency
//
// Prior to MUX 2.7, we set TabViewItemHeaderBackground, but now we can
// use TabViewItem().Background() for that. HOWEVER,
// TabViewItem().Background() only sets the color of the tab background

View File

@@ -36,6 +36,7 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> DetachRoot();
std::shared_ptr<Pane> DetachPane();
void AttachPane(std::shared_ptr<Pane> pane);
void AttachRootPane(std::shared_ptr<Pane> rootPane);
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
@@ -71,6 +72,7 @@ namespace winrt::TerminalApp::implementation
std::optional<winrt::Windows::UI::Color> GetTabColor();
void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& color);
void SetRuntimeTabColor(const winrt::Windows::UI::Color& color);
void ResetRuntimeTabColor();
void ActivateColorPicker();
@@ -81,7 +83,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;
@@ -113,10 +115,10 @@ namespace winrt::TerminalApp::implementation
winrt::hstring _lastIconPath{};
winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
std::optional<winrt::Windows::UI::Color> _themeTabColor{};
std::optional<winrt::Windows::UI::Color> _runtimeTabColor{};
winrt::TerminalApp::TabHeaderControl _headerControl{};
winrt::TerminalApp::TerminalTabStatus _tabStatus{};
winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr };
struct ControlEventTokens
{

View File

@@ -8,6 +8,8 @@
#include "TitlebarControl.h"
#include "ColorHelper.h"
#include "TitlebarControl.g.cpp"
namespace winrt::TerminalApp::implementation
@@ -21,6 +23,25 @@ namespace winrt::TerminalApp::implementation
MinMaxCloseControl().MinimizeClick({ this, &TitlebarControl::Minimize_Click });
MinMaxCloseControl().MaximizeClick({ this, &TitlebarControl::Maximize_Click });
MinMaxCloseControl().CloseClick({ this, &TitlebarControl::Close_Click });
// Listen for changes to the Background. If the Background changes,
// we'll want to manually adjust the RequestedTheme of our caption
// buttons, so the foreground stands out against whatever BG color was
// selected for us.
//
// This is how you register a PropertyChanged event for the Background
// property of a Grid. The Background property is defined in the base
// class Panel.
const auto bgProperty{ winrt::Windows::UI::Xaml::Controls::Panel::BackgroundProperty() };
RegisterPropertyChangedCallback(bgProperty, [weakThis = get_weak(), bgProperty](auto& /*sender*/, auto& e) {
if (auto self{ weakThis.get() })
{
if (e == bgProperty)
{
self->_backgroundChanged(self->Background());
}
}
});
}
double TitlebarControl::CaptionButtonWidth()
@@ -144,4 +165,26 @@ namespace winrt::TerminalApp::implementation
MinMaxCloseControl().ReleaseButtons();
}
void TitlebarControl::_backgroundChanged(winrt::Windows::UI::Xaml::Media::Brush brush)
{
// Loosely cribbed from TerminalPage::_SetNewTabButtonColor
til::color c;
if (auto acrylic = brush.try_as<winrt::Windows::UI::Xaml::Media::AcrylicBrush>())
{
c = acrylic.TintColor();
}
else if (auto solidColor = brush.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>())
{
c = solidColor.Color();
}
else
{
return;
}
const auto isBrightColor = ColorHelper::IsBrightColor(c);
MinMaxCloseControl().RequestedTheme(isBrightColor ? winrt::Windows::UI::Xaml::ElementTheme::Light :
winrt::Windows::UI::Xaml::ElementTheme::Dark);
}
}

View File

@@ -31,6 +31,8 @@ namespace winrt::TerminalApp::implementation
private:
void _OnMaximizeOrRestore(byte flag);
HWND _window{ nullptr }; // non-owning handle; should not be freed in the dtor.
void _backgroundChanged(winrt::Windows::UI::Xaml::Media::Brush brush);
};
}

View File

@@ -13,7 +13,6 @@
VerticalAlignment="Top"
d:DesignHeight="36"
d:DesignWidth="400"
Background="{ThemeResource TabViewBackground}"
SizeChanged="Root_SizeChanged"
mc:Ignorable="d">

View File

@@ -53,7 +53,12 @@ CATCH_RETURN()
HRESULT CTerminalHandoff::s_StopListening()
{
std::unique_lock lock{ _mtx };
return s_StopListeningLocked();
}
// See s_StopListening()
HRESULT CTerminalHandoff::s_StopListeningLocked()
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff);
_pfnHandoff = nullptr;
@@ -101,14 +106,16 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE sign
{
try
{
// Stash a local copy of _pfnHandoff before we stop listening.
std::unique_lock lock{ _mtx };
// s_StopListeningLocked sets _pfnHandoff to nullptr.
// localPfnHandoff is tested for nullness below.
#pragma warning(suppress : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
auto localPfnHandoff = _pfnHandoff;
// Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call.
// COM does not automatically clean that up for us. We must do it.
s_StopListening();
std::unique_lock lock{ _mtx };
LOG_IF_FAILED(s_StopListeningLocked());
// Report an error if no one registered a handoff function before calling this.
THROW_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff);

View File

@@ -43,6 +43,9 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff))
static HRESULT s_StartListening(NewHandoffFunction pfnHandoff);
static HRESULT s_StopListening();
private:
static HRESULT s_StopListeningLocked();
};
// Disable warnings from the CoCreatableClass macro as the value it provides for

View File

@@ -36,21 +36,59 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
TerminalConnection::ITerminalConnection connection{ nullptr };
// A couple short-circuits, for connections that _we_ implement.
// Sometimes, RoActivateInstance is weird and fails with errors like the
// following
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
/*
onecore\com\combase\inc\RegistryKey.hpp(527)\combase.dll!01234:
(caller: 01234) LogHr(2) tid(83a8) 800700A1 The specified
path is invalid.
Msg:[StaticNtOpen failed with
path:\REGISTRY\A\{A41685A4-AD85-4C4C-BA5D-A849ADBF3C40}\ActivatableClassId
\REGISTRY\MACHINE\Software\Classes\ActivatableClasses]
...\src\cascadia\TerminalConnection\ConnectionInformation.cpp(47)\TerminalConnection.dll!01234:
(caller: 01234) LogHr(1) tid(83a8) 800700A1 The specified
path is invalid.
[...TerminalConnection::implementation::ConnectionInformation::CreateConnection(RoActivateInstance(name,
raw))]
*/
//
// So to avoid those, we'll manually instantiate these
if (info.ClassName() == winrt::name_of<TerminalConnection::ConptyConnection>())
{
return nullptr;
connection = TerminalConnection::ConptyConnection();
}
else if (info.ClassName() == winrt::name_of<TerminalConnection::AzureConnection>())
{
connection = TerminalConnection::AzureConnection();
}
else if (info.ClassName() == winrt::name_of<TerminalConnection::EchoConnection>())
{
connection = TerminalConnection::EchoConnection();
}
else
{
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
{
return nullptr;
}
connection = inspectable.try_as<TerminalConnection::ITerminalConnection>();
}
// Now that thing we made, make sure it's actually a ITerminalConnection
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
if (connection)
{
// Initialize it, and return it.
connection.Initialize(info.Settings());

View File

@@ -0,0 +1,147 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ContentProcess.h"
#include "ContentProcess.g.cpp"
#include "ControlCore.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
ContentProcess::ContentProcess(winrt::guid g) :
_ourPID{ GetCurrentProcessId() },
_guid{ g } {}
bool ContentProcess::Initialize(Control::IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ConnectionInformation connectionInfo)
{
auto conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectionInfo) };
if (conn == nullptr)
{
return false;
}
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, conn);
return true;
}
ContentProcess::~ContentProcess()
{
}
// See https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/details-about-destructors#deferred-destruction
winrt::fire_and_forget ContentProcess::final_release(std::unique_ptr<ContentProcess> ptr) noexcept
{
winrt::com_ptr<ControlCore> coreImpl;
coreImpl.copy_from(winrt::get_self<ControlCore>(ptr->_interactivity.Core()));
if (coreImpl)
{
// Close() requires that it is called on the "main" thread. So we
// need to switch over to the DIspatcher thread, before calling
// Close.
co_await wil::resume_foreground(coreImpl->Dispatcher(), winrt::Windows::System::DispatcherQueuePriority::Normal);
// Typically, Close() runs async, closing the connection on a BG
// thread, so that the UI doesn't hang while waiting for the client
// process to exit. When we're running as a content process, that's
// not relevant. In that case, we need to close the connection NOW,
// because we're about to exit the whole process. If we close the
// process asynchronously (on a bg thread), then we might
// accidentally leak it as we exit() before the thread gets a time
// slice.
coreImpl->Close(false);
}
// DANGER - We're straight up going to EXIT THE ENTIRE PROCESS when we
// get destructed. This eliminates the need to do any sort of
// ref-counting weirdness. This entire process exists to host one
// singular ContentProcess instance. When we're destructed, it's because
// every other window process was done with us. We can die now, knowing
// that our job is complete.
std::exit(0);
}
Control::ControlInteractivity ContentProcess::GetInteractivity()
{
return _interactivity;
}
uint64_t ContentProcess::GetPID()
{
return _ourPID;
}
// Method Description:
// - Duplicate the swap chain handle to the provided process.
// - If the provided PID is our pid, then great - we don't need to do anything.
// Arguments:
// - callersPid: the PID of the process calling this method.
// Return Value:
// - The value of the swapchain handle in the callers process
// Notes:
// - This is BODGY! We're basically asking to marshal a HANDLE here. WinRT
// has no good mechanism for doing this, so we're doing it by casting the
// value to a uint64_t. In all reality, we _should_ be using a COM
// interface for this, because it can set up the security on these handles
// more appropriately. Fortunately, all we're dealing with is swapchains,
// so the security doesn't matter all that much.
uint64_t ContentProcess::RequestSwapChainHandle(const uint64_t callersPid)
{
auto ourPid = GetCurrentProcessId();
HANDLE ourHandle = reinterpret_cast<HANDLE>(_interactivity.Core().SwapChainHandle());
if (callersPid == ourPid)
{
return reinterpret_cast<uint64_t>(ourHandle);
}
wil::unique_handle hWindowProcess{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(callersPid)) };
if (hWindowProcess.get() == nullptr)
{
TraceLoggingWrite(g_hTerminalControlProvider,
"ContentProcess::RequestSwapChainHandle_OpenOtherProcessFailed",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
LOG_LAST_ERROR();
return 0;
}
HANDLE theirHandle{ nullptr };
BOOL success = DuplicateHandle(GetCurrentProcess(),
ourHandle,
hWindowProcess.get(),
&theirHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!success)
{
TraceLoggingWrite(g_hTerminalControlProvider,
"ContentProcess::RequestSwapChainHandle_DuplicateHandleFailed",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
LOG_LAST_ERROR();
return 0;
}
// At this point, the handle is now in their process space, with value
// theirHandle
return reinterpret_cast<uint64_t>(theirHandle);
}
winrt::guid ContentProcess::Guid()
{
return _guid;
}
void ContentProcess::Attach()
{
// TODO! This feels like a hack and I'm sure cppwinrt gives us some sort
// of hook for when we get an incremented refcount
_AttachedHandlers(*this, nullptr);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ContentProcess.g.h"
#include "ControlInteractivity.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
struct ContentProcess : ContentProcessT<ContentProcess>
{
ContentProcess(winrt::guid g);
~ContentProcess();
static winrt::fire_and_forget final_release(std::unique_ptr<ContentProcess> ptr) noexcept;
bool Initialize(Control::IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ConnectionInformation connectionInfo);
Control::ControlInteractivity GetInteractivity();
uint64_t GetPID();
uint64_t RequestSwapChainHandle(const uint64_t pid);
winrt::guid Guid();
void Attach();
TYPED_EVENT(Attached, IInspectable, IInspectable);
private:
Control::ControlInteractivity _interactivity{ nullptr };
uint64_t _ourPID;
winrt::guid _guid;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ContentProcess);
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ControlInteractivity.idl";
namespace Microsoft.Terminal.Control
{
runtimeclass ContentProcess {
ContentProcess(Guid g);
Boolean Initialize(IControlSettings settings,
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ConnectionInformation connectionInfo);
ControlInteractivity GetInteractivity();
UInt64 GetPID();
UInt64 RequestSwapChainHandle(UInt64 pid);
Guid Guid { get; };
void Attach();
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
};
}

View File

@@ -218,6 +218,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
UpdateSettings(settings, unfocusedAppearance);
}
winrt::Windows::System::DispatcherQueue ControlCore::Dispatcher()
{
return _dispatcher;
}
ControlCore::~ControlCore()
{
Close();
@@ -417,12 +422,43 @@ namespace winrt::Microsoft::Terminal::Control::implementation
vkey != VK_SNAPSHOT &&
keyDown)
{
if (_terminal->IsInMarkMode() && modifiers.IsCtrlPressed() && vkey == 'A')
const auto isInMarkMode = _terminal->SelectionMode() == ::Terminal::SelectionInteractionMode::Mark;
if (isInMarkMode)
{
auto lock = _terminal->LockForWriting();
_terminal->SelectAll();
_renderer->TriggerSelection();
return true;
if (modifiers.IsCtrlPressed() && vkey == 'A')
{
// Ctrl + A --> Select all
auto lock = _terminal->LockForWriting();
_terminal->SelectAll();
_updateSelectionUI();
return true;
}
else if (vkey == VK_TAB && _settings->DetectURLs())
{
// [Shift +] Tab --> next/previous hyperlink
auto lock = _terminal->LockForWriting();
const auto direction = modifiers.IsShiftPressed() ? ::Terminal::SearchDirection::Backward : ::Terminal::SearchDirection::Forward;
_terminal->SelectHyperlink(direction);
_updateSelectionUI();
return true;
}
else if (vkey == VK_RETURN && modifiers.IsCtrlPressed() && _terminal->IsTargetingUrl())
{
// Ctrl + Enter --> Open URL
auto lock = _terminal->LockForReading();
const auto uri = _terminal->GetHyperlinkAtBufferPosition(_terminal->GetSelectionAnchor());
_OpenHyperlinkHandlers(*this, winrt::make<OpenHyperlinkEventArgs>(winrt::hstring{ uri }));
return true;
}
else if (vkey == VK_RETURN)
{
// [Shift +] Enter --> copy text
// Don't lock here! CopySelectionToClipboard already locks for you!
CopySelectionToClipboard(modifiers.IsShiftPressed(), nullptr);
_terminal->ClearSelection();
_updateSelectionUI();
return true;
}
}
// try to update the selection
@@ -430,7 +466,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
auto lock = _terminal->LockForWriting();
_terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers);
_renderer->TriggerSelection();
_updateSelectionUI();
return true;
}
@@ -438,7 +474,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (!modifiers.IsWinPressed())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
_updateSelectionUI();
}
// When there is a selection active, escape should clear it and NOT flow through
@@ -590,12 +626,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_lastHoveredCell = terminalPosition;
uint16_t newId{ 0u };
// we can't use auto here because we're pre-declaring newInterval.
decltype(_terminal->GetHyperlinkIntervalFromPosition({})) newInterval{ std::nullopt };
decltype(_terminal->GetHyperlinkIntervalFromViewportPosition({})) newInterval{ std::nullopt };
if (terminalPosition.has_value())
{
auto lock = _terminal->LockForReading(); // Lock for the duration of our reads.
newId = _terminal->GetHyperlinkIdAtPosition(*terminalPosition);
newInterval = _terminal->GetHyperlinkIntervalFromPosition(*terminalPosition);
newId = _terminal->GetHyperlinkIdAtViewportPosition(*terminalPosition);
newInterval = _terminal->GetHyperlinkIntervalFromViewportPosition(*terminalPosition);
}
// If the hyperlink ID changed or the interval changed, trigger a redraw all
@@ -625,7 +661,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// Lock for the duration of our reads.
auto lock = _terminal->LockForReading();
return winrt::hstring{ _terminal->GetHyperlinkAtPosition(til::point{ pos }) };
return winrt::hstring{ _terminal->GetHyperlinkAtViewportPosition(til::point{ pos }) };
}
winrt::hstring ControlCore::HoveredUriText() const
@@ -633,7 +669,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto lock = _terminal->LockForReading(); // Lock for the duration of our reads.
if (_lastHoveredCell.has_value())
{
return winrt::hstring{ _terminal->GetHyperlinkAtPosition(*_lastHoveredCell) };
return winrt::hstring{ _terminal->GetHyperlinkAtViewportPosition(*_lastHoveredCell) };
}
return {};
}
@@ -934,6 +970,30 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetSelectionAnchor(position);
}
// Method Description:
// - Retrieves selection metadata from Terminal necessary to draw the
// selection markers.
// - Since all of this needs to be done under lock, it is more performant
// to throw it all in a struct and pass it along.
Control::SelectionData ControlCore::SelectionInfo() const
{
auto lock = _terminal->LockForReading();
Control::SelectionData info;
const auto start{ _terminal->SelectionStartForRendering() };
info.StartPos = { start.X, start.Y };
const auto end{ _terminal->SelectionEndForRendering() };
info.EndPos = { end.X, end.Y };
info.Endpoint = static_cast<SelectionEndpointTarget>(_terminal->SelectionEndpointTarget());
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
info.StartAtLeftBoundary = _terminal->GetSelectionAnchor().x == bufferSize.Left();
info.EndAtRightBoundary = _terminal->GetSelectionEnd().x == bufferSize.RightInclusive();
return info;
}
// Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
@@ -956,7 +1016,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// save location (for rendering) + render
_terminal->SetSelectionEnd(terminalPosition);
_renderer->TriggerSelection();
_updateSelectionUI();
}
// Called when the Terminal wants to set something to the clipboard, i.e.
@@ -969,7 +1029,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
// - CopyOnSelect does NOT clear the selection
// Arguments:
// - singleLine: collapse all of the text to one line
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
@@ -1015,12 +1074,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bgColor) :
"";
if (!_settings->CopyOnSelect())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
}
// send data up for clipboard
_CopyToClipboardHandlers(*this,
winrt::make<CopyToClipboardEventArgs>(winrt::hstring{ textData },
@@ -1034,7 +1087,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
auto lock = _terminal->LockForWriting();
_terminal->SelectAll();
_renderer->TriggerSelection();
_updateSelectionUI();
}
void ControlCore::ClearSelection()
{
auto lock = _terminal->LockForWriting();
_terminal->ClearSelection();
_updateSelectionUI();
}
bool ControlCore::ToggleBlockSelection()
@@ -1044,6 +1104,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->SetBlockSelection(!_terminal->IsBlockSelection());
_renderer->TriggerSelection();
// do not update the selection markers!
// if we were showing them, keep it that way.
// otherwise, continue to not show them
return true;
}
return false;
@@ -1053,12 +1116,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
auto lock = _terminal->LockForWriting();
_terminal->ToggleMarkMode();
_renderer->TriggerSelection();
_updateSelectionUI();
}
bool ControlCore::IsInMarkMode() const
Control::SelectionInteractionMode ControlCore::SelectionMode() const
{
return _terminal->IsInMarkMode();
return static_cast<Control::SelectionInteractionMode>(_terminal->SelectionMode());
}
bool ControlCore::SwitchSelectionEndpoint()
{
if (_terminal->IsSelectionActive())
{
_terminal->SwitchSelectionEndpoint();
_updateSelectionUI();
return true;
}
return false;
}
// Method Description:
@@ -1068,7 +1142,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->WritePastedText(hstr);
_terminal->ClearSelection();
_renderer->TriggerSelection();
_updateSelectionUI();
_terminal->TrySnapOnInput();
}
@@ -1274,6 +1348,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - duration - How long the note should be sustained (in microseconds).
void ControlCore::_terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
{
// TODO! GH#1256 This is intentionally here to conflict with https://github.com/microsoft/terminal/pull/13471#pullrequestreview-1039353718
// When we do tearout, we'll need to also recreate the midi thing
// We create the audio instance on demand, and lock it for the duration
// of the note output so it can't be destroyed while in use.
auto& midiAudio = _getMidiAudio();
@@ -1388,7 +1465,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->SetBlockSelection(false);
search.Select();
// this is used for search,
// DO NOT call _updateSelectionUI() here.
// We don't want to show the markers so manually tell it to clear it.
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}
// Raise a FoundMatch event, which the control will use to notify
@@ -1423,7 +1505,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::Close()
void ControlCore::Close(const bool async)
{
if (!_IsClosing())
{
@@ -1433,12 +1515,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
// GH#1996 - Close the connection asynchronously on a background
// thread.
// Since TermControl::Close is only ever triggered by the UI, we
// don't really care to wait for the connection to be completely
// closed. We can just do it whenever.
_asyncCloseConnection();
if (async)
{
// GH#1996 - Close the connection asynchronously on a background
// thread.
// Since TermControl::Close is only ever triggered by the UI, we
// don't really care to wait for the connection to be completely
// closed. We can just do it whenever.
_asyncCloseConnection();
}
else
{
// see notes in ContentProcess::final_release for why there's
// _also_ a synchronous version of this.
_connection.Close();
}
}
}
@@ -1589,8 +1680,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->MultiClickSelection(terminalPosition, mode);
selectionNeedsToBeCopied = true;
}
_updateSelectionUI();
}
// Method Description:
// - Updates the renderer's representation of the selection as well as the selection marker overlay in TermControl
void ControlCore::_updateSelectionUI()
{
_renderer->TriggerSelection();
// only show the markers if we're doing a keyboard selection or in mark mode
const bool showMarkers{ _terminal->SelectionMode() >= ::Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Keyboard };
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(!showMarkers));
}
void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
@@ -1859,13 +1959,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - This is related to work done for GH#2988.
void ControlCore::GotFocus()
{
_terminal->FocusChanged(true);
_focusChanged(true);
}
// See GotFocus.
void ControlCore::LostFocus()
{
_terminal->FocusChanged(false);
_focusChanged(false);
}
void ControlCore::_focusChanged(bool focused)
{
// GH#13461 - temporarily turn off read-only mode, send the focus event,
// then turn it back on. Even in focus mode, focus events are fine to
// send. We don't want to pop a warning every time the control is
// focused.
const auto previous = std::exchange(_isReadOnly, false);
const auto restore = wil::scope_exit([&]() { _isReadOnly = previous; });
_terminal->FocusChanged(focused);
}
bool ControlCore::_isBackgroundTransparent()
@@ -1881,6 +1992,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return Opacity() < 1.0f || UseAcrylic() || !_settings->BackgroundImage().empty() || _settings->UseBackgroundImageForWindow();
}
double ControlCore::DisplayScale() const
{
return _compositionScale;
}
uint64_t ControlCore::OwningHwnd()
{
return _owningHwnd;
@@ -1890,6 +2006,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
if (owner != _owningHwnd && _connection)
{
// TODO GH#1256 change the midi HWND too
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
{
conpty.ReparentWindow(owner);
@@ -2017,19 +2134,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
const auto viewHeight = ViewHeight();
const auto bufferSize = BufferHeight();
// UserScrollViewport, to update the Terminal about where the viewport should be
// then raise a _terminalScrollPositionChanged to inform the control to update the scrollbar.
if (tgt.has_value())
{
UserScrollViewport(tgt->start.y);
_terminalScrollPositionChanged(tgt->start.y, viewHeight, bufferSize);
}
else
{
if (direction == ScrollToMarkDirection::Last || direction == ScrollToMarkDirection::Next)
{
UserScrollViewport(BufferHeight());
_terminalScrollPositionChanged(BufferHeight(), viewHeight, bufferSize);
}
else if (direction == ScrollToMarkDirection::First || direction == ScrollToMarkDirection::Previous)
{
UserScrollViewport(0);
_terminalScrollPositionChanged(0, viewHeight, bufferSize);
}
}
}

View File

@@ -65,6 +65,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
double DisplayScale() const;
uint64_t SwapChainHandle() const;
void AdjustFontSize(int fontSizeDelta);
@@ -82,9 +83,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void PasteText(const winrt::hstring& hstr);
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
void SelectAll();
void ClearSelection();
bool ToggleBlockSelection();
void ToggleMarkMode();
bool IsInMarkMode() const;
Control::SelectionInteractionMode SelectionMode() const;
bool SwitchSelectionEndpoint();
void GotFocus();
void LostFocus();
@@ -102,7 +105,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
void Close();
void Close(const bool async = true);
#pragma region ICoreState
const size_t TaskbarState() const noexcept;
@@ -159,6 +162,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool HasSelection() const;
bool CopyOnSelect() const;
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
Control::SelectionData SelectionInfo() const;
void SetSelectionAnchor(const til::point position);
void SetEndSelectionPoint(const til::point position);
@@ -189,6 +193,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
uint64_t OwningHwnd();
void OwningHwnd(uint64_t owner);
winrt::Windows::System::DispatcherQueue Dispatcher();
RUNTIME_SETTING(double, Opacity, _settings->Opacity());
RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic());
@@ -214,6 +220,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable);
TYPED_EVENT(FoundMatch, IInspectable, Control::FoundResultsArgs);
TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
// clang-format on
private:
@@ -269,6 +277,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _setFontSizeUnderLock(int fontSize);
void _updateFont(const bool initialUpdate = false);
void _refreshSizeUnderLock();
void _updateSelectionUI();
void _sendInputToConnection(std::wstring_view wstr);
@@ -306,10 +315,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _setOpacity(const double opacity);
bool _isBackgroundTransparent();
void _focusChanged(bool focused);
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
// This may not be strictly true if the core is running out of proc
// with XAML. This assert was hit frequently during initial x-proc
// development, but seemingly not anymore. Keep an eye on it.
if (_dispatcher)
{
// _closing isn't atomic and may only be accessed from the main thread.

View File

@@ -29,6 +29,30 @@ namespace Microsoft.Terminal.Control
All
};
enum SelectionInteractionMode
{
None,
Mouse,
Keyboard,
Mark
};
[flags]
enum SelectionEndpointTarget
{
Start = 0x1,
End = 0x2
};
struct SelectionData
{
Microsoft.Terminal.Core.Point StartPos;
Microsoft.Terminal.Core.Point EndPos;
SelectionEndpointTarget Endpoint;
Boolean StartAtLeftBoundary;
Boolean EndAtRightBoundary;
};
[default_interface] runtimeclass ControlCore : ICoreState
{
ControlCore(IControlSettings settings,
@@ -48,6 +72,7 @@ namespace Microsoft.Terminal.Control
Boolean HasUnfocusedAppearance();
UInt64 SwapChainHandle { get; };
Double DisplayScale { get; };
Windows.Foundation.Size FontSize { get; };
String FontFaceName { get; };
@@ -65,10 +90,11 @@ namespace Microsoft.Terminal.Control
void SendInput(String text);
void PasteText(String text);
void SelectAll();
void ClearSelection();
Boolean ToggleBlockSelection();
void ToggleMarkMode();
Boolean SwitchSelectionEndpoint();
void ClearBuffer(ClearBufferType clearType);
Boolean IsInMarkMode();
void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition);
void ClearHoveredCell();
@@ -90,6 +116,8 @@ namespace Microsoft.Terminal.Control
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
SelectionData SelectionInfo { get; };
SelectionInteractionMode SelectionMode();
String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
@@ -125,6 +153,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
event Windows.Foundation.TypedEventHandler<Object, FoundResultsArgs> FoundMatch;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
};
}

View File

@@ -142,7 +142,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
// - CopyOnSelect does NOT clear the selection
// Arguments:
// - singleLine: collapse all of the text to one line
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
@@ -257,15 +256,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsRightButtonDown))
{
// CopyOnSelect right click always pastes
if (_core->CopyOnSelect() || !_core->HasSelection())
// Try to copy the text and clear the selection
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
_core->ClearSelection();
if (_core->CopyOnSelect() || !successfulCopy)
{
// CopyOnSelect: right click always pastes!
// Otherwise: no selection --> paste
RequestPasteTextFromClipboard();
}
else
{
CopySelectionToClipboard(shiftEnabled, nullptr);
}
}
}
@@ -383,6 +382,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
isLeftMouseRelease &&
_selectionNeedsToBeCopied)
{
// IMPORTANT!
// DO NOT clear the selection here!
// Otherwise, the selection will be cleared immediately after you make it.
CopySelectionToClipboard(false, nullptr);
}

View File

@@ -13,3 +13,4 @@
#include "TransparencyChangedEventArgs.g.cpp"
#include "FoundResultsArgs.g.cpp"
#include "ShowWindowArgs.g.cpp"
#include "UpdateSelectionMarkersEventArgs.g.cpp"

View File

@@ -13,6 +13,7 @@
#include "TransparencyChangedEventArgs.g.h"
#include "FoundResultsArgs.g.h"
#include "ShowWindowArgs.g.h"
#include "UpdateSelectionMarkersEventArgs.g.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
@@ -157,4 +158,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(bool, ShowOrHide);
};
struct UpdateSelectionMarkersEventArgs : public UpdateSelectionMarkersEventArgsT<UpdateSelectionMarkersEventArgs>
{
public:
UpdateSelectionMarkersEventArgs(const bool clearMarkers) :
_ClearMarkers(clearMarkers)
{
}
WINRT_PROPERTY(bool, ClearMarkers, false);
};
}

View File

@@ -69,7 +69,6 @@ namespace Microsoft.Terminal.Control
Double Opacity { get; };
}
runtimeclass FoundResultsArgs
{
Boolean FoundMatch { get; };
@@ -79,4 +78,9 @@ namespace Microsoft.Terminal.Control
{
Boolean ShowOrHide { get; };
}
runtimeclass UpdateSelectionMarkersEventArgs
{
Boolean ClearMarkers { get; };
}
}

View File

@@ -43,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_controlPadding = til::rect{ til::math::rounding, padding };
}
void InteractivityAutomationPeer::ParentProvider(AutomationPeer parentProvider)
void InteractivityAutomationPeer::ParentProvider(const Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple& parentProvider)
{
_parentProvider = parentProvider;
}
@@ -171,7 +171,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
double InteractivityAutomationPeer::GetScaleFactor() const noexcept
{
return DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
// If we were being used in a process that had a CoreWindow, we could just call
// DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
//
// We won't always be though, so instead, ask our model what the DPI is.
return _interactivity->Core().DisplayScale();
}
void InteractivityAutomationPeer::ChangeViewport(const til::inclusive_rect& NewWindow)
@@ -182,15 +186,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::_CreateXamlUiaTextRange(UIA::ITextRangeProvider* returnVal) const
{
// LOAD-BEARING: use _parentProvider->ProviderFromPeer(_parentProvider) instead of this->ProviderFromPeer(*this).
// Since we split the automation peer into TermControlAutomationPeer and InteractivityAutomationPeer,
// using "this" returns null. This can cause issues with some UIA Client scenarios like any navigation in Narrator.
const auto parent{ _parentProvider.get() };
if (!parent)
if (!_parentProvider)
{
return nullptr;
}
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parent.ProviderFromPeer(parent));
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, _parentProvider);
return xutr.as<XamlAutomation::ITextRangeProvider>();
};

View File

@@ -43,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SetControlBounds(const Windows::Foundation::Rect bounds);
void SetControlPadding(const Core::Padding padding);
void ParentProvider(Windows::UI::Xaml::Automation::Peers::AutomationPeer parentProvider);
void ParentProvider(const Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple& parentProvider);
#pragma region IUiaEventDispatcher
void SignalSelectionChanged() override;
@@ -81,7 +81,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
winrt::Microsoft::Terminal::Control::implementation::ControlInteractivity* _interactivity;
weak_ref<Windows::UI::Xaml::Automation::Peers::AutomationPeer> _parentProvider;
winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple _parentProvider{ nullptr };
til::rect _controlBounds{};
til::rect _controlPadding{};

View File

@@ -3,13 +3,11 @@
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass InteractivityAutomationPeer :
Windows.UI.Xaml.Automation.Peers.AutomationPeer,
Windows.UI.Xaml.Automation.Provider.ITextProvider
{
void SetControlBounds(Windows.Foundation.Rect bounds);
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
void ParentProvider(Windows.UI.Xaml.Automation.Peers.AutomationPeer parentProvider);
void ParentProvider(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple provider);
event Windows.Foundation.TypedEventHandler<Object, Object> SelectionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TextChanged;

View File

@@ -208,4 +208,10 @@ Please either install the missing font or choose another one.</value>
<value>No results found</value>
<comment>Announced to a screen reader when the user searches for some text and there are no matches for that text in the terminal.</comment>
</data>
<data name="TermControl_ContentDiedTextBlock.Text" xml:space="preserve">
<value>The content of this terminal was closed unexpectedly.</value>
</data>
<data name="TermControl_ContentDiedButton.Content" xml:space="preserve">
<value>Close</value>
</data>
</root>

View File

@@ -1,5 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// The functions for handling content processes in the TermControl are largely
// in TermControlContentManagement.cpp
#include "pch.h"
#include "TermControl.h"
@@ -31,8 +34,10 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread
// The minimum delay between updating the TSF input control. This is already
// throttled primarily in the ControlCore, with a timeout of 100ms. We're adding
// another smaller one here, as the (potentially x-proc) call will come in off
// the UI thread
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the locations of regex patterns
@@ -50,6 +55,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TermControl::TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection) :
TermControl(winrt::guid{}, settings, unfocusedAppearance, connection) {}
TermControl::TermControl(winrt::guid contentGuid,
IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection) :
_initializedTerminal{ false },
_closing{ false },
_isInternalScrollBarUpdate{ false },
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
@@ -61,30 +74,52 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
InitializeComponent();
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, connection);
if (contentGuid != winrt::guid{})
{
_contentProc = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
if (_contentProc != nullptr)
{
// TODO! think harder about ordering of Attach here.
_contentProc.Attach();
_interactivity = _contentProc.GetInteractivity();
_contentWaitInterrupt.create();
_createContentWaitThread();
}
}
if (_interactivity == nullptr)
{
_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 });
_core.ScrollPositionChanged({ get_weak(), &TermControl::_ScrollPositionChanged });
_core.WarningBell({ get_weak(), &TermControl::_coreWarningBell });
_core.CursorPositionChanged({ get_weak(), &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 });
_core.ConnectionStateChanged({ get_weak(), &TermControl::_coreConnectionStateChanged });
// 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 });
_interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
_core.BackgroundColorChanged({ get_weak(), &TermControl::_coreBackgroundColorChanged });
_core.FontSizeChanged({ get_weak(), &TermControl::_coreFontSizeChanged });
_core.TransparencyChanged({ get_weak(), &TermControl::_coreTransparencyChanged });
_core.RaiseNotice({ get_weak(), &TermControl::_coreRaisedNotice });
_core.HoveredHyperlinkChanged({ get_weak(), &TermControl::_hoveredHyperlinkChanged });
_core.FoundMatch({ get_weak(), &TermControl::_coreFoundMatch });
_core.UpdateSelectionMarkers({ get_weak(), &TermControl::_updateSelectionMarkers });
_core.OpenHyperlink({ get_weak(), &TermControl::_HyperlinkHandler });
_interactivity.OpenHyperlink({ get_weak(), &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ get_weak(), &TermControl::_ScrollPositionChanged });
// 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
@@ -135,6 +170,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_ApplyUISettings();
}
Control::ContentProcess TermControl::ContentProc() const noexcept
{
return _contentProc;
}
void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update)
{
// Assumptions:
@@ -358,6 +398,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// switch from a solid color brush to an acrylic one.
_changeBackgroundColor(bg);
// Update selection markers
Windows::UI::Xaml::Media::SolidColorBrush cursorColorBrush{ til::color{ newAppearance.CursorColor() } };
SelectionStartMarker().Fill(cursorColorBrush);
SelectionEndMarker().Fill(cursorColorBrush);
// Set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
if (_core.Settings().UseBackgroundImageForWindow())
@@ -426,12 +471,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// achieve the intended effect.
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
ScrollBar().Visibility(Visibility::Collapsed);
ScrollMarksGrid().Visibility(Visibility::Collapsed);
}
else // (default or Visible)
{
// Default behavior
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
ScrollBar().Visibility(Visibility::Visible);
ScrollMarksGrid().Visibility(Visibility::Visible);
}
_interactivity.UpdateSettings();
@@ -562,6 +609,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
RootGrid().Background(solidColor);
}
BackgroundBrush(RootGrid().Background());
}
// Method Description:
@@ -607,6 +656,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
solidColor.Color(bg);
}
BackgroundBrush(RootGrid().Background());
// Don't use the normal BackgroundBrush() Observable Property setter
// here. (e.g. `BackgroundBrush()`). The one from the macro will
// automatically ignore changes where the value doesn't _actually_
// change. In our case, most of the time when changing the colors of the
// background, the _Brush_ itself doesn't change, we simply change the
// Color() of the brush. This results in the event not getting bubbled
// up.
//
// Firing it manually makes sure it does.
_BackgroundBrush = RootGrid().Background();
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"BackgroundBrush" });
}
// Method Description:
@@ -646,6 +709,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TermControl::~TermControl()
{
if (_contentIsOutOfProc())
{
_contentWaitInterrupt.SetEvent();
_contentWaitThread.join();
}
Close();
}
@@ -692,7 +760,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TerminalConnection::ConnectionState TermControl::ConnectionState() const
{
return _core.ConnectionState();
try
{
return _core.ConnectionState();
}
CATCH_LOG();
return TerminalConnection::ConnectionState::Failed;
}
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable /*args*/)
@@ -706,8 +779,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (auto control{ weakThis.get() })
{
const auto chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
_AttachDxgiSwapChainToXaml(chainHandle);
control->_acquireAndAttachSwapChainHandle();
}
}
@@ -759,6 +831,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
nativePanel->SetSwapChainHandle(swapChainHandle);
}
void TermControl::_acquireAndAttachSwapChainHandle()
{
const auto chainHandle = reinterpret_cast<HANDLE>(_contentIsOutOfProc() ?
_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
_core.SwapChainHandle());
// If we're in-proc, then the render engine has it's own ownership
// handle to the swapchain. We don't need to also own it in the XAML
// layer. It doesn't really make sense for us to duplicate it to
// ourself again, so we're just gonna not.
//
// If we're out of proc though, the content proc has one handle in
// it's process, and now there's a HANDLE in our process with this
// value. Wrap that boy up in a unique_handle so that we can release
// our handle when needed.
if (_contentIsOutOfProc())
{
_contentSwapChain.reset(chainHandle);
}
_AttachDxgiSwapChainToXaml(chainHandle);
}
bool TermControl::_InitializeTerminal()
{
if (_initializedTerminal)
@@ -788,13 +882,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
panelScaleX);
if (!coreInitialized)
// ControlCore::Initialize will return false if it was already
// initialized. In that case, don't init the rest of the interactivity,
// but do go on and hook us up to the core's swapchain and callbacks.
if (coreInitialized)
{
return false;
_interactivity.Initialize();
}
_interactivity.Initialize();
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
_acquireAndAttachSwapChainHandle();
// Tell the DX Engine to notify us when the swap chain changes. We do
// this after we initially set the swapchain so as to avoid unnecessary
@@ -1182,7 +1278,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// Manually show the cursor when a key is pressed. Restarting
// the timer prevents flickering.
_core.CursorOn(!_core.IsInMarkMode());
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
_cursorTimer->Start();
}
@@ -1653,7 +1749,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (_cursorTimer)
{
// When the terminal focuses, show the cursor immediately
_core.CursorOn(!_core.IsInMarkMode());
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
_cursorTimer->Start();
}
@@ -1783,6 +1879,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
if (!_IsClosing())
{
// TODO:GH#5000 - move all the blinking inside of ControlCore.
// There's no reason that the timer should live in TermControl just
// to hop across the process boundary to toggle the content process.
// That can all live in the control core.
_core.BlinkCursor();
}
}
@@ -1797,6 +1897,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
if (!_IsClosing())
{
// TODO:GH#5000 - move all the blinking inside of ControlCore.
// There's no reason that the timer should live in TermControl just
// to hop across the process boundary to toggle the content process.
// That can all live in the control core.
_core.BlinkAttributeTick();
}
}
@@ -1831,6 +1935,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();
_updateScrollBar->Run(update);
// if a selection marker is already visible,
// update the position of those markers
if (SelectionStartMarker().Visibility() == Visibility::Visible || SelectionEndMarker().Visibility() == Visibility::Visible)
{
_updateSelectionMarkers(nullptr, winrt::make<UpdateSelectionMarkersEventArgs>(false));
}
}
// Method Description:
@@ -1889,7 +2000,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return false;
}
return _interactivity.CopySelectionToClipboard(singleLine, formats);
const auto successfulCopy = _interactivity.CopySelectionToClipboard(singleLine, formats);
_core.ClearSelection();
return successfulCopy;
}
// Method Description:
@@ -1914,6 +2027,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ToggleMarkMode();
}
bool TermControl::SwitchSelectionEndpoint()
{
return _core.SwitchSelectionEndpoint();
}
void TermControl::Close()
{
if (!_IsClosing())
@@ -1925,8 +2043,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();
_core.Close();
// THIS IS IMPORTANT!
//
// If we're out of proc, the control can be closed, but we DON'T
// want that to close our content. We'll want the content proc's
// teardown to be responsible for closing the core.
if (!_contentIsOutOfProc())
{
_core.Close();
}
}
}
@@ -2735,8 +2860,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ClearHoveredCell();
}
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable sender,
IInspectable args)
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable /*sender*/,
IInspectable /*args*/)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
@@ -2763,12 +2888,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
HyperlinkTooltipBorder().BorderThickness(newThickness);
// Compute the location of the top left corner of the cell in DIPS
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::point startPos{ lastHoveredCell.Value() };
const til::size fontSize{ til::math::rounding, _core.FontSize() };
const auto posInPixels{ startPos * fontSize };
const til::point posInDIPs{ til::math::flooring, posInPixels.x / scale, posInPixels.y / scale };
const auto locationInDIPs{ posInDIPs + marginsInDips };
const til::point locationInDIPs{ _toPosInDips(lastHoveredCell.Value()) };
// Move the border to the top left corner of the cell
OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), locationInDIPs.x - offset.x);
@@ -2778,10 +2898,118 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args)
{
auto weakThis{ get_weak() };
co_await resume_foreground(Dispatcher());
if (weakThis.get() && args)
{
if (_core.HasSelection() && !args.ClearMarkers())
{
// retrieve all of the necessary selection marker data
// from the TerminalCore layer under one lock to improve performance
const auto markerData{ _core.SelectionInfo() };
// lambda helper function that can be used to display a selection marker
// - targetEnd: if true, target the "end" selection marker. Otherwise, target "start".
auto displayMarker = [&](bool targetEnd) {
const auto flipMarker{ targetEnd ? markerData.EndAtRightBoundary : markerData.StartAtLeftBoundary };
const auto& marker{ targetEnd ? SelectionEndMarker() : SelectionStartMarker() };
// 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);
// Compute the location of the top left corner of the cell in DIPS
auto terminalPos{ targetEnd ? markerData.EndPos : markerData.StartPos };
if (flipMarker)
{
// When we flip the marker, a negative scaling makes us be one cell-width to the left.
// Add one to the viewport pos' x-coord to fix that.
terminalPos.X += 1;
}
const til::point locationInDIPs{ _toPosInDips(terminalPos) };
// Move the marker to the top left corner of the cell
SelectionCanvas().SetLeft(marker,
(locationInDIPs.x - SwapChainPanel().ActualOffset().x));
SelectionCanvas().SetTop(marker,
(locationInDIPs.y - SwapChainPanel().ActualOffset().y));
marker.Visibility(Visibility::Visible);
};
// show/update selection markers
// figure out which endpoint to move, get it and the relevant icon (hide the other icon)
const auto movingEnd{ WI_IsFlagSet(markerData.Endpoint, SelectionEndpointTarget::End) };
const auto selectionAnchor{ movingEnd ? markerData.EndPos : markerData.StartPos };
const auto& marker{ movingEnd ? SelectionEndMarker() : SelectionStartMarker() };
const auto& otherMarker{ movingEnd ? SelectionStartMarker() : SelectionEndMarker() };
if (selectionAnchor.Y < 0 || selectionAnchor.Y >= _core.ViewHeight())
{
// if the endpoint is outside of the viewport,
// just hide the markers
marker.Visibility(Visibility::Collapsed);
otherMarker.Visibility(Visibility::Collapsed);
co_return;
}
else if (WI_AreAllFlagsSet(markerData.Endpoint, SelectionEndpointTarget::Start | SelectionEndpointTarget::End))
{
// display both markers
displayMarker(true);
displayMarker(false);
}
else
{
// display one marker,
// but hide the other
displayMarker(movingEnd);
otherMarker.Visibility(Visibility::Collapsed);
}
}
else
{
// hide selection markers
SelectionStartMarker().Visibility(Visibility::Collapsed);
SelectionEndMarker().Visibility(Visibility::Collapsed);
}
}
}
til::point TermControl::_toPosInDips(const Core::Point terminalCellPos)
{
const til::point terminalPos{ terminalCellPos };
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::size fontSize{ til::math::rounding, _core.FontSize() };
const til::point posInPixels{ terminalPos * fontSize };
const auto scale{ SwapChainPanel().CompositionScaleX() };
const til::point posInDIPs{ til::math::flooring, posInPixels.x / scale, posInPixels.y / scale };
return posInDIPs + marginsInDips;
}
void TermControl::_coreFontSizeChanged(const int fontWidth,
const int fontHeight,
const bool isInitialChange)
{
// scale the selection markers to be the size of a cell
auto scaleMarker = [fontWidth, fontHeight, dpiScale{ SwapChainPanel().CompositionScaleX() }](const Windows::UI::Xaml::Shapes::Path& shape) {
// The selection markers were designed to be 5x14 in size,
// so use those dimensions below for the scaling
const auto scaleX = fontWidth / 5.0 / dpiScale;
const auto scaleY = fontHeight / 14.0 / dpiScale;
Windows::UI::Xaml::Media::ScaleTransform transform;
transform.ScaleX(scaleX);
transform.ScaleY(scaleY);
shape.RenderTransform(transform);
// now hide the shape
shape.Visibility(Visibility::Collapsed);
};
scaleMarker(SelectionStartMarker());
scaleMarker(SelectionEndMarker());
// Don't try to inspect the core here. The Core is raising this while
// it's holding its write lock. If the handlers calls back to some
// method on the TermControl on the same thread, and that _method_ calls
@@ -2908,6 +3136,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.OwningHwnd();
}
void TermControl::_coreConnectionStateChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/)
{
_ConnectionStateChangedHandlers(*this, nullptr);
}
void TermControl::AddMark(const Control::ScrollMark& mark)
{
_core.AddMark(mark);

View File

@@ -29,10 +29,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection);
TermControl(winrt::guid contentGuid,
IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection);
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);
@@ -40,6 +47,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SelectAll();
bool ToggleBlockSelection();
void ToggleMarkMode();
bool SwitchSelectionEndpoint();
void Close();
Windows::Foundation::Size CharacterDimensions() const;
Windows::Foundation::Size MinimumSize();
@@ -92,6 +100,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args);
void _RenderRetryButton_Click(const IInspectable& button, const IInspectable& args);
void _ContentDiedCloseButton_Click(const IInspectable& button, const IInspectable& args);
winrt::fire_and_forget _RendererWarning(IInspectable sender,
Control::RendererWarningArgs args);
@@ -129,6 +138,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
Control::ContentProcess ContentProc() const noexcept;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
@@ -137,9 +150,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
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);
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
@@ -152,6 +166,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
// clang-format on
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
@@ -166,6 +182,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::TermControlAutomationPeer _automationPeer{ nullptr };
Control::ControlInteractivity _interactivity{ nullptr };
Control::ControlCore _core{ nullptr };
Control::ContentProcess _contentProc{ nullptr };
winrt::com_ptr<SearchBoxControl> _searchBox;
@@ -204,6 +221,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
bool _showMarksInScrollbar{ false };
wil::unique_event _contentWaitInterrupt;
std::thread _contentWaitThread;
wil::unique_handle _contentSwapChain;
void _createContentWaitThread();
bool _contentIsOutOfProc() const;
void _acquireAndAttachSwapChainHandle();
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
@@ -287,6 +312,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
winrt::fire_and_forget _hoveredHyperlinkChanged(IInspectable sender, IInspectable args);
winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args);
void _coreFontSizeChanged(const int fontWidth,
const int fontHeight,
@@ -294,7 +320,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
void _coreFoundMatch(const IInspectable& sender, const Control::FoundResultsArgs& args);
winrt::fire_and_forget _raiseContentDied();
void _coreConnectionStateChanged(const IInspectable& sender, const IInspectable& args);
til::point _toPosInDips(const Core::Point terminalCellPos);
void _throttledUpdateScrollbar(const ScrollBarUpdate& update);
};
}

View File

@@ -7,6 +7,7 @@ import "IDirectKeyListener.idl";
import "EventArgs.idl";
import "ICoreState.idl";
import "ControlCore.idl";
import "ContentProcess.idl";
namespace Microsoft.Terminal.Control
{
@@ -14,8 +15,14 @@ namespace Microsoft.Terminal.Control
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl,
IDirectKeyListener,
IMouseWheelListener,
ICoreState
ICoreState,
Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TermControl(Guid contentGuid,
IControlSettings settings,
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
TermControl(IControlSettings settings,
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
@@ -25,6 +32,9 @@ namespace Microsoft.Terminal.Control
void UpdateControlSettings(IControlSettings settings);
void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance);
Guid ContentGuid{ get; };
ContentProcess ContentProc{ get; };
Microsoft.Terminal.Control.IControlSettings Settings { get; };
event FontSizeChangedEventArgs FontSizeChanged;
@@ -53,6 +63,7 @@ namespace Microsoft.Terminal.Control
void SelectAll();
Boolean ToggleBlockSelection();
void ToggleMarkMode();
Boolean SwitchSelectionEndpoint();
void ClearBuffer(ClearBufferType clearType);
void Close();
Windows.Foundation.Size CharacterDimensions { get; };
@@ -89,5 +100,6 @@ namespace Microsoft.Terminal.Control
// opacity set by the settings should call this instead.
Double BackgroundOpacity { get; };
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}
}

View File

@@ -1213,6 +1213,16 @@
</ToolTipService.ToolTip>
</Border>
</Canvas>
<Canvas x:Name="SelectionCanvas"
Visibility="Visible">
<Path Name="SelectionStartMarker"
Data="M 0 0 L 4 5.5996094 L 4 14 L 5 14 L 5 7 L 5 4.1992188 L 5 0 L 0 0 z "
Visibility="Collapsed" />
<Path Name="SelectionEndMarker"
Data="M 0 0 L 0 4.1992188 L 0 7 L 0 14 L 1 14 L 1 5.5996094 L 5 0 L 0 0 z "
Visibility="Collapsed" />
</Canvas>
</SwapChainPanel>
<!--
@@ -1295,6 +1305,27 @@
</Border>
</Grid>
<Grid x:Name="ContentDiedNotice"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Load="False">
<Border Margin="8,8,8,8"
Padding="8,8,8,8"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{ThemeResource SystemAccentColor}"
BorderThickness="2,2,2,2"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<StackPanel>
<TextBlock x:Uid="TermControl_ContentDiedTextBlock"
HorizontalAlignment="Center"
TextWrapping="WrapWholeWords" />
<Button x:Uid="TermControl_ContentDiedButton"
HorizontalAlignment="Right"
Click="_ContentDiedCloseButton_Click" />
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -4,6 +4,8 @@
#include "pch.h"
#include <UIAutomationCore.h>
#include <LibraryResources.h>
#include <ScopedResourceLoader.h>
#include "TermControlAutomationPeer.h"
#include "TermControl.h"
#include "TermControlAutomationPeer.g.cpp"
@@ -83,7 +85,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_contentAutomationPeer.TextChanged([this](auto&&, auto&&) { SignalTextChanged(); });
_contentAutomationPeer.CursorChanged([this](auto&&, auto&&) { SignalCursorChanged(); });
_contentAutomationPeer.NewOutput([this](auto&&, hstring newOutput) { NotifyNewOutput(newOutput); });
_contentAutomationPeer.ParentProvider(*this);
_contentAutomationPeer.ParentProvider(GetParentProvider());
};
// Method Description:
@@ -117,6 +119,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
XamlAutomation::IRawElementProviderSimple TermControlAutomationPeer::GetParentProvider()
{
const auto parentProvider = this->ProviderFromPeer(*this);
return parentProvider;
}
// Method Description:
// - Signals the ui automation client that the terminal's selection has changed and should be updated
// Arguments:
@@ -261,7 +269,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
{
return RS_(L"TerminalControl_ControlType");
// TerminalControl_ControlType is "terminal" in english. Usually, this
// will return the localized version of that string. For applications
// that use the TermControl that don't package up all the resources in a
// way that they can be accessed, this will fall back to just "terminal"
// (notably, the scratch solution doesn't package these resources)
//
// Stash this in a static variable in the off chance that this is called
// in a hot path by some a11y tool. There's absolutely no chance it's
// gonna change, so this is safe enough.
static auto resMap{ ScopedResourceLoader(L"Microsoft.Terminal.Control/Resources").GetResourceMap() };
return resMap ?
RS_(L"TerminalControl_ControlType") :
L"terminal";
}
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const

View File

@@ -48,8 +48,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void UpdateControlBounds();
void SetControlPadding(const Core::Padding padding);
void RecordKeyEvent(const WORD vkey);
Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple GetParentProvider();
#pragma region FrameworkElementAutomationPeer
hstring GetClassNameCore() const;
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;

View File

@@ -12,5 +12,6 @@ namespace Microsoft.Terminal.Control
void UpdateControlBounds();
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple GetParentProvider();
}
}

View File

@@ -0,0 +1,160 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// The functions in this class are specific to the handling of out-of-proc
// content processes by the TermControl. Putting them all in one file keeps
// TermControl.cpp a little less cluttered.
#include "pch.h"
#include "TermControl.h"
using namespace ::Microsoft::Console::Types;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
namespace winrt::Microsoft::Terminal::Control::implementation
{
winrt::guid TermControl::ContentGuid() const
{
return _contentIsOutOfProc() ? _contentProc.Guid() : winrt::guid{};
}
bool TermControl::_contentIsOutOfProc() const
{
return _contentProc != nullptr;
}
bool s_waitOnContentProcess(uint64_t contentPid, HANDLE contentWaitInterrupt)
{
// This is the array of HANDLEs that we're going to wait on in
// WaitForMultipleObjects below.
// * waits[0] will be the handle to the content process. It gets
// signalled when the process exits / dies.
// * waits[1] is the handle to our _contentWaitInterrupt 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] = contentWaitInterrupt;
bool displayError = true;
// At any point in all this, the content process might die. If it does,
// we want to raise an error message, to inform that this control is now
// dead.
try
{
// This might fail to even ask the content for it's PID.
wil::unique_handle hContent{ OpenProcess(SYNCHRONIZE,
FALSE,
static_cast<DWORD>(contentPid)) };
// If we fail to open the content, then they don't exist
// anymore! We'll need to immediately raise the notification that the content has died.
if (hContent.get() == nullptr)
{
const auto gle = GetLastError();
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_FailedToOpenContent",
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return displayError;
}
waits[0] = hContent.get();
switch (WaitForMultipleObjects(2, waits, FALSE, INFINITE))
{
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the content process
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentDied",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
break;
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentWaitInterrupted",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
displayError = false;
break;
case WAIT_TIMEOUT:
// This should be impossible.
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentWaitTimeout",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
break;
default:
{
// Returning any other value is invalid. Just die.
const auto gle = GetLastError();
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_WaitFailed",
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
break;
}
}
}
catch (...)
{
// Theoretically, if window[1] dies when we're trying to get
// its PID we'll get here. We can probably just exit here.
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ExceptionInWaitThread",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
return displayError;
}
void TermControl::_createContentWaitThread()
{
_contentWaitThread = std::thread([weakThis = get_weak(), contentPid = _contentProc.GetPID(), contentWaitInterrupt = _contentWaitInterrupt.get()] {
if (s_waitOnContentProcess(contentPid, contentWaitInterrupt))
{
// When s_waitOnContentProcess returns, if it returned true, we
// should display a dialog in our bounds to indicate that we
// were closed unexpectedly. If we closed in an expected way,
// then s_waitOnContentProcess will return false.
if (auto control{ weakThis.get() })
{
control->_raiseContentDied();
}
}
});
}
winrt::fire_and_forget TermControl::_raiseContentDied()
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
if (auto loadedUiElement{ FindName(L"ContentDiedNotice") })
{
if (auto uiElement{ loadedUiElement.try_as<::winrt::Windows::UI::Xaml::UIElement>() })
{
uiElement.Visibility(Visibility::Visible);
}
}
}
}
// Method Description:
// - Handler for when the "Content Died" dialog's button is clicked.
void TermControl::_ContentDiedCloseButton_Click(IInspectable const& /*sender*/, IInspectable const& /*args*/)
{
// Alert whoever's hosting us that the connection was closed.
// When they come asking what the new connection state is, we'll reply with Closed
_ConnectionStateChangedHandlers(*this, nullptr);
}
}

View File

@@ -34,6 +34,9 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ContentProcess.h">
<DependentUpon>ContentProcess.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ControlCore.h">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClInclude>
@@ -64,13 +67,18 @@
<ClInclude Include="TSFInputControl.h">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="XamlUiaTextRange.h" />
<ClInclude Include="XamlUiaTextRange.h" >
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
</ClInclude>
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ContentProcess.cpp">
<DependentUpon>ContentProcess.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ControlCore.cpp">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClCompile>
@@ -93,6 +101,9 @@
<ClCompile Include="TermControl.cpp">
<DependentUpon>TermControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TermControlContentManagement.cpp">
<DependentUpon>TermControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TSFInputControl.cpp">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</ClCompile>
@@ -103,10 +114,13 @@
<ClCompile Include="InteractivityAutomationPeer.cpp">
<DependentUpon>InteractivityAutomationPeer.idl</DependentUpon>
</ClCompile>
<ClCompile Include="XamlUiaTextRange.cpp" />
<ClCompile Include="XamlUiaTextRange.cpp" >
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
</ClCompile>
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
<Midl Include="ContentProcess.idl" />
<Midl Include="ControlCore.idl" />
<Midl Include="ControlInteractivity.idl" />
<Midl Include="ICoreState.idl" />
@@ -129,6 +143,7 @@
<Midl Include="TSFInputControl.idl">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</Midl>
<Midl Include="XamlUiaTextRange.idl" />
</ItemGroup>
<!-- ========================= XAML Files ======================== -->
<ItemGroup>

View File

@@ -21,13 +21,13 @@ Author(s):
#pragma once
#include "TermControlAutomationPeer.h"
#include "XamlUiaTextRange.g.h"
#include <UIAutomationCore.h>
#include "../types/TermControlUiaTextRange.hpp"
namespace winrt::Microsoft::Terminal::Control::implementation
{
class XamlUiaTextRange :
public winrt::implements<XamlUiaTextRange, Windows::UI::Xaml::Automation::Provider::ITextRangeProvider>
class XamlUiaTextRange : public XamlUiaTextRangeT<XamlUiaTextRange>
{
public:
XamlUiaTextRange(::ITextRangeProvider* uiaProvider, Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider) :

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass XamlUiaTextRange :
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider
{
}
}

View File

@@ -47,7 +47,9 @@
#include <winrt/Windows.ui.xaml.markup.h>
#include <winrt/Windows.ui.xaml.shapes.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.UI.Xaml.Shapes.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Core.h>

View File

@@ -45,8 +45,11 @@ Terminal::Terminal() :
_snapOnInput{ true },
_altGrAliasing{ true },
_blockSelection{ false },
_markMode{ false },
_selectionMode{ SelectionInteractionMode::None },
_isTargetingUrl{ false },
_selection{ std::nullopt },
_selectionEndpoint{ static_cast<SelectionEndpoint>(0) },
_anchorInactiveSelectionEndpoint{ false },
_taskbarState{ 0 },
_taskbarProgress{ 0 },
_trimBlockSelection{ false },
@@ -561,17 +564,24 @@ bool Terminal::ShouldSendAlternateScroll(const unsigned int uiButton,
// Method Description:
// - Given a coord, get the URI at that location
// Arguments:
// - The position
std::wstring Terminal::GetHyperlinkAtPosition(const til::point position)
// - The position relative to the viewport
std::wstring Terminal::GetHyperlinkAtViewportPosition(const til::point viewportPos)
{
auto attr = _activeBuffer().GetCellDataAt(_ConvertToBufferCell(position))->TextAttr();
return GetHyperlinkAtBufferPosition(_ConvertToBufferCell(viewportPos));
}
std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
{
auto attr = _activeBuffer().GetCellDataAt(bufferPos)->TextAttr();
if (attr.IsHyperlink())
{
auto uri = _activeBuffer().GetHyperlinkUriFromId(attr.GetHyperlinkId());
return uri;
}
// also look through our known pattern locations in our pattern interval tree
const auto result = GetHyperlinkIntervalFromPosition(position);
auto viewportPos = bufferPos;
_GetVisibleViewport().ConvertToOrigin(&viewportPos);
const auto result = GetHyperlinkIntervalFromViewportPosition(viewportPos);
if (result.has_value() && result->value == _hyperlinkPatternId)
{
const auto start = result->start;
@@ -592,23 +602,23 @@ std::wstring Terminal::GetHyperlinkAtPosition(const til::point position)
// Method Description:
// - Gets the hyperlink ID of the text at the given terminal position
// Arguments:
// - The position of the text
// - The position of the text relative to the viewport
// Return value:
// - The hyperlink ID
uint16_t Terminal::GetHyperlinkIdAtPosition(const til::point position)
uint16_t Terminal::GetHyperlinkIdAtViewportPosition(const til::point viewportPos)
{
return _activeBuffer().GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId();
return _activeBuffer().GetCellDataAt(_ConvertToBufferCell(viewportPos))->TextAttr().GetHyperlinkId();
}
// Method description:
// - Given a position in a URI pattern, gets the start and end coordinates of the URI
// Arguments:
// - The position
// - The position relative to the viewport
// Return value:
// - The interval representing the start and end coordinates
std::optional<PointTree::interval> Terminal::GetHyperlinkIntervalFromPosition(const til::point position)
std::optional<PointTree::interval> Terminal::GetHyperlinkIntervalFromViewportPosition(const til::point viewportPos)
{
const auto results = _patternIntervalTree.findOverlapping({ position.X + 1, position.Y }, position);
const auto results = _patternIntervalTree.findOverlapping({ viewportPos.X + 1, viewportPos.Y }, viewportPos);
if (results.size() > 0)
{
for (const auto& result : results)
@@ -1369,7 +1379,7 @@ void Terminal::SetCursorOn(const bool isOn)
bool Terminal::IsCursorBlinkingAllowed() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return !_markMode && cursor.IsBlinkingAllowed();
return _selectionMode != SelectionInteractionMode::Mark && cursor.IsBlinkingAllowed();
}
// Method Description:

View File

@@ -162,9 +162,10 @@ public:
void FocusChanged(const bool focused) noexcept override;
std::wstring GetHyperlinkAtPosition(const til::point position);
uint16_t GetHyperlinkIdAtPosition(const til::point position);
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> GetHyperlinkIntervalFromPosition(const til::point position);
std::wstring GetHyperlinkAtViewportPosition(const til::point viewportPos);
std::wstring GetHyperlinkAtBufferPosition(const til::point bufferPos);
uint16_t GetHyperlinkIdAtViewportPosition(const til::point viewportPos);
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> GetHyperlinkIntervalFromViewportPosition(const til::point viewportPos);
#pragma endregion
#pragma region IBaseData(base to IRenderData and IUiaData)
@@ -233,6 +234,14 @@ public:
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionInteractionMode
{
None,
Mouse,
Keyboard,
Mark
};
enum class SelectionDirection
{
Left,
@@ -241,6 +250,12 @@ public:
Down
};
enum class SearchDirection
{
Forward,
Backward
};
enum class SelectionExpansion
{
Char,
@@ -249,17 +264,30 @@ public:
Viewport,
Buffer
};
enum class SelectionEndpoint
{
Start = 0x1,
End = 0x2
};
void MultiClickSelection(const til::point viewportPos, SelectionExpansion expansionMode);
void SetSelectionAnchor(const til::point position);
void SetSelectionEnd(const til::point position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
void UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods);
void SelectAll();
bool IsInMarkMode() const;
SelectionInteractionMode SelectionMode() const noexcept;
void SwitchSelectionEndpoint();
void ToggleMarkMode();
void SelectHyperlink(const SearchDirection dir);
bool IsTargetingUrl() const noexcept;
using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const;
til::point SelectionStartForRendering() const;
til::point SelectionEndForRendering() const;
const SelectionEndpoint SelectionEndpointTarget() const noexcept;
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
@@ -329,7 +357,10 @@ private:
bool _blockSelection;
std::wstring _wordDelimiters;
SelectionExpansion _multiClickSelectionMode;
bool _markMode;
SelectionInteractionMode _selectionMode;
bool _isTargetingUrl;
SelectionEndpoint _selectionEndpoint;
bool _anchorInactiveSelectionEndpoint;
#pragma endregion
std::unique_ptr<TextBuffer> _mainBuffer;
@@ -403,6 +434,7 @@ private:
std::pair<til::point, til::point> _PivotSelection(const til::point targetPos, bool& targetStart) const;
std::pair<til::point, til::point> _ExpandSelectionAnchors(std::pair<til::point, til::point> anchors) const;
til::point _ConvertToBufferCell(const til::point viewportPos) const;
void _ScrollToPoint(const til::point pos);
void _MoveByChar(SelectionDirection direction, til::point& pos);
void _MoveByWord(SelectionDirection direction, til::point& pos);
void _MoveByViewport(SelectionDirection direction, til::point& pos);

View File

@@ -6,6 +6,9 @@
#include "unicode.hpp"
using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Types;
DEFINE_ENUM_FLAG_OPERATORS(Terminal::SelectionEndpoint);
/* Selection Pivot Description:
* The pivot helps properly update the selection when a user moves a selection over itself
@@ -82,6 +85,49 @@ const til::point Terminal::GetSelectionEnd() const noexcept
return _selection->end;
}
// Method Description:
// - Gets the viewport-relative position of where to draw the marker
// for the selection's start endpoint
til::point Terminal::SelectionStartForRendering() const
{
auto pos{ _selection->start };
const auto bufferSize{ GetTextBuffer().GetSize() };
if (pos.x != bufferSize.Left())
{
// In general, we need to draw the marker one before the
// beginning of the selection.
// When we're at the left boundary, we want to
// flip the marker, so we skip this step.
bufferSize.DecrementInBounds(pos);
}
pos.Y = base::ClampSub(pos.Y, _VisibleStartIndex());
return til::point{ pos };
}
// Method Description:
// - Gets the viewport-relative position of where to draw the marker
// for the selection's end endpoint
til::point Terminal::SelectionEndForRendering() const
{
auto pos{ _selection->end };
const auto bufferSize{ GetTextBuffer().GetSize() };
if (pos.x != bufferSize.RightInclusive())
{
// In general, we need to draw the marker one after the
// end of the selection.
// When we're at the right boundary, we want to
// flip the marker, so we skip this step.
bufferSize.IncrementInBounds(pos);
}
pos.Y = base::ClampSub(pos.Y, _VisibleStartIndex());
return til::point{ pos };
}
const Terminal::SelectionEndpoint Terminal::SelectionEndpointTarget() const noexcept
{
return _selectionEndpoint;
}
// Method Description:
// - Checks if selection is active
// Return Value:
@@ -170,6 +216,7 @@ void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<Selec
// expand both anchors
std::tie(_selection->start, _selection->end) = expandedAnchors;
}
_selectionMode = SelectionInteractionMode::Mouse;
}
// Method Description:
@@ -182,7 +229,7 @@ void Terminal::SetSelectionEnd(const til::point viewportPos, std::optional<Selec
// - the new start/end for a selection
std::pair<til::point, til::point> Terminal::_PivotSelection(const til::point targetPos, bool& targetStart) const
{
if (targetStart = _activeBuffer().GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
if (targetStart = targetPos <= _selection->pivot)
{
// target is before pivot
// treat target as start
@@ -235,14 +282,14 @@ void Terminal::SetBlockSelection(const bool isEnabled) noexcept
_blockSelection = isEnabled;
}
bool Terminal::IsInMarkMode() const
Terminal::SelectionInteractionMode Terminal::SelectionMode() const noexcept
{
return _markMode;
return _selectionMode;
}
void Terminal::ToggleMarkMode()
{
if (_markMode)
if (_selectionMode == SelectionInteractionMode::Mark)
{
// Exit Mark Mode
ClearSelection();
@@ -257,14 +304,195 @@ void Terminal::ToggleMarkMode()
_selection->start = cursorPos;
_selection->end = cursorPos;
_selection->pivot = cursorPos;
_markMode = true;
_ScrollToPoint(cursorPos);
_selectionMode = SelectionInteractionMode::Mark;
_blockSelection = false;
_isTargetingUrl = false;
WI_SetAllFlags(_selectionEndpoint, SelectionEndpoint::Start | SelectionEndpoint::End);
}
}
// Method Description:
// - switch the targeted selection endpoint with the other one (i.e. start <--> end)
void Terminal::SwitchSelectionEndpoint()
{
if (IsSelectionActive())
{
if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) && WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End))
{
// moving cursor --> anchor start, move end
_selectionEndpoint = SelectionEndpoint::End;
_anchorInactiveSelectionEndpoint = true;
}
else if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End))
{
// moving end --> now we're moving start
_selectionEndpoint = SelectionEndpoint::Start;
_selection->pivot = _selection->end;
}
else if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start))
{
// moving start --> now we're moving end
_selectionEndpoint = SelectionEndpoint::End;
_selection->pivot = _selection->start;
}
}
}
// Method Description:
// - selects the next/previous hyperlink, if one is available
// Arguments:
// - dir: the direction we're scanning the buffer in to find the hyperlink of interest
// Return Value:
// - true if we found a hyperlink to select (and selected it). False otherwise.
void Terminal::SelectHyperlink(const SearchDirection dir)
{
if (_selectionMode != SelectionInteractionMode::Mark)
{
// This feature only works in mark mode
_isTargetingUrl = false;
return;
}
// 0. Useful tools/vars
const auto bufferSize = _activeBuffer().GetSize();
const auto viewportHeight = _GetMutableViewport().Height();
// The patterns are stored relative to the "search area". Initially, this search area will be the viewport,
// but as we progressively search through more of the buffer, this will change.
// Keep track of the search area here, and use the lambda below to convert points to the search area coordinate space.
auto searchArea = _GetVisibleViewport();
auto convertToSearchArea = [&searchArea](const til::point pt) {
auto copy = pt;
searchArea.ConvertToOrigin(&copy);
return copy;
};
// extracts the next/previous hyperlink from the list of hyperlink ranges provided
auto extractResultFromList = [&](std::vector<interval_tree::Interval<til::point, size_t>>& list) {
const auto selectionStartInSearchArea = convertToSearchArea(_selection->start);
std::optional<std::pair<til::point, til::point>> resultFromList;
if (!list.empty())
{
if (dir == SearchDirection::Forward)
{
// pattern tree includes the currently selected range when going forward,
// so we need to check if we're pointing to that one before returning it.
auto range = list.front();
if (_isTargetingUrl && range.start == selectionStartInSearchArea)
{
if (list.size() > 1)
{
// if we're pointing to the currently selected URL,
// pick the next one.
range = til::at(list, 1);
resultFromList = { range.start, range.stop };
}
else
{
// LOAD-BEARING: the only range here is the one that's currently selected.
// Make sure this is set to nullopt so that we keep searching through the buffer.
resultFromList = std::nullopt;
}
}
else
{
// not on currently selected range, return the first one
resultFromList = { range.start, range.stop };
}
}
else if (dir == SearchDirection::Backward)
{
// moving backwards excludes the currently selected range,
// simply return the last one in the list as it's ordered
const auto range = list.back();
resultFromList = { range.start, range.stop };
}
}
// pattern tree stores everything as viewport coords,
// so we need to convert them on the way out
if (resultFromList)
{
searchArea.ConvertFromOrigin(&resultFromList->first);
searchArea.ConvertFromOrigin(&resultFromList->second);
}
return resultFromList;
};
// 1. Look for the hyperlink
til::point searchStart = dir == SearchDirection::Forward ? _selection->start : til::point{ bufferSize.Left(), _VisibleStartIndex() };
til::point searchEnd = dir == SearchDirection::Forward ? til::point{ bufferSize.RightInclusive(), _VisibleEndIndex() } : _selection->start;
// 1.A) Try searching the current viewport (no scrolling required)
auto resultList = _patternIntervalTree.findContained(convertToSearchArea(searchStart), convertToSearchArea(searchEnd));
std::optional<std::pair<til::point, til::point>> result = extractResultFromList(resultList);
if (!result)
{
// 1.B) Incrementally search through more of the space
if (dir == SearchDirection::Forward)
{
searchStart = { bufferSize.Left(), searchEnd.y + 1 };
searchEnd = { bufferSize.RightInclusive(), std::min(searchStart.y + viewportHeight, ViewEndIndex()) };
}
else
{
searchEnd = { bufferSize.RightInclusive(), searchStart.y - 1 };
searchStart = { bufferSize.Left(), std::max(searchStart.y - viewportHeight, bufferSize.Top()) };
}
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
const til::point bufferStart{ bufferSize.Origin() };
const til::point bufferEnd{ bufferSize.RightInclusive(), ViewEndIndex() };
while (!result && bufferSize.IsInBounds(searchStart) && bufferSize.IsInBounds(searchEnd) && searchStart <= searchEnd && bufferStart <= searchStart && searchEnd <= bufferEnd)
{
auto patterns = _activeBuffer().GetPatterns(searchStart.y, searchEnd.y);
resultList = patterns.findContained(convertToSearchArea(searchStart), convertToSearchArea(searchEnd));
result = extractResultFromList(resultList);
if (!result)
{
if (dir == SearchDirection::Forward)
{
searchStart.y += 1;
searchEnd.y = std::min(searchStart.y + viewportHeight, ViewEndIndex());
}
else
{
searchEnd.y -= 1;
searchStart.y = std::max(searchEnd.y - viewportHeight, bufferSize.Top());
}
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
}
}
// 1.C) Nothing was found. Bail!
if (!result.has_value())
{
return;
}
}
// 2. Select the hyperlink
_selection->start = result->first;
_selection->pivot = result->first;
_selection->end = result->second;
bufferSize.DecrementInBounds(_selection->end);
_isTargetingUrl = true;
_selectionEndpoint = SelectionEndpoint::End;
// 3. Scroll to the selected area (if necessary)
_ScrollToPoint(_selection->end);
}
bool Terminal::IsTargetingUrl() const noexcept
{
return _isTargetingUrl;
}
Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const
{
if ((_markMode || mods.IsShiftPressed()) && !mods.IsAltPressed())
if ((_selectionMode == SelectionInteractionMode::Mark || mods.IsShiftPressed()) && !mods.IsAltPressed())
{
if (mods.IsCtrlPressed())
{
@@ -318,14 +546,34 @@ Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams
// - mods: the key modifiers pressed when performing this update
void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods)
{
// 1. Figure out which endpoint to update
// If we're in mark mode, shift dictates whether you are moving the end or not.
// Otherwise, we're updating an existing selection, so one of the endpoints is the pivot,
// signifying that the other endpoint is the one we want to move.
const auto movingEnd{ _markMode ? mods.IsShiftPressed() : _selection->start == _selection->pivot };
auto targetPos{ movingEnd ? _selection->end : _selection->start };
// This is a special variable used to track if we should move the cursor when in mark mode.
// We have special functionality where if you use the "switchSelectionEndpoint" action
// when in mark mode (moving the cursor), we anchor an endpoint and you can use the
// plain arrow keys to move the endpoint. This way, you don't have to hold shift anymore!
const bool shouldMoveBothEndpoints = _selectionMode == SelectionInteractionMode::Mark && !_anchorInactiveSelectionEndpoint && !mods.IsShiftPressed();
// 2. Perform the movement
// 1. Figure out which endpoint to update
// [Mark Mode]
// - shift pressed --> only move "end" (or "start" if "pivot" == "end")
// - otherwise --> move both "start" and "end" (moving cursor)
// [Quick Edit]
// - just move "end" (or "start" if "pivot" == "end")
_selectionEndpoint = static_cast<SelectionEndpoint>(0);
if (shouldMoveBothEndpoints)
{
WI_SetAllFlags(_selectionEndpoint, SelectionEndpoint::Start | SelectionEndpoint::End);
}
else if (_selection->start == _selection->pivot)
{
WI_SetFlag(_selectionEndpoint, SelectionEndpoint::End);
}
else if (_selection->end == _selection->pivot)
{
WI_SetFlag(_selectionEndpoint, SelectionEndpoint::Start);
}
auto targetPos{ WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) ? _selection->start : _selection->end };
// 2 Perform the movement
switch (mode)
{
case SelectionExpansion::Char:
@@ -342,43 +590,31 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
break;
}
// 3. Actually modify the selection
// NOTE: targetStart doesn't matter here
if (_markMode)
// 3. Actually modify the selection state
_isTargetingUrl = false;
_selectionMode = std::max(_selectionMode, SelectionInteractionMode::Keyboard);
if (shouldMoveBothEndpoints)
{
// [Mark Mode]
// - moveSelectionEnd --> just move end (i.e. shift + arrow keys)
// - !moveSelectionEnd --> move all three (i.e. just use arrow keys)
// [Mark Mode] + shift unpressed --> move all three (i.e. just use arrow keys)
_selection->start = targetPos;
_selection->end = targetPos;
if (!movingEnd)
{
_selection->start = targetPos;
_selection->pivot = targetPos;
}
_selection->pivot = targetPos;
}
else
{
auto targetStart = false;
// [Mark Mode] + shift --> updating a standard selection
// This is also standard quick-edit modification
bool targetStart = false;
std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart);
// IMPORTANT! Pivoting the selection here might have changed which endpoint we're targeting.
// So let's set the targeted endpoint again.
WI_UpdateFlag(_selectionEndpoint, SelectionEndpoint::Start, targetStart);
WI_UpdateFlag(_selectionEndpoint, SelectionEndpoint::End, !targetStart);
}
// 4. Scroll (if necessary)
if (const auto viewport = _GetVisibleViewport(); !viewport.IsInBounds(targetPos))
{
if (const auto amtAboveView = viewport.Top() - targetPos.Y; amtAboveView > 0)
{
// anchor is above visible viewport, scroll by that amount
_scrollOffset += amtAboveView;
}
else
{
// anchor is below visible viewport, scroll by that amount
const auto amtBelowView = targetPos.Y - viewport.BottomInclusive();
_scrollOffset -= amtBelowView;
}
_NotifyScrollEvent();
_activeBuffer().TriggerScroll();
}
_ScrollToPoint(targetPos);
}
void Terminal::SelectAll()
@@ -388,6 +624,7 @@ void Terminal::SelectAll()
_selection->start = bufferSize.Origin();
_selection->end = { bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
_selection->pivot = _selection->end;
_selectionMode = SelectionInteractionMode::Keyboard;
}
void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos)
@@ -405,13 +642,16 @@ void Terminal::_MoveByChar(SelectionDirection direction, til::point& pos)
case SelectionDirection::Up:
{
const auto bufferSize{ _activeBuffer().GetSize() };
pos = { pos.X, std::clamp(pos.Y - 1, bufferSize.Top(), bufferSize.BottomInclusive()) };
const auto newY{ pos.Y - 1 };
pos = newY < bufferSize.Top() ? bufferSize.Origin() : til::point{ pos.X, newY };
break;
}
case SelectionDirection::Down:
{
const auto bufferSize{ _activeBuffer().GetSize() };
pos = { pos.X, std::clamp(pos.Y + 1, bufferSize.Top(), bufferSize.BottomInclusive()) };
const auto mutableBottom{ _GetMutableViewport().BottomInclusive() };
const auto newY{ pos.Y + 1 };
pos = newY > mutableBottom ? til::point{ bufferSize.RightInclusive(), mutableBottom } : til::point{ pos.X, newY };
break;
}
}
@@ -424,7 +664,7 @@ void Terminal::_MoveByWord(SelectionDirection direction, til::point& pos)
case SelectionDirection::Left:
{
const auto wordStartPos{ _activeBuffer().GetWordStart(pos, _wordDelimiters) };
if (_activeBuffer().GetSize().CompareInBounds(_selection->pivot, pos) < 0)
if (_selection->pivot < pos)
{
// If we're moving towards the pivot, move one more cell
pos = wordStartPos;
@@ -447,7 +687,7 @@ void Terminal::_MoveByWord(SelectionDirection direction, til::point& pos)
case SelectionDirection::Right:
{
const auto wordEndPos{ _activeBuffer().GetWordEnd(pos, _wordDelimiters) };
if (_activeBuffer().GetSize().CompareInBounds(pos, _selection->pivot) < 0)
if (pos < _selection->pivot)
{
// If we're moving towards the pivot, move one more cell
pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters);
@@ -529,7 +769,10 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos)
void Terminal::ClearSelection()
{
_selection = std::nullopt;
_markMode = false;
_selectionMode = SelectionInteractionMode::None;
_isTargetingUrl = false;
_selectionEndpoint = static_cast<SelectionEndpoint>(0);
_anchorInactiveSelectionEndpoint = false;
}
// Method Description:
@@ -572,6 +815,30 @@ til::point Terminal::_ConvertToBufferCell(const til::point viewportPos) const
return bufferPos;
}
// Method Description:
// - if necessary, scroll the viewport such that the given point is visible
// Arguments:
// - pos: a coordinate relative to the buffer (not viewport)
void Terminal::_ScrollToPoint(const til::point pos)
{
if (const auto visibleViewport = _GetVisibleViewport(); !visibleViewport.IsInBounds(pos))
{
if (const auto amtAboveView = visibleViewport.Top() - pos.Y; amtAboveView > 0)
{
// anchor is above visible viewport, scroll by that amount
_scrollOffset += amtAboveView;
}
else
{
// anchor is below visible viewport, scroll by that amount
const auto amtBelowView = pos.Y - visibleViewport.BottomInclusive();
_scrollOffset -= amtBelowView;
}
_NotifyScrollEvent();
_activeBuffer().TriggerScroll();
}
}
// Method Description:
// - This method won't be used. We just throw and do nothing. For now we
// need this method to implement UiaData interface

View File

@@ -2,10 +2,8 @@
// Licensed under the MIT license.
#include "pch.h"
#include "EnumEntry.h"
#include "GlobalAppearance.h"
#include "GlobalAppearance.g.cpp"
#include "GlobalAppearancePageNavigationState.g.cpp"
#include <LibraryResources.h>
#include <WtExeUtils.h>
@@ -19,180 +17,13 @@ using namespace winrt::Windows::Foundation::Collections;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// For ComboBox an empty SelectedItem string denotes no selection.
// What we want instead is for "Use system language" to be selected by default.
// --> "und" is synonymous for "Use system language".
constexpr std::wstring_view systemLanguageTag{ L"und" };
static constexpr std::array appLanguageTags{
L"en-US",
L"de-DE",
L"es-ES",
L"fr-FR",
L"it-IT",
L"ja",
L"ko",
L"pt-BR",
L"qps-PLOC",
L"qps-PLOCA",
L"qps-PLOCM",
L"ru",
L"zh-Hans",
L"zh-Hant",
};
GlobalAppearance::GlobalAppearance()
{
InitializeComponent();
INITIALIZE_BINDABLE_ENUM_SETTING(Theme, ElementTheme, winrt::Windows::UI::Xaml::ElementTheme, L"Globals_Theme", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(TabWidthMode, TabViewWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, L"Globals_TabWidthMode", L"Content");
}
void GlobalAppearance::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::GlobalAppearancePageNavigationState>();
_ViewModel = e.Parameter().as<Editor::GlobalAppearanceViewModel>();
}
winrt::hstring GlobalAppearance::LanguageDisplayConverter(const winrt::hstring& tag)
{
if (tag == systemLanguageTag)
{
return RS_(L"Globals_LanguageDefault");
}
winrt::Windows::Globalization::Language language{ tag };
return language.NativeName();
}
// Returns whether the language selector is available/shown.
//
// winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride()
// doesn't work for unpackaged applications. The corresponding code in TerminalApp is disabled.
// It would be confusing for our users if we presented a dysfunctional language selector.
bool GlobalAppearance::LanguageSelectorAvailable()
{
return IsPackaged();
}
// Returns the list of languages the user may override the application language with.
// The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}.
// "und" is short for "undefined" and is synonymous for "Use system language" in this code.
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> GlobalAppearance::LanguageList()
{
if (_languageList)
{
return _languageList;
}
if (!LanguageSelectorAvailable())
{
_languageList = {};
return _languageList;
}
// In order to return the language list this code does the following:
// [1] Get all possible languages we want to allow the user to choose.
// We have to acquire languages from multiple sources, creating duplicates. See below at [1].
// [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order.
// I wanted to sort the localized language names initially, but it turned out to be complex.
// [3] Remove potential duplicates in our language list from [1].
// We don't want to have en-US twice in the list, do we?
// [4] Optionally remove unwanted language tags (like pseudo-localizations).
std::vector<winrt::hstring> tags;
// [1]:
{
// ManifestLanguages contains languages the app ships with.
// Unfortunately, we cannot use this source. Our manifest must contain the
// ~100 languages that are localized for the shell extension and start menu
// presentation so we align with Windows display languages for those surfaces.
// However, the actual content of our application is limited to a much smaller
// subset of approximately 14 languages. As such, we will code the limited
// subset of languages that we support for selection within the Settings
// dropdown to steer users towards the ones that we can display in the app.
// As per the function definition, the first item
// is always "Use system language" ("und").
tags.emplace_back(systemLanguageTag);
// Add our hardcoded languages after the system definition.
for (const auto& v : appLanguageTags)
{
tags.push_back(v);
}
}
// NOTE: The size of tags is always >0, due to tags[0] being hardcoded to "und".
const auto tagsBegin = ++tags.begin();
const auto tagsEnd = tags.end();
// [2]:
std::sort(tagsBegin, tagsEnd);
// I'd love for both, std::unique and std::remove_if, to occur in a single loop,
// but the code turned out to be complex and even less maintainable, so I gave up.
{
// [3] part 1:
auto it = std::unique(tagsBegin, tagsEnd);
// The qps- languages are useful for testing ("pseudo-localization").
// --> Leave them in if debug features are enabled.
if (!_State.Globals().DebugFeaturesEnabled())
{
// [4] part 1:
it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool {
return til::starts_with(tag, L"qps-");
});
}
// [3], [4] part 2 (completing the so called "erase-remove idiom"):
tags.erase(it, tagsEnd);
}
_languageList = winrt::single_threaded_observable_vector(std::move(tags));
return _languageList;
}
winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentLanguage()
{
if (_currentLanguage)
{
return _currentLanguage;
}
if (!LanguageSelectorAvailable())
{
_currentLanguage = {};
return _currentLanguage;
}
// NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
auto currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (currentLanguage.empty())
{
currentLanguage = systemLanguageTag;
}
_currentLanguage = winrt::box_value(currentLanguage);
return _currentLanguage;
}
void GlobalAppearance::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag)
{
_currentLanguage = tag;
const auto currentLanguage = winrt::unbox_value<winrt::hstring>(_currentLanguage);
const auto globals = _State.Globals();
if (currentLanguage == systemLanguageTag)
{
globals.ClearLanguage();
}
else
{
globals.Language(currentLanguage);
}
}
}

View File

@@ -4,20 +4,10 @@
#pragma once
#include "GlobalAppearance.g.h"
#include "GlobalAppearancePageNavigationState.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct GlobalAppearancePageNavigationState : GlobalAppearancePageNavigationStateT<GlobalAppearancePageNavigationState>
{
public:
GlobalAppearancePageNavigationState(const Model::GlobalAppSettings& settings) :
_Globals{ settings } {}
WINRT_PROPERTY(Model::GlobalAppSettings, Globals, nullptr)
};
struct GlobalAppearance : public HasScrollViewer<GlobalAppearance>, GlobalAppearanceT<GlobalAppearance>
{
public:
@@ -25,24 +15,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr);
GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals().Theme);
GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals().TabWidthMode);
public:
// LanguageDisplayConverter maps the given BCP 47 tag to a localized string.
// For instance "en-US" produces "English (United States)", while "de-DE" produces
// "Deutsch (Deutschland)". This works independently of the user's locale.
static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag);
bool LanguageSelectorAvailable();
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> LanguageList();
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);
private:
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList;
winrt::Windows::Foundation::IInspectable _currentLanguage;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(Editor::GlobalAppearanceViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
};
}

View File

@@ -1,29 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "EnumEntry.idl";
import "GlobalAppearanceViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass GlobalAppearancePageNavigationState
{
Microsoft.Terminal.Settings.Model.GlobalAppSettings Globals;
};
[default_interface] runtimeclass GlobalAppearance : Windows.UI.Xaml.Controls.Page
{
GlobalAppearance();
GlobalAppearancePageNavigationState State { get; };
static String LanguageDisplayConverter(String tag);
Boolean LanguageSelectorAvailable { get; };
Windows.Foundation.Collections.IObservableVector<String> LanguageList { get; };
IInspectable CurrentLanguage;
IInspectable CurrentTheme;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> ThemeList { get; };
IInspectable CurrentTabWidthMode;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> TabWidthModeList { get; };
GlobalAppearanceViewModel ViewModel { get; };
}
}

View File

@@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
@@ -28,12 +29,12 @@
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language"
Visibility="{x:Bind LanguageSelectorAvailable}">
<ComboBox ItemsSource="{x:Bind LanguageList}"
SelectedItem="{x:Bind CurrentLanguage, Mode=TwoWay}">
Visibility="{x:Bind ViewModel.LanguageSelectorAvailable}">
<ComboBox ItemsSource="{x:Bind ViewModel.LanguageList}"
SelectedItem="{x:Bind ViewModel.CurrentLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind local:GlobalAppearance.LanguageDisplayConverter((x:String))}" />
<TextBlock Text="{x:Bind local:GlobalAppearanceViewModel.LanguageDisplayConverter((x:String))}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
@@ -42,39 +43,53 @@
<!-- Theme -->
<local:SettingContainer x:Uid="Globals_Theme">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTheme, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
ItemsSource="{x:Bind ViewModel.ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.CurrentTheme, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="model:Theme">
<TextBlock Text="{x:Bind local:GlobalAppearanceViewModel.ThemeNameConverter((model:Theme)), Mode=OneWay}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</local:SettingContainer>
<!-- Always show tabs -->
<local:SettingContainer x:Uid="Globals_AlwaysShowTabs">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysShowTabs, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysShowTabs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Position of new tab -->
<local:SettingContainer x:Uid="Globals_NewTabPosition">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ViewModel.NewTabPositionList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.CurrentNewTabPosition, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
<!-- Show Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitlebar">
<ToggleSwitch IsOn="{x:Bind State.Globals.ShowTabsInTitlebar, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTabsInTitlebar, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show Acrylic in Tab Row -->
<local:SettingContainer x:Uid="Globals_AcrylicTabRow">
<ToggleSwitch IsOn="{x:Bind State.Globals.UseAcrylicInTabRow, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.UseAcrylicInTabRow, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show Title in Titlebar -->
<local:SettingContainer x:Uid="Globals_ShowTitleInTitlebar">
<ToggleSwitch IsOn="{x:Bind State.Globals.ShowTitleInTitlebar, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTitleInTitlebar, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Always on Top -->
<local:SettingContainer x:Uid="Globals_AlwaysOnTop">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysOnTop, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysOnTop, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
@@ -82,27 +97,27 @@
<local:SettingContainer x:Uid="Globals_TabWidthMode">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind TabWidthModeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTabWidthMode, Mode=TwoWay}"
ItemsSource="{x:Bind ViewModel.TabWidthModeList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.CurrentTabWidthMode, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
<!-- Disable Animations -->
<!-- NOTE: the UID is "DisablePaneAnimationsReversed" not "DisablePaneAnimations". See GH#9124 for more details. -->
<local:SettingContainer x:Uid="Globals_DisableAnimationsReversed">
<ToggleSwitch IsOn="{x:Bind local:Converters.InvertBoolean(State.Globals.DisableAnimations), BindBack=State.Globals.SetInvertedDisableAnimationsValue, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.InvertedDisableAnimations, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Always Show Notification Icon -->
<local:SettingContainer x:Uid="Globals_AlwaysShowNotificationIcon">
<ToggleSwitch IsOn="{x:Bind State.Globals.AlwaysShowNotificationIcon, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.AlwaysShowNotificationIcon, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Minimize To Notification Area -->
<local:SettingContainer x:Uid="Globals_MinimizeToNotificationArea">
<ToggleSwitch IsOn="{x:Bind State.Globals.MinimizeToNotificationArea, Mode=TwoWay}"
<ToggleSwitch IsOn="{x:Bind ViewModel.MinimizeToNotificationArea, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
</StackPanel>

View File

@@ -0,0 +1,263 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "GlobalAppearanceViewModel.h"
#include "GlobalAppearanceViewModel.g.cpp"
#include "EnumEntry.h"
#include <LibraryResources.h>
#include <WtExeUtils.h>
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Windows::Foundation::Collections;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// For ComboBox an empty SelectedItem string denotes no selection.
// What we want instead is for "Use system language" to be selected by default.
// --> "und" is synonymous for "Use system language".
constexpr std::wstring_view systemLanguageTag{ L"und" };
static constexpr std::array appLanguageTags{
L"en-US",
L"de-DE",
L"es-ES",
L"fr-FR",
L"it-IT",
L"ja",
L"ko",
L"pt-BR",
L"qps-PLOC",
L"qps-PLOCA",
L"qps-PLOCM",
L"ru",
L"zh-Hans",
L"zh-Hant",
};
constexpr std::wstring_view systemThemeName{ L"system" };
constexpr std::wstring_view darkThemeName{ L"dark" };
constexpr std::wstring_view lightThemeName{ L"light" };
GlobalAppearanceViewModel::GlobalAppearanceViewModel(Model::GlobalAppSettings globalSettings) :
_GlobalSettings{ globalSettings },
_ThemeList{ single_threaded_observable_vector<Model::Theme>() }
{
INITIALIZE_BINDABLE_ENUM_SETTING(NewTabPosition, NewTabPosition, NewTabPosition, L"Globals_NewTabPosition", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(TabWidthMode, TabViewWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, L"Globals_TabWidthMode", L"Content");
_UpdateThemeList();
}
winrt::hstring GlobalAppearanceViewModel::LanguageDisplayConverter(const winrt::hstring& tag)
{
if (tag == systemLanguageTag)
{
return RS_(L"Globals_LanguageDefault");
}
winrt::Windows::Globalization::Language language{ tag };
return language.NativeName();
}
// Returns whether the language selector is available/shown.
//
// winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride()
// doesn't work for unpackaged applications. The corresponding code in TerminalApp is disabled.
// It would be confusing for our users if we presented a dysfunctional language selector.
bool GlobalAppearanceViewModel::LanguageSelectorAvailable()
{
return IsPackaged();
}
// Returns the list of languages the user may override the application language with.
// The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}.
// "und" is short for "undefined" and is synonymous for "Use system language" in this code.
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> GlobalAppearanceViewModel::LanguageList()
{
if (_languageList)
{
return _languageList;
}
if (!LanguageSelectorAvailable())
{
_languageList = {};
return _languageList;
}
// In order to return the language list this code does the following:
// [1] Get all possible languages we want to allow the user to choose.
// We have to acquire languages from multiple sources, creating duplicates. See below at [1].
// [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order.
// I wanted to sort the localized language names initially, but it turned out to be complex.
// [3] Remove potential duplicates in our language list from [1].
// We don't want to have en-US twice in the list, do we?
// [4] Optionally remove unwanted language tags (like pseudo-localizations).
std::vector<winrt::hstring> tags;
// [1]:
{
// ManifestLanguages contains languages the app ships with.
// Unfortunately, we cannot use this source. Our manifest must contain the
// ~100 languages that are localized for the shell extension and start menu
// presentation so we align with Windows display languages for those surfaces.
// However, the actual content of our application is limited to a much smaller
// subset of approximately 14 languages. As such, we will code the limited
// subset of languages that we support for selection within the Settings
// dropdown to steer users towards the ones that we can display in the app.
// As per the function definition, the first item
// is always "Use system language" ("und").
tags.emplace_back(systemLanguageTag);
// Add our hardcoded languages after the system definition.
for (const auto& v : appLanguageTags)
{
tags.push_back(v);
}
}
// NOTE: The size of tags is always >0, due to tags[0] being hardcoded to "und".
const auto tagsBegin = ++tags.begin();
const auto tagsEnd = tags.end();
// [2]:
std::sort(tagsBegin, tagsEnd);
// I'd love for both, std::unique and std::remove_if, to occur in a single loop,
// but the code turned out to be complex and even less maintainable, so I gave up.
{
// [3] part 1:
auto it = std::unique(tagsBegin, tagsEnd);
// The qps- languages are useful for testing ("pseudo-localization").
// --> Leave them in if debug features are enabled.
if (!_GlobalSettings.DebugFeaturesEnabled())
{
// [4] part 1:
it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool {
return til::starts_with(tag, L"qps-");
});
}
// [3], [4] part 2 (completing the so called "erase-remove idiom"):
tags.erase(it, tagsEnd);
}
_languageList = winrt::single_threaded_observable_vector(std::move(tags));
return _languageList;
}
winrt::Windows::Foundation::IInspectable GlobalAppearanceViewModel::CurrentLanguage()
{
if (_currentLanguage)
{
return _currentLanguage;
}
if (!LanguageSelectorAvailable())
{
_currentLanguage = {};
return _currentLanguage;
}
// NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
auto currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (currentLanguage.empty())
{
currentLanguage = systemLanguageTag;
}
_currentLanguage = winrt::box_value(currentLanguage);
return _currentLanguage;
}
void GlobalAppearanceViewModel::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag)
{
_currentLanguage = tag;
const auto currentLanguage = winrt::unbox_value<winrt::hstring>(_currentLanguage);
if (currentLanguage == systemLanguageTag)
{
_GlobalSettings.ClearLanguage();
}
else
{
_GlobalSettings.Language(currentLanguage);
}
}
// Function Description:
// - Updates the list of all themes available to choose from.
void GlobalAppearanceViewModel::_UpdateThemeList()
{
// Surprisingly, though this is called every time we navigate to the page,
// the list does not keep growing on each navigation.
const auto& ThemeMap{ _GlobalSettings.Themes() };
for (const auto& pair : ThemeMap)
{
_ThemeList.Append(pair.Value());
}
}
winrt::Windows::Foundation::IInspectable GlobalAppearanceViewModel::CurrentTheme()
{
return _GlobalSettings.CurrentTheme();
}
// Get the name out of the newly selected item, stash that as the Theme name
// set for the globals. That controls which theme is actually the current
// theme.
void GlobalAppearanceViewModel::CurrentTheme(const winrt::Windows::Foundation::IInspectable& tag)
{
if (const auto& theme{ tag.try_as<Model::Theme>() })
{
_GlobalSettings.Theme(theme.Name());
}
}
// Method Description:
// - Convert the names of the inbox themes to some more descriptive,
// well-known values. If the passed in theme isn't an inbox one, then just
// return its set Name.
// - "light" becomes "Light"
// - "dark" becomes "Dark"
// - "system" becomes "Use Windows theme"
// - These values are all localized based on the app language.
// Arguments:
// - theme: the theme to get the display name for.
// Return Value:
// - the potentially localized name to use for this Theme.
winrt::hstring GlobalAppearanceViewModel::ThemeNameConverter(const Model::Theme& theme)
{
if (theme.Name() == darkThemeName)
{
return RS_(L"Globals_ThemeDark/Content");
}
else if (theme.Name() == lightThemeName)
{
return RS_(L"Globals_ThemeLight/Content");
}
else if (theme.Name() == systemThemeName)
{
return RS_(L"Globals_ThemeSystem/Content");
}
return theme.Name();
}
bool GlobalAppearanceViewModel::InvertedDisableAnimations()
{
return !_GlobalSettings.DisableAnimations();
}
void GlobalAppearanceViewModel::InvertedDisableAnimations(bool value)
{
_GlobalSettings.DisableAnimations(!value);
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "GlobalAppearanceViewModel.g.h"
#include "ViewModelHelpers.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct GlobalAppearanceViewModel : GlobalAppearanceViewModelT<GlobalAppearanceViewModel>, ViewModelHelper<GlobalAppearanceViewModel>
{
public:
GlobalAppearanceViewModel(Model::GlobalAppSettings globalSettings);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Model::Theme>, ThemeList, nullptr);
GETSET_BINDABLE_ENUM_SETTING(NewTabPosition, Model::NewTabPosition, _GlobalSettings.NewTabPosition);
GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, _GlobalSettings.TabWidthMode);
public:
// LanguageDisplayConverter maps the given BCP 47 tag to a localized string.
// For instance "en-US" produces "English (United States)", while "de-DE" produces
// "Deutsch (Deutschland)". This works independently of the user's locale.
static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag);
bool LanguageSelectorAvailable();
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> LanguageList();
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);
winrt::Windows::Foundation::IInspectable CurrentTheme();
void CurrentTheme(const winrt::Windows::Foundation::IInspectable& tag);
static winrt::hstring ThemeNameConverter(const Model::Theme& theme);
bool InvertedDisableAnimations();
void InvertedDisableAnimations(bool value);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, AlwaysShowTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowTabsInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, UseAcrylicInTabRow);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowTitleInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, AlwaysOnTop);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, AlwaysShowNotificationIcon);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, MinimizeToNotificationArea);
private:
Model::GlobalAppSettings _GlobalSettings;
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList;
winrt::Windows::Foundation::IInspectable _currentLanguage;
winrt::Windows::Foundation::IInspectable _currentTheme;
void _UpdateThemeList();
};
};
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(GlobalAppearanceViewModel);
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "EnumEntry.idl";
#include "ViewModelHelpers.idl.h"
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass GlobalAppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
GlobalAppearanceViewModel(Microsoft.Terminal.Settings.Model.GlobalAppSettings globalSettings);
static String LanguageDisplayConverter(String tag);
Boolean LanguageSelectorAvailable { get; };
Windows.Foundation.Collections.IObservableVector<String> LanguageList { get; };
IInspectable CurrentLanguage;
IInspectable CurrentTheme;
static String ThemeNameConverter(Microsoft.Terminal.Settings.Model.Theme theme);
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.Theme> ThemeList { get; };
IInspectable CurrentNewTabPosition;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> NewTabPositionList { get; };
IInspectable CurrentTabWidthMode;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> TabWidthModeList { get; };
Boolean InvertedDisableAnimations;
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, AlwaysShowTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowTabsInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, UseAcrylicInTabRow);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowTitleInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, AlwaysOnTop);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, AlwaysShowNotificationIcon);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, MinimizeToNotificationArea);
}
}

View File

@@ -4,8 +4,6 @@
#include "pch.h"
#include "Interaction.h"
#include "Interaction.g.cpp"
#include "InteractionPageNavigationState.g.cpp"
#include "EnumEntry.h"
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Microsoft::Terminal::Settings::Model;
@@ -15,13 +13,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Interaction::Interaction()
{
InitializeComponent();
INITIALIZE_BINDABLE_ENUM_SETTING(TabSwitcherMode, TabSwitcherMode, TabSwitcherMode, L"Globals_TabSwitcherMode", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(CopyFormat, CopyFormat, winrt::Microsoft::Terminal::Control::CopyFormat, L"Globals_CopyFormat", L"Content");
}
void Interaction::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::InteractionPageNavigationState>();
_ViewModel = e.Parameter().as<Editor::InteractionViewModel>();
}
}

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