Compare commits

...

71 Commits

Author SHA1 Message Date
Dustin Howett
28571c5671 HAX: What if the WPF control used Atlas? 2023-06-28 15:59:19 -05:00
Leonard Hecker
72b44888b5 Reduce GdiEngine input latency (#15608)
This commit reduces GdiEngine's average display latency by 8ms,
which caused it to miss a v-blank about half the time at 60Hz.

Closes #15607

## Validation Steps Performed
Input latency with `frarees/typometer` matches conhost from Win10 
2023-06-28 00:14:42 +02:00
Dustin L. Howett
99c18ce57e wpf: delay-load UIAutomationCore because it's incomplete in RS1 (#15614)
UiaRaiseNotificationEvent is not present on Windows Server 2016, even
though it is documented as being present.
This also removes the cost of loading up UIAutomationCore from the
critical path.
2023-06-27 21:42:20 +00:00
Leonard Hecker
58e1380b8f Properly filter focus events from the console API (#15606)
This is an improved fix for #13238. Instead of handling focus events in
the `TerminalInput::HandleKey` function and the need to filter them
out depending on where they came from, we simply don't call `HandleKey`
in the first place. This makes the somewhat unreliable `CameFromApi`
function unnecessary and the code a bit more robust.

This change is required because `CameFromApi` is not representable
in a `INPUT_RECORD` and I'm getting rid of `IInputEvent`.

## Validation Steps Performed
* No `[O` when exiting nvim 
* Mouse input in nvim works 
2023-06-27 17:50:51 +02:00
Leonard Hecker
a5960049bf Deduplicate INPUT_RECORD based ApiRoutines (#15605)
`(Peek|Read)ConsoleInput(A|W)Impl` make a distinction that doesn't make
a lot of sense in our code base: On the calling side (`ApiDispatchers`)
there's just one function calling all 4 (`ServerGetConsoleInput`) and
on the callee side they all 4 just call `_DoGetConsoleInput` anyways.

## Validation Steps Performed
* It compiles 
2023-06-27 15:33:57 +02:00
Leonard Hecker
f0291c6501 Remove boolean success return values from TextBuffer (#15566)
I've removed these because it made some of my new code pretty
convoluted for now good reason as most of these functions aren't
exception safe to begin with. Basically, their boolean status
is often just a pretense because they can crash or throw anyways.

Furthermore, `WriteCharsLegacy` failed to check the status code
returned by `AdjustCursorPosition` in some of its parts too.

In the future we should instead probably strive to continue
make our legacy code more exception safe.
2023-06-22 16:24:10 -07:00
Leonard Hecker
e594d97c90 Allow ROW::CopyRangeFrom to be vectorized (#15267)
By rewriting the first major copy loop in `CopyRangeFrom` to use
pointers/iterators instead of indices for iteration, the autovectorizer
kicks in end neatly rewrites it as an unrolled SIMD loop. This improves
performance during traditional window resizes by roughly 2x and will
be quite helpful in the future for our more complex reflow resize.

Unfortunately, MSVC unrolls the loop by 4x which is too much for our
purpose, but there's no option to change that. It's still better than
not having any vectorization however, since it kicks in at 32 columns.

It also renames the function to `CopyTextFrom` be more in line with
the others and to avoid confusion, because it doesn't copy attributes.

## Validation Steps Performed
* Traditional resizing works 
2023-06-22 17:17:46 -05:00
Dustin L. Howett
191eb00f43 Add the drop validator task, rework some build artifacts (#15568)
I originally intended to add the Drop Validator (which is a compliance
requirement) task to the build, but I quickly realized that we weren't
generating a complete SBOM manifest covering every artifact that we
produced.

We were generating the SBOM manifest, and then re-packing the Terminal
app which very likely invalidated all of the hashes and signatures in
the SBOM manifest!

We were also missing the unpackaged build.

I've removed the `appx-PLATFORM-CONFIG` and `unpackaged-PLAT-CONF`
artifacts and combined them into a single one, `terminal-PLAT-CONF`.
2023-06-21 16:01:45 -05:00
Dustin L. Howett
a46a2719b8 Stop committing the whole buffer when determining if it's empty (#15582)
As a shortcut, GetLastNonSpaceCharacter can start with the last
committed row. It's guaranteed that there isn't anything of worth below
that point, so why bother checking?

Without this, Terminal immediately commits the entire 9031-line buffer
on startup while trying to--get this!--clear the screen!

---------

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
2023-06-21 13:52:01 -07:00
MPela
40963c7b18 Add an action to search the web for selected text (#15539)
This PR adds a `searchWeb` command to search the selected text on the web.
Arguments:
- `queryUrl`: URL of the web page to launch (the selected text will be
inserted where the first `%s` is found in the query string)

To make the search text more "compact" and handle multi-line selections,
I'm concatenating the selected lines and replacing consecutive
whitespaces with a single space (we may change this with something more
clever in case).

## Validation Steps Performed
Manual testing with single, multi-line, block selections.

Closes #10175

---------

Co-authored-by: Marco Pelagatti <marco.pelagatti@iongroup.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
2023-06-21 12:24:45 -05:00
Tushar Singh
2386abb8df unhide nuget packages/ for VSCode (#15579)
Don't exclude nuget `packages/` in vscode. Excluding via `file.exclude`
also excludes them from c++ language extension's `includePath` and
generates missing include files and header errors.

We might still like to exclude them from full text search, so we do it
using `search.exclude`.

Closes #15578
2023-06-21 11:46:05 -05:00
Leonard Hecker
b8f402f64b Reduce cost of resetting row attributes (#15497)
Performance of printing enwik8.txt at the following block sizes:
4KiB (printf): 54MB/s -> 54MB/s
128KiB (cat): 101MB/s -> 104MB/s

## Validation Steps Performed
This change is easily verifiable via review.
2023-06-15 15:34:29 +00:00
Leonard Hecker
f3e2890084 Vectorize ROW initialization (#15501)
Performance of printing enwik8.txt at the following block sizes:
4KiB (printf): 51MB/s -> 54MB/s
128KiB (cat): 92MB/s -> 103MB/s

## Validation Steps Performed
* Rows are properly filled with whitespace at various
  window sizes as observed under a debugger 
2023-06-15 14:45:35 +00:00
Leonard Hecker
427b37c07d Fix a crash when duplicating tabs with elevate:true (#15548)
When `elevate` is set to `true`, `_maybeElevate` would try to
modify `newTerminalArgs` and crash, because during tab duplication
there aren't any `newTerminalArgs`. This issue may happen for instance
when receiving hand-off from a non-elevated client and then trying
to duplicate that tab.

Closes #15534

## Validation Steps Performed
* Launch with `"elevate": false`
* Set `"elevate": true`
* Duplicate a tab
* Doesn't crash 
2023-06-15 14:43:43 +00:00
Leonard Hecker
a38388615e Fix new AuditMode failures up to VS 17.7 Preview 2 (#15547)
* Fixes warnings related to missing `const` in 2 places, which seems
  to be something that's being detected more reliably by 17.6 now.
* Fixes `DxSoftFont` not initializing all members,
  which is also suddenly being detected by 17.6 now.
* Fixes 1 new VS 17.7 warning (C26435) by removing `virtual` from
  methods declared as `override` already.
* Disables 2 new VS 17.7 warnings that are part of C++ Core Guidelines
  c.128, because they don't really bring any benefit to this project.

As an additional bonus it disables a spellcheck warning that has been
going around ever since I put a Punycode URL in a comment.
2023-06-15 00:24:21 +02:00
Leonard Hecker
c183d12649 Move AdaptDispatch::_FillRect into TextBuffer (#15541)
This commit makes 2 changes:
* Expose dirty-range information from `ROW::CopyTextFrom`
  This will allow us to call `TriggerRedraw`, which is an aspect
  I haven't previously considered as something this API needs.
* Add a `FillRect` API to `TextBuffer` and refactor `AdeptDispatch`
  to use that API. Even if we determine that the new text APIs are
  unfit (for instance too difficult to use), this will make it simpler
  to write efficient implementations right inside `TextBuffer`.

Since the new `FillRect` API lacks bounds checks the way `WriteLine`
has them, it breaks `AdaptDispatch::_EraseAll` which failed to adjust
the bottom parameter after scrolling the contents. This would result
in more rows being erased than intended.

## Validation Steps Performed
* `chcp 65001`
* Launch `pwsh`
* ``"`e[29483`$x"`` fills the viewport with cats 
* `ResizeTraditional` still doesn't work any worse than it used to 
2023-06-14 14:34:42 -05:00
Leonard Hecker
8f8c79ff58 Various fixes and improvements for ConsoleTypes.natvis (#15543)
Fixes the broken types for `TextAttribute`, `til::size`, `til::point`
and `til::rect` and adds a new type for `TextBuffer` which without
this would now be much harder to inspect due to introduction of
the manual virtual memory management in 612b00c.
2023-06-13 15:12:10 -05:00
Leonard Hecker
612b00cd44 Initialize rows lazily (#15524)
For a 120x9001 terminal, a01500f reduced the private working set of
conhost by roughly 0.7MB, presumably due to tighter `ROW` packing, but
also increased it by 2.1MB due to the addition of the `_charOffsets`
array on each `ROW` instance. An option to fix this would be to only
allocate a `_charOffsets` if the first wide or complex Unicode glyph
is encountered. But on one hand this would be quite western-centric
and unfairly hurt most languages that exist and on another we can get
rid of the `_charOffsets` array entirely in the future by injecting
ZWNJs if a write begins with a combining glyph and just recount each
row from the start. That's still faster than fragmented memory.

This commit goes a different way and instead reduces the working
set of conhost after it launches from 7MB down to just 2MB,
by only committing ROWs when they're first used.

Finally, it adds a "scratchpad" row which can be used to build
more complex contents, for instance to horizontally scroll them.

## Validation Steps Performed
* Traditional resize
  * Horizontal shrinking works 
  * Vertical shrinking works  and cursor stays in the viewport 
* Reflow works 
* Filling the buffer with ASCII works  and no leaks 
* Filling the buffer with complex Unicode works  and no leaks 
* `^[[3J` erases scrollback 
* Test `ScrollRows` with a positive delta 
* I don't know how to test `Reset`.  Unit tests use it though
2023-06-10 13:17:18 +00:00
Jvr for school
17596d2623 Improve Run-Tests.ps1's maintainability and readability (#15407)
Improvements and explanations:
* Added proper indentation and spacing for better readability.
* Added comments to explain the purpose of different sections of the
  code.
* Utilized the $LASTEXITCODE variable instead of $lastexitcode to ensure
  consistency.
* Changed the variable name from $testdlls to $testDlls for better
  naming convention.
* Moved the Exit 0 statement to the end (outside the if condition).
* Since Exit statements terminate the script immediately, it's better to
  have them at the end of the script to ensure that all necessary
  cleanup or additional operations are performed before exiting.
* These improvements enhance the code's readability, maintainability,
  and adherence to best practices.
2023-06-09 23:06:29 +00:00
Dustin L. Howett
37e8aff967 Detect likely Powerline fonts, add a special powerline preview (#15365)
When we detect a font that has a glyph for `U+E0B6`, we will switch the
preview connection text to contain a special powerline prompt. This will
allow people to see how different settings might impact their real-world
environment.

When we _don't_ detect such support, we fall back to the CMD-style
`C:\>` prompt.

Pros:
- It's beautiful.

Cons:
- More code

Risks:
- `U+E0B6` is part of the private use area, and fonts that have symbols
there (such as Cirth as sub-allocated by the ConScript Unicode Registry)
will result in something unexpected.
- Actually, `E0B6` isn't part of base powerline... but I think this
specific set of characters looks too good to pass up.
2023-06-09 22:22:21 +00:00
Leonard Hecker
c16a74ba39 Fix small_vector issues when assigning it to itself (#15525)
This commit achieves fixes the issue as described in the title by
checking whether the `this` and `other` pointer are identical.
As an added bonus it makes the copy and move constructors slightly
cheaper, as they don't try to destruct existing data anymore,
which doesn't exist anyways.

## Validation Steps Performed
* It blends 
2023-06-09 18:18:34 +00:00
James Holderness
7a3bf7017c Add support for Erase Color Mode (DECECM) (#15469)
The _Erase Color Mode_ determines what attributes are written to the
buffer when erasing content, or when new content is scrolled onto the
screen. When the mode is reset (which is the default), we erase with the
active colors, but with rendition attributes cleared. When the mode is
set, we erase with the default attributes, i.e. with neither color nor
rendition attributes applied.

This could be used to address the problem described in issue #10556.

Most of the affected operations are handled within the `AdaptDispatch`
class, so I've simply updated them all to use a new helper method which
returns the appropriate erase attributes for the active mode.

However, there were a couple of operations that are handled elsewhere,
and which now require the erase attributes to be passed to them as a
parameter.

* The `TextBuffer::IncrementCircularBuffer` method, which is used to
recycle the topmost row when scrolling past the bottom of the buffer.

* The `TextBuffer::SetCurrentLineRendition` method, which has to clear
the second half of the line when switching to a double width rendition.

* The `ITerminalApi::UseAlternateScreenBuffer` method, which has to
clear the screen when switching to the alternate buffer.

Then there is also a Clear Buffer action in Windows Terminal, which is
ultimately handled by the `SCREEN_INFORMATION::ClearBuffer` method in
ConHost. That class has no access to the erase color mode, so has no way
of knowing which attributes to use. So I've now rewritten it to use the
`AdaptDispatch::EraseInDisplay` method to handle the erasing.

## Validation Steps Performed

I wrote a little test script that exercises the operations affected by
`DECECM`, which @al20878 kindly tested for us on a real DEC VT525, and
provided screenshots of the output. I've manually confirmed that our
implementation exactly matches those results.

I've also added a unit test that runs through the same set of operations
and verified that each of them is using the appropriate attributes when
`DECECM` is enabled and enabled.

Closes #14983
2023-06-09 00:02:49 +00:00
Leonard Hecker
f1aa6993f1 Display Unicode URIs side-by-side with their Punycode encoding (#15488)
06174a9 didn't properly fix the issue of us showing homoglyphs in our
URI tooltip. This commit introduces a different approach where we
display both, the Punycode and Unicode encoding, whenever we encounter
an IDN. This isn't perfect but simple to implement.

Closes #15432

## Validation Steps Performed
* `https://www.xn--fcbook-3nf5b.com/` (which contains confusing glyphs)
  is shown both in its Punycode and Unicode form simultaneously. 

---------

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
2023-06-08 23:56:04 +00:00
Mike Griese
8f83322322 Add support for setting the window frame color (#15441)
Add support for `$theme.window.frame`, `.unfocusedFrame`, and `.rainbowFrame`. The first two accept a `ThemeColor` to set the window frame, using [`DwmSetWindowAttribute`](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute) with [`DWMWA_BORDER_COLOR`](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute). `rainbowFrame` accepts a `bool`. When enabled, it'll cycle the color of the frame through all the hues, ala [this gif](https://user-images.githubusercontent.com/18356694/164307822-e4267965-2ce0-4294-8499-59c3ba7edbae.gif) (but, constantly, instead of just when the window moves). 

This only works on Windows 11.

## Validation Steps Performed
* Works on Windows 11
* Doesn't explode on Windows 10

## PR Checklist
- [x] Closes #12950
- See also #3327
- [x] Schema updated (if necessary)


### other details

There's probably some impact to perf with `rainbowFrame`. It's one `DispatcherTimer` per window. That could probably be optimized somehow to like, one per process, but meh?
2023-06-06 16:17:03 -07:00
ebarnabas
c627991522 Project build with space in filepath fix (#15447)
## Summary of the Pull Request
Fixing a problem where the repo build failed when the project location
path contained space character.

## References and Relevant Issues
Closes #15370 

## Detailed Description of the Pull Request / Additional comments
Placing missing quote, amp and apos symbols when calling commands with
filepath parameters.

## Validation Steps Performed
Built locally.

## PR Checklist
- [x] Closes #xxx
- [x] Tests added/passed
- [x] Documentation updated
- If checked, please file a pull request on [our docs
repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
- [x] Schema updated (if necessary)
2023-06-06 23:15:37 +00:00
Leonard Hecker
f0705fb7f7 AtlasEngine: Fix some Windows 10 <14393 bugs (#15485)
This fixes a couple spots where I wasn't properly checking
for the existence of some optional D2D interfaces.

## Validation Steps Performed
I haven't tested this and don't intend to do it just yet.
Windows Terminal requires build 19041 at least anyways.
2023-06-05 16:36:09 -07:00
Carlos Zamora
0425ab0c1d Expose 'Default' tag to Screen Readers in Color Schemes page (#15486)
## Summary of the Pull Request
This removes the "default" text box from the UI Automation tree, thus
preventing screen readers from navigating to it. This was a confusing
scenario for users because the "default" tag was unclear if it was a
part of the previous or next color scheme (i.e. consider hearing
"Campbell, default, Campbell PowerShell"; it's unclear which one is
default).

This also appends the "default" string to the `ToString` function of the
color scheme view model. This makes it so that the combo box and list
view visually appear the same, but can be quick searched or read out by
the screen reader with the 'default' tag.

## Validation Steps Performed
- [x] Verified this works on Windows 11
- [x] Verified this works on Windows 10
- Scenarios tested:
   - [x] saving settings after changing the default scheme
   - [x] saving settings.json to force a refresh in SUI

Closes #14401
2023-06-05 19:50:23 +00:00
James Holderness
e9de646e54 Remove the telemetry for VT sequences (#15494)
This removes the telemetry tracking which counted how many times each VT
sequence was executed, and how many times there were "failures". This
information isn't needed any more, and we were reaching the limit of how
many sequences we could track anyway.

Essentially what's been removed is the `TermTelemetry` class, but we are
still tracking some statemachine telemetry in the `ParserTracing` class.
And since that used the same trace logging provider as `TermTelemetry`,
I've now moved that definition into the `tracing.cpp` file. 

The code still compiles and runs without exploding.

Closes #15482
2023-06-05 16:41:32 +00:00
James Holderness
8aefc7a697 Make sure RIS re-enables win32 input and focus events (#15476)
When an `RIS` (hard reset) sequence is executed, ConHost will pass that
through to the connected conpty terminal, which results in all modes
being reset to their initial state. To work best, though, conpty needs
to have the win32 input mode enabled, as well as the focus event mode.
This PR addresses that by explicitly requesting the required modes after
an `RIS` is passed through.

Originally these modes were only set if the `--win32input` option was
given, but there is really no need for that, since terminals that don't
support them should simply ignore the request. To avoid that additional
complication, I've now removed the option (i.e. ConHost will now always
attempt to set the modes it needs).

I've manually confirmed that keypresses are still passed through with
win32 input sequences after a hard reset, and that focus events are
still being generated. I've also updated the existing conpty round-trip
test for `RIS` to confirm that it's now also passing through the mode
requests that it needs.

Closes #15461
2023-06-02 13:41:49 -05:00
Leonard Hecker
1bec08ec0a Modernize til::static_map with C++20 (#15484)
I wanted to show `til::static_map` to someone and noticed it hasn't been
updated since we updated to C++20. We can now make use of `constexpr`
`std::sort` and `constinit` to skip the initialization of the maps in
`KeyChordSerialization.cpp`. Also, I removed the comparator argument
to make the map a little more compact.
2023-05-31 12:59:03 -05:00
Mike Griese
c9e993a38e Exit the process after commandline-only invocations (#15445)
Yep it's that dumb

Closes #15443
2023-05-26 19:59:38 +00:00
Leonard Hecker
cd6b0832e2 Hotfix block selection linebreaks in conhost (#15423)
This regressed in a1f42e8 which only made changes to Windows Terminal
but forgot to make equivalent ones in OpenConsole/conhost.
Without this fix, line breaks in block selections are missing if the
line doesn't force a wrap via an explicit newline.

Closes #15153

## Validation Steps Performed
* Run Far or print long lines of text
* Trigger block selection via Ctrl+M or Edit > Mark
* Clipboard contains N-1 newlines lines for N selected rows 
2023-05-26 14:32:15 -05:00
Mike Griese
8611d901b6 Theoretical fix for some crashes (#15457)
RE: 
* #15454
* MSFT:44725712 "WindowsTerminal.exe!NonClientIslandWindow::OnSize"
* MSFT:44754014 "NonClientIslandWindow::GetTotalNonClientExclusiveSize"

I think this should fix all of those, but I want to ship and verify
live, since I can't repro this locally.
2023-05-26 14:31:21 -05:00
Leonard Hecker
a19d30a25a AtlasEngine: Improve scroll and swap chain invalidation (#15425)
`_p.MarkAllAsDirty()` sets `_p.scrollOffset` to 0, so we need to use
that instead of `_api.scrollOffset` when getting the offset.
Additionally, the previous code failed to release the swap chain
when recreating the backend, which is technically not correct.
I'm not sure to what issues this might have led, as it didn't had any
negative effects on my PC, but it's definitely not according to spec.

## Validation Steps Performed
Difficult to test but it seems alright.
2023-05-26 14:30:57 -05:00
Steve Otteson
709189d471 env: always expand PATH vars (#15444)
Make sure we always expand path env vars, even if they're REG_SZ in the
registry.

## Detailed Description of the Pull Request / Additional comments
On some systems path vars are REG_SZ instead of REG_EXPAND_SZ. We need
to make sure we always expand them. We looked at the system code, and it
also makes to sure to always expand them.

## Validation Steps Performed
Built locally and made sure the problem went away. Also stepped through
in the debugger to make sure things were working correctly.

Closes #15442
2023-05-26 18:45:10 +00:00
Mike Griese
aa8ed8c2d4 AGAIN, intentionally leak our App, so that we DON'T crash on exit (#15451)
This is a resurrection of #5629. As it so happens, this crash-on-exit
was _not_ specific to my laptop. It's a bug in the XAML platform
somewhere, only on Windows 10.

In #14843, we moved this leak into `becomeMonarch`. Turns out, we don't
just need this leak for the monarch process, but for all of them.

It's not a real "leak", because ultimately, our `App` lives for the
entire lifetime of our process, and then gets cleaned up when we do. But
`dtor`ing the `App` - that's apparently a no-no.

Was originally in #15424, but I'm pulling it out for a super-hotfix
release.


Closes #15410

MSFT:35761869 looks like it was closed as no repro many moons ago. This
should close out our hits there (firmly **40% of the crashes we've
gotten on 1.18**)
2023-05-26 13:09:00 -05:00
James Holderness
3c3b1aac02 Add support for horizontal scrolling sequences (#15368)
This PR introduces four new escapes sequences: `DECIC` (Insert Column),
`DECDC` (Delete Column), `DECBI` (Back Index), and `DECFI` (Forward
Index), which allow for horizontal scrolling within a margin area.

## References and Relevant Issues

This follows on from the horizontal margins PR #15084 to complete the
requirements for the horizontal scrolling extension.

## Detailed Description of the Pull Request / Additional comments

The implementation is fairly straightforward, since they're all built on
top of the existing `_ScrollRectHorizontally` method.

## Validation Steps Performed

Thanks to @al20878, these operations have been extensively tested on a
number of DEC terminals and I've manually confirmed our implementation
matches their behavior.

I've also added a unit tests that covers the basic execution of each of
the operations.

Closes #15109
2023-05-25 20:36:56 +00:00
Mike Griese
6775300f42 Fix focusFollowMouse (#15420)
Because this looks like it's entirely broken in `main`, and possibly in
1.17(?)

We didn't take a strong ref to the coroutine parameter. As to be
expected, that explodes.

Closes #15412
2023-05-25 22:25:10 +02:00
Jaswir
f5a703c711 Add support for a "Move Tab to New Window" tab context menu item (#15376)
## Summary of the Pull Request
Add the "Move Tab to New Window" item to the context menu of the tabs 

## References and Relevant Issues
https://github.com/microsoft/terminal/issues/15127

## Detailed Description of the Pull Request / Additional comments
Add the "Move Tab to New Window" item to the context menu of the tabs. 

![Detailed_description_of_commit_PR1](https://github.com/microsoft/terminal/assets/15957528/915ac07b-1fdd-456b-b180-2645dbc29e48)

## Validation Steps Performed
Checked Code Style
https://github.com/microsoft/terminal/blob/main/doc/STYLE.md

## PR Checklist
- [ V] Closes #15127
-  [?] Tests added/passed
- [ X] Documentation updated
- If checked, please file a pull request on [our docs
repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
- [ V] Schema updated (if necessary)

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2023-05-25 18:22:58 +00:00
Leonard Hecker
7650ecf658 AtlasEngine: Fix cursor invalidation for BackendD2D (#15427)
TIL: `CreateCompatibleRenderTarget` does not initialize the bitmap
it returns. You got to do that yourself just like in D3D.

## Validation Steps Performed
* Set `ATLAS_DEBUG_FORCE_D2D_MODE` to 1
* Changing the cursor in the settings immediately updates it 
2023-05-25 18:22:07 +00:00
Leonard Hecker
0073e36d81 Fix VS profile command generation (#15439)
This regressed in f06cd17. It seems like the change went untested,
because it appends an extra " after -startdir=none.
This changeset also avoids calling `append()` twice.

Closes #15436

## Validation Steps Performed
* VS Developer Command Prompt works 
2023-05-25 18:21:57 +00:00
Leonard Hecker
245b13b94e AtlasEngine: Fix nullptr crash when using soft fonts (#15419)
Woops. Regressed in #15343. Fixes #15409.

## Validation Steps Performed
* Run `RenderingTests.exe`
* Soft fonts work 
2023-05-25 18:21:55 +00:00
Leonard Hecker
a9f34e3095 AtlasEngine: Fix Present() of out of bounds glyphs (#15403)
`til::rect`'s truthiness check (= rect is valid) returns `false` for
any rects that have negative coordinates. This makes sense for buffer
handling, but breaks AtlasEngine, where glyph coordinates can go out
of bounds and it's entirely valid for that to happen.

Closes #15416

## Validation Steps Performed
* Use MesloLGM NF and print NF glyphs in the first row
* Text rendering, selection, etc., still works 

---------

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
2023-05-25 17:39:44 +00:00
Mike Griese
c589784b54 Leak the window when it closes on Windows 10 (#15397)
Re: #15384

Basically, when we close a `DesktopWindowXamlSource`, it calls to `Windows_UI_Xaml!DirectUI::MetadataAPI::Reset`, which resets the XAML metadata provider _for the process_. So, closing one DWXS on one thread will force an A/V next time another thread tries to do something like... display a tooltip. Not immediately, but surely soon enough.

This was fixed in Windows 11 by os.2020!5837001. That wasn't backported to Windows 10.

This will cause a ~15MB memory leak PER WINDOW. OBVIOUSLY, this is bad, but it's less bad than crashing. 

We're gonna keep using #15384 for other ideas here too.
2023-05-22 11:00:44 -05:00
Mike Griese
910c61b7e1 Manually close the ContentDialog in teardown (#15387)
As discussed. Closes #15364.

Prevents one crash on Windows 10. Opens the door to may more horrors.

Co-authored-by: James Holderness <j4_james@hotmail.com>
2023-05-22 06:07:59 -05:00
Dustin L. Howett
0ee2c74cd4 Switch the Preview text emoji to one that exists on Win10 (#15381) 2023-05-18 16:27:33 +00:00
Dustin L. Howett
125026dbb6 Remove the pending update version from the About dialog (#15378)
It turns out that the store API *doesn't* tell us what the new version
is. We were loading up our own package and checking its version instead.

The best we can do is tell users that an update--any update--is
available.
2023-05-18 10:22:29 -05:00
Dustin L. Howett
ce60bf290a Make the preview text 100% more accurate (#15366) 2023-05-16 17:59:36 -05:00
Dustin L. Howett
e269945a74 Reword the AdjustIndistinguishableColors subhead and perf. note (#15361)
I've changed the wording so that it flows better.
2023-05-16 15:01:31 -05:00
Dustin L. Howett
fbe45fafb5 Add a fun new preview text in the SUI, enable the cursor (#15363)
Our existing preview text was not very helpful in learning how different
settings impacted the display of text in Terminal.

This new preview text contains:
* Bold text, which is controlled by intenseTextStyle
* Colors
* Emoji
* A cursor, which overlaps a single character to show inversion behavior
2023-05-16 14:59:49 -05:00
Dustin L. Howett
62766db94d Reword or remove a bunch of subheadings in the SUI (#15362)
Some of these were reundant, and some didn't feel right when I read
them.

Oh, and I got rid of all of these particularly unhelpful or non-additive
resources:

```
Color Scheme        [                     v ]
Is a color scheme
```
2023-05-16 14:59:28 -05:00
James Holderness
b00b77a7ac Add support for horizontal margin sequences (#15084)
This PR introduces two new escapes sequences: `DECLRMM` (Left Right
Margin Mode), which enables the horizontal margin support, and `DECSLRM`
(Set Left and Right Margins), which does exactly what the name suggests.

A whole lot of existing operations have then been updated to take those
horizontal margins into account.

## Detailed Description of the Pull Request / Additional comments

The main complication was in figuring out in what way each operation is
affected, since there's a fair amount of variation.

* When writing text to the buffer, we need to wrap within the horizontal
margins, but only when the cursor is also within the vertical margins,
otherwise we just wrap within the boundaries of the screen.

* Not all cursor movement operations are constrained by the margins, but
for those that are, we clamp within both the vertical and horizontal
margins. But if the cursor is already outside the margins, it is just
clamped at the edges of the screen.

* The `ICH` and `DCH` operations are constrained by the horizontal
margins, but only when inside the vertical margins. And if the cursor is
outside the horizontal margins, these operations have no effect at all.

* The rectangular area operations are clamped within the horizontal
margins when in the origin mode, the same way they're clamped within the
vertical margins.

* The scrolling operations only scroll the area inside both horizontal
and vertical margins. This includes the `IL` and `DL` operations, but
they also won't have any effect at all unless the cursor is already
inside the margin area.

* `CR` returns to the left margin rather than the start of the line,
unless the cursor is already left of that margin, or outside the
vertical margins.

* `LF`, `VT`, `FF`, and `IND` only trigger a scroll at the bottom margin
if the cursor is already inside both vertical and horizontal margins.
The same rules apply to `RI` when triggering a scroll at the top margin.

Another thing worth noting is the change to the `ANSISYSSC` operation.
Since that shares the same escape sequence as `DECSLRM`, they can't both
be active at the same time. However, the latter is only meant to be
active when `DECLRMM` is set, so by default `ANSISYSC` will still work,
but it'll no longer apply once the `DECLRMM` mode is enabled.

## Validation Steps Performed

Thanks to @al20878, these operations have been extensively tested on a
number of DEC terminals and I've manually confirmed our implementation
matches their behavior.

I've also extended some of our existing unit tests to cover at least the
basic margin handling, although not all of the edge cases.

Closes #14876
2023-05-15 22:32:30 +00:00
Leonard Hecker
4628ceb295 AtlasEngine: Clip box glyphs to their cells (#15343)
Overhangs for box glyphs can produce unsightly effects, where the
antialiased edges of horizontal and vertical lines overlap between
neighboring glyphs and produce "boldened" intersections.
This avoids the issue in most cases by simply clipping the glyph to the
size of a single cell. The downside is that it fails to work well for
custom line heights, etc.

## Validation Steps Performed

* With Cascadia Code, printing ``"`u{2593}`n`u{2593}"`` in pwsh
  doesn't produce a brightened overlap anymore 
* ``"`e#3`u{2502}`n`e#4`u{2502}"`` produces a fat vertical line 
2023-05-16 00:05:16 +02:00
Mike Griese
9a4f4abaf2 Manually pre-evaluate the starting directory when calling elevate-shim (#15286)
_targets #15280_

When ctrl+clicking on a profile, pre-evaluate the starting directory of
that profile, and stash that in the commandline we pass to elevate shim.

So in the case of something like "use parent process directory", we'll
run `elevate-shim new-tab -p {guid} -d "C:\\the path\\of\\terminal\\."`


Closes #15173

---------

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
2023-05-15 20:10:43 +00:00
Leonard Hecker
f6e9f91504 Fix AtlasEngine not being used in the appearance settings (#15355)
`TermControl` cannot change the text rendering engine after its
construction. Fix the issue by deferring the construction until
after we got the initial profile settings.

## Validation Steps Performed
* A line height of 0.5 shows up with overlapping rows 
2023-05-15 21:14:21 +02:00
Leonard Hecker
457bc65d7e AtlasEngine: Fix animated shaders (#15353)
We need to avoid calling `Present1()` with an empty dirty rect, but the
backends are what determines the resulting dirty rect, so we need to
first run the backend code and then decide if we `Present1()` or not.

## Validation Steps Performed
* `Animate_scan.hlsl` shows a smoothly animated line 
2023-05-15 13:46:39 -05:00
Leonard Hecker
4c3d3d83a5 AtlasEngine: Fix han unification issues (#15358)
This commit ensures that we pass the user's locale to `MapCharacters`.

## Validation Steps Performed
See: https://heistak.github.io/your-code-displays-japanese-wrong/
After modifying the `userLocaleName` to contain `ja-JP`, `zh-CN` and
`zh-TW`, printing "刃直海角骨入" produces the expected, localized result.
2023-05-15 20:40:41 +02:00
Leonard Hecker
af5a6ea640 AtlasEngine: Fix multiple minor issues (#15357)
This commit fixes 3 bugs that I found while working on another feature:
* `GetGlyphIndices` doesn't return an error when the codepoint couldn't
  be found, it simply returns a glyph index of 0.
* `_resetGlyphAtlas` failed to reset the `linear_flat_set` "load" to 0
  which would result in an unbounded memory growth over time.
* `linear_flat_set` was missing move constructors/operators, which
  would've led to crashes, etc., but thankfully we haven't made use
  of these operators yet. But better fix it now than never.
2023-05-15 20:40:13 +02:00
Leonard Hecker
ba39db52d7 Fix DesktopWindowXamlSource related crashes when closing a window (#15338)
XAML/WinUI may pump the event loop internally. One of the functions
that does this right now is `DesktopWindowXamlSource::Close()`.

This is problematic in the previous code, because we'd set `_window`
to `nullptr` before calling `Close()` and so any of the `IslandWindow`
callbacks may be invoked during shutdown, which then try to potentially
access `_window` and end up crashing. This commit fixes the issue by
simply not nulling out the `_window` and calling `Close()` directly.

Furthermore, `NonClientIslandWindow` may directly access WinUI
objects in its message handlers which also crashes.

I've had this happen roughly ~1% of my test exits in a debug build
and every single time on a (artificial) very slow CPU.

## Validation Steps Performed
* Closing a window destroys the rendering instance 
2023-05-15 14:11:22 +00:00
James Holderness
3d737214a4 Add support for LNM (Line Feed/New Line Mode) (#15261)
This PR adds support for the ANSI Line Feed/New Line mode (`LNM`), which
determines whether outputting a linefeed control should also trigger a
carriage return, and whether the `Return` key should generate an `LF` in
addition to `CR`.

## Detailed Description of the Pull Request / Additional comments

In ConHost, there was already a console mode which handled the output
side of things, but I've now also added a `TerminalInput` mode that
controls the behavior of the `Return` key. When `LNM` is set, both the
output and input modes are enabled, and when reset, they're disabled.

If they're not already matching, then `LNM` has no effect, and will be
reported as unknown when queried. This is the typical state for legacy
console applications, which expect a linefeed to trigger a carriage
return, but wouldn't want the `Return` key generating both `CR`+`LF`.

As part of this PR, I've also refactored the `ITerminalApi` interface to
consolidate what I'm now calling the "system" modes: bracketed paste,
auto wrap, and the new line feed mode. This closes another gap between
Terminal and ConHost, so both auto wrap, and line feed mode will now be
supported for conpty pass through.

## Validation Steps Performed

I've added an `LNM` test that checks the escape sequence is triggering
both of the expected mode changes, and added an additional `DECRQM` test
covering the currently implemented standard modes: the new `LNM`, and
the existing `IRM` (which wasn't previously tested). I've also extended
the `DECRQM` private mode test to cover `DECAWM` and Bracketed Paste
(which we also weren't previously testing).

I've manually tested `LNM` in Vttest to confirm the keyboard is working
as expected.

Closes #15167
2023-05-12 18:16:48 -05:00
Mike Griese
1324a0148a Don't even try to MoveContent to the same window you started in (#15325)
Don't go.
Tracked in #14957
2023-05-12 17:24:31 -05:00
Mike Griese
1bf2fcb6e0 Don't dismiss the command palette when the new tab menu closes (#15340)
Transient UIs are hard.

Regressed in #15077.

Closes #15305
2023-05-12 17:22:21 -05:00
Leonard Hecker
95944e5939 Make path-string conversions cheaper (#15332)
`native()` returns a `const std::wstring&`, whereas `wstring()`
returns a copy. Use the former to make path conversions cheaper.
2023-05-12 20:51:00 +00:00
Leonard Hecker
488de2d42c Fix WindowEmperor exiting too early (#15337)
`WindowEmperor` would exit as soon as the last window would enter
`RundownForExit()`, which is too early and triggers leak checks.
This commit splits up the shutdown up into deregistering the window from
the list of windows and into actually decrementing the window count.

Closes #15306

## Validation Steps Performed
* D2D leak warnings seem to disappear 
2023-05-12 20:15:50 +00:00
Leonard Hecker
bf8ef638b7 Avoid recreating the bell MediaPlayer every time (#15333)
We don't need to recreate the `MediaPlayer` to avoid the influence of
media keys if we simply opt out of media key controls.

## Validation Steps Performed
* Set a random .wav as the bell sound
* Bell is audible 
* Media keys have no effect while the sound plays 
2023-05-12 20:15:36 +00:00
Leonard Hecker
6a26fd68c4 Fix race conditions in ControlCore (#15334)
`ControlCore` contained two bugs:
* Race condition on access of the 3 throttled funcs which may now
  be `reset()` during tear out
* The `ScrollPositionChanged` event emitter was written incorrectly
  and would emit the event from the background thread without
  throttling during tear out
2023-05-12 20:11:22 +00:00
Dustin L. Howett
d6eb022975 Stage the fonts with the Helix payload (#15317)
I found that in all our Helix runs, we had a pesky dialog sitting on top
of the Terminal. Probably the entire time.

This will, as a side effect, PGO the nearby font loader.
2023-05-12 14:26:47 -05:00
Mike Griese
d0f66b9668 Restore the tab padding (#15339)
Honestly, I don't really know where it regressed. There isn't time for
me to go digging.

See also
* #15313
* #15164

Closes #15326
2023-05-12 13:26:05 -05:00
Mike Griese
5c08a86c49 Use a "virtual CWD" for each terminal window (#15280)
Before process model v3, each Terminal window was running in its own process, each with its own CWD. This allowed `startingDirectory: .` to work relative to the terminal's own CWD. However, now all windows share the same process, so there can only be one CWD. That's not great - folks who right-click "open in terminal", then "Use parent process directory" are only ever going to be able to use the CWD of the _first_ terminal opened. 

This PR remedies this issue, with a theory we had for another issue. Essentially, we'll give each Terminal window a "virtual" CWD. The Terminal isn't actually in that CWD, the terminal is in `system32`. This also will prevent the Terminal from locking the directory it was originally opened in. 

* Closes #5506
* There wasn't a 1.18 issue for "Use parent process directory is broken" yet, presumably selfhosters aren't using that feature
* Related to #14957

Many more notes on this topic in https://github.com/microsoft/terminal/issues/4637#issuecomment-1531979200


> **Warning** 
> ## Breaking change‼️

This will break a profile like 

```json
{
    "commandline": "media-test.exe",
    "name": "Use CWD for media-test",
    "startingDirectory": "."
},
```

if the user right-clicks "open in terminal", then attempts to open that profile. There's some theoretical work we could do in a follow up to fix this, but I'm inclined to say that relative paths for `commandline`s were already dangerous and should have been avoided.
2023-05-12 18:20:27 +00:00
Leonard Hecker
99abb2a6b5 Fix theoretical new-tab file drop crash (#15336)
After retrieving the items via `GetStorageItemsAsync()` inside a try
clause it fails to check if the pointer is actually non-null.
Apart from this this commit fixes the unsafe use of `this` by properly
using `get_weak()`. Finally it allows >1 paths to be dropped.

## Validation Steps Performed
* Dropping >1 file works 
* Dropping >1 directory works 
2023-05-12 13:14:07 -05:00
Dustin Howett
c6215c8b51 version: bump to 1.19 on main
Signed-off-by: Dustin Howett <duhowett@microsoft.com>
2023-05-11 16:33:49 -05:00
208 changed files with 4347 additions and 3003 deletions

View File

@@ -78,6 +78,8 @@ ok'd
overlined
pipeline
postmodern
Powerline
powerline
ptys
qof
qps

View File

@@ -29,6 +29,7 @@ Dacl
dataobject
dcomp
DERR
delayimp
dlldata
DNE
DONTADDTORECENT
@@ -75,6 +76,7 @@ IConnection
ICustom
IDialog
IDirect
Idn
IExplorer
IFACEMETHOD
IFile
@@ -86,6 +88,7 @@ IObject
iosfwd
IPackage
IPeasant
isa
ISetup
isspace
IStorage
@@ -165,6 +168,7 @@ roundf
RSHIFT
SACL
schandle
SEH
semver
serializer
SETVERSION
@@ -212,6 +216,7 @@ UOI
UPDATEINIFILE
userenv
USEROBJECTFLAGS
Vcpp
Viewbox
virtualalloc
vsnwprintf

View File

@@ -371,8 +371,6 @@ ipfs://[0-9a-z]*
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
# Punycode
\bxn--[-0-9a-z]+
# sha
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture

View File

@@ -9,6 +9,7 @@ ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRS
ABCDEFGHIJKLMNOPQRST
ABCG
ABE
@@ -21,9 +22,12 @@ BBGGRR
efg
EFG
EFGh
KLMNOQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRS
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRST
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty

View File

@@ -1,4 +1,5 @@
aabbcc
aarch
ABANDONFONT
abbcc
ABCDEFGHIJKLMNOPQRSTUVWXY
@@ -157,7 +158,6 @@ capslock
CARETBLINKINGENABLED
CARRIAGERETURN
cascadia
castsi
catid
cazamor
CBash
@@ -216,11 +216,11 @@ cmder
CMDEXT
cmh
CMOUSEBUTTONS
cmpeq
cmt
cmw
cmyk
CNL
cnn
cnt
CNTRL
Codeflow
@@ -260,7 +260,6 @@ condrv
conechokey
conemu
configurability
confusables
conhost
conime
conimeinfo
@@ -410,6 +409,7 @@ DECANM
DECARM
DECAUPSS
DECAWM
DECBI
DECBKM
DECCARA
DECCIR
@@ -418,13 +418,17 @@ DECCKSR
DECCOLM
DECCRA
DECCTR
DECDC
DECDHL
decdld
DECDMAC
DECDWL
DECECM
DECEKBD
DECERA
DECFI
DECFRA
DECIC
DECID
DECINVM
DECKPAM
@@ -435,6 +439,7 @@ DECMSR
DECNKM
DECNRCM
DECOM
decommit
DECPCTERM
DECPS
DECRARA
@@ -571,6 +576,7 @@ EDITTEXT
EDITUPDATE
edputil
Efast
efghijklmn
EHsc
EINS
EJO
@@ -808,7 +814,6 @@ HIBYTE
hicon
HIDEWINDOW
hinst
Hirots
HISTORYBUFS
HISTORYNODUP
HISTORYSIZE
@@ -825,6 +830,8 @@ HMK
hmod
hmodule
hmon
homeglyphs
homoglyph
HORZ
hostable
hostlib
@@ -971,12 +978,15 @@ KLF
KLMNO
KLMNOPQRST
KLMNOPQRSTQQQQQ
KLMNOPQRSTUVWXY
KLMNOPQRSTY
KOK
KPRIORITY
KVM
langid
LANGUAGELIST
lasterror
LASTEXITCODE
lastexitcode
LAYOUTRTL
lbl
@@ -1013,7 +1023,6 @@ lnkd
lnkfile
LNM
LOADONCALL
loadu
LOBYTE
localappdata
locsrc
@@ -1134,6 +1143,7 @@ mmsystem
MNC
MNOPQ
MNOPQR
MNOPQRSTUVWXY
MODALFRAME
MODERNCORE
MONITORINFO
@@ -1143,7 +1153,6 @@ MOUSEACTIVATE
MOUSEFIRST
MOUSEHWHEEL
MOUSEMOVE
movemask
MOVESTART
msb
msctf
@@ -1279,7 +1288,6 @@ nullability
nullness
nullonfailure
nullopts
NULs
numlock
numpad
NUMSCROLL
@@ -1768,7 +1776,6 @@ somefile
SOURCEBRANCH
sourced
spammy
spand
SRCCODEPAGE
SRCCOPY
SRCINVERT
@@ -2044,6 +2051,7 @@ USRDLL
utr
UVWX
UVWXY
UVWXYZ
uwa
uwp
uxtheme
@@ -2054,6 +2062,7 @@ vcpkg
vcprintf
vcxitems
vec
vectorize
vectorized
VERCTRL
VERTBAR
@@ -2293,7 +2302,6 @@ xunit
xutr
XVIRTUALSCREEN
XWalk
xwwyzz
xxyyzz
yact
YCast
@@ -2304,7 +2312,9 @@ YOffset
YSubstantial
YVIRTUALSCREEN
YWalk
Zab
zabcd
Zabcdefghijklmn
Zabcdefghijklmnopqrstuvwxyz
ZCmd
ZCtrl

View File

@@ -27,6 +27,12 @@ ROY\sG\.\sBIV
# Python stringprefix / binaryprefix
\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'
# SSE intrinsics like "_mm_subs_epu16"
\b_mm(?:|256|512)_\w+\b
# ARM NEON intrinsics like "vsubq_u16"
\bv\w+_[fsu](?:8|16|32|64)\b
# Automatically suggested patterns
# hit-count: 3831 file-count: 582
# IServiceProvider

View File

@@ -103,7 +103,9 @@
"files.exclude": {
"**/bin/**": true,
"**/obj/**": true,
"**/packages/**": true,
"**/Generated Files/**": true
},
"search.exclude": {
"**/packages/**": true
}
}

View File

@@ -59,3 +59,4 @@ Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
# where it will create a subdirectory named terminal-0.0.1.0
# This is referenced in TerminalApp.cs later as part of the test harness.
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\WindowsTerminalDev_0.0.1.0_x64.zip" -C "$payloadDir"
Copy-Item "res\fonts\*.ttf" "$payloadDir\terminal-0.0.1.0"

View File

@@ -245,10 +245,6 @@ jobs:
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
OverWrite: true
flattenFolders: true
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
displayName: 'Generate SBOM manifest'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
- pwsh: |-
$Package = (Get-ChildItem "$(Build.ArtifactStagingDirectory)/appx" -Recurse -Filter "Cascadia*.msix" | Select -First 1)
@@ -271,20 +267,29 @@ jobs:
& "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
displayName: Re-pack the new Terminal package after signing
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (appx)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
- pwsh: |-
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged"
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/appx"
displayName: Build Unpackaged Distribution
- publish: $(Build.ArtifactStagingDirectory)/unpackaged
artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration)
displayName: Publish Artifact (unpackaged)
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
displayName: 'Generate SBOM manifest (application)'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
- task: DropValidatorTask@0
displayName: 'Validate application SBOM manifest'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
OutputPath: 'output.json'
ValidateSignature: true
Verbosity: 'Verbose'
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Terminal app)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
ArtifactName: terminal-$(BuildPlatform)-$(BuildConfiguration)
- ${{ if eq(parameters.buildConPTY, true) }}:
- task: CopyFiles@2
@@ -355,10 +360,13 @@ jobs:
inputs:
disableOutputRedirect: true
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
- task: DownloadBuildArtifacts@1
displayName: Download Artifacts ${{ platform }}
inputs:
artifactName: appx-${{ platform }}-Release
# Make sure to download the entire artifact, because it includes the SPDX SBOM
artifactName: terminal-${{ platform }}-Release
# Downloading to the source directory should ensure that the later SBOM generator can see the earlier SBOMs.
downloadPath: '$(Build.SourcesDirectory)/appx-artifacts'
# Add 3000 to the major version component, but only for the bundle.
# This is to ensure that it is newer than "2022.xx.yy.zz" or whatever the original bundle versions were before
# we switched to uniform naming.
@@ -368,7 +376,7 @@ jobs:
$Components[0] = ([int]$Components[0] + $VersionEpoch)
$BundleVersion = $Components -Join "."
New-Item -Type Directory "$(System.ArtifactsDirectory)\bundle"
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(Build.SourcesDirectory)/appx-artifacts" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
displayName: Create WindowsTerminal*.msixbundle
- task: EsrpCodeSigning@1
displayName: Submit *.msixbundle to ESRP for code signing
@@ -405,6 +413,20 @@ jobs:
}
]
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
displayName: 'Generate SBOM manifest (bundle)'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/bundle'
BuildComponentPath: '$(Build.SourcesDirectory)/appx-artifacts'
- task: DropValidatorTask@0
displayName: 'Validate bundle SBOM manifest'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/bundle'
OutputPath: 'output.json'
ValidateSignature: true
Verbosity: 'Verbose'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: appxbundle-signed'
inputs:
@@ -431,7 +453,7 @@ jobs:
inputs:
disableOutputRedirect: true
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
- task: DownloadBuildArtifacts@1
displayName: Download ${{ platform }} ConPTY binaries
inputs:
artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)
@@ -522,7 +544,7 @@ jobs:
inputs:
disableOutputRedirect: true
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
- task: DownloadBuildArtifacts@1
displayName: Download ${{ platform }} PublicTerminalCore
inputs:
artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)
@@ -621,12 +643,13 @@ jobs:
- template: .\templates\restore-nuget-steps.yml
# Download the appx-PLATFORM-CONFIG-VERSION artifact for every platform/version combo
# Download the terminal-PLATFORM-CONFIG-VERSION artifact for every platform/version combo
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadBuildArtifacts@0
- task: DownloadBuildArtifacts@1
displayName: Download Symbols ${{ platform }}
inputs:
artifactName: appx-${{ platform }}-Release
artifactName: terminal-${{ platform }}-Release
itemPattern: '**/*.appxsym'
# It seems easier to do this -- download every appxsym -- then enumerate all the PDBs in the build directory for the
# public symbol push. Otherwise, we would have to list all of the PDB files one by one.
@@ -683,7 +706,7 @@ jobs:
submodules: true
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
- task: DownloadBuildArtifacts@0
- task: DownloadBuildArtifacts@1
displayName: Download Build Artifacts
inputs:
artifactName: appxbundle-signed

View File

@@ -30,7 +30,7 @@
DependsOnTargets="_ConsoleMapWinmdsToManifestFiles">
<!-- This target is batched and a new Exec is spawned for each entry in _ConsoleWinmdManifest. -->
<Exec Command="mt.exe -winmd:%(_ConsoleWinmdManifest.WinMDPath) -dll:%(_ConsoleWinmdManifest.Implementation) -out:%(_ConsoleWinmdManifest.Identity)" />
<Exec Command="mt.exe -winmd:&quot;%(_ConsoleWinmdManifest.WinMDPath)&quot; -dll:%(_ConsoleWinmdManifest.Implementation) -out:&quot;%(_ConsoleWinmdManifest.Identity)&quot;" />
<ItemGroup>
<!-- Emit the generated manifest into the Link inputs. -->

View File

@@ -1,27 +1,36 @@
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)][string]$MatchPattern,
[Parameter(Mandatory=$true, Position=1)][string]$Platform,
[Parameter(Mandatory=$true, Position=2)][string]$Configuration,
[Parameter(Mandatory=$false, Position=3)][string]$LogPath,
[Parameter(Mandatory=$false)][string]$Root = ".\bin\$Platform\$Configuration"
[Parameter(Mandatory=$true, Position=0)]
[string]$MatchPattern,
[Parameter(Mandatory=$true, Position=1)]
[string]$Platform,
[Parameter(Mandatory=$true, Position=2)]
[string]$Configuration,
[Parameter(Mandatory=$false, Position=3)]
[string]$LogPath,
[Parameter(Mandatory=$false)]
[string]$Root = ".\bin\$Platform\$Configuration"
)
$testdlls = Get-ChildItem -Path "$Root" -Recurse -Filter $MatchPattern
# Find test DLLs based on the provided root, match pattern, and recursion
$testDlls = Get-ChildItem -Path $Root -Recurse -Filter $MatchPattern
$args = @()
$args = @();
if ($LogPath)
{
$args += '/enablewttlogging';
$args += '/appendwttlogging';
$args += "/logFile:$LogPath";
Write-Host "Wtt Logging Enabled";
# Check if the LogPath parameter is provided and enable WTT logging
if ($LogPath) {
$args += '/enablewttlogging'
$args += '/appendwttlogging'
$args += "/logFile:$LogPath"
Write-Host "WTT Logging Enabled"
}
&"$Root\te.exe" $args $testdlls.FullName
# Invoke the te.exe executable with arguments and test DLLs
& "$Root\te.exe" $args $testDlls.FullName
if ($lastexitcode -Ne 0) { Exit $lastexitcode }
# Check the exit code of the te.exe process and exit accordingly
if ($LASTEXITCODE -ne 0) {
Exit $LASTEXITCODE
}
Exit 0

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>18</VersionMinor>
<VersionMinor>19</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -421,6 +421,7 @@
"scrollToMark",
"clearMark",
"clearAllMarks",
"searchWeb",
"experimental.colorSelection",
"unbound"
],
@@ -1709,6 +1710,29 @@
}
]
},
"SearchWebAction": {
"description": "Search the web for selected text",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"const": "searchWeb"
},
"queryUrl": {
"type": "string",
"description": "URL of the web page to launch, %s is replaced with the selected text"
}
}
}
],
"required": [
"queryUrl"
]
},
"AdjustOpacityAction": {
"description": "Changes the opacity of the active Terminal window. If `relative` is specified, then this action will increase/decrease relative to the current opacity.",
"allOf": [
@@ -1813,6 +1837,19 @@
"description": "True if the Terminal should use a Mica backdrop for the window. This will apply underneath all controls (including the terminal panes and the titlebar)",
"type": "boolean",
"default": false
},
"experimental.rainbowFrame": {
"description": "When enabled, the frame of the window will cycle through all the colors. Enabling this will override the `frame` and `unfocusedFrame` settings.",
"type": "boolean",
"default": false
},
"frame": {
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
"$ref": "#/$defs/ThemeColor"
},
"unfocusedFrame": {
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
"$ref": "#/$defs/ThemeColor"
}
}
},
@@ -1991,6 +2028,9 @@
{
"$ref": "#/$defs/ColorSelectionAction"
},
{
"$ref": "#/$defs/SearchWebAction"
},
{
"type": "null"
}

View File

@@ -9,6 +9,8 @@
#include "textBuffer.hpp"
#include "../../types/inc/GlyphWidth.hpp"
extern "C" int __isa_available;
// The STL is missing a std::iota_n analogue for std::iota, so I made my own.
template<typename OutIt, typename Diff, typename T>
constexpr OutIt iota_n(OutIt dest, Diff count, T val)
@@ -82,10 +84,7 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c
_attr{ rowWidth, fillAttribute },
_columnCount{ rowWidth }
{
if (_chars.data())
{
_init();
}
_init();
}
void ROW::SetWrapForced(const bool wrap) noexcept
@@ -124,11 +123,13 @@ LineRendition ROW::GetLineRendition() const noexcept
// - Attr - The default attribute (color) to fill
// Return Value:
// - <none>
void ROW::Reset(const TextAttribute& attr)
void ROW::Reset(const TextAttribute& attr) noexcept
{
_charsHeap.reset();
_chars = { _charsBuffer, _columnCount };
_attr = { _columnCount, attr };
// Constructing and then moving objects into place isn't free.
// Modifying the existing object is _much_ faster.
*_attr.runs().unsafe_shrink_to_size(1) = til::rle_pair{ attr, _columnCount };
_lineRendition = LineRendition::SingleWidth;
_wrapForced = false;
_doubleBytePadded = false;
@@ -137,8 +138,117 @@ void ROW::Reset(const TextAttribute& attr)
void ROW::_init() noexcept
{
std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE);
#pragma warning(push)
#pragma warning(disable : 26462) // The value pointed to by '...' is assigned only once, mark it as a pointer to const (con.4).
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
// Fills _charsBuffer with whitespace and correspondingly _charOffsets
// with successive numbers from 0 to _columnCount+1.
#if defined(TIL_SSE_INTRINSICS)
alignas(__m256i) static constexpr uint16_t whitespaceData[]{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
alignas(__m256i) static constexpr uint16_t offsetsData[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
alignas(__m256i) static constexpr uint16_t increment16Data[]{ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 };
alignas(__m128i) static constexpr uint16_t increment8Data[]{ 8, 8, 8, 8, 8, 8, 8, 8 };
// The AVX loop operates on 32 bytes at a minimum. Since _charsBuffer/_charOffsets uses 2 byte large
// wchar_t/uint16_t respectively, this translates to 16-element writes, which equals a _columnCount of 15,
// because it doesn't include the past-the-end char-offset as described in the _charOffsets member comment.
if (__isa_available >= __ISA_AVAILABLE_AVX2 && _columnCount >= 15)
{
auto chars = _charsBuffer;
auto charOffsets = _charOffsets.data();
// The backing buffer for both chars and charOffsets is guaranteed to be 16-byte aligned,
// but AVX operations are 32-byte large. As such, when we write out the last chunk, we
// have to align it to the ends of the 2 buffers. This results in a potential overlap of
// 16 bytes between the last write in the main loop below and the final write afterwards.
//
// An example:
// If you have a terminal between 16 and 23 columns the buffer has a size of 48 bytes.
// The main loop below will iterate once, as it writes out bytes 0-31 and then exits.
// The final write afterwards cannot write bytes 32-63 because that would write
// out of bounds. Instead it writes bytes 16-47, overwriting 16 overlapping bytes.
// This is better than branching and switching to SSE2, because both things are slow.
//
// Since we want to exit the main loop with at least 1 write left to do as the final write,
// we need to subtract 1 alignment from the buffer length (= 16 bytes). Since _columnCount is
// in wchar_t's we subtract -8. The same applies to the ~7 here vs ~15. If you squint slightly
// you'll see how this is effectively the inverse of what CalculateCharsBufferStride does.
const auto tailColumnOffset = gsl::narrow_cast<uint16_t>((_columnCount - 8u) & ~7);
const auto charsEndLoop = chars + tailColumnOffset;
const auto charOffsetsEndLoop = charOffsets + tailColumnOffset;
const auto whitespace = _mm256_load_si256(reinterpret_cast<const __m256i*>(&whitespaceData[0]));
auto offsetsLoop = _mm256_load_si256(reinterpret_cast<const __m256i*>(&offsetsData[0]));
const auto offsets = _mm256_add_epi16(offsetsLoop, _mm256_set1_epi16(tailColumnOffset));
if (chars < charsEndLoop)
{
const auto increment = _mm256_load_si256(reinterpret_cast<const __m256i*>(&increment16Data[0]));
do
{
_mm256_storeu_si256(reinterpret_cast<__m256i*>(chars), whitespace);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(charOffsets), offsetsLoop);
offsetsLoop = _mm256_add_epi16(offsetsLoop, increment);
chars += 16;
charOffsets += 16;
} while (chars < charsEndLoop);
}
_mm256_storeu_si256(reinterpret_cast<__m256i*>(charsEndLoop), whitespace);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(charOffsetsEndLoop), offsets);
}
else
{
auto chars = _charsBuffer;
auto charOffsets = _charOffsets.data();
const auto charsEnd = chars + _columnCount;
const auto whitespace = _mm_load_si128(reinterpret_cast<const __m128i*>(&whitespaceData[0]));
const auto increment = _mm_load_si128(reinterpret_cast<const __m128i*>(&increment8Data[0]));
auto offsets = _mm_load_si128(reinterpret_cast<const __m128i*>(&offsetsData[0]));
do
{
_mm_storeu_si128(reinterpret_cast<__m128i*>(chars), whitespace);
_mm_storeu_si128(reinterpret_cast<__m128i*>(charOffsets), offsets);
offsets = _mm_add_epi16(offsets, increment);
chars += 8;
charOffsets += 8;
// If _columnCount is something like 120, the actual backing buffer for charOffsets is 121 items large.
// --> The while loop uses <= to emit at least 1 more write.
} while (chars <= charsEnd);
}
#elif defined(TIL_ARM_NEON_INTRINSICS)
alignas(uint16x8_t) static constexpr uint16_t offsetsData[]{ 0, 1, 2, 3, 4, 5, 6, 7 };
auto chars = _charsBuffer;
auto charOffsets = _charOffsets.data();
const auto charsEnd = chars + _columnCount;
const auto whitespace = vdupq_n_u16(L' ');
const auto increment = vdupq_n_u16(8);
auto offsets = vld1q_u16(&offsetsData[0]);
do
{
vst1q_u16(chars, whitespace);
vst1q_u16(charOffsets, offsets);
offsets = vaddq_u16(offsets, increment);
chars += 8;
charOffsets += 8;
// If _columnCount is something like 120, the actual backing buffer for charOffsets is 121 items large.
// --> The while loop uses <= to emit at least 1 more write.
} while (chars <= charsEnd);
#else
#error "Vectorizing this function improves overall performance by up to 40%. Don't remove this warning, just add the vectorized code."
std::fill_n(_charsBuffer, _columnCount, UNICODE_SPACE);
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
#endif
#pragma warning(push)
}
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
@@ -147,6 +257,15 @@ void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& a
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
}
void ROW::CopyFrom(const ROW& source)
{
RowCopyTextFromState state{ .source = source };
CopyTextFrom(state);
TransferAttributes(source.Attributes(), _columnCount);
_lineRendition = source._lineRendition;
_wrapForced = source._wrapForced;
}
// Returns the previous possible cursor position, preceding the given column.
// Returns 0 if column is less than or equal to 0.
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
@@ -316,10 +435,9 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c
return it;
}
bool ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute attr)
void ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute attr)
{
_attr.replace(_clampedColumnInclusive(columnBegin), _attr.size(), attr);
return true;
}
void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordType endIndex, const TextAttribute& newAttr)
@@ -445,43 +563,51 @@ catch (...)
charsConsumed = ch - chBeg;
}
til::CoordType ROW::CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit)
void ROW::CopyTextFrom(RowCopyTextFromState& state)
try
{
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
const auto otherColLimit = other._clampedColumnInclusive(otherLimit);
std::span<uint16_t> charOffsets;
auto& source = state.source;
const auto sourceColBeg = source._clampedColumnInclusive(state.sourceColumnBegin);
const auto sourceColLimit = source._clampedColumnInclusive(state.sourceColumnLimit);
std::span<const uint16_t> charOffsets;
std::wstring_view chars;
if (otherColBeg < otherColLimit)
if (sourceColBeg < sourceColLimit)
{
charOffsets = other._charOffsets.subspan(otherColBeg, static_cast<size_t>(otherColLimit) - otherColBeg + 1);
charOffsets = source._charOffsets.subspan(sourceColBeg, static_cast<size_t>(sourceColLimit) - sourceColBeg + 1);
const auto charsOffset = charOffsets.front() & CharOffsetsMask;
// We _are_ using span. But C++ decided that string_view and span aren't convertible.
// _chars is a std::span for performance and because it refers to raw, shared memory.
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
chars = { other._chars.data() + charsOffset, other._chars.size() - charsOffset };
chars = { source._chars.data() + charsOffset, source._chars.size() - charsOffset };
}
WriteHelper h{ *this, columnBegin, columnLimit, chars };
if (!h.IsValid())
WriteHelper h{ *this, state.columnBegin, state.columnLimit, chars };
if (!h.IsValid() ||
// If we were to copy text from ourselves, we'd overwrite
// our _charOffsets and break Finish() which reads from it.
this == &state.source ||
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
// element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really
// need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that.
charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer))
{
return h.colBeg;
state.columnEnd = h.colBeg;
state.columnBeginDirty = h.colBeg;
state.columnEndDirty = h.colBeg;
state.sourceColumnEnd = source._columnCount;
return;
}
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
// element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really
// need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that.
if (charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer))
{
assert(false);
otherBegin = other.size();
return h.colBeg;
}
h.CopyRangeFrom(charOffsets);
h.CopyTextFrom(charOffsets);
h.Finish();
otherBegin += h.colEnd - h.colBeg;
return h.colEndDirty;
// state.columnEnd is computed identical to ROW::ReplaceText. Check it out for more information.
state.columnEnd = h.charsConsumed == chars.size() ? h.colEnd : h.colLimit;
state.columnBeginDirty = h.colBegDirty;
state.columnEndDirty = h.colEndDirty;
state.sourceColumnEnd = sourceColBeg + h.colEnd - h.colBeg;
}
catch (...)
{
@@ -489,7 +615,7 @@ catch (...)
throw;
}
[[msvc::forceinline]] void ROW::WriteHelper::CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept
[[msvc::forceinline]] void ROW::WriteHelper::CopyTextFrom(const std::span<const uint16_t>& charOffsets) noexcept
{
// Since our `charOffsets` input is already in columns (just like the `ROW::_charOffsets`),
// we can directly look up the end char-offset, but...
@@ -504,26 +630,36 @@ catch (...)
const auto baseOffset = til::at(charOffsets, 0);
const auto endOffset = til::at(charOffsets, colEndInput);
const auto inToOutOffset = gsl::narrow_cast<uint16_t>(chBeg - baseOffset);
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
const auto dst = row._charOffsets.data() + colEnd;
// Now with the `colEndInput` figured out, we can easily copy the `charOffsets` into the `_charOffsets`.
// It's possible to use SIMD for this loop for extra perf gains. Something like this for SSE2 (~8x faster):
// const auto in = _mm_loadu_si128(...);
// const auto off = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsMask));
// const auto trailer = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsTrailer));
// const auto out = _mm_or_epi32(_mm_add_epi16(off, _mm_set1_epi16(inToOutOffset)), trailer);
// _mm_store_si128(..., out);
for (uint16_t i = 0; i < colEndInput; ++i, ++colEnd)
{
const auto ch = til::at(charOffsets, i);
const auto off = ch & CharOffsetsMask;
const auto trailer = ch & CharOffsetsTrailer;
til::at(row._charOffsets, colEnd) = gsl::narrow_cast<uint16_t>((off + inToOutOffset) | trailer);
}
_copyOffsets(dst, charOffsets.data(), colEndInput, inToOutOffset);
colEnd += colEndInput;
colEndDirty = gsl::narrow_cast<uint16_t>(colBeg + colEndDirtyInput);
charsConsumed = endOffset - baseOffset;
}
#pragma warning(push)
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
[[msvc::forceinline]] void ROW::WriteHelper::_copyOffsets(uint16_t* __restrict dst, const uint16_t* __restrict src, uint16_t size, uint16_t offset) noexcept
{
__assume(src != nullptr);
__assume(dst != nullptr);
// All tested compilers (including MSVC) will neatly unroll and vectorize
// this loop, which is why it's written in this particular way.
for (const auto end = src + size; src != end; ++src, ++dst)
{
const uint16_t ch = *src;
const uint16_t off = ch & CharOffsetsMask;
const uint16_t trailer = ch & CharOffsetsTrailer;
const uint16_t newOff = off + offset;
*dst = newOff | trailer;
}
}
#pragma warning(pop)
[[msvc::forceinline]] void ROW::WriteHelper::Finish()
{
colEndDirty = row._adjustForward(colEndDirty);

View File

@@ -26,6 +26,7 @@ Revision History:
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
class ROW;
class TextBuffer;
enum class DelimiterClass
@@ -43,7 +44,7 @@ struct RowWriteState
// The column at which to start writing.
til::CoordType columnBegin = 0; // IN
// The first column which should not be written to anymore.
til::CoordType columnLimit = 0; // IN
til::CoordType columnLimit = til::CoordTypeMax; // IN
// The column 1 past the last glyph that was successfully written into the row. If you need to call
// ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want.
@@ -57,9 +58,56 @@ struct RowWriteState
til::CoordType columnEndDirty = 0; // OUT
};
struct RowCopyTextFromState
{
// The row to copy text from.
const ROW& source; // IN
// The column at which to start writing.
til::CoordType columnBegin = 0; // IN
// The first column which should not be written to anymore.
til::CoordType columnLimit = til::CoordTypeMax; // IN
// The column at which to start reading from source.
til::CoordType sourceColumnBegin = 0; // IN
// The first column which should not be read from anymore.
til::CoordType sourceColumnLimit = til::CoordTypeMax; // IN
til::CoordType columnEnd = 0; // OUT
// The first column that got modified by this write operation. In case that the first glyph we write overwrites
// the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg.
til::CoordType columnBeginDirty = 0; // OUT
// This is 1 past the last column that was modified and will be 1 past columnEnd if we overwrote
// the leading half of a wide glyph and had to fill the trailing half with whitespace.
til::CoordType columnEndDirty = 0; // OUT
// This is 1 past the last column that was read from.
til::CoordType sourceColumnEnd = 0; // OUT
};
class ROW final
{
public:
// The implicit agreement between ROW and TextBuffer is that the `charsBuffer` and `charOffsetsBuffer`
// arrays have a minimum alignment of 16 Bytes and a size of `rowWidth+1`. The former is used to
// implement Reset() efficiently via SIMD and the latter is used to store the past-the-end offset
// into the `charsBuffer`. Even though the `charsBuffer` could be only `rowWidth` large we need them
// to be the same size so that the SIMD code can process both arrays in the same loop simultaneously.
// This wastes up to 5.8% memory but increases overall scrolling performance by around 40%.
// These methods exists to make this agreement explicit and serve as a reminder.
//
// TextBuffer calculates the distance in bytes between two ROWs (_bufferRowStride) as the sum of these values.
// As such it's important that we return sizes with a minimum alignment of alignof(ROW).
static constexpr size_t CalculateRowSize() noexcept
{
return (sizeof(ROW) + 15) & ~15;
}
static constexpr size_t CalculateCharsBufferSize(size_t columns) noexcept
{
return (columns * sizeof(wchar_t) + 16) & ~15;
}
static constexpr size_t CalculateCharOffsetsBufferSize(size_t columns) noexcept
{
return (columns * sizeof(uint16_t) + 16) & ~15;
}
ROW() = default;
ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
@@ -76,19 +124,20 @@ public:
void SetLineRendition(const LineRendition lineRendition) noexcept;
LineRendition GetLineRendition() const noexcept;
void Reset(const TextAttribute& attr);
void Reset(const TextAttribute& attr) noexcept;
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
void CopyFrom(const ROW& source);
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
void ClearCell(til::CoordType column);
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr);
void SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr);
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
void ReplaceText(RowWriteState& state);
til::CoordType CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit);
void CopyTextFrom(RowCopyTextFromState& state);
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
@@ -121,7 +170,8 @@ private:
bool IsValid() const noexcept;
void ReplaceCharacters(til::CoordType width) noexcept;
void ReplaceText() noexcept;
void CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept;
void CopyTextFrom(const std::span<const uint16_t>& charOffsets) noexcept;
static void _copyOffsets(uint16_t* dst, const uint16_t* src, uint16_t size, uint16_t offset) noexcept;
void Finish();
// Parent pointer.

File diff suppressed because it is too large Load Diff

View File

@@ -72,14 +72,22 @@ public:
const UINT cursorSize,
const bool isActiveBuffer,
Microsoft::Console::Render::Renderer& renderer);
TextBuffer(const TextBuffer& a) = delete;
TextBuffer(const TextBuffer&) = delete;
TextBuffer(TextBuffer&&) = delete;
TextBuffer& operator=(const TextBuffer&) = delete;
TextBuffer& operator=(TextBuffer&&) = delete;
~TextBuffer();
// Used for duplicating properties to another text buffer
void CopyProperties(const TextBuffer& OtherBuffer) noexcept;
// row manipulation
const ROW& GetRowByOffset(const til::CoordType index) const noexcept;
ROW& GetRowByOffset(const til::CoordType index) noexcept;
ROW& GetScratchpadRow();
ROW& GetScratchpadRow(const TextAttribute& attributes);
const ROW& GetRowByOffset(til::CoordType index) const;
ROW& GetRowByOffset(til::CoordType index);
TextBufferCellIterator GetCellDataAt(const til::point at) const;
TextBufferCellIterator GetCellLineDataAt(const til::point at) const;
@@ -91,6 +99,7 @@ public:
// Text insertion functions
static void ConsumeGrapheme(std::wstring_view& chars) noexcept;
void WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state);
void FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes);
OutputCellIterator Write(const OutputCellIterator givenIt);
@@ -103,13 +112,13 @@ public:
const std::optional<bool> setWrap = std::nullopt,
const std::optional<til::CoordType> limitRight = std::nullopt);
bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool IncrementCursor();
bool NewlineCursor();
void InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
void InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
void IncrementCursor();
void NewlineCursor();
// Scroll needs access to this to quickly rotate around the buffer.
bool IncrementCircularBuffer(const bool inVtMode = false);
void IncrementCircularBuffer(const TextAttribute& fillAttributes = {});
til::point GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional = std::nullopt) const;
@@ -128,17 +137,17 @@ public:
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
void SetCurrentLineRendition(const LineRendition lineRendition);
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow) noexcept;
LineRendition GetLineRendition(const til::CoordType row) const noexcept;
bool IsDoubleWidthLine(const til::CoordType row) const noexcept;
void SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes);
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow);
LineRendition GetLineRendition(const til::CoordType row) const;
bool IsDoubleWidthLine(const til::CoordType row) const;
til::CoordType GetLineWidth(const til::CoordType row) const noexcept;
til::point ClampPositionWithinLine(const til::point position) const noexcept;
til::point ScreenToBufferPosition(const til::point position) const noexcept;
til::point BufferToScreenPosition(const til::point position) const noexcept;
til::CoordType GetLineWidth(const til::CoordType row) const;
til::point ClampPositionWithinLine(const til::point position) const;
til::point ScreenToBufferPosition(const til::point position) const;
til::point BufferToScreenPosition(const til::point position) const;
void Reset();
void Reset() noexcept;
[[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept;
@@ -219,23 +228,27 @@ public:
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;
private:
static wil::unique_virtualalloc_ptr<std::byte> _allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows);
void _reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes);
void _commit(const std::byte* row);
void _decommit() noexcept;
void _construct(const std::byte* until) noexcept;
void _destroy() const noexcept;
ROW& _getRowByOffsetDirect(size_t offset);
til::CoordType _estimateOffsetOfLastCommittedRow() const noexcept;
void _UpdateSize();
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
til::point _GetPreviousFromCursor() const noexcept;
void _SetWrapOnCurrentRow() noexcept;
void _AdjustWrapOnCurrentRow(const bool fSet) noexcept;
til::point _GetPreviousFromCursor() const;
void _SetWrapOnCurrentRow();
void _AdjustWrapOnCurrentRow(const bool fSet);
// Assist with maintaining proper buffer state for Double Byte character sequences
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
void _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
ROW& _GetFirstRow() noexcept;
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
@@ -249,13 +262,67 @@ private:
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
size_t _currentPatternId = 0;
wil::unique_virtualalloc_ptr<std::byte> _charBuffer;
std::vector<ROW> _storage;
// This block describes the state of the underlying virtual memory buffer that holds all ROWs, text and attributes.
// Initially memory is only allocated with MEM_RESERVE to reduce the private working set of conhost.
// ROWs are laid out like this in memory:
// ROW <-- sizeof(ROW), stores
// (padding)
// ROW::_charsBuffer <-- _width * sizeof(wchar_t)
// (padding)
// ROW::_charOffsets <-- (_width + 1) * sizeof(uint16_t)
// (padding)
// ...
// Padding may exist for alignment purposes.
//
// The base (start) address of the memory arena.
wil::unique_virtualalloc_ptr<std::byte> _buffer;
// The past-the-end pointer of the memory arena.
std::byte* _bufferEnd = nullptr;
// The range between _buffer (inclusive) and _commitWatermark (exclusive) is the range of
// memory that has already been committed via MEM_COMMIT and contains ready-to-use ROWs.
//
// The problem is that calling VirtualAlloc(MEM_COMMIT) on each ROW one by one is extremely expensive, which forces
// us to commit ROWs in batches and avoid calling it on already committed ROWs. Let's say we commit memory in
// batches of 128 ROWs. One option to know whether a ROW has already been committed is to allocate a vector<uint8_t>
// of size `(height + 127) / 128` and mark the corresponding slot as 1 if that 128-sized batch has been committed.
// That way we know not to commit it again. But ROWs aren't accessed randomly. Instead, they're usually accessed
// fairly linearly from row 1 to N. As such we can just commit ROWs up to the point of the highest accessed ROW
// plus some read-ahead of 128 ROWs. This is exactly what _commitWatermark stores: The highest accessed ROW plus
// some read-ahead. It's the amount of memory that has been committed and is ready to use.
//
// _commitWatermark will always be a multiple of _bufferRowStride away from _buffer.
// In other words, _commitWatermark itself will either point exactly onto the next ROW
// that should be committed or be equal to _bufferEnd when all ROWs are committed.
std::byte* _commitWatermark = nullptr;
// This will MEM_COMMIT 128 rows more than we need, to avoid us from having to call VirtualAlloc too often.
// This equates to roughly the following commit chunk sizes at these column counts:
// * 80 columns (the usual minimum) = 60KB chunks, 4.1MB buffer at 9001 rows
// * 120 columns (the most common) = 80KB chunks, 5.6MB buffer at 9001 rows
// * 400 columns (the usual maximum) = 220KB chunks, 15.5MB buffer at 9001 rows
// There's probably a better metric than this. (This comment was written when ROW had both,
// a _chars array containing text and a _charOffsets array contain column-to-text indices.)
static constexpr size_t _commitReadAheadRowCount = 128;
// Before TextBuffer was made to use virtual memory it initialized the entire memory arena with the initial
// attributes right away. To ensure it continues to work the way it used to, this stores these initial attributes.
TextAttribute _initialAttributes;
// ROW ---------------+--+--+
// (padding) | | v _bufferOffsetChars
// ROW::_charsBuffer | |
// (padding) | v _bufferOffsetCharOffsets
// ROW::_charOffsets |
// (padding) v _bufferRowStride
size_t _bufferRowStride = 0;
size_t _bufferOffsetChars = 0;
size_t _bufferOffsetCharOffsets = 0;
// The width of the buffer in columns.
uint16_t _width = 0;
// The height of the buffer in rows, excluding the scratchpad row.
uint16_t _height = 0;
TextAttribute _currentAttributes;
til::CoordType _firstRow = 0; // indexes top row (not necessarily 0)
Cursor _cursor;
Microsoft::Console::Types::Viewport _size;
bool _isActiveBuffer = false;

View File

@@ -287,7 +287,7 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
// Arguments:
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
void TextBufferCellIterator::_SetPos(const til::point newPos)
{
if (newPos.y != _pos.y)
{
@@ -315,7 +315,7 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
// - pos - Position inside screen buffer bounds to retrieve row
// Return Value:
// - Pointer to the underlying CharRow structure
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos)
{
return &buffer.GetRowByOffset(pos.y);
}

View File

@@ -49,9 +49,9 @@ public:
til::point Pos() const noexcept;
protected:
void _SetPos(const til::point newPos) noexcept;
void _SetPos(const til::point newPos);
void _GenerateView() noexcept;
static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept;
static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos);
til::small_rle<TextAttribute, uint16_t, 1>::const_iterator _attrIter;
OutputCellView _view;

View File

@@ -204,7 +204,7 @@ HRESULT HwndTerminal::Initialize()
RETURN_HR_IF_NULL(E_POINTER, localPointerToThread);
RETURN_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
auto dxEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
RETURN_IF_FAILED(dxEngine->SetHwnd(_hwnd.get()));
RETURN_IF_FAILED(dxEngine->Enable());
_renderer->AddRenderEngine(dxEngine.get());

View File

@@ -4,7 +4,7 @@
#pragma once
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
#include "../../renderer/atlas/AtlasEngine.h"
#include "../../renderer/uia/UiaRenderer.hpp"
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include "../../types/IControlAccessibilityInfo.h"
@@ -79,7 +79,7 @@ private:
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
std::unique_ptr<::Microsoft::Console::Render::AtlasEngine> _renderEngine;
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
bool _focused{ false };

View File

@@ -5,6 +5,7 @@
#include "HwndTerminalAutomationPeer.hpp"
#include "../../types/UiaTracing.h"
#include <UIAutomationCoreApi.h>
#include <delayimp.h>
#pragma warning(suppress : 4471) // We don't control UIAutomationClient
#include <UIAutomationClient.h>
@@ -13,6 +14,15 @@ using namespace Microsoft::Console::Types;
static constexpr wchar_t UNICODE_NEWLINE{ L'\n' };
static int CheckDelayedProcException(int exception) noexcept
{
if (exception == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
{
return EXCEPTION_EXECUTE_HANDLER;
}
return EXCEPTION_CONTINUE_SEARCH;
}
// Method Description:
// - creates a copy of the provided text with all of the control characters removed
// Arguments:
@@ -123,6 +133,12 @@ void HwndTerminalAutomationPeer::SignalCursorChanged()
void HwndTerminalAutomationPeer::NotifyNewOutput(std::wstring_view newOutput)
{
if (_notificationsUnavailable) [[unlikely]]
{
// What if you tried to notify, but God said no
return;
}
// Try to suppress any events (or event data)
// that is just the keypress the user made
auto sanitized{ Sanitize(newOutput) };
@@ -155,5 +171,19 @@ void HwndTerminalAutomationPeer::NotifyNewOutput(std::wstring_view newOutput)
const auto sanitizedBstr = wil::make_bstr_nothrow(sanitized.c_str());
static auto activityId = wil::make_bstr_nothrow(L"TerminalTextOutput");
LOG_IF_FAILED(UiaRaiseNotificationEvent(this, NotificationKind_ActionCompleted, NotificationProcessing_All, sanitizedBstr.get(), activityId.get()));
_tryNotify(sanitizedBstr.get(), activityId.get());
}
// This needs to be a separate function because it is using SEH try/except, which
// is incompatible with C++ unwinding
void HwndTerminalAutomationPeer::_tryNotify(BSTR string, BSTR activity)
{
__try
{
LOG_IF_FAILED(UiaRaiseNotificationEvent(this, NotificationKind_ActionCompleted, NotificationProcessing_All, string, activity));
}
__except (CheckDelayedProcException(GetExceptionCode()))
{
_notificationsUnavailable = true;
}
}

View File

@@ -38,5 +38,7 @@ public:
void NotifyNewOutput(std::wstring_view newOutput) override;
#pragma endregion
private:
void _tryNotify(BSTR string, BSTR activity);
std::deque<wchar_t> _keyEvents;
bool _notificationsUnavailable{};
};

View File

@@ -57,7 +57,8 @@
instead of APISet forwarders for easier Windows 7 compatibility. -->
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>delayimp.lib;Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>uiautomationcore.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
</Project>

View File

@@ -10,7 +10,7 @@ namespace Microsoft.Terminal.Remoting
CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand);
String[] Commandline { get; set; };
String CurrentDirectory();
String CurrentDirectory { get; };
UInt32 ShowWindowCommand { get; };
};

View File

@@ -41,9 +41,8 @@ namespace
// Make a temporary monarch CLSID based on the unpackaged install root
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
modulePath.remove_filename();
std::wstring pathRootAsString{ modulePath.wstring() };
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ pathRootAsString }));
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ modulePath.native() }));
}();
return processRootHashedGuid;
}

View File

@@ -58,23 +58,6 @@ namespace winrt::TerminalApp::implementation
ShellExecute(nullptr, nullptr, currentPath.c_str(), nullptr, nullptr, SW_SHOW);
}
bool AboutDialog::UpdatesAvailable() const
{
return !_pendingUpdateVersion.empty();
}
winrt::hstring AboutDialog::PendingUpdateVersion() const
{
return _pendingUpdateVersion;
}
void AboutDialog::_SetPendingUpdateVersion(const winrt::hstring& version)
{
_pendingUpdateVersion = version;
_PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"PendingUpdateVersion" });
_PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"UpdatesAvailable" });
}
winrt::fire_and_forget AboutDialog::_queueUpdateCheck()
{
auto strongThis = get_strong();
@@ -91,7 +74,7 @@ namespace winrt::TerminalApp::implementation
}
co_await wil::resume_foreground(strongThis->Dispatcher());
_SetPendingUpdateVersion({});
UpdatesAvailable(false);
CheckingForUpdates(true);
try
@@ -101,7 +84,7 @@ namespace winrt::TerminalApp::implementation
// there is an update available. This lets us test the system.
co_await winrt::resume_after(std::chrono::seconds{ 3 });
co_await wil::resume_foreground(strongThis->Dispatcher());
_SetPendingUpdateVersion(L"X.Y.Z");
UpdatesAvailable(true);
#else // release build, likely has a store context
if (auto storeContext{ winrt::Windows::Services::Store::StoreContext::GetDefault() })
{
@@ -110,10 +93,7 @@ namespace winrt::TerminalApp::implementation
const auto numUpdates = updates.Size();
if (numUpdates > 0)
{
const auto update = updates.GetAt(0);
const auto version = update.Package().Id().Version();
const auto str = fmt::format(FMT_COMPILE(L"{}.{}.{}"), version.Major, version.Minor, version.Build);
_SetPendingUpdateVersion(winrt::hstring{ str });
UpdatesAvailable(true);
}
}
#endif

View File

@@ -14,19 +14,16 @@ namespace winrt::TerminalApp::implementation
winrt::hstring ApplicationDisplayName();
winrt::hstring ApplicationVersion();
bool UpdatesAvailable() const;
winrt::hstring PendingUpdateVersion() const;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(bool, UpdatesAvailable, _PropertyChangedHandlers, false);
WINRT_OBSERVABLE_PROPERTY(bool, CheckingForUpdates, _PropertyChangedHandlers, false);
private:
friend struct AboutDialogT<AboutDialog>; // for Xaml to bind events
std::chrono::system_clock::time_point _lastUpdateCheck{};
winrt::hstring _pendingUpdateVersion;
void _SetPendingUpdateVersion(const winrt::hstring& pendingUpdateVersion);
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _SendFeedbackOnClick(const IInspectable& sender, const Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs& eventArgs);
winrt::fire_and_forget _queueUpdateCheck();

View File

@@ -12,6 +12,5 @@ namespace TerminalApp
Boolean CheckingForUpdates { get; };
Boolean UpdatesAvailable { get; };
String PendingUpdateVersion { get; };
}
}

View File

@@ -40,9 +40,7 @@
Orientation="Vertical"
Visibility="{x:Bind UpdatesAvailable, Mode=OneWay}">
<TextBlock IsTextSelectionEnabled="False">
<Run x:Uid="AboutDialog_UpdateAvailableLabel" /> <LineBreak />
<Run x:Uid="AboutDialog_VersionLabel" />
<Run Text="{x:Bind PendingUpdateVersion, Mode=OneWay}" />
<Run x:Uid="AboutDialog_UpdateAvailableLabel" />
</TextBlock>
<!-- <Button x:Uid="AboutDialog_InstallUpdateButton"
Margin="0" />-->

View File

@@ -45,7 +45,7 @@
Color="{ThemeResource SystemErrorTextColor}" />
<!-- Suppress top padding -->
<Thickness x:Key="TabViewHeaderPadding">9,0,5,0</Thickness>
<Thickness x:Key="TabViewHeaderPadding">0,0,0,0</Thickness>
<Thickness x:Key="TabViewItemBorderThickness">1,1,1,0</Thickness>

View File

@@ -1021,6 +1021,49 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
void TerminalPage::_HandleSearchForText(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto termControl{ _GetActiveControl() })
{
if (termControl.HasSelection())
{
const auto selections{ termControl.SelectedText(true) };
// concatenate the selection into a single line
auto searchText = std::accumulate(selections.begin(), selections.end(), std::wstring());
// make it compact by replacing consecutive whitespaces with a single space
searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" ");
std::wstring queryUrl;
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SearchForTextArgs>())
{
queryUrl = realArgs.QueryUrl().c_str();
}
}
// use global default if query URL is unspecified
if (queryUrl.empty())
{
queryUrl = _settings.GlobalSettings().SearchWebDefaultQueryUrl().c_str();
}
constexpr std::wstring_view queryToken{ L"%s" };
if (const auto pos{ queryUrl.find(queryToken) }; pos != std::wstring_view::npos)
{
queryUrl.replace(pos, queryToken.length(), Windows::Foundation::Uri::EscapeComponent(searchText));
}
winrt::Microsoft::Terminal::Control::OpenHyperlinkEventArgs shortcut{ queryUrl };
_OpenHyperlinkHandler(termControl, shortcut);
args.Handled(true);
}
}
}
void TerminalPage::_HandleGlobalSummon(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -146,7 +146,7 @@
-->
<x:Double x:Key="CaptionButtonHeightWindowed">40.0</x:Double>
<!-- 32 + (1 to compensate for GH#10746) + (-1 for GH#15164) -->
<x:Double x:Key="CaptionButtonHeightMaximized">33.0</x:Double>
<x:Double x:Key="CaptionButtonHeightMaximized">32.0</x:Double>
<Style x:Key="CaptionButton"
TargetType="Button">

View File

@@ -1121,19 +1121,15 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
co_await wil::resume_foreground(_root.Dispatcher());
if (auto pane{ weakThis.lock() })
{
// BODGY
// GH#12258: We learned that if you leave the MediaPlayer open, and
// press the media keys (like play/pause), then the OS will _replay the
// bell_. So we have to re-create the MediaPlayer each time we want to
// play the bell, to make sure a subsequent play doesn't come through
// and reactivate the old one.
if (!_bellPlayer)
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
@@ -1143,27 +1139,6 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
// This lambda will clean up the bell player when we're done with it.
auto weakThis2{ weak_from_this() };
_mediaEndedRevoker = _bellPlayer.MediaEnded(winrt::auto_revoke, [weakThis2](auto&&, auto&&) {
if (auto self{ weakThis2.lock() })
{
if (self->_bellPlayer)
{
// We need to make sure clear out the current track
// that's being played, again, so that the system can't
// come through and replay it. In testing, we needed to
// do this, closing the MediaPlayer alone wasn't good
// enough.
self->_bellPlayer.Pause();
self->_bellPlayer.Source(nullptr);
self->_bellPlayer.Close();
}
self->_mediaEndedRevoker.revoke();
self->_bellPlayer = nullptr;
}
});
}
}
}
@@ -1269,14 +1244,14 @@ void Pane::Shutdown()
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
_mediaEndedRevoker.revoke();
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
_bellPlayer = nullptr;
if (_IsLeaf())
{

View File

@@ -265,7 +265,7 @@ private:
bool _zoomed{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
winrt::Windows::Media::Playback::MediaPlayer::MediaEnded_revoker _mediaEndedRevoker;
bool _bellPlayerCreated{ false };
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;

View File

@@ -214,6 +214,9 @@
<data name="SplitPaneText" xml:space="preserve">
<value>Split Pane</value>
</data>
<data name="SearchWebText" xml:space="preserve">
<value>Web Search</value>
</data>
<data name="TabColorChoose" xml:space="preserve">
<value>Color...</value>
</data>
@@ -827,4 +830,10 @@
<value>Reset tab color</value>
<comment>Text used to identify the reset button</comment>
</data>
</root>
<data name="MoveTabToNewWindowText" xml:space="preserve">
<value>Move Tab to New Window</value>
</data>
<data name="MoveTabToNewWindowToolTip" xml:space="preserve">
<value>Moves tab to a new window </value>
</data>
</root>

View File

@@ -188,6 +188,18 @@ namespace winrt::TerminalApp::implementation
}
});
newTabImpl->MoveTabToNewWindowRequested([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
if (page && tab)
{
MoveTabArgs args{ hstring{ L"new" }, MoveTabDirection::Forward };
page->_SetFocusedTab(*tab);
page->_MoveTab(args);
}
});
newTabImpl->ExportTabRequested([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };

View File

@@ -238,12 +238,7 @@ namespace winrt::TerminalApp::implementation
page->_OpenNewTerminalViaDropdown(NewTerminalArgs());
}
});
_newTabButton.Drop([weakThis{ get_weak() }](const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e) {
if (auto page{ weakThis.get() })
{
page->NewTerminalByDrop(e);
}
});
_newTabButton.Drop({ get_weak(), &TerminalPage::_NewTerminalByDrop });
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
@@ -402,35 +397,46 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
winrt::fire_and_forget TerminalPage::_NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e)
try
{
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
try
const auto data = e.DataView();
if (!data.Contains(StandardDataFormats::StorageItems()))
{
items = co_await e.DataView().GetStorageItemsAsync();
co_return;
}
CATCH_LOG();
if (items.Size() == 1)
const auto weakThis = get_weak();
const auto items = co_await data.GetStorageItemsAsync();
const auto strongThis = weakThis.get();
if (!strongThis)
{
std::filesystem::path path(items.GetAt(0).Path().c_str());
co_return;
}
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
for (const auto& item : items)
{
auto directory = item.Path();
std::filesystem::path path(std::wstring_view{ directory });
if (!std::filesystem::is_directory(path))
{
path = path.parent_path();
directory = winrt::hstring{ path.parent_path().native() };
}
NewTerminalArgs args;
args.StartingDirectory(winrt::hstring{ path.wstring() });
this->_OpenNewTerminalViaDropdown(args);
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
args.StartingDirectory(directory);
_OpenNewTerminalViaDropdown(args);
}
}
CATCH_LOG()
// Method Description:
// - This method is called once command palette action was chosen for dispatching
@@ -551,27 +557,28 @@ namespace winrt::TerminalApp::implementation
// Handle it on a subsequent pass of the UI thread.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
// If the caller provided a CWD, switch to that directory, then switch
// If the caller provided a CWD, "switch" to that directory, then switch
// back once we're done. This looks weird though, because we have to set
// up the scope_exit _first_. We'll release the scope_exit if we don't
// actually need it.
auto originalCwd{ wil::GetCurrentDirectoryW<std::wstring>() };
auto restoreCwd = wil::scope_exit([&originalCwd]() {
auto originalVirtualCwd{ _WindowProperties.VirtualWorkingDirectory() };
auto restoreCwd = wil::scope_exit([&originalVirtualCwd, this]() {
// ignore errors, we'll just power on through. We'd rather do
// something rather than fail silently if the directory doesn't
// actually exist.
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(originalCwd.c_str()));
_WindowProperties.VirtualWorkingDirectory(originalVirtualCwd);
});
if (cwd.empty())
{
// We didn't actually need to change the virtual CWD, so we don't
// need to restore it
restoreCwd.release();
}
else
{
// ignore errors, we'll just power on through. We'd rather do
// something rather than fail silently if the directory doesn't
// actually exist.
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(cwd.c_str()));
_WindowProperties.VirtualWorkingDirectory(cwd);
}
if (auto page{ weakThis.get() })
@@ -851,7 +858,10 @@ namespace winrt::TerminalApp::implementation
});
// Necessary for fly-out sub items to get focus on a tab before collapsing. Related to #15049
newTabFlyout.Closing([this](auto&&, auto&&) {
_FocusCurrentTab(true);
if (!_commandPaletteIs(Visibility::Visible))
{
_FocusCurrentTab(true);
}
});
_newTabButton.Flyout(newTabFlyout);
}
@@ -1113,6 +1123,7 @@ namespace winrt::TerminalApp::implementation
if (profile)
{
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(profile.EvaluatedStartingDirectory()));
}
}
@@ -1158,6 +1169,11 @@ namespace winrt::TerminalApp::implementation
}
}
std::wstring TerminalPage::_evaluatePathForCwd(const std::wstring_view path)
{
return Utils::EvaluateStartingDirectory(_WindowProperties.VirtualWorkingDirectory(), path);
}
// Method Description:
// - Creates a new connection based on the profile settings
// Arguments:
@@ -1188,7 +1204,7 @@ namespace winrt::TerminalApp::implementation
connection = TerminalConnection::ConptyConnection{};
}
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
L".",
L"Azure",
nullptr,
@@ -1226,16 +1242,7 @@ namespace winrt::TerminalApp::implementation
// construction, because the connection might not spawn the child
// process until later, on another thread, after we've already
// restored the CWD to its original value.
auto newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() == 0 || newWorkingDirectory.size() == 1 &&
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
auto cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
}
auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) };
auto conhostConn = TerminalConnection::ConptyConnection();
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
@@ -1444,9 +1451,10 @@ namespace winrt::TerminalApp::implementation
return;
}
if (const auto p = CommandPaletteElement(); p && p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
if (_commandPaletteIs(Visibility::Visible) &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
CommandPaletteElement().Visibility(Visibility::Collapsed);
}
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
@@ -1778,6 +1786,11 @@ namespace winrt::TerminalApp::implementation
return _loadCommandPaletteSlowPath();
}
bool TerminalPage::_commandPaletteIs(WUX::Visibility visibility)
{
const auto p = CommandPaletteElement();
return p && p.Visibility() == visibility;
}
CommandPalette TerminalPage::_loadCommandPaletteSlowPath()
{
@@ -1789,7 +1802,7 @@ namespace winrt::TerminalApp::implementation
// When the visibility of the command palette changes to "collapsed",
// the palette has been closed. Toss focus back to the currently active control.
p.RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (CommandPaletteElement().Visibility() == Visibility::Collapsed)
if (_commandPaletteIs(Visibility::Collapsed))
{
_FocusActiveControl(nullptr, nullptr);
}
@@ -2077,6 +2090,13 @@ namespace winrt::TerminalApp::implementation
const auto windowId{ args.Window() };
if (!windowId.empty())
{
// if the windowId is the same as our name, do nothing
if (windowId == WindowProperties().WindowName() ||
windowId == winrt::to_hstring(WindowProperties().WindowId()))
{
return true;
}
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
auto startupActions = terminalTab->BuildStartupActions(true);
@@ -2756,9 +2776,8 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - sender (not used)
// - args: the arguments specifying how to set the display status to ShowWindow for our window handle
winrt::fire_and_forget TerminalPage::_ShowWindowChangedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
void TerminalPage::_ShowWindowChangedHandler(const IInspectable& /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
{
co_await resume_foreground(Dispatcher());
_ShowWindowChangedHandlers(*this, args);
}
@@ -4223,23 +4242,29 @@ namespace winrt::TerminalApp::implementation
const TerminalSettingsCreateResult& controlSettings,
const Profile& profile)
{
// Try to handle auto-elevation
const auto requestedElevation = controlSettings.DefaultSettings().Elevate();
const auto currentlyElevated = IsRunningElevated();
// We aren't elevated, but we want to be.
if (requestedElevation && !currentlyElevated)
// When duplicating a tab there aren't any newTerminalArgs.
if (!newTerminalArgs)
{
// Manually set the Profile of the NewTerminalArgs to the guid we've
// resolved to. If there was a profile in the NewTerminalArgs, this
// will be that profile's GUID. If there wasn't, then we'll use
// whatever the default profile's GUID is.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
_OpenElevatedWT(newTerminalArgs);
return true;
return false;
}
return false;
const auto defaultSettings = controlSettings.DefaultSettings();
// If we don't even want to elevate we can return early.
// If we're already elevated we can also return, because it doesn't get any more elevated than that.
if (!defaultSettings.Elevate() || IsRunningElevated())
{
return false;
}
// Manually set the Profile of the NewTerminalArgs to the guid we've
// resolved to. If there was a profile in the NewTerminalArgs, this
// will be that profile's GUID. If there wasn't, then we'll use
// whatever the default profile's GUID is.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(defaultSettings.StartingDirectory()));
_OpenElevatedWT(newTerminalArgs);
return true;
}
// Method Description:
@@ -4412,6 +4437,16 @@ namespace winrt::TerminalApp::implementation
til::color bgColor = backgroundSolidBrush.Color();
Media::Brush terminalBrush{ nullptr };
if (const auto& control{ _GetActiveControl() })
{
terminalBrush = control.BackgroundBrush();
}
else if (const auto& settingsTab{ _GetFocusedTab().try_as<TerminalApp::SettingsTab>() })
{
terminalBrush = settingsTab.Content().try_as<Settings::Editor::MainPage>().BackgroundBrush();
}
if (_settings.GlobalSettings().UseAcrylicInTabRow())
{
const auto acrylicBrush = Media::AcrylicBrush();
@@ -4426,18 +4461,6 @@ namespace winrt::TerminalApp::implementation
theme.TabRow().UnfocusedBackground()) :
ThemeColor{ nullptr } })
{
const auto terminalBrush = [this]() -> Media::Brush {
if (const auto& control{ _GetActiveControl() })
{
return control.BackgroundBrush();
}
else if (auto settingsTab = _GetFocusedTab().try_as<TerminalApp::SettingsTab>())
{
return settingsTab.Content().try_as<Settings::Editor::MainPage>().BackgroundBrush();
}
return nullptr;
}();
const auto themeBrush{ tabRowBg.Evaluate(res, terminalBrush, true) };
bgColor = ThemeColor::ColorFromBrush(themeBrush);
TitlebarBrush(themeBrush);
@@ -4467,11 +4490,27 @@ namespace winrt::TerminalApp::implementation
tabImpl->ThemeColor(tabBackground, tabUnfocusedBackground, bgColor);
}
}
// Update the new tab button to have better contrast with the new color.
// In theory, it would be convenient to also change these for the
// inactive tabs as well, but we're leaving that as a follow up.
_SetNewTabButtonColor(bgColor, bgColor);
// Third: the window frame. This is basically the same logic as the tab row background.
// We'll set our `FrameBrush` property, for the window to later use.
const auto windowTheme{ theme.Window() };
if (auto windowFrame{ windowTheme ? (_activated ? windowTheme.Frame() :
windowTheme.UnfocusedFrame()) :
ThemeColor{ nullptr } })
{
const auto themeBrush{ windowFrame.Evaluate(res, terminalBrush, true) };
FrameBrush(themeBrush);
}
else
{
// Nothing was set in the theme - fall back to null. The window will
// use that as an indication to use the default window frame.
FrameBrush(nullptr);
}
}
// Function Description:
@@ -4542,7 +4581,7 @@ namespace winrt::TerminalApp::implementation
}
void TerminalPage::_PopulateContextMenu(const IInspectable& sender,
const bool /*withSelection*/)
const bool withSelection)
{
// withSelection can be used to add actions that only appear if there's
// selected text, like "search the web". In this initial draft, it's not
@@ -4599,6 +4638,11 @@ namespace winrt::TerminalApp::implementation
makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
}
if (withSelection)
{
makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr });
}
makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
}

View File

@@ -106,8 +106,6 @@ namespace winrt::TerminalApp::implementation
void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
hstring Title();
void TitlebarClicked();
@@ -195,6 +193,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, _PropertyChangedHandlers, nullptr);
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
@@ -275,7 +274,11 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
winrt::fire_and_forget _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e);
__declspec(noinline) CommandPalette _loadCommandPaletteSlowPath();
bool _commandPaletteIs(winrt::Windows::UI::Xaml::Visibility visibility);
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
void _ShowAboutDialog();
@@ -293,6 +296,9 @@ namespace winrt::TerminalApp::implementation
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
std::wstring _evaluatePathForCwd(std::wstring_view path);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings, const bool inheritCursor);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(std::shared_ptr<Pane> pane);
void _restartPaneConnection(const std::shared_ptr<Pane>& pane);
@@ -505,7 +511,7 @@ namespace winrt::TerminalApp::implementation
void _updateAllTabCloseButtons(const winrt::TerminalApp::TabBase& focusedTab);
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
void _ShowWindowChangedHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);

View File

@@ -51,6 +51,8 @@ namespace TerminalApp
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
String VirtualWorkingDirectory { get; set; };
Boolean IsQuakeWindow();
};

View File

@@ -950,7 +950,7 @@ namespace winrt::TerminalApp::implementation
}
});
events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto&& sender, auto&&) -> winrt::fire_and_forget {
events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (const auto tab{ weakThis.get() })
{
@@ -1336,6 +1336,28 @@ namespace winrt::TerminalApp::implementation
Automation::AutomationProperties::SetHelpText(splitTabMenuItem, splitTabToolTip);
}
Controls::MenuFlyoutItem moveTabToNewWindowMenuItem;
{
// "Move Tab to New Window Tab"
Controls::FontIcon moveTabToNewWindowTabSymbol;
moveTabToNewWindowTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
moveTabToNewWindowTabSymbol.Glyph(L"\xE8A7");
moveTabToNewWindowMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_MoveTabToNewWindowRequestedHandlers();
}
});
moveTabToNewWindowMenuItem.Text(RS_(L"MoveTabToNewWindowText"));
moveTabToNewWindowMenuItem.Icon(moveTabToNewWindowTabSymbol);
const auto moveTabToNewWindowToolTip = RS_(L"MoveTabToNewWindowToolTip");
WUX::Controls::ToolTipService::SetToolTip(moveTabToNewWindowMenuItem, box_value(moveTabToNewWindowToolTip));
Automation::AutomationProperties::SetHelpText(moveTabToNewWindowMenuItem, moveTabToNewWindowToolTip);
}
Controls::MenuFlyoutItem closePaneMenuItem = _closePaneMenuItem;
{
// "Close Pane"
@@ -1404,7 +1426,7 @@ namespace winrt::TerminalApp::implementation
contextMenuFlyout.Items().Append(renameTabMenuItem);
contextMenuFlyout.Items().Append(duplicateTabMenuItem);
contextMenuFlyout.Items().Append(splitTabMenuItem);
contextMenuFlyout.Items().Append(moveTabToNewWindowMenuItem);
contextMenuFlyout.Items().Append(exportTabMenuItem);
contextMenuFlyout.Items().Append(findMenuItem);
contextMenuFlyout.Items().Append(menuSeparator);

View File

@@ -100,6 +100,7 @@ namespace winrt::TerminalApp::implementation
WINRT_CALLBACK(TabRaiseVisualBell, winrt::delegate<>);
WINRT_CALLBACK(DuplicateRequested, winrt::delegate<>);
WINRT_CALLBACK(SplitTabRequested, winrt::delegate<>);
WINRT_CALLBACK(MoveTabToNewWindowRequested, winrt::delegate<>);
WINRT_CALLBACK(FindRequested, winrt::delegate<>);
WINRT_CALLBACK(ExportTabRequested, winrt::delegate<>);
WINRT_CALLBACK(ColorPickerRequested, winrt::delegate<>);

View File

@@ -944,12 +944,13 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::Media::Brush TerminalWindow::TitlebarBrush()
{
if (_root)
{
return _root->TitlebarBrush();
}
return { nullptr };
return _root ? _root->TitlebarBrush() : nullptr;
}
winrt::Windows::UI::Xaml::Media::Brush TerminalWindow::FrameBrush()
{
return _root ? _root->FrameBrush() : nullptr;
}
void TerminalWindow::WindowActivated(const bool activated)
{
if (_root)
@@ -1019,11 +1020,14 @@ namespace winrt::TerminalApp::implementation
// returned.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// - cwd: The CWD that this window should treat as its own "virtual" CWD
// Return Value:
// - the result of the first command who's parsing returned a non-zero code,
// or 0. (see TerminalWindow::_ParseArgs)
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args)
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args, winrt::hstring cwd)
{
_WindowProperties->SetInitialCwd(std::move(cwd));
// This is called in AppHost::ctor(), before we've created the window
// (or called TerminalWindow::Initialize)
const auto result = _appArgs.ParseArgs(args);
@@ -1345,6 +1349,7 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
}
}
uint64_t WindowProperties::WindowId() const noexcept
{
return _WindowId;

View File

@@ -48,8 +48,14 @@ namespace winrt::TerminalApp::implementation
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, VirtualWorkingDirectory, _PropertyChangedHandlers, L"");
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
public:
// Used for setting the initial CWD, before we have XAML set up for property change notifications.
void SetInitialCwd(winrt::hstring cwd) { _VirtualWorkingDirectory = std::move(cwd); };
private:
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
@@ -71,7 +77,7 @@ namespace winrt::TerminalApp::implementation
bool HasCommandlineArguments() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions, winrt::hstring cwd);
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
@@ -122,6 +128,7 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TaskbarState TaskbarState();
winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush();
winrt::Windows::UI::Xaml::Media::Brush FrameBrush();
void WindowActivated(const bool activated);
bool GetMinimizeToNotificationArea();

View File

@@ -53,7 +53,7 @@ namespace TerminalApp
Boolean HasCommandlineArguments();
Int32 SetStartupCommandline(String[] commands);
Int32 SetStartupCommandline(String[] commands, String cwd);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
Int32 ExecuteCommandline(String[] commands, String cwd);
String ParseCommandlineMessage { get; };
@@ -95,6 +95,7 @@ namespace TerminalApp
TaskbarState TaskbarState{ get; };
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
Windows.UI.Xaml.Media.Brush FrameBrush { get; };
void WindowActivated(Boolean activated);
String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position);

View File

@@ -325,7 +325,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// handoff from an already-started PTY process.
if (!_inPipe)
{
DWORD flags = PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE;
DWORD flags = PSEUDOCONSOLE_RESIZE_QUIRK;
// If we're using an existing buffer, we want the new connection
// to reuse the existing cursor. When not setting this flag, the

View File

@@ -82,9 +82,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
{
_settings = winrt::make_self<implementation::ControlSettings>(settings, unfocusedAppearance);
_terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>();
_setupDispatcherAndCallbacks();
Connection(connection);
_terminal->SetWriteInputCallback([this](std::wstring_view wstr) {
@@ -137,17 +138,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); });
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
if (auto strongThis{ weakThis.get() })
{
strongThis->_RendererEnteredErrorStateHandlers(*strongThis, nullptr);
}
});
_renderer->SetRendererEnteredErrorStateCallback([this]() { _RendererEnteredErrorStateHandlers(nullptr, nullptr); });
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
}
_setupDispatcherAndCallbacks();
UpdateSettings(settings, unfocusedAppearance);
}
@@ -179,7 +173,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// need to hop across the process boundary every time text is output.
// We can throttle this to once every 8ms, which will get us out of
// the way of the main output & rendering threads.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
const auto shared = _shared.lock();
shared->tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
TsfRedrawInterval,
[weakThis = get_weak()]() {
@@ -191,7 +186,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// NOTE: Calling UpdatePatternLocations from a background
// thread is a workaround for us to hit GH#12607 less often.
_updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
shared->updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
UpdatePatternLocationsInterval,
[weakTerminal = std::weak_ptr{ _terminal }]() {
if (const auto t = weakTerminal.lock())
@@ -201,7 +196,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
shared->updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
_dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
@@ -231,9 +226,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Clear out any throttled funcs that we had wired up to run on this UI
// thread. These will be recreated in _setupDispatcherAndCallbacks, when
// we're re-attached to a new control (on a possibly new UI thread).
_tsfTryRedrawCanvas.reset();
_updatePatternLocations.reset();
_updateScrollBar.reset();
const auto shared = _shared.lock();
shared->tsfTryRedrawCanvas.reset();
shared->updatePatternLocations.reset();
shared->updateScrollBar.reset();
}
void ControlCore::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
@@ -318,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ // scope for terminalLock
auto terminalLock = _terminal->LockForWriting();
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
return false;
}
@@ -403,7 +399,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
THROW_IF_FAILED(_renderEngine->Enable());
_initializedTerminal = true;
_initializedTerminal.store(true, std::memory_order_relaxed);
} // scope for TerminalLock
// Start the connection outside of lock, because it could
@@ -423,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void ControlCore::EnablePainting()
{
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
_renderer->EnablePainting();
}
@@ -656,9 +652,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// itself - it was initiated by the mouse wheel, or the scrollbar.
_terminal->UserScrollViewport(viewTop);
if (_updatePatternLocations)
const auto shared = _shared.lock_shared();
if (shared->updatePatternLocations)
{
(*_updatePatternLocations)();
(*shared->updatePatternLocations)();
}
}
@@ -825,7 +822,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(*_settings);
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
// If we haven't initialized, there's no point in continuing.
// Initialization will handle the renderer settings.
@@ -1434,10 +1431,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const int viewHeight,
const int bufferSize)
{
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
return;
}
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
// We're **NOT** taking the lock here unlike _scrollbarChangeHandler because
// we are already under lock (since this usually happens as a result of writing).
@@ -1448,20 +1446,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests && _updateScrollBar)
{
_updateScrollBar->Run(update);
}
else
if (_inUnitTests) [[unlikely]]
{
_ScrollPositionChangedHandlers(*this, update);
}
// Additionally, start the throttled update of where our links are.
if (_updatePatternLocations)
else
{
(*_updatePatternLocations)();
const auto shared = _shared.lock_shared();
if (shared->updateScrollBar)
{
shared->updateScrollBar->Run(update);
}
}
}
@@ -1469,9 +1465,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
if (_tsfTryRedrawCanvas)
const auto shared = _shared.lock_shared();
if (shared->tsfTryRedrawCanvas)
{
_tsfTryRedrawCanvas->Run();
shared->tsfTryRedrawCanvas->Run();
}
}
@@ -1482,11 +1479,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::_terminalShowWindowChanged(bool showOrHide)
{
if (_initializedTerminal)
{
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
// Method Description:
@@ -1617,6 +1611,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto weakThis{ get_weak() };
// Concurrent read of _dispatcher is safe, because Detach() calls WaitForPaintCompletionAndDisable()
// which blocks until this call returns. _dispatcher will only be changed afterwards.
co_await wil::resume_foreground(_dispatcher);
if (auto core{ weakThis.get() })
@@ -1686,7 +1682,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Core::Point ControlCore::CursorPosition() const
{
// If we haven't been initialized yet, then fake it.
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
return { 0, 0 };
}
@@ -1805,9 +1801,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->Write(hstr);
// Start the throttled update of where our hyperlinks are.
if (_updatePatternLocations)
const auto shared = _shared.lock_shared();
if (shared->updatePatternLocations)
{
(*_updatePatternLocations)();
(*shared->updatePatternLocations)();
}
}
catch (...)
@@ -2016,7 +2013,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void ControlCore::WindowVisibilityChanged(const bool showOrHide)
{
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
// show is true, hide is false
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })

View File

@@ -145,6 +145,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
int ViewHeight() const;
int BufferHeight() const;
bool HasSelection() const;
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
bool BracketedPasteEnabled() const noexcept;
Windows::Foundation::Collections::IVector<Control::ScrollMark> ScrollMarks() const;
@@ -188,9 +191,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const;
Core::Point CursorPosition() const;
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);
@@ -266,7 +267,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// clang-format on
private:
bool _initializedTerminal{ false };
struct SharedState
{
std::shared_ptr<ThrottledFuncTrailing<>> tsfTryRedrawCanvas;
std::unique_ptr<til::throttled_func_trailing<>> updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
};
std::atomic<bool> _initializedTerminal{ false };
bool _closing{ false };
TerminalConnection::ITerminalConnection _connection{ nullptr };
@@ -313,9 +321,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
uint64_t _owningHwnd{ 0 };
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
til::shared_mutex<SharedState> _shared;
til::point _contextMenuBufferPosition{ 0, 0 };

View File

@@ -124,8 +124,6 @@ namespace Microsoft.Terminal.Control
void Search(String text, Boolean goForward, Boolean caseSensitive);
Microsoft.Terminal.Core.Color BackgroundColor { get; };
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
SelectionData SelectionInfo { get; };
SelectionInteractionMode SelectionMode();
@@ -149,26 +147,28 @@ namespace Microsoft.Terminal.Control
Boolean ShouldShowSelectCommand();
Boolean ShouldShowSelectOutput();
event FontSizeChangedEventArgs FontSizeChanged;
// These events are called from some background thread
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
// These events are always called from the UI thread (bugs aside)
event FontSizeChangedEventArgs FontSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
event Windows.Foundation.TypedEventHandler<Object, RendererWarningArgs> RendererWarning;
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
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;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;

View File

@@ -180,3 +180,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(bool, ClearMarkers, false);
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(OpenHyperlinkEventArgs);
}

View File

@@ -40,6 +40,7 @@ namespace Microsoft.Terminal.Control
runtimeclass OpenHyperlinkEventArgs
{
OpenHyperlinkEventArgs(String uri);
String Uri { get; };
}

View File

@@ -45,6 +45,9 @@ namespace Microsoft.Terminal.Control
Int32 ViewHeight { get; };
Int32 BufferHeight { get; };
Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
Boolean BracketedPasteEnabled { get; };
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };

View File

@@ -178,6 +178,10 @@
<data name="HowToOpenRun.Text" xml:space="preserve">
<value>Ctrl+Click to follow link</value>
</data>
<data name="InvalidUri" xml:space="preserve">
<value>Invalid URI</value>
<comment>Whenever we encounter an invalid URI or URL we show this string as a warning.</comment>
</data>
<data name="NoticeFontNotFound" xml:space="preserve">
<value>Unable to find the selected font "{0}".

View File

@@ -997,7 +997,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// _cursorTimer doesn't exist, and it would never turn on the
// cursor. To mitigate, we'll initialize the cursor's 'on' state
// with `_focused` here.
_core.CursorOn(_focused);
_core.CursorOn(_focused || DisplayCursorWhileBlurred);
if (DisplayCursorWhileBlurred)
{
_cursorTimer->Start();
}
}
else
{
@@ -1873,7 +1877,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TSFInputControl().NotifyFocusLeave();
}
if (_cursorTimer)
if (_cursorTimer && !DisplayCursorWhileBlurred)
{
_cursorTimer->Stop();
_core.CursorOn(false);
@@ -3038,56 +3042,85 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ClearHoveredCell();
}
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable /*sender*/,
IInspectable /*args*/)
// Attackers abuse Unicode characters that happen to look similar to ASCII characters. Cyrillic for instance has
// its own glyphs for а, с, е, о, р, х, and у that look practically identical to their ASCII counterparts.
// This is called an "IDN homoglyph attack".
//
// But outright showing Punycode URIs only is similarly flawed as they can end up looking similar to valid ASCII URIs.
// xn--cnn.com for instance looks confusingly similar to cnn.com, but actually represents U+407E.
//
// An optimal solution would detect any URI that contains homoglyphs and show them in their Punycode form.
// Such a detector however is not quite trivial and requires constant maintenance, which this project's
// maintainers aren't currently well equipped to handle. As such we do the next best thing and show the
// Punycode encoding side-by-side with the Unicode string for any IDN.
static winrt::hstring sanitizeURI(winrt::hstring uri)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto self{ weakThis.get() })
if (uri.empty())
{
auto lastHoveredCell = _core.HoveredCell();
if (lastHoveredCell)
{
winrt::hstring uriText = _core.HoveredUriText();
if (uriText.empty())
{
co_return;
}
try
{
// DisplayUri will filter out non-printable characters and confusables.
Windows::Foundation::Uri parsedUri{ uriText };
if (!parsedUri)
{
co_return;
}
uriText = parsedUri.DisplayUri();
const auto panel = SwapChainPanel();
const auto scale = panel.CompositionScaleX();
const auto offset = panel.ActualOffset();
// Update the tooltip with the URI
HoveredUri().Text(uriText);
// Set the border thickness so it covers the entire cell
const auto charSizeInPixels = CharacterDimensions();
const auto htInDips = charSizeInPixels.Height / scale;
const auto wtInDips = charSizeInPixels.Width / scale;
const Thickness newThickness{ wtInDips, htInDips, 0, 0 };
HyperlinkTooltipBorder().BorderThickness(newThickness);
// Compute the location of the top left corner of the cell in DIPS
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);
OverlayCanvas().SetTop(HyperlinkTooltipBorder(), locationInDIPs.y - offset.y);
}
CATCH_LOG();
}
return uri;
}
wchar_t punycodeBuffer[256];
wchar_t unicodeBuffer[256];
// These functions return int, but are documented to only return positive numbers.
// Better make sure though. It allows us to pass punycodeLength right into IdnToUnicode.
const auto punycodeLength = std::max(0, IdnToAscii(0, uri.data(), gsl::narrow<int>(uri.size()), &punycodeBuffer[0], 256));
const auto unicodeLength = std::max(0, IdnToUnicode(0, &punycodeBuffer[0], punycodeLength, &unicodeBuffer[0], 256));
if (punycodeLength <= 0 || unicodeLength <= 0)
{
return RS_(L"InvalidUri");
}
const std::wstring_view punycode{ &punycodeBuffer[0], gsl::narrow_cast<size_t>(punycodeLength) };
const std::wstring_view unicode{ &unicodeBuffer[0], gsl::narrow_cast<size_t>(unicodeLength) };
// IdnToAscii/IdnToUnicode return the input string as is if it's all
// plain ASCII. But we don't know if the input URI is Punycode or not.
// --> It's non-Punycode and ASCII if it round-trips.
if (uri == punycode && uri == unicode)
{
return uri;
}
return winrt::hstring{ fmt::format(FMT_COMPILE(L"{}\n({})"), punycode, unicode) };
}
void TermControl::_hoveredHyperlinkChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/)
{
const auto lastHoveredCell = _core.HoveredCell();
if (!lastHoveredCell)
{
return;
}
const auto uriText = sanitizeURI(_core.HoveredUriText());
if (uriText.empty())
{
return;
}
const auto panel = SwapChainPanel();
const auto scale = panel.CompositionScaleX();
const auto offset = panel.ActualOffset();
// Update the tooltip with the URI
HoveredUri().Text(uriText);
// Set the border thickness so it covers the entire cell
const auto charSizeInPixels = CharacterDimensions();
const auto htInDips = charSizeInPixels.Height / scale;
const auto wtInDips = charSizeInPixels.Width / scale;
const Thickness newThickness{ wtInDips, htInDips, 0, 0 };
HyperlinkTooltipBorder().BorderThickness(newThickness);
// Compute the location of the top left corner of the cell in DIPS
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);
OverlayCanvas().SetTop(HyperlinkTooltipBorder(), locationInDIPs.y - offset.y);
}
winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args)
@@ -3302,6 +3335,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.Opacity();
}
bool TermControl::HasSelection() const
{
return _core.HasSelection();
}
Windows::Foundation::Collections::IVector<winrt::hstring> TermControl::SelectedText(bool trimTrailingWhitespace) const
{
return _core.SelectedText(trimTrailingWhitespace);
}
// Method Description:
// - Called when the core raises a FoundMatch event. That's done in response
// to us starting a search query with ControlCore::Search.

View File

@@ -69,6 +69,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
int ViewHeight() const;
int BufferHeight() const;
bool HasSelection() const;
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
bool BracketedPasteEnabled() const noexcept;
double BackgroundOpacity() const;
@@ -182,6 +185,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
public:
til::property<bool> DisplayCursorWhileBlurred{ false };
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
@@ -335,7 +341,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
winrt::fire_and_forget _hoveredHyperlinkChanged(IInspectable sender, IInspectable args);
void _hoveredHyperlinkChanged(const IInspectable& sender, const IInspectable& args);
winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args);
void _coreFontSizeChanged(const int fontWidth,

View File

@@ -111,6 +111,8 @@ namespace Microsoft.Terminal.Control
// opacity set by the settings should call this instead.
Double BackgroundOpacity { get; };
Boolean DisplayCursorWhileBlurred;
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);

View File

@@ -235,7 +235,7 @@ void Terminal::EraseScrollback()
bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept
{
return _bracketedPasteMode;
return _systemMode.test(Mode::BracketedPaste);
}
std::wstring_view Terminal::GetWorkingDirectory() noexcept

View File

@@ -111,23 +111,20 @@ public:
til::rect GetViewport() const noexcept override;
void SetViewportPosition(const til::point position) noexcept override;
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
void SetAutoWrapMode(const bool wrapAtEOL) noexcept override;
bool GetAutoWrapMode() const noexcept override;
void SetSystemMode(const Mode mode, const bool enabled) noexcept override;
bool GetSystemMode(const Mode mode) const noexcept override;
void WarningBell() override;
bool GetLineFeedMode() const noexcept override;
void SetWindowTitle(const std::wstring_view title) override;
CursorType GetUserDefaultCursorStyle() const noexcept override;
bool ResizeWindow(const til::CoordType width, const til::CoordType height) noexcept override;
void SetConsoleOutputCP(const unsigned int codepage) noexcept override;
unsigned int GetConsoleOutputCP() const noexcept override;
void SetBracketedPasteMode(const bool enabled) noexcept override;
bool GetBracketedPasteMode() const noexcept override;
void CopyToClipboard(std::wstring_view content) override;
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
void SetWorkingDirectory(std::wstring_view uri) override;
void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override;
void ShowWindow(bool showOrHide) override;
void UseAlternateScreenBuffer() override;
void UseAlternateScreenBuffer(const TextAttribute& attrs) override;
void UseMainScreenBuffer() override;
void MarkPrompt(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) override;
@@ -183,7 +180,7 @@ public:
ULONG GetCursorHeight() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
bool IsCursorDoubleWidth() const noexcept override;
bool IsCursorDoubleWidth() const override;
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override;
const std::wstring GetHyperlinkUri(uint16_t id) const override;
@@ -320,10 +317,11 @@ private:
CursorType _defaultCursorShape = CursorType::Legacy;
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
bool _snapOnInput = true;
bool _altGrAliasing = true;
bool _suppressApplicationTitle = false;
bool _bracketedPasteMode = false;
bool _trimBlockSelection = false;
bool _autoMarkPrompts = false;

View File

@@ -61,15 +61,14 @@ void Terminal::SetTextAttributes(const TextAttribute& attrs) noexcept
_activeBuffer().SetCurrentAttributes(attrs);
}
void Terminal::SetAutoWrapMode(const bool /*wrapAtEOL*/) noexcept
void Terminal::SetSystemMode(const Mode mode, const bool enabled) noexcept
{
// TODO: This will be needed to support DECAWM.
_systemMode.set(mode, enabled);
}
bool Terminal::GetAutoWrapMode() const noexcept
bool Terminal::GetSystemMode(const Mode mode) const noexcept
{
// TODO: This will be needed to support DECAWM.
return true;
return _systemMode.test(mode);
}
void Terminal::WarningBell()
@@ -77,12 +76,6 @@ void Terminal::WarningBell()
_pfnWarningBell();
}
bool Terminal::GetLineFeedMode() const noexcept
{
// TODO: This will be needed to support LNM.
return false;
}
void Terminal::SetWindowTitle(const std::wstring_view title)
{
if (!_suppressApplicationTitle)
@@ -114,16 +107,6 @@ unsigned int Terminal::GetConsoleOutputCP() const noexcept
return CP_UTF8;
}
void Terminal::SetBracketedPasteMode(const bool enabled) noexcept
{
_bracketedPasteMode = enabled;
}
bool Terminal::GetBracketedPasteMode() const noexcept
{
return _bracketedPasteMode;
}
void Terminal::CopyToClipboard(std::wstring_view content)
{
_pfnCopyToClipboard(content);
@@ -202,7 +185,7 @@ void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std:
_pfnPlayMidiNote(noteNumber, velocity, duration);
}
void Terminal::UseAlternateScreenBuffer()
void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs)
{
// the new alt buffer is exactly the size of the viewport.
_altBufferSize = _mutableViewport.Dimensions();
@@ -214,7 +197,7 @@ void Terminal::UseAlternateScreenBuffer()
// Create a new alt buffer
_altBuffer = std::make_unique<TextBuffer>(_altBufferSize,
TextAttribute{},
attrs,
cursorSize,
true,
_mainBuffer->GetRenderer());

View File

@@ -70,7 +70,7 @@ CursorType Terminal::GetCursorStyle() const noexcept
return _activeBuffer().GetCursor().GetType();
}
bool Terminal::IsCursorDoubleWidth() const noexcept
bool Terminal::IsCursorDoubleWidth() const
{
const auto& buffer = _activeBuffer();
const auto position = buffer.GetCursor().GetPosition();

View File

@@ -20,6 +20,28 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
bool Font::HasPowerlineCharacters()
{
if (!_hasPowerlineCharacters.has_value())
{
try
{
winrt::com_ptr<IDWriteFont> font;
THROW_IF_FAILED(_family->GetFont(0, font.put()));
BOOL exists{};
// We're actually checking for the "Extended" PowerLine glyph set.
// They're more fun.
THROW_IF_FAILED(font->HasCharacter(0xE0B6, &exists));
_hasPowerlineCharacters = (exists == TRUE);
}
catch (...)
{
_hasPowerlineCharacters = false;
}
}
return _hasPowerlineCharacters.value_or(false);
}
AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) :
_appearance{ appearance }
{
@@ -288,25 +310,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
IInspectable Appearances::CurrentFontFace() const
{
// look for the current font in our shown list of fonts
const auto& appearanceVM{ Appearance() };
const auto appearanceFontFace{ appearanceVM.FontFace() };
const auto& currentFontList{ ShowAllFonts() ? ProfileViewModel::CompleteFontList() : ProfileViewModel::MonospaceFontList() };
IInspectable fallbackFont;
for (const auto& font : currentFontList)
{
if (font.LocalizedName() == appearanceFontFace)
{
return box_value(font);
}
else if (font.LocalizedName() == L"Cascadia Mono")
{
fallbackFont = box_value(font);
}
}
// we couldn't find the desired font, set to "Cascadia Mono" since that ships by default
return fallbackFont;
return box_value(ProfileViewModel::FindFontWithLocalizedName(appearanceFontFace));
}
void Appearances::FontFace_SelectionChanged(const IInspectable& /*sender*/, const SelectionChangedEventArgs& e)

View File

@@ -36,14 +36,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct Font : FontT<Font>
{
public:
Font(std::wstring name, std::wstring localizedName) :
Font(std::wstring name, std::wstring localizedName, IDWriteFontFamily* family) :
_Name{ name },
_LocalizedName{ localizedName } {};
_LocalizedName{ localizedName }
{
_family.copy_from(family);
}
hstring ToString() { return _LocalizedName; }
bool HasPowerlineCharacters();
WINRT_PROPERTY(hstring, Name);
WINRT_PROPERTY(hstring, LocalizedName);
private:
winrt::com_ptr<IDWriteFontFamily> _family;
std::optional<bool> _hasPowerlineCharacters;
};
struct AppearanceViewModel : AppearanceViewModelT<AppearanceViewModel>, ViewModelHelper<AppearanceViewModel>

View File

@@ -19,6 +19,7 @@ namespace Microsoft.Terminal.Settings.Editor
{
String Name { get; };
String LocalizedName { get; };
Boolean HasPowerlineCharacters { get; };
}
runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged

View File

@@ -50,6 +50,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _Name;
}
// This is used in the ComboBox and ListView.
// It's the only way to expose the name of the inner UI item so the ComboBox can do quick search
// and screen readers can read the item out loud.
winrt::hstring ColorSchemeViewModel::ToString()
{
if (IsDefaultScheme())
{
return hstring{ fmt::format(L"{0} ({1})", Name(), RS_(L"ColorScheme_DefaultTag/Text")) };
}
return Name();
}
bool ColorSchemeViewModel::IsDefaultScheme()
{
const auto defaultAppearance = _settings.ProfileDefaults().DefaultAppearance();

View File

@@ -25,10 +25,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::hstring Name();
void Name(winrt::hstring newName);
hstring ToString()
{
return Name();
}
hstring ToString();
bool RequestRename(winrt::hstring newName);

View File

@@ -20,7 +20,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AddNew_Click(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void ListView_PreviewKeyDown(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void ListView_SelectionChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e);
WINRT_PROPERTY(Model::ColorScheme, CurrentColorScheme, nullptr);
WINRT_OBSERVABLE_PROPERTY(Editor::ColorSchemesPageViewModel, ViewModel, _PropertyChangedHandlers, nullptr);

View File

@@ -210,6 +210,7 @@
Visibility="{x:Bind IsDefaultScheme, Mode=OneWay}">
<TextBlock x:Uid="ColorScheme_DefaultTag"
Grid.Column="1"
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Border>
</Grid>

View File

@@ -8,7 +8,22 @@
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;
static constexpr std::wstring_view PreviewText{ L"Windows Terminal\r\nCopyright (c) Microsoft Corporation\r\n\nC:\\Windows\\Terminal> " };
static constexpr std::wstring_view PromptTextPlain{ L"C:\\> " };
static constexpr std::wstring_view PromptTextPowerline{ L"\x1b[49;34m\xe0b6\x1b[1;97;44m C:\\ \x1b[m\x1b[46;34m\xe0b8\x1b[49;36m\xe0b8\x1b[m " };
// clang-format off
static constexpr std::wstring_view PreviewText{
L"\x001b"
L"c" // Hard Reset (RIS); on separate lines to avoid becoming 0x01BC
L"Windows Terminal\r\n"
L"{0}\x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
L"\x1b[1m" L"diff --git a/win b/win\x1b[m\r\n"
L"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
L"\x1b[31m- Windows Console\x1b[m\r\n"
L"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
L"{0}\x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
};
// clang-format on
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@@ -16,10 +31,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Start() noexcept
{
// First send a sequence to disable cursor blinking
_TerminalOutputHandlers(L"\x1b[?12l");
// Send the preview text
_TerminalOutputHandlers(PreviewText);
_TerminalOutputHandlers(fmt::format(PreviewText, _displayPowerlineGlyphs ? PromptTextPowerline : PromptTextPlain));
}
void PreviewConnection::Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) noexcept
@@ -37,4 +50,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Close() noexcept
{
}
void PreviewConnection::DisplayPowerlineGlyphs(bool d) noexcept
{
if (_displayPowerlineGlyphs != d)
{
_displayPowerlineGlyphs = d;
Start();
}
}
}

View File

@@ -27,9 +27,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Resize(uint32_t rows, uint32_t columns) noexcept;
void Close() noexcept;
void DisplayPowerlineGlyphs(bool d) noexcept;
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, IInspectable);
private:
bool _displayPowerlineGlyphs{ false };
};
}

View File

@@ -21,6 +21,8 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family);
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_FontList{ nullptr };
@@ -118,12 +120,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
com_ptr<IDWriteFontFamily> fontFamily;
THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put()));
// get the font's localized names
com_ptr<IDWriteLocalizedStrings> localizedFamilyNames;
THROW_IF_FAILED(fontFamily->GetFamilyNames(localizedFamilyNames.put()));
// construct a font entry for tracking
if (const auto fontEntry{ _GetFont(localizedFamilyNames) })
if (const auto fontEntry{ _FontObjectForDWriteFont(fontFamily.get()) })
{
// check if the font is monospaced
try
@@ -159,7 +157,32 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
CATCH_LOG();
Editor::Font ProfileViewModel::_GetFont(com_ptr<IDWriteLocalizedStrings> localizedFamilyNames)
Editor::Font ProfileViewModel::FindFontWithLocalizedName(const winrt::hstring& name) noexcept
{
// look for the current font in our shown list of fonts
Editor::Font fallbackFont{ nullptr };
try
{
const auto& currentFontList{ CompleteFontList() };
for (const auto& font : currentFontList)
{
if (font.LocalizedName() == name)
{
return font;
}
else if (font.LocalizedName() == L"Cascadia Mono")
{
fallbackFont = font;
}
}
}
CATCH_LOG();
// we couldn't find the desired font, set to "Cascadia Mono" if we found that since it ships by default
return fallbackFont;
}
static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family)
{
// used for the font's name as an identifier (i.e. text block's font family property)
std::wstring nameID;
@@ -169,6 +192,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
std::wstring localizedName;
UINT32 localizedNameIndex;
// get the font's localized names
winrt::com_ptr<IDWriteLocalizedStrings> localizedFamilyNames;
THROW_IF_FAILED(family->GetFamilyNames(localizedFamilyNames.put()));
// use our current locale to find the localized name
auto exists{ FALSE };
HRESULT hr;
@@ -211,7 +238,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (!nameID.empty() && !localizedName.empty())
{
return make<Font>(nameID, localizedName);
return make<Font>(nameID, localizedName, family);
}
return nullptr;
}

View File

@@ -34,6 +34,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
static void UpdateFontList() noexcept;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> CompleteFontList() noexcept { return _FontList; };
static Windows::Foundation::Collections::IObservableVector<Editor::Font> MonospaceFontList() noexcept { return _MonospaceFontList; };
static Editor::Font FindFontWithLocalizedName(winrt::hstring const& name) noexcept;
ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings);
Model::TerminalSettings TermSettings() const;
@@ -123,8 +124,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
static Editor::Font _GetFont(com_ptr<IDWriteLocalizedStrings> localizedFamilyNames);
Model::CascadiaSettings _appSettings;
Editor::AppearanceViewModel _unfocusedAppearanceViewModel;
};

View File

@@ -36,6 +36,7 @@ namespace Microsoft.Terminal.Settings.Editor
{
static Windows.Foundation.Collections.IObservableVector<Font> CompleteFontList { get; };
static Windows.Foundation.Collections.IObservableVector<Font> MonospaceFontList { get; };
static Font FindFontWithLocalizedName(String name);
Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; };

View File

@@ -16,14 +16,10 @@ using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Profiles_Appearance::Profiles_Appearance() :
_previewControl{ Control::TermControl(Model::TerminalSettings{}, nullptr, make<PreviewConnection>()) }
Profiles_Appearance::Profiles_Appearance()
{
InitializeComponent();
_previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
ControlPreview().Child(_previewControl);
_previewConnection = winrt::make_self<PreviewConnection>();
}
void Profiles_Appearance::OnNavigatedTo(const NavigationEventArgs& e)
@@ -38,25 +34,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
ProfileViewModel::UpdateFontList();
}
if (!_previewControl)
{
const auto settings = _Profile.TermSettings();
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
_previewControl = Control::TermControl(settings, settings, *_previewConnection);
_previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
_previewControl.DisplayCursorWhileBlurred(true);
ControlPreview().Child(_previewControl);
}
// Subscribe to some changes in the view model
// These changes should force us to update our own set of "Current<Setting>" members,
// and propagate those changes to the UI
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
// The Appearances object handles updating the values in the settings UI, but
// we still need to listen to the changes here just to update the preview control
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
// There is a possibility that the control has not fully initialized yet,
// so wait for it to initialize before updating the settings (so we know
// that the renderer is set up)
_previewControl.Initialized([&](auto&& /*s*/, auto&& /*e*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
}
void Profiles_Appearance::OnNavigatedFrom(const NavigationEventArgs& /*e*/)
@@ -74,4 +69,26 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_Profile.DeleteUnfocusedAppearance();
}
bool Profiles_Appearance::_looksLikePowerlineFont() const
{
if (_Profile && _Profile.DefaultAppearance())
{
if (const auto fontName = _Profile.DefaultAppearance().FontFace(); !fontName.empty())
{
if (const auto font = ProfileViewModel::FindFontWithLocalizedName(fontName))
{
return font.HasPowerlineCharacters();
}
}
}
return false;
}
void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const
{
const auto settings = _Profile.TermSettings();
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
_previewControl.UpdateControlSettings(settings, settings);
}
}

View File

@@ -5,6 +5,7 @@
#include "Profiles_Appearance.g.h"
#include "Utils.h"
#include "PreviewConnection.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@@ -25,7 +26,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
private:
Microsoft::Terminal::Control::TermControl _previewControl;
void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const;
bool _looksLikePowerlineFont() const;
winrt::com_ptr<PreviewConnection> _previewConnection{ nullptr };
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
Editor::IHostedInWindow _windowRoot;

View File

@@ -47,8 +47,8 @@
<!-- Control Preview -->
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<Border x:Name="ControlPreview"
Width="350"
Height="160"
Width="400"
Height="180"
Margin="0,0,0,12"
HorizontalAlignment="Left"
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}"

View File

@@ -140,7 +140,7 @@
<comment>This is the header for a control that allows the user to set the currently selected color scheme as their default.</comment>
</data>
<data name="ColorScheme_SetAsDefault.HelpText" xml:space="preserve">
<value>Apply this color scheme to all your profiles. This sets this color scheme in your 'Defaults' profile, it can still be overridden in each individual profile.</value>
<value>Apply this color scheme to all your profiles, by default. Individual profiles can still select their own color schemes.</value>
<comment>A description explaining how this control changes the user's default color scheme.</comment>
</data>
<data name="ColorScheme_Rename.Header" xml:space="preserve">
@@ -232,8 +232,8 @@
<comment>The header for a control allowing users to choose the app's language.</comment>
</data>
<data name="Globals_Language.HelpText" xml:space="preserve">
<value>Sets an override for the app's preferred language.</value>
<comment>A description explaining how this control changes the app's language.</comment>
<value>Selects a display language for the application. This will override your default Windows interface language.</value>
<comment>A description explaining how this control changes the app's language. {Locked="Windows"}</comment>
</data>
<data name="Globals_LanguageDefault" xml:space="preserve">
<value>Use system default</value>
@@ -252,7 +252,7 @@
<comment>Header for a control to select position of newly created tabs.</comment>
</data>
<data name="Globals_NewTabPosition.HelpText" xml:space="preserve">
<value>Specifies where new tabs appear in the tab row</value>
<value>Specifies where new tabs appear in the tab row.</value>
<comment>A description for what the "Position of newly created tabs" setting does.</comment>
</data>
<data name="Globals_NewTabPositionAfterLastTab.Content" xml:space="preserve">
@@ -450,10 +450,6 @@
<value>Use acrylic material in the tab row</value>
<comment>Header for a control to toggle whether "acrylic material" is used. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
</data>
<data name="Globals_AcrylicTabRow.HelpText" xml:space="preserve">
<value>When checked, the tab row will have the acrylic material.</value>
<comment>A description for the "Globals_AcrylicTabRow.Header" setting does. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
</data>
<data name="Globals_ShowTitleInTitlebar.Header" xml:space="preserve">
<value>Use active terminal title as application title</value>
<comment>Header for a control to toggle whether the terminal's title is shown as the application title, or not.</comment>
@@ -483,8 +479,8 @@
<comment>Header for a control to toggle whether the app should launch when the user's machine starts up, or not.</comment>
</data>
<data name="Globals_StartOnUserLogin.HelpText" xml:space="preserve">
<value>When enabled, this enables the launch of Terminal at machine startup.</value>
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header".</comment>
<value>Automatically launch Terminal when you log in to Windows.</value>
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". {Locked="Windows"}</comment>
</data>
<data name="Globals_CenterOnLaunchCentered" xml:space="preserve">
<value>Centered</value>
@@ -511,7 +507,7 @@
<comment>Header for a control to choose how wide the tabs are.</comment>
</data>
<data name="Globals_TabWidthMode.HelpText" xml:space="preserve">
<value>Compact will shrink unfocused tabs to the size of the icon.</value>
<value>Compact will shrink inactive tabs to the size of the icon.</value>
<comment>A description for what the "tab width mode" setting does. Presented near "Globals_TabWidthMode.Header". 'Compact' must match the value for &lt;Globals_TabWidthModeCompact.Content&gt;.</comment>
</data>
<data name="Globals_TabWidthModeCompact.Content" xml:space="preserve">
@@ -527,13 +523,9 @@
<comment>An option to choose from for the "tab width mode" setting. When selected, each tab adjusts its width to the content within the tab.</comment>
</data>
<data name="Globals_Theme.Header" xml:space="preserve">
<value>Theme</value>
<value>Application Theme</value>
<comment>Header for a control to choose the theme colors used in the app.</comment>
</data>
<data name="Globals_Theme.HelpText" xml:space="preserve">
<value>Sets the theme of the application.</value>
<comment>A description for what the "theme" setting does. Presented near "Globals_Theme.Header".</comment>
</data>
<data name="Globals_ThemeDark.Content" xml:space="preserve">
<value>Dark</value>
<comment>An option to choose from for the "theme" setting. When selected, the app is in dark theme and darker colors are used throughout the app.</comment>
@@ -563,8 +555,8 @@
<comment>Header for a control to determine what delimiters to use to identify separate words during word selection.</comment>
</data>
<data name="Globals_WordDelimiters.HelpText" xml:space="preserve">
<value>Symbols used to define boundaries between words.</value>
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header".</comment>
<value>These symbols will be used when you double-click text in the terminal or activate "Mark mode".</value>
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header". "Mark" is used in the sense of "choosing something to interact with."</comment>
</data>
<data name="Nav_Appearance.Content" xml:space="preserve">
<value>Appearance</value>
@@ -619,8 +611,8 @@
<comment>Header for a control to toggle whether the app treats ctrl+alt as the AltGr (also known as the Alt Graph) modifier key found on keyboards. {Locked="AltGr"}</comment>
</data>
<data name="Profile_AltGrAliasing.HelpText" xml:space="preserve">
<value>By default Windows treats Ctrl+Alt as an alias for AltGr. When disabled, this behavior will be disabled.</value>
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header".</comment>
<value>By default, Windows treats Ctrl+Alt as an alias for AltGr. This setting will override Windows' default behavior.</value>
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header". {Locked="Windows"}</comment>
</data>
<data name="Profile_AntialiasingMode.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Text antialiasing</value>
@@ -786,10 +778,6 @@
<value>Color scheme</value>
<comment>Header for a control to select the scheme (or set) of colors used in the session. This is selected from a list of options managed by the user.</comment>
</data>
<data name="Profile_ColorScheme.HelpText" xml:space="preserve">
<value>Name of the color scheme to use.</value>
<comment>A description for what the "color scheme" setting does. Presented near "Profile_ColorScheme".</comment>
</data>
<data name="Profile_Commandline.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Command line</value>
<comment>Name for a control to determine commandline executable (i.e. a .exe file) to run when a terminal session of this profile is launched.</comment>
@@ -835,7 +823,7 @@
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColorsAlways.Content" xml:space="preserve">
<value>Always (More performance intensive)</value>
<value>Always</value>
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.</comment>
</data>
<data name="Profile_CursorShapeBar.Content" xml:space="preserve">
@@ -870,10 +858,6 @@
<value>Font face</value>
<comment>Name for a control to select the font for text in the app.</comment>
</data>
<data name="Profile_FontFace.HelpText" xml:space="preserve">
<value>Name of the font face used in the profile.</value>
<comment>A description for what the "font face" setting does. Presented near "Profile_FontFace".</comment>
</data>
<data name="Profile_FontSize.Header" xml:space="preserve">
<value>Font size</value>
<comment>Header for a control to determine the size of the text in the app.</comment>
@@ -895,7 +879,7 @@
<comment>Header for a control that sets the text line height.</comment>
</data>
<data name="Profile_LineHeight.HelpText" xml:space="preserve">
<value>Sets the height of each line in the terminal as a multiple of the font size. The default depends on your font and is usually around 1.2.</value>
<value>Override the line height of the terminal. Measured as a multiple of the font size. The default value depends on your font and is usually around 1.2.</value>
<comment>A description for what the "line height" setting does. Presented near "Profile_LineHeight".</comment>
</data>
<data name="Profile_LineHeightBox.PlaceholderText" xml:space="preserve">
@@ -987,15 +971,15 @@
<comment>Header for a control to toggle classic CRT display effects, which gives the terminal a retro look.</comment>
</data>
<data name="Profile_RetroTerminalEffect.HelpText" xml:space="preserve">
<value>When enabled, enables retro terminal effects such as glowing text and scan lines.</value>
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect".</comment>
<value>Show retro-style terminal effects such as glowing text and scan lines.</value>
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect". "Retro" is a common English prefix that suggests a nostalgic, dated appearance.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColors.Header" xml:space="preserve">
<value>Automatically adjust lightness of indistinguishable text</value>
<comment>Header for a control to toggle if we should adjust the foreground color's lightness to make it more visible when necessary, based on the background color.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColors.HelpText" xml:space="preserve">
<value>When enabled, enables automatic adjustment of indistinguishable colors, which will, only when necessary, adjust the foreground color's lightness to make it more visible (based on the background color).</value>
<value>Automatically brightens or darkens text to make it more visible. Even when enabled, this adjustment will only occur when a combination of foreground and background colors would result in poor contrast.</value>
<comment>A description for what the "adjust indistinguishable colors" setting does. Presented near "Profile_AdjustIndistinguishableColors".</comment>
</data>
<data name="Profile_ScrollbarVisibility.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
@@ -1055,8 +1039,8 @@
<comment>Header for a control to toggle changes in the app title.</comment>
</data>
<data name="Profile_SuppressApplicationTitle.HelpText" xml:space="preserve">
<value>Use the tab title to override the default title of the tab and suppress any title change messages from the application.</value>
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle".</comment>
<value>Ignore application requests to change the title (OSC 2).</value>
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle". "OSC 2" is a technical term that is understood in the industry. {Locked="OSC 2"}</comment>
</data>
<data name="Profile_TabTitle.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Tab title</value>
@@ -1095,7 +1079,7 @@
<comment>A supplementary setting to the "background image" setting. When enabled, the OS desktop wallpaper is used as the background image. Presented near "Profile_BackgroundImage".</comment>
</data>
<data name="Profile_UseDesktopImage.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>When enabled, use the desktop wallpaper image as the background image for the terminal.</value>
<value>Use the desktop wallpaper image as the background image for the terminal.</value>
<comment>A description for what the supplementary "use desktop image" setting does. Presented near "Profile_UseDesktopImage".</comment>
</data>
<data name="Settings_ResetSettingsButton.Content" xml:space="preserve">
@@ -1370,7 +1354,7 @@
<comment>Header for a control to choose how the tab switcher operates.</comment>
</data>
<data name="Globals_TabSwitcherMode.HelpText" xml:space="preserve">
<value>Defines the terminal behavior when switching tabs with the keyboard.</value>
<value>Selects which interface will be used when you switch tabs using the keyboard.</value>
<comment>A description for what the "tab switcher mode" setting does. Presented near "Globals_TabSwitcherMode.Header".</comment>
</data>
<data name="Globals_TabSwitcherModeMru.Content" xml:space="preserve">
@@ -1386,13 +1370,9 @@
<comment>An option to choose from for the "tab switcher mode" setting. The tab switcher overlay is hidden and does not appear in a separate window.</comment>
</data>
<data name="Globals_CopyFormat.Header" xml:space="preserve">
<value>Text format when copying</value>
<value>Text formats to copy to the clipboard</value>
<comment>Header for a control to select the format of copied text.</comment>
</data>
<data name="Globals_CopyFormat.HelpText" xml:space="preserve">
<value>Defines the type of formatting in which selected text is copied to your clipboard.</value>
<comment>A description for what the "copy formatting" setting does. Presented near "Globals_CopyFormat.Header".</comment>
</data>
<data name="Globals_CopyFormatNone.Content" xml:space="preserve">
<value>Plain text only</value>
<comment>An option to choose from for the "copy formatting" setting. Store only plain text data.</comment>
@@ -1571,10 +1551,10 @@
</data>
<data name="Globals_AutoHideWindow.Header" xml:space="preserve">
<value>Automatically hide window</value>
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the window will be hidden as soon as it loses focus.</comment>
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the terminal will be hidden as soon as you switch to another window.</comment>
</data>
<data name="Globals_AutoHideWindow.HelpText" xml:space="preserve">
<value>If enabled, the window will be hidden as soon as it loses focus.</value>
<value>If enabled, the terminal will be hidden as soon as you switch to another window.</value>
<comment>A description for what the "Automatically hide window" setting does.</comment>
</data>
<data name="ColorScheme_DeleteDisclaimerInBox" xml:space="preserve">

View File

@@ -72,6 +72,7 @@ static constexpr std::string_view IdentifyWindowKey{ "identifyWindow" };
static constexpr std::string_view IdentifyWindowsKey{ "identifyWindows" };
static constexpr std::string_view RenameWindowKey{ "renameWindow" };
static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
static constexpr std::string_view SearchForTextKey{ "searchWeb" };
static constexpr std::string_view GlobalSummonKey{ "globalSummon" };
static constexpr std::string_view QuakeModeKey{ "quakeMode" };
static constexpr std::string_view FocusPaneKey{ "focusPane" };
@@ -403,6 +404,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::RenameWindow, RS_(L"ResetWindowNameCommandKey") },
{ ShortcutAction::OpenWindowRenamer, RS_(L"OpenWindowRenamerCommandKey") },
{ ShortcutAction::GlobalSummon, MustGenerate },
{ ShortcutAction::SearchForText, MustGenerate },
{ ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },
{ ShortcutAction::FocusPane, MustGenerate },
{ ShortcutAction::OpenSystemMenu, RS_(L"OpenSystemMenuCommandKey") },

View File

@@ -37,6 +37,7 @@
#include "PrevTabArgs.g.cpp"
#include "NextTabArgs.g.cpp"
#include "RenameWindowArgs.g.cpp"
#include "SearchForTextArgs.g.cpp"
#include "GlobalSummonArgs.g.cpp"
#include "FocusPaneArgs.g.cpp"
#include "ExportBufferArgs.g.cpp"
@@ -765,6 +766,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_(L"ResetWindowNameCommandKey");
}
winrt::hstring SearchForTextArgs::GenerateName() const
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"SearchForTextCommandKey")),
Windows::Foundation::Uri(QueryUrl()).Domain().c_str())
};
}
winrt::hstring GlobalSummonArgs::GenerateName() const
{
// GH#10210 - Is this action literally the same thing as the `quakeMode`

View File

@@ -39,6 +39,7 @@
#include "PrevTabArgs.g.h"
#include "NextTabArgs.g.h"
#include "RenameWindowArgs.g.h"
#include "SearchForTextArgs.g.h"
#include "GlobalSummonArgs.g.h"
#include "FocusPaneArgs.g.h"
#include "ExportBufferArgs.g.h"
@@ -227,6 +228,10 @@ private: \
#define RENAME_WINDOW_ARGS(X) \
X(winrt::hstring, Name, "name", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define SEARCH_FOR_TEXT_ARGS(X) \
X(winrt::hstring, QueryUrl, "queryUrl", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define GLOBAL_SUMMON_ARGS(X) \
X(winrt::hstring, Name, "name", false, L"") \
@@ -711,6 +716,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARGS_STRUCT(RenameWindowArgs, RENAME_WINDOW_ARGS);
ACTION_ARGS_STRUCT(SearchForTextArgs, SEARCH_FOR_TEXT_ARGS);
struct GlobalSummonArgs : public GlobalSummonArgsT<GlobalSummonArgs>
{
ACTION_ARG_BODY(GlobalSummonArgs, GLOBAL_SUMMON_ARGS)

View File

@@ -349,6 +349,11 @@ namespace Microsoft.Terminal.Settings.Model
String Name { get; };
};
[default_interface] runtimeclass SearchForTextArgs : IActionArgs
{
String QueryUrl { get; };
};
[default_interface] runtimeclass GlobalSummonArgs : IActionArgs
{
String Name { get; };

View File

@@ -85,6 +85,7 @@
ON_ALL_ACTIONS(IdentifyWindows) \
ON_ALL_ACTIONS(RenameWindow) \
ON_ALL_ACTIONS(OpenWindowRenamer) \
ON_ALL_ACTIONS(SearchForText) \
ON_ALL_ACTIONS(GlobalSummon) \
ON_ALL_ACTIONS(QuakeMode) \
ON_ALL_ACTIONS(FocusPane) \
@@ -115,6 +116,7 @@
ON_ALL_ACTIONS_WITH_ARGS(CopyText) \
ON_ALL_ACTIONS_WITH_ARGS(ExecuteCommandline) \
ON_ALL_ACTIONS_WITH_ARGS(FindMatch) \
ON_ALL_ACTIONS_WITH_ARGS(SearchForText) \
ON_ALL_ACTIONS_WITH_ARGS(GlobalSummon) \
ON_ALL_ACTIONS_WITH_ARGS(MoveFocus) \
ON_ALL_ACTIONS_WITH_ARGS(MovePane) \

View File

@@ -100,6 +100,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, EnableColorSelection);
INHERITABLE_SETTING(Boolean, IsolatedMode);
INHERITABLE_SETTING(Boolean, AllowHeadless);
INHERITABLE_SETTING(String, SearchWebDefaultQueryUrl);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);

View File

@@ -124,7 +124,7 @@ static int32_t parseNumericCode(const std::wstring_view& str, const std::wstring
static KeyChord _fromString(std::wstring_view wstr)
{
using nameToVkeyPair = std::pair<std::wstring_view, int32_t>;
static const til::static_map nameToVkey{
static constinit til::static_map nameToVkey{
// The above VKEY_NAME_PAIRS macro contains a list of key-binding names for each virtual key.
// This god-awful macro inverts VKEY_NAME_PAIRS and creates a static map of key-binding names to virtual keys.
// clang-format off
@@ -240,7 +240,7 @@ static KeyChord _fromString(std::wstring_view wstr)
static std::wstring _toString(const KeyChord& chord)
{
using vkeyToNamePair = std::pair<int32_t, std::wstring_view>;
static const til::static_map vkeyToName{
static constinit til::static_map vkeyToName{
// The above VKEY_NAME_PAIRS macro contains a list of key-binding strings for each virtual key.
// This macro picks the first (most preferred) name and creates a static map of virtual keys to key-binding names.
#define GENERATOR(vkey, name1, ...) vkeyToNamePair{ vkey, name1 },

View File

@@ -65,7 +65,8 @@ Author(s):
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
X(bool, IsolatedMode, "compatibility.isolatedMode", false)
X(bool, IsolatedMode, "compatibility.isolatedMode", false) \
X(hstring, SearchWebDefaultQueryUrl, "searchWebDefaultQueryUrl", L"https://www.bing.com/search?q=%22%s%22")
#define MTSM_PROFILE_SETTINGS(X) \
X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \
@@ -132,6 +133,9 @@ Author(s):
#define MTSM_THEME_WINDOW_SETTINGS(X) \
X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "applicationTheme", winrt::Windows::UI::Xaml::ElementTheme::Default) \
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Frame, "frame", nullptr) \
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedFrame, "unfocusedFrame", nullptr) \
X(bool, RainbowFrame, "experimental.rainbowFrame", false) \
X(bool, UseMica, "useMica", false)
#define MTSM_THEME_TABROW_SETTINGS(X) \

View File

@@ -327,7 +327,7 @@
<!-- PowerShell version check, outputs error if the wrong version is installed. -->
<Target Name="_WindowsPowershellVersionCheck" BeforeTargets="BeforeClCompile">
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Unrestricted &quot;$(OpenConsoleDir)\tools\WindowsCheckPSVersion.ps1&quot;" />
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Unrestricted -File &quot;$(OpenConsoleDir)\tools\WindowsCheckPSVersion.ps1&quot;" />
</Target>
<Target Name="_PowershellVersionCheck" BeforeTargets="BeforeClCompile">
<Exec Command="pwsh.exe -NoProfile -ExecutionPolicy Unrestricted &quot;$(OpenConsoleDir)\tools\CheckPSVersion.ps1&quot;" />

View File

@@ -157,7 +157,7 @@ static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstr
const auto executable = versionedPath / PWSH_EXE;
if (std::filesystem::exists(executable))
{
const auto preview = versionedPath.filename().wstring().find(L"-preview") != std::wstring::npos;
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None;
out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()),
PowerShellFlags::Traditional | flags | previewFlag,

View File

@@ -699,4 +699,12 @@
<data name="SelectCommandPreviousCommandKey" xml:space="preserve">
<value>Select previous command</value>
</data>
<data name="SearchForTextCommandKey" xml:space="preserve">
<value>Search {0}</value>
<comment>{0} will be replaced with a user-specified URL to a web page</comment>
</data>
<data name="SearchWebCommandKey" xml:space="preserve">
<value>Search the web for selected text</value>
<comment>This will open a web browser to search for some user-selected text</comment>
</data>
</root>

View File

@@ -49,6 +49,9 @@ namespace Microsoft.Terminal.Settings.Model
runtimeclass WindowTheme {
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
Boolean UseMica { get; };
Boolean RainbowFrame { get; };
ThemeColor Frame { get; };
ThemeColor UnfocusedFrame { get; };
}
runtimeclass TabRowTheme {

View File

@@ -44,11 +44,12 @@ std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration
commandLine.append(GetDevCmdScriptPath(instance));
// The "-startdir" parameter will prevent "vsdevcmd" from automatically
// setting the shell path so the path in the profile will be used instead.
commandLine.append(LR"(" -startdir=none)");
#if defined(_M_ARM64)
commandLine.append(LR"(" -arch=arm64 -host_arch=x64)");
commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=x64)");
#elif defined(_M_AMD64)
commandLine.append(LR"(" -arch=x64 -host_arch=x64)");
commandLine.append(LR"(" -startdir=none -arch=x64 -host_arch=x64)");
#else
commandLine.append(LR"(" -startdir=none)");
#endif
return commandLine;
}

View File

@@ -466,6 +466,9 @@
{ "command": "expandSelectionToWord" },
{ "command": "showContextMenu", "keys": "menu" },
// Web Search
{ "command": { "action": "searchWeb" }, "name": { "key": "SearchWebCommandKey" } },
// Scrollback
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },

View File

@@ -46,6 +46,7 @@ Licensed under the MIT license.
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/mutex.h>
#include <til/winrt.h>
#include "ThrottledFunc.h"

View File

@@ -1200,7 +1200,9 @@ void ConptyRoundtripTests::PassthroughHardReset()
}
// Write a Hard Reset VT sequence to the host, it should come through to the Terminal
// along with a DECSET sequence to re-enable win32 input and focus events.
expectedOutput.push_back("\033c");
expectedOutput.push_back("\033[?9001;1004h");
hostSm.ProcessString(L"\033c");
const auto termSecondView = term->GetViewport();

View File

@@ -28,6 +28,8 @@ using namespace std::chrono_literals;
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
constexpr const auto FrameUpdateInterval = std::chrono::milliseconds(16);
AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const Remoting::WindowManager& manager,
@@ -38,6 +40,8 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
_peasant{ peasant },
_desktopManager{ winrt::try_create_instance<IVirtualDesktopManager>(__uuidof(VirtualDesktopManager)) }
{
_started = std::chrono::high_resolution_clock::now();
_HandleCommandlineArgs(args);
_HandleSessionRestore(!args.Content().empty());
@@ -65,20 +69,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
std::placeholders::_2);
_window->SetCreateCallback(pfn);
// Event handlers:
// MAKE SURE THEY ARE ALL:
// * winrt::auto_revoke
// * revoked manually in the dtor before the window is nulled out.
//
// If you don't, then it's possible for them to get triggered as the app is
// tearing down, after we've nulled out the window, during the dtor. That
// can cause unexpected AV's everywhere.
//
// _window callbacks don't need to be treated this way, because:
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like this)
// * This particular bug scenario applies when we've already freed the window.
//
// (Most of these events are actually set up in AppHost::Initialize)
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
_window->WindowMoved({ this, &AppHost::_WindowMoved });
@@ -91,21 +81,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
_window->MakeWindow();
}
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
// revoke ALL our event handlers. There's a large class of bugs where we
// might get a callback to one of these when we call app.Close() below. Make
// sure to revoke these first, so we won't get any more callbacks, then null
// out the window, then close the app.
_revokers = {};
_showHideWindowThrottler.reset();
_window = nullptr;
}
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
{
if (_windowLogic)
@@ -177,7 +152,7 @@ void AppHost::_HandleCommandlineArgs(const Remoting::WindowRequestedArgs& window
}
else if (args)
{
const auto result = _windowLogic.SetStartupCommandline(args.Commandline());
const auto result = _windowLogic.SetStartupCommandline(args.Commandline(), args.CurrentDirectory());
const auto message = _windowLogic.ParseCommandlineMessage();
if (!message.empty())
{
@@ -443,6 +418,25 @@ void AppHost::Initialize()
_window->OnAppInitialized();
}
void AppHost::Close()
{
// After calling _window->Close() we should avoid creating more WinUI related actions.
// I suspect WinUI wouldn't like that very much. As such unregister all event handlers first.
_revokers = {};
if (_frameTimer)
{
_frameTimer.Tick(_frameTimerToken);
}
_showHideWindowThrottler.reset();
_window->Close();
if (_windowLogic)
{
_windowLogic.DismissDialog();
_windowLogic = nullptr;
}
}
// Method Description:
// - Called every time when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
@@ -498,7 +492,7 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
// event handler finishes.
_windowManager.SignalClose(_peasant);
_window->Close();
PostQuitMessage(0);
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
@@ -1050,24 +1044,138 @@ static bool _isActuallyDarkTheme(const auto requestedTheme)
}
}
// DwmSetWindowAttribute(... DWMWA_BORDER_COLOR.. ) doesn't work on Windows 10,
// but it _will_ spew to the debug console. This helper just no-ops the call on
// Windows 10, so that we don't even get that spew
void _frameColorHelper(const HWND h, const COLORREF color)
{
static const bool isWindows11 = []() {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
if (isWindows11)
{
LOG_IF_FAILED(DwmSetWindowAttribute(h, DWMWA_BORDER_COLOR, &color, sizeof(color)));
}
}
void AppHost::_updateTheme()
{
auto theme = _appLogic.Theme();
_window->OnApplicationThemeChanged(theme.RequestedTheme());
const auto windowTheme{ theme.Window() };
const auto b = _windowLogic.TitlebarBrush();
const auto color = ThemeColor::ColorFromBrush(b);
const auto colorOpacity = b ? color.A / 255.0 : 0.0;
const auto brushOpacity = _opacityFromBrush(b);
const auto opacity = std::min(colorOpacity, brushOpacity);
_window->UseMica(theme.Window() ? theme.Window().UseMica() : false, opacity);
_window->UseMica(windowTheme ? windowTheme.UseMica() : false, opacity);
// This is a hack to make the window borders dark instead of light.
// It must be done before WM_NCPAINT so that the borders are rendered with
// the correct theme.
// For more information, see GH#6620.
LOG_IF_FAILED(TerminalTrySetDarkTheme(_window->GetHandle(), _isActuallyDarkTheme(theme.RequestedTheme())));
// Update the window frame. If `rainbowFrame:true` is enabled, then that
// will be used. Otherwise we'll try to use the `FrameBrush` set in the
// terminal window, as that will have the right color for the ThemeColor for
// this setting. If that value is null, then revert to the default frame
// color.
if (windowTheme)
{
if (windowTheme.RainbowFrame())
{
_startFrameTimer();
}
else if (const auto b{ _windowLogic.FrameBrush() })
{
_stopFrameTimer();
const auto color = ThemeColor::ColorFromBrush(b);
COLORREF ref{ til::color{ color } };
_frameColorHelper(_window->GetHandle(), ref);
}
else
{
_stopFrameTimer();
// DWMWA_COLOR_DEFAULT is the magic "reset to the default" value
_frameColorHelper(_window->GetHandle(), DWMWA_COLOR_DEFAULT);
}
}
}
void AppHost::_startFrameTimer()
{
// Instantiate the frame color timer, if we haven't already. We'll only ever
// create one instance of this. We'll set up the callback for the timers as
// _updateFrameColor, which will actually handle setting the colors. If we
// already have a timer, just start that one.
if (_frameTimer == nullptr)
{
_frameTimer = winrt::Windows::UI::Xaml::DispatcherTimer();
_frameTimer.Interval(FrameUpdateInterval);
_frameTimerToken = _frameTimer.Tick({ this, &AppHost::_updateFrameColor });
}
_frameTimer.Start();
}
void AppHost::_stopFrameTimer()
{
if (_frameTimer)
{
_frameTimer.Stop();
}
}
// Method Description:
// - Updates the color of the window frame to cycle through all the colors. This
// is called as the `_frameTimer` Tick callback, roughly 60 times per second.
void AppHost::_updateFrameColor(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&)
{
// First, a couple helper functions:
static const auto saturateAndToColor = [](const float a, const float b, const float c) -> til::color {
return til::color{
base::saturated_cast<uint8_t>(255.f * std::clamp(a, 0.f, 1.f)),
base::saturated_cast<uint8_t>(255.f * std::clamp(b, 0.f, 1.f)),
base::saturated_cast<uint8_t>(255.f * std::clamp(c, 0.f, 1.f))
};
};
// Helper for converting a hue [0, 1) to an RGB value.
// Credit to https://www.chilliant.com/rgb2hsv.html
static const auto hueToRGB = [&](const float H) -> til::color {
float R = abs(H * 6 - 3) - 1;
float G = 2 - abs(H * 6 - 2);
float B = 2 - abs(H * 6 - 4);
return saturateAndToColor(R, G, B);
};
// Now, the main body of work.
// - Convert the time delta between when we were started and now, to a hue. This will cycle us through all the colors.
// - Convert that hue to an RGB value.
// - Set the frame's color to that RGB color.
const auto now = std::chrono::high_resolution_clock::now();
const std::chrono::duration<float> delta{ now - _started };
const auto seconds = delta.count() / 4; // divide by four, to make the effect slower. Otherwise it flashes way to fast.
float ignored;
const auto color = hueToRGB(modf(seconds, &ignored));
_frameColorHelper(_window->GetHandle(), color);
}
void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/,
@@ -1110,10 +1218,7 @@ void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&
// should prevent scenarios where the Terminal window state and PTY window
// state get de-sync'd, and cause the window to minimize/restore constantly
// in a loop.
if (_showHideWindowThrottler)
{
_showHideWindowThrottler->Run(args.ShowOrHide());
}
_showHideWindowThrottler->Run(args.ShowOrHide());
}
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
@@ -1220,6 +1325,10 @@ void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspect
_updateTheme();
}
}
else if (e.PropertyName() == L"FrameBrush")
{
_updateTheme();
}
}
winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,

View File

@@ -13,11 +13,11 @@ public:
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
~AppHost();
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args);
void Initialize();
void Close();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
@@ -51,6 +51,9 @@ private:
std::shared_ptr<ThrottledFuncTrailing<bool>> _showHideWindowThrottler;
std::chrono::time_point<std::chrono::steady_clock> _started;
winrt::Windows::UI::Xaml::DispatcherTimer _frameTimer{ nullptr };
uint32_t _launchShowWindowCommand{ SW_NORMAL };
void _preInit();
@@ -151,7 +154,12 @@ private:
void _handleSendContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args);
void _startFrameTimer();
void _stopFrameTimer();
void _updateFrameColor(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
winrt::event_token _GetWindowLayoutRequestedToken;
winrt::event_token _frameTimerToken;
// Helper struct. By putting these all into one struct, we can revoke them
// all at once, by assigning _revokers to a fresh Revokers instance. That'll

View File

@@ -37,7 +37,66 @@ IslandWindow::IslandWindow() noexcept :
IslandWindow::~IslandWindow()
{
_source.Close();
Close();
}
void IslandWindow::Close()
{
static const bool isWindows11 = []() {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
if (!isWindows11)
{
// BODGY
// ____ ____ _____ _______ __
// | _ \ / __ \| __ \ / ____\ \ / /
// | |_) | | | | | | | | __ \ \_/ /
// | _ <| | | | | | | | |_ | \ /
// | |_) | |__| | |__| | |__| | | |
// |____/ \____/|_____/ \_____| |_|
//
// There's a bug in Windows 10 where closing a DesktopWindowXamlSource
// on any thread will free an internal static resource that's used by
// XAML for the entire process. This would result in closing window
// essentially causing the entire app to crash.
//
// To avoid this, leak the XAML island. We only need to leak this on
// Windows 10, since the bug is fixed in Windows 11.
//
// See GH #15384, MSFT:32109540
auto a{ _source };
winrt::detach_abi(_source);
// </BODGY>
}
// GH#15454: Unset the user data for the window. This will prevent future
// callbacks that come onto our window message loop from being sent to the
// IslandWindow (or other derived class's) implementation.
//
// Specifically, this prevents a pending coroutine from being able to call
// something like ShowWindow, and have that come back on the IslandWindow
// message loop, where it'll end up asking XAML something that XAML is no
// longer able to answer.
SetWindowLongPtr(_window.get(), GWLP_USERDATA, 0);
if (_source)
{
_source.Close();
_source = nullptr;
}
}
HWND IslandWindow::GetInteropHandle() const
@@ -89,17 +148,6 @@ void IslandWindow::MakeWindow() noexcept
WINRT_ASSERT(_window);
}
// Method Description:
// - Called when no tab is remaining to close the window.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::Close()
{
PostQuitMessage(0);
}
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the proper size.

View File

@@ -20,7 +20,7 @@ public:
virtual ~IslandWindow() override;
virtual void MakeWindow() noexcept;
void Close();
virtual void Close();
virtual void OnSize(const UINT width, const UINT height);
HWND GetInteropHandle() const;

View File

@@ -26,7 +26,13 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme)
{
}
NonClientIslandWindow::~NonClientIslandWindow() = default;
void NonClientIslandWindow::Close()
{
// Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource
// inside `IslandWindow::Close()`. XAML thanks us for doing that by not crashing. Thank you XAML.
SetWindowLongPtr(_dragBarWindow.get(), GWLP_USERDATA, 0);
IslandWindow::Close();
}
static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" };

View File

@@ -30,8 +30,8 @@ public:
static constexpr const int topBorderVisibleHeight = 1;
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
virtual ~NonClientIslandWindow() override;
virtual void Close() override;
void MakeWindow() noexcept override;
virtual void OnSize(const UINT width, const UINT height) override;

View File

@@ -37,6 +37,20 @@ WindowEmperor::WindowEmperor() noexcept :
});
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
// BODGY
//
// There's a mysterious crash in XAML on Windows 10 if you just let the App
// get dtor'd. By all accounts, it doesn't make sense. To mitigate this, we
// need to intentionally leak a reference to our App. Crazily, if you just
// let the app get cleaned up with the rest of the process when the process
// exits, then it doesn't crash. But if you let it get explicitly dtor'd, it
// absolutely will crash on exit.
//
// GH#15410 has more details.
auto a{ _app };
::winrt::detach_abi(a);
}
WindowEmperor::~WindowEmperor()
@@ -71,7 +85,17 @@ bool WindowEmperor::HandleCommandlineArgs()
{
std::vector<winrt::hstring> args;
_buildArgsFromCommandline(args);
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
const auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
{
// ALWAYS change the _real_ CWD of the Terminal to system32, so that we
// don't lock the directory we were spawned in.
std::wstring system32{};
if (SUCCEEDED_LOG(wil::GetSystemDirectoryW<std::wstring>(system32)))
{
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(system32.c_str()));
}
}
// Get the requested initial state of the window from our startup info. For
// something like `start /min`, this will set the wShowWindow member to
@@ -99,7 +123,7 @@ bool WindowEmperor::HandleCommandlineArgs()
if (!res.Message.empty())
{
AppHost::s_DisplayMessageBox(res);
ExitThread(res.ExitCode);
std::quick_exit(res.ExitCode);
}
}
@@ -140,10 +164,16 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
std::thread t([weakThis, window]() {
try
{
auto cleanup = wil::scope_exit([&]() {
const auto decrementWindowCount = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_windowExitedHandler(window->Peasant().GetID());
self->_decrementWindowCount();
}
});
auto removeWindow = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_removeWindow(window->PeasantID());
}
});
@@ -160,7 +190,7 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
// remove the window from our list of windows, before we release the
// AppHost (and subsequently, the host's Logic() member that we use
// elsewhere).
cleanup.reset();
removeWindow.reset();
// Now that we no longer care about this thread's window, let it
// release it's app host and flush the rest of the XAML queue.
@@ -204,17 +234,20 @@ void WindowEmperor::_windowStartedHandlerPostXAML(const std::shared_ptr<WindowTh
lockedWindows->push_back(sender);
}
}
void WindowEmperor::_windowExitedHandler(uint64_t senderID)
void WindowEmperor::_removeWindow(uint64_t senderID)
{
auto lockedWindows{ _windows.lock() };
// find the window in _windows who's peasant's Id matches the peasant's Id
// and remove it
std::erase_if(*lockedWindows,
[&](const auto& w) {
return w->Peasant().GetID() == senderID;
});
std::erase_if(*lockedWindows, [&](const auto& w) {
return w->PeasantID() == senderID;
});
}
void WindowEmperor::_decrementWindowCount()
{
// When we run out of windows, exit our process if and only if:
// * We're not allowed to run headless OR
// * we've explicitly been told to "quit", which should fully exit the Terminal.
@@ -226,6 +259,7 @@ void WindowEmperor::_windowExitedHandler(uint64_t senderID)
_close();
}
}
// Method Description:
// - Set up all sorts of handlers now that we've determined that we're a process
// that will end up hosting the windows. These include:
@@ -276,24 +310,6 @@ void WindowEmperor::_becomeMonarch()
// We want at least some delay to prevent the first save from overwriting
_getWindowLayoutThrottler.emplace(std::move(std::chrono::seconds(10)), std::move([this]() { _saveWindowLayoutsRepeat(); }));
_getWindowLayoutThrottler.value()();
// BODGY
//
// We've got a weird crash that happens terribly inconsistently, but pretty
// readily on migrie's laptop, only in Debug mode. Apparently, there's some
// weird ref-counting magic that goes on during teardown, and our
// Application doesn't get closed quite right, which can cause us to crash
// into the debugger. This of course, only happens on exit, and happens
// somewhere in the XamlHost.dll code.
//
// Crazily, if we _manually leak the Application_ here, then the crash
// doesn't happen. This doesn't matter, because we really want the
// Application to live for _the entire lifetime of the process_, so the only
// time when this object would actually need to get cleaned up is _during
// exit_. So we can safely leak this Application object, and have it just
// get cleaned up normally when our process exits.
auto a{ _app };
::winrt::detach_abi(a);
}
// sender and args are always nullptr

View File

@@ -55,7 +55,8 @@ private:
bool _quitting{ false };
void _windowStartedHandlerPostXAML(const std::shared_ptr<WindowThread>& sender);
void _windowExitedHandler(uint64_t senderID);
void _removeWindow(uint64_t senderID);
void _decrementWindowCount();
void _becomeMonarch();
void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);

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