Merge branch 'main' into dev/migrie/fhl/non-terminal-panes-2023

This commit is contained in:
Mike Griese
2023-07-24 14:10:40 -05:00
23 changed files with 926 additions and 339 deletions

View File

@@ -0,0 +1,499 @@
---
author: Mike Griese
created on: 2022-03-28
last updated: 2023-07-19
issue id: 11000, 1527, 6232
---
##### [Original issue: [#1527]] [experimental PR [#12948]] [remaining marks [#14341]]
# Windows Terminal - Shell Integration (Marks)
## Abstract
_"Shell integration" refers to a broad category of ways by which a commandline
shell can drive richer integration with the terminal. This spec in particular is
most concerned with "marks" and other semantic markup of the buffer._
Marks are a new buffer-side feature that allow the commandline application or
user to add a bit of metadata to a range of text. This can be used for marking a
region of text as a prompt, marking a command as succeeded or failed, quickly
marking errors in the output. These marks can then be exposed to the user as
pips on the scrollbar, or as icons in the margins. Additionally, the user can
quickly scroll between different marks, to allow easy navigation between
important information in the buffer.
Marks in the Windows Terminal are a combination of functionality from a variety
of different terminal emulators. "Marks" attempts to unify these different, but
related pieces of functionality.
## Background
There's a large amount of prior art on this subject. I've attempted to collect
as much as possible in the ["Relevant external docs"](#Relevant-external-docs)
section below. "Marks" have been used in different scenarios by different
emulators for different purposes. The common thread running between them of
marking a region of text in the buffer with a special meaning.
* iTerm2, ConEmu, FinalTerm et.al. support emitting a VT sequence to indicate
that a line is a "prompt" line. This is often used for quick navigation
between these prompts.
* FinalTerm (and xterm.js) also support marking up more than just the prompt.
They go so far as to differentiate the start/end of the prompt, the start of
the commandline input, and the start/end of the command output.
`FTCS_COMMAND_FINISHED` is even designed to include metadata indicating
whether a command succeeded or failed.
* Additionally, Terminal.app allows users to "bookmark" lines via the UI. That
allows users to quickly come back to something they feel is important.
* Consider also editors like Visual Studio. VS also uses little marks on the
scrollbar to indicate where other matches are for whatever the given search
term is.
### "Elevator" pitch
The Terminal provides a way for command line shells to semantically mark parts
of the command-line output. By marking up parts of the output, the Terminal can
provide richer experiences. The Terminal will know where each command starts and
stops, what the actual command was and what the output of that command is. This
allows the terminal to expose quick actions for:
* Quickly navigating the history by scrolling between commands
* Re-running a previous command in the history
* Copying all the output of a single command
* A visual indicator to separate out one command-line from the next, for quicker
mental parsing of the output of the command-line.
* Collapsing the output of a command, as to reduce noise
* Visual indicators that highlight commands that succeeded or failed.
* Jumping to previously used directories
### User Stories
This is a bit of a unusual section, as this feature was already partially
implemented when this spec was written.
Story | Size | Description
--|-----------|--
A | ✅ Done | The user can use mark each prompt and have a mark displayed on the scrollbar
B | ✅ Done | The user can perform an action to scroll between marks
C | ✅ Done | The user can manually add marks to the buffer
D | ✅ Done | The shell can emit different marks to differentiate between prompt, command, and output
E | ✅ Done | Clearing the buffer clears marks
F | 🐣 Crawl | Marks stay in the same place you'd expect after resizing the buffer.
G | ✅ Done | Users can perform an action to select the previous command's output
H | 🚶 Walk | The find dialog can display marks on the scrollbar indicating the position of search matches
I | 🏃‍♂️ Run | The terminal can display icons in the gutter, with quick actions for that command (re-run, copy output, etc)
J | 🏃‍♂️ Run | The terminal can display a faint horizontal separator between commands in the buffer.
K | 🚀 Sprint | The terminal can "collapse" content between two marks.
L | 🚀 Sprint | The terminal can display a sticky header above the control which displays the most recent command
M | 🚀 Sprint | The user can open a dialog to manually bookmark a line with a custom comment
## Solution Design
### Supported VT sequences
* [x] iTerm2's OSC `SetMark` (in [#12948])
* [x] FinalTerm prompt markup sequences
- [x] **FTCS_PROMPT** was added in [#13163]
- [x] The rest in [#14341]
* [ ] additionally, VsCode's FinalTerm prompt markup variant, `OSC 663`
* [ ] [ConEmu's
`OSC9;12`](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC)
* [ ] Any custom OSC we may want to author ourselves.
The FinalTerm prompt sequences are probably the most complicated version of all
these, so it's important to give these a special callout. Almost all the other
VT sequences are roughly equivalent to **FTCS_PROMPT**. The xterm.js / VsCode
version has additional cases, that they ironically added to work around conpty
not understanding these sequences originally.
#### FinalTerm sequences
The relevant FinalTerm sequences for marking up the prompt are as follows.
![image](FTCS-diagram.png)
* **FTCS_PROMPT**: `OSC 133 ; A ST`
- The start of a prompt. Internally, this sets a marker in the buffer
indicating we started a prompt at the current cursor position, and that
marker will be used when we get a **FTCS_COMMAND_START**
* **FTCS_COMMAND_START**: `OSC 133 ; B ST`
- The start of a commandline (READ: the end of the prompt). When it follows a
**FTCS_PROMPT**, it creates a mark in the buffer from the location of the
**FTCS_PROMPT** to the current cursor position, with the category of
`prompt`
* **FTCS_COMMAND_EXECUTED**: `OSC 133 ; C ST`
- The start of the command output / the end of the commandline.
* **FTCS_COMMAND_FINISHED**: `OSC 133 ; D ; [Ps] ST`
- the end of a command.
Same deal for the **FTCS_COMMAND_EXECUTED**/**FTCS_COMMAND_FINISHED** ones.
**FTCS_COMMAND_EXECUTED** does nothing until we get a **FTCS_COMMAND_FINISHED**,
and the `[Ps]` parameter determines the category.
- `[Ps] == 0`: success
- anything else: error
This whole sequence will get turned into a single mark.
When we get the **FTCS_COMMAND_FINISHED**, set the category of the prompt mark
that preceded it, so that the `prompt` becomes an `error` or a `success`.
### Buffer implementation
In the initial PR ([#12948]), marks were stored simply as a `vector<Mark>`,
where a mark had a start and end point. These wouldn't reflow on resize, and
didn't support all of the FTCS sequences.
There's ultimately three types of region here we need to mark:
* The prompt (starting from A)
* the command (starting from B)
* the output (starting from C)
That intuitively feels a bit like a text attribute almost. Additionally, the
prompt should be connected to its subsequent command and output, s.t. we can
* Select command output
* re-run command
easily. Supposedly, we could do this by iterating through the whole buffer to
find the previous/next {whatever}[[1](#footnote-1)]. Additionally, the prompt
needs to be able to contain the status / category, and a `133;D` needs to be
able to change the category of the previous prompt/command.
If we instead do a single mark for each command, from `133;A` to `133;A`, and
have sub-points for elements within the command
* `133;A` starts a mark on the current line, at the current position.
* `133;B` sets the end of the mark to the current position.
* `133;C` updates the mark's `commandStart` to the current end, then sets the
end of the mark to the current position.
* `133;D` updates the mark's `outputStart` to the current end, then sets the end
of the mark to the current position. It also updates the category of the mark,
if needed.
Each command then only shows up as a single mark on the scrollbar. Jumping
between commands is easy, `scrollToMark` operates on `mark.start`, which is
where the prompt started. "Bookmarks", i.e. things started by the user wouldn't
have `commandStart` or `outputStart` in them. Getting the text of the command,
of the output is easy - it's just the text between sub-points.
Reflow still sucks though - we'd need to basically iterate over all the marks as
we're reflowing, to make sure we put them into the right place in the new
buffer. This is annoying and tedious, but shouldn't realistically be a
performance problem.
#### Cmd.exe considerations
`cmd.exe` is generally a pretty bad shell, and doesn't have a lot of the same
hooks that other shells do, that might allow for us to emit the
**FTCS_COMMAND_EXECUTED** sequence. However, cmd.exe also doesn't allow
multiline prompts, so we can be relatively certain that when the user presses
<kbd>enter</kbd>, that's the end of the prompt. We will treat the
`autoMarkPrompts` setting (which auto-marks <kbd>enter</kbd>) as the _end of the
prompt_. That would at least allow cmd.exe to emit a {command finished}{prompt
start}{prompt...}{command start} in the prompt, and have us add the command
executed. It is not perfect (we wouldn't be able to get error information), but
it does work well enough.
```cmd
PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\%PROMPT%$e]133;B$e\
```
## Settings proposals
The below are the proposed additions to the settings for supporting marks and
interacting with them. Some of these have already been added as experimental
settings - these would be promoted to no longer be experimental.
Many of the sub-points on these settings are definitely "Future Consideration"
level settings. For example, the `scrollToMark` `"highlight"` property. That one
is certainly not something we need to ship with.
### Actions
In addition to driving marks via the output, we will also want to support adding
marks manually. These can be thought of like "bookmarks" - a user indicated
region that means something to the user.
* [ ] `addMark`: add a mark to the buffer. If there's a selection, place the
mark covering at the selection. Otherwise, place the mark on the cursor row.
- [x] `color`: a color for the scrollbar mark. (in [#12948])
- [ ] `category`: one of `{"prompt", "error", "warning", "success", "info"}`
* [ ] `scrollToMark`
- [x] `direction`: `["first", "previous", "next", "last"]` (in [#12948])
- [ ] `category`: `flags({categories}...)`, default `"all"`. Only scroll to
one of the categories specified (e.g. only scroll to the previous error,
only the previous prompt, or just any mark)
- [ ] [#13449] - `center` or some other setting that controls how the mark is scrolled in.
- Maybe `top` (current) /`center` (as proposed) /`nearestEdge` (when
scrolling down, put the mark at the bottom line of viewport , up -> top
line)?
- [ ] [#13455] - `highlight`: `bool`, default true. Display a temporary
highlight around the mark when scrolling to it. ("Highlight" != "select")
- If the mark has prompt/command/output sections, only select the prompt and command.
- If the mark has zero width (i.e. the user just wanted to bookmark a line),
then highlight the entire row.
* [x] `clearMark`: Remove any marks in the selected region (or at the cursor
position) (in [#12948])
* [x] `clearAllMarks`: Remove all the marks from the buffer. (in [#12948])
#### Selecting commands & output
_Inspired by a long weekend of manually copying .csv output from the Terminal to
a spreadsheet, only to discover that we rejected [#4588] some years ago._
* [x] `selectCommand(direction=[prev, next])`: Starting at the active selection
anchor, (or the cursor if there's no selection), select the command that
starts before/after this point (exclusive). Probably shouldn't wrap around
the buffer.
* Since this will create a selection including the start of the command,
performing this again will select the _next_ command (in whatever
direction).
* [x] `selectOutput(direction=[prev, next])`: same as above, but with command outputs.
A convenient workflow might be a `multipleActions([selectOutput(prev),
copy()])`, to quickly select the previous commands output.
### Per-profile settings
* [x] `autoMarkPrompts`: `bool`, default `false`. (in [#12948])
* [ ] `showFindMatchesOnScrollbar`: `bool`, default `true`.
* [ ] `showMarksOnScrollbar`: `bool` or `flags({categories}...)`
* As an example: `"showMarksOnScrollbar": ["error", "success"]`).
* Controls if marks should be displayed on the scrollbar.
* If `true`/`"all"`, then all marks are displayed.
* If `false`/`"none"`, then no marks are displayed.
* If a set of categories are provided, only display marks from those categories.
* [x] the bool version is (in [#12948])
* [ ] The `flags({categories}...)` version is not done yet.
* [ ] `showGutterIcons`, for displaying gutter icons.
## UX Design
An example of what colored marks look like:
![Select the entire output of a command](https://user-images.githubusercontent.com/18356694/207696859-a227abe2-ccd4-4b81-8a2c-8a22219cd0dd.gif)
This gif demos both prompt marks and marks for search results:
![](https://user-images.githubusercontent.com/18356694/191330278-3f6bc207-1bd5-4ebd-bb0e-1f84b0170f49.gif)
### Gutter icons
![](vscode-shell-integration-gutter-mark.png)
_An example of what the icons in the VsCode gutter look like_
### Multiple marks on the same line
When it comes to displaying marks on the scrollbar, or in the margins, the
relative priority of these marks matters. Marks are given the following
priority, with errors being the highest priority.
* Error
* Warning
* Success
* Prompt
* Info (default)
## Work needed to get marks to v1
* [x] Clearing the screen leaves marks behind
* [x] Make sure `ED2` works to clear/move marks
* [x] Same with `ED3`
* [x] Same with `cls` / `Clear-Host`
* [x] Clear Buffer action too.
* [X] Circling doesn't update scrollbar
* I think this was fixed in [#14341], or in [#14045]
* [ ] Resizing / reflowing marks
* [x] marks should be stored in the `TextBuffer`
## Future Considerations
* adding a timestamp for when a line was marked?
* adding a comment to the mark. How do we display that comment? a TeachingTip on
the scrollbar maybe (actually that's a cool idea)
* adding a shape to the mark? Terminal.app marks prompt lines with brackets in
the margins
* Marks are currently just displayed as "last one in wins", they should have a
real sort order
* Should the height of a mark on the scrollbar be dependent on font size &
buffer height? I think I've got it set to like, a static 2dip, but maybe it
should represent the actual height of the row (down to a min 1dip)
* [#13455] - highlight a mark when scrolled to with the `scrollToMark` action.
This is left as a future consideration to figure out what kind of UI we want
here. Do we want to highlight
- the prompt?
- the whole row of the prompt?
- the prompt and the command?
- The whole prompt & command & output?
* an `addBookmark` action: This one's basically just `addMark`, but opens a prompt
(like the window renamer) to add some text as a comment. Automatically
populated with the selected text (if there was some).
- A dedicated transient pane for displaying non-terminal content might be
useful for such a thing.
- This might just make more sense as a parameter to `addMark`.
* Other ideas for `addMark` parameters:
- `icon`: This would require us to better figure out how we display gutter
icons. This would probably be like, a _shape_ rather than an arbitrary
image.
- `note`: a note to stick on the mark, as a comment. Might be more valuable
with something like `addBookmark`.
### Gutter icons
VsCode implements a set of gutter icons to the left of the buffer lines, to
provide a UI element for exposing some quick actions to perform, powered by
shell integration.
Gutter icons don't need to implement app-level actions at all. They _should_ be
part of the control. At least, part of the UWP `TermControl`. These are some
basic "actions" we could add to that menu. Since these are all attached to a
mark anyways, we already know what mark the user interacted with, and where the
start/end already is.
* Copy command
* Copy output
* Re-run command
* Save as task
* Explain this (for errors)
To allow comments in marks (ala "bookmarks"), we can use
the gutter flyout to display the comment, and have the tooltip display that
comment.
This is being left as a future consideration for now. We need to really sit and
consider what the UX is like for this.
* Do we just stick the gutter icons in the margin/padding?
* Have it be a separate space in the "buffer"
- If it's in the buffer itself, we can render it with the renderer, which by
all accounts, we probably should.
### Edge cases where these might not work as expected
Much of the benefits of shell integration come from literal buffer text parsing.
This can lead to some rough edge cases, such as:
* the user presses <kbd>Ctrl+V</kbd><kbd>Escape</kbd> to input an ESC character
and the shell displays it as `^[`
* the user presses <kbd>Ctrl+V</kbd><kbd>Ctrl+J</kbd> to input an LF character
and the shell displays it as a line break
* the user presses <kbd>Enter</kbd> within a quoted string and the shell
displays a continuation prompt
* the user types a command including an exclamation point, and the shell invokes
history expansion and echoes the result of expansion before it runs the
command
* The user has a prompt with a right-aligned status, ala
![](https://user-images.githubusercontent.com/189190/254475719-5007df07-6cc3-42e8-baf7-2572579eb2b9.png)
In these cases, the effects of shell integration will likely not work as
intended. There are various possible solutions that are being explored. We might
want to in the future also use [VsCode's extension to the FTCS sequences] to
enable the shell to tell the terminal the literal resulting commandline.
There's been [other proposals] to extend shell integration features as well.
### Rejected ideas
There was originally some discussion as to whether this is a design that should
be unified with generic pattern matchers. Something like the URL detection,
which identifies part of the buffer and then "marks" it. Prototypes for both of
these features are going in very different directions, however. Likely best to
leave them separate.
## Resources
### Other related issues
Not necessarily marks related, but could happily leverage this functionality.
* [#5916] and [#12366], which are likely to be combined into a single thread
- Imagine a trigger that automatically detects `error:.*` and then marks the line
* [#9583]
- Imagine selecting some text, colorizing & marking it all at once
- `addMark(selection:false)`, above, was inspired by this.
* [#7561] (and broadly, [#3920])
- Search results should maybe show up here on the scrollbar too.
* [#13455]
* [#13449]
* [#4588]
* [#14754] - A "sticky header" for the `TermControl` that could display the
previous command at the top of the buffer.
* [#2226] - a scrollable "minimap" in te scrollbar, as opposed to marks
### Relevant external docs
* **GREAT** summary of the state of the ecosystem: https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/28
* https://iterm2.com/documentation-escape-codes.html
- `OSC 1337 ; SetMark ST` under "Set Mark"
- under "Shell Integration/FinalTerm
* https://support.apple.com/en-ca/guide/terminal/trml135fbc26/mac
- discusses auto-marked lines on `enter`/`^C`/`^D`
- allows bookmarking lines with selection
- bookmarks can have a name (maybe not super important)
* [How to Use Marks in OS Xs Terminal for Easier Navigation](https://www.howtogeek.com/256548/how-to-use-marks-in-os-xs-terminal-for-easier-navigation/)
* [Terminal: The \[ Marks the Spot](https://scriptingosx.com/2017/03/terminal-the-marks-the-spot/)
* Thread with VsCode (xterm.js) implementation notes: https://github.com/microsoft/terminal/issues/1527#issuecomment-1076455642
* [xterm.js prompt markup sequences](https://github.com/microsoft/vscode/blob/39cc1e1c42b2e53e83b1846c2857ca194848cc1d/src/vs/workbench/contrib/terminal/browser/xterm/shellIntegrationAddon.ts#L50-L52)
* [VsCode command tracking release notes](https://code.visualstudio.com/updates/v1_22#_command-tracking), also [Terminal shell integration](https://code.visualstudio.com/updates/v1_65#_terminal-shell-integration)
* ConEMU:
Sequence | Description
-- | --
ESC ] 9 ; 12 ST | Let ConEmu treat current cursor position as prompt start. Useful with PS1.
* https://iterm2.com/documentation-one-page.html#documentation-triggers.html"
### Footnotes
<a name="footnote-1"><a>[1]: Intuitively, this feels prohibitively expensive,
but you'd be mistaken.
An average device right now (I mean something that was alright about 5 years
ago, like an 8700k with regular DDR4) does about 4GB/s of random, un-cached
memory access. While low-end devices are probably a bit slower, I think 4GB/s is
a good estimate regardless. That's because non-random memory access is way way
faster than that at around 20GB/s (DDR4 2400 - what most laptops had for the
last decade).
Assuming a 120 column * 32k line buffer (our current maximum), the buffer would
be about 21MB large. Going through the entire buffer linearly at 20GB/s would
take just 1ms (including all text and metadata). If we assume that each row has
a mark, that marks are 36 byte large and assuming the worst case of random
access, we can go through all 32k within about 0.3ms.
_(Thanks lhecker for these notes)_
[#1527]: https://github.com/microsoft/terminal/issues/1527
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#1527]: https://github.com/microsoft/terminal/issues/1527
[#6232]: https://github.com/microsoft/terminal/issues/6232
[#2226]: https://github.com/microsoft/terminal/issues/2226
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#13163]: https://github.com/microsoft/terminal/issues/13163
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#13455]: https://github.com/microsoft/terminal/issues/13455
[#13449]: https://github.com/microsoft/terminal/issues/13449
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#4588]: https://github.com/microsoft/terminal/issues/4588
[#5804]: https://github.com/microsoft/terminal/issues/5804
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#12948]: https://github.com/microsoft/terminal/issues/12948
[#13455]: https://github.com/microsoft/terminal/issues/13455
[#5916]: https://github.com/microsoft/terminal/issues/5916
[#12366]: https://github.com/microsoft/terminal/issues/12366
[#9583]: https://github.com/microsoft/terminal/issues/9583
[#7561]: https://github.com/microsoft/terminal/issues/7561
[#3920]: https://github.com/microsoft/terminal/issues/3920
[#13455]: https://github.com/microsoft/terminal/issues/13455
[#13449]: https://github.com/microsoft/terminal/issues/13449
[#4588]: https://github.com/microsoft/terminal/issues/4588
[#14341]: https://github.com/microsoft/terminal/issues/14341
[#14045]: https://github.com/microsoft/terminal/issues/14045
[#14754]: https://github.com/microsoft/terminal/issues/14754
[#14341]: https://github.com/microsoft/terminal/issues/14341
[VsCode's extension to the FTCS sequences]: https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
[other proposals]: https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/6#f6de1e5703f5806d0821d92b0274e895c4b6d850

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1021,6 +1021,16 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
void TerminalPage::_HandleDisplayWorkingDirectory(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
ShowTerminalWorkingDirectory();
args.Handled(true);
}
}
void TerminalPage::_HandleSearchForText(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -216,7 +216,7 @@
</data>
<data name="SearchWebText" xml:space="preserve">
<value>Web Search</value>
</data>
</data>
<data name="TabColorChoose" xml:space="preserve">
<value>Color...</value>
</data>
@@ -836,4 +836,8 @@
<data name="MoveTabToNewWindowToolTip" xml:space="preserve">
<value>Moves tab to a new window </value>
</data>
</root>
<data name="RunAsAdminFlyout.Text" xml:space="preserve">
<value>Run as Administrator</value>
<comment>This text is displayed on context menu for profile entries in add new tab button.</comment>
</data>
</root>

View File

@@ -1057,6 +1057,15 @@ namespace winrt::TerminalApp::implementation
}
});
// Using the static method on the base class seems to do what we want in terms of placement.
WUX::Controls::Primitives::FlyoutBase::SetAttachedFlyout(profileMenuItem, _CreateRunAsAdminFlyout(profileIndex));
// Since we are not setting the ContextFlyout property of the item we have to handle the ContextRequested event
// and rely on the base class to show our menu.
profileMenuItem.ContextRequested([profileMenuItem](auto&&, auto&&) {
WUX::Controls::Primitives::FlyoutBase::ShowAttachedFlyout(profileMenuItem);
});
return profileMenuItem;
}
@@ -4113,6 +4122,33 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::ShowTerminalWorkingDirectory()
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// If we haven't ever loaded the TeachingTip, then do so now and
// create the toast for it.
if (page->_windowCwdToast == nullptr)
{
if (auto tip{ page->FindName(L"WindowCwdToast").try_as<MUX::Controls::TeachingTip>() })
{
page->_windowCwdToast = std::make_shared<Toast>(tip);
// Make sure to use the weak ref when setting up this
// callback.
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(WindowCwdToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
if (page->_windowCwdToast != nullptr)
{
page->_windowCwdToast->Open();
}
}
}
// Method Description:
// - Called when the user hits the "Ok" button on the WindowRenamer TeachingTip.
// - Will raise an event that will bubble up to the monarch, asking if this
@@ -4940,4 +4976,42 @@ namespace winrt::TerminalApp::implementation
// _RemoveTab will make sure to null out the _stashed.draggedTab
_RemoveTab(*_stashed.draggedTab);
}
/// <summary>
/// Creates a sub flyout menu for profile items in the split button menu that when clicked will show a menu item for
/// Run as Administrator
/// </summary>
/// <param name="profileIndex">The index for the profileMenuItem</param>
/// <returns>MenuFlyout that will show when the context is request on a profileMenuItem</returns>
WUX::Controls::MenuFlyout TerminalPage::_CreateRunAsAdminFlyout(int profileIndex)
{
// Create the MenuFlyout and set its placement
WUX::Controls::MenuFlyout profileMenuItemFlyout{};
profileMenuItemFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedRight);
// Create the menu item and an icon to use in the menu
WUX::Controls::MenuFlyoutItem runAsAdminItem{};
WUX::Controls::FontIcon adminShieldIcon{};
adminShieldIcon.Glyph(L"\xEA18");
adminShieldIcon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
runAsAdminItem.Icon(adminShieldIcon);
runAsAdminItem.Text(RS_(L"RunAsAdminFlyout/Text"));
// Click handler for the flyout item
runAsAdminItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
NewTerminalArgs args{ profileIndex };
args.Elevate(true);
page->_OpenNewTerminalViaDropdown(args);
}
});
profileMenuItemFlyout.Items().Append(runAsAdminItem);
return profileMenuItemFlyout;
}
}

View File

@@ -147,6 +147,7 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget IdentifyWindow();
winrt::fire_and_forget RenameFailed();
winrt::fire_and_forget ShowTerminalWorkingDirectory();
winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions,
const bool initial,
@@ -256,6 +257,7 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Toast> _windowIdToast{ nullptr };
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
std::shared_ptr<Toast> _windowCwdToast{ nullptr };
winrt::Windows::UI::Xaml::Controls::TextBox::LayoutUpdated_revoker _renamerLayoutUpdatedRevoker;
int _renamerLayoutCount{ 0 };
@@ -530,7 +532,7 @@ namespace winrt::TerminalApp::implementation
void _ContextMenuOpened(const IInspectable& sender, const IInspectable& args);
void _SelectionMenuOpened(const IInspectable& sender, const IInspectable& args);
void _PopulateContextMenu(const IInspectable& sender, const bool withSelection);
winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);

View File

@@ -205,5 +205,11 @@
Text="{x:Bind WindowProperties.WindowName, Mode=OneWay}" />
</mux:TeachingTip.Content>
</mux:TeachingTip>
<mux:TeachingTip x:Name="WindowCwdToast"
x:Uid="WindowCwdToast"
Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}"
x:Load="False"
IsLightDismissEnabled="True" />
</Grid>
</Page>

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 DisplayWorkingDirectoryKey{ "debugTerminalCwd" };
static constexpr std::string_view SearchForTextKey{ "searchWeb" };
static constexpr std::string_view GlobalSummonKey{ "globalSummon" };
static constexpr std::string_view QuakeModeKey{ "quakeMode" };
@@ -404,6 +405,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::IdentifyWindows, RS_(L"IdentifyWindowsCommandKey") },
{ ShortcutAction::RenameWindow, RS_(L"ResetWindowNameCommandKey") },
{ ShortcutAction::OpenWindowRenamer, RS_(L"OpenWindowRenamerCommandKey") },
{ ShortcutAction::DisplayWorkingDirectory, RS_(L"DisplayWorkingDirectoryCommandKey") },
{ ShortcutAction::GlobalSummon, MustGenerate },
{ ShortcutAction::SearchForText, MustGenerate },
{ ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },

View File

@@ -85,6 +85,7 @@
ON_ALL_ACTIONS(IdentifyWindows) \
ON_ALL_ACTIONS(RenameWindow) \
ON_ALL_ACTIONS(OpenWindowRenamer) \
ON_ALL_ACTIONS(DisplayWorkingDirectory) \
ON_ALL_ACTIONS(SearchForText) \
ON_ALL_ACTIONS(GlobalSummon) \
ON_ALL_ACTIONS(QuakeMode) \

View File

@@ -511,6 +511,9 @@
<data name="OpenWindowRenamerCommandKey" xml:space="preserve">
<value>Rename window...</value>
</data>
<data name="DisplayWorkingDirectoryCommandKey" xml:space="preserve">
<value>Display Terminal's current working directory</value>
</data>
<data name="GlobalSummonCommandKey" xml:space="preserve">
<value>Show/Hide the Terminal window</value>
</data>

View File

@@ -962,8 +962,6 @@ winrt::fire_and_forget AppHost::_peasantNotifyActivateWindow()
// - The window layout as a json string.
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> AppHost::_GetWindowLayoutAsync()
{
winrt::apartment_context peasant_thread;
winrt::hstring layoutJson = L"";
// Use the main thread since we are accessing controls.
co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher());
@@ -974,9 +972,6 @@ winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> AppHost::_GetWindowL
}
CATCH_LOG()
// go back to give the result to the peasant.
co_await peasant_thread;
co_return layoutJson;
}
@@ -1052,9 +1047,6 @@ void AppHost::_DisplayWindowId(const winrt::Windows::Foundation::IInspectable& /
winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Foundation::IInspectable /*sender*/,
const winrt::TerminalApp::RenameWindowRequestedArgs args)
{
// Capture calling context.
winrt::apartment_context ui_thread;
// Switch to the BG thread - anything x-proc must happen on a BG thread
co_await winrt::resume_background();
@@ -1064,12 +1056,9 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou
_peasant.RequestRename(requestArgs);
// Switch back to the UI thread. Setting the WindowName needs to happen
// on the UI thread, because it'll raise a PropertyChanged event
co_await ui_thread;
if (requestArgs.Succeeded())
{
co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher());
_windowLogic.WindowName(args.ProposedName());
}
else

View File

@@ -18,7 +18,7 @@ enum TraceKeywords
Output = 0x008, // _DBGOUTPUT
General = 0x100,
Input = 0x200,
API = 0x400,
//API = 0x400, // No longer used
UIA = 0x800,
CookedRead = 0x1000,
ConsoleAttachDetach = 0x2000,
@@ -26,241 +26,8 @@ enum TraceKeywords
};
DEFINE_ENUM_FLAG_OPERATORS(TraceKeywords);
// Routine Description:
// - Creates a tracing object to assist with automatically firing a stop event
// when this object goes out of scope.
// - Give it back to the caller and they will release it when the event period is over.
// Arguments:
// - onExit - Function to process when the object is destroyed (on exit)
Tracing::Tracing(std::function<void()> onExit) :
_onExit(onExit)
{
}
// Routine Description:
// - Destructs a tracing object, running any on exit routine, if necessary.
Tracing::~Tracing()
{
if (_onExit)
{
_onExit();
}
}
// Routine Description:
// - Provides generic tracing for all API call types in the form of
// start/stop period events for timing and region-of-interest purposes
// while doing performance analysis.
// Arguments:
// - result - Reference to the area where the result code from the Api call
// will be stored for use in the stop event.
// - traceName - The name of the API call to list in the trace details
// Return Value:
// - An object for the caller to hold until the API call is complete.
// Then destroy it to signal that the call is over so the stop trace can be written.
Tracing Tracing::s_TraceApiCall(const NTSTATUS result, PCSTR traceName)
{
// clang-format off
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ApiCall",
TraceLoggingString(traceName, "ApiName"),
TraceLoggingOpcode(WINEVENT_OPCODE_START),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
return Tracing([traceName, &result] {
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ApiCall",
TraceLoggingString(traceName, "ApiName"),
TraceLoggingHResult(result, "Result"),
TraceLoggingOpcode(WINEVENT_OPCODE_STOP),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
});
// clang-format on
}
ULONG Tracing::s_ulDebugFlag = 0x0;
void Tracing::s_TraceApi(const NTSTATUS status, const CONSOLE_GETLARGESTWINDOWSIZE_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_GetLargestWindowSize",
TraceLoggingHexInt32(status, "ResultCode"),
TraceLoggingInt32(a->Size.X, "MaxWindowWidthInChars"),
TraceLoggingInt32(a->Size.Y, "MaxWindowHeightInChars"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceApi(const NTSTATUS status, const CONSOLE_SCREENBUFFERINFO_MSG* const a, const bool fSet)
{
// Duplicate copies required by TraceLogging documentation ("don't get cute" examples)
// Using logic inside these macros can make problems. Do all logic outside macros.
if (fSet)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_SetConsoleScreenBufferInfo",
TraceLoggingHexInt32(status, "ResultCode"),
TraceLoggingInt32(a->Size.X, "BufferWidthInChars"),
TraceLoggingInt32(a->Size.Y, "BufferHeightInChars"),
TraceLoggingInt32(a->CurrentWindowSize.X, "WindowWidthInChars"),
TraceLoggingInt32(a->CurrentWindowSize.Y, "WindowHeightInChars"),
TraceLoggingInt32(a->MaximumWindowSize.X, "MaxWindowWidthInChars"),
TraceLoggingInt32(a->MaximumWindowSize.Y, "MaxWindowHeightInChars"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
else
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_GetConsoleScreenBufferInfo",
TraceLoggingHexInt32(status, "ResultCode"),
TraceLoggingInt32(a->Size.X, "BufferWidthInChars"),
TraceLoggingInt32(a->Size.Y, "BufferHeightInChars"),
TraceLoggingInt32(a->CurrentWindowSize.X, "WindowWidthInChars"),
TraceLoggingInt32(a->CurrentWindowSize.Y, "WindowHeightInChars"),
TraceLoggingInt32(a->MaximumWindowSize.X, "MaxWindowWidthInChars"),
TraceLoggingInt32(a->MaximumWindowSize.Y, "MaxWindowHeightInChars"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
}
void Tracing::s_TraceApi(const NTSTATUS status, const CONSOLE_SETSCREENBUFFERSIZE_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_SetConsoleScreenBufferSize",
TraceLoggingHexInt32(status, "ResultCode"),
TraceLoggingInt32(a->Size.X, "BufferWidthInChars"),
TraceLoggingInt32(a->Size.Y, "BufferHeightInChars"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceApi(const NTSTATUS status, const CONSOLE_SETWINDOWINFO_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_SetConsoleWindowInfo",
TraceLoggingHexInt32(status, "ResultCode"),
TraceLoggingBool(a->Absolute, "IsWindowRectAbsolute"),
TraceLoggingInt32(a->Window.Left, "WindowRectLeft"),
TraceLoggingInt32(a->Window.Right, "WindowRectRight"),
TraceLoggingInt32(a->Window.Top, "WindowRectTop"),
TraceLoggingInt32(a->Window.Bottom, "WindowRectBottom"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceApi(_In_ const void* const buffer, const CONSOLE_WRITECONSOLE_MSG* const a)
{
// clang-format off
if (a->Unicode)
{
const auto buf = static_cast<const wchar_t* const>(buffer);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_WriteConsole",
TraceLoggingBoolean(a->Unicode, "Unicode"),
TraceLoggingUInt32(a->NumBytes, "NumBytes"),
TraceLoggingCountedWideString(buf, static_cast<UINT16>(a->NumBytes / sizeof(wchar_t)), "input buffer"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
else
{
const auto buf = static_cast<const char* const>(buffer);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_WriteConsole",
TraceLoggingBoolean(a->Unicode, "Unicode"),
TraceLoggingUInt32(a->NumBytes, "NumBytes"),
TraceLoggingCountedString(buf, static_cast<UINT16>(a->NumBytes / sizeof(char)), "input buffer"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
// clang-format on
}
void Tracing::s_TraceApi(const CONSOLE_SCREENBUFFERINFO_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_GetConsoleScreenBufferInfo",
TraceLoggingInt16(a->Size.X, "Size.X"),
TraceLoggingInt16(a->Size.Y, "Size.Y"),
TraceLoggingInt16(a->CursorPosition.X, "CursorPosition.X"),
TraceLoggingInt16(a->CursorPosition.Y, "CursorPosition.Y"),
TraceLoggingInt16(a->ScrollPosition.X, "ScrollPosition.X"),
TraceLoggingInt16(a->ScrollPosition.Y, "ScrollPosition.Y"),
TraceLoggingHexUInt16(a->Attributes, "Attributes"),
TraceLoggingInt16(a->CurrentWindowSize.X, "CurrentWindowSize.X"),
TraceLoggingInt16(a->CurrentWindowSize.Y, "CurrentWindowSize.Y"),
TraceLoggingInt16(a->MaximumWindowSize.X, "MaximumWindowSize.X"),
TraceLoggingInt16(a->MaximumWindowSize.Y, "MaximumWindowSize.Y"),
TraceLoggingHexUInt16(a->PopupAttributes, "PopupAttributes"),
TraceLoggingBoolean(a->FullscreenSupported, "FullscreenSupported"),
TraceLoggingHexUInt32FixedArray((UINT32 const*)a->ColorTable, _countof(a->ColorTable), "ColorTable"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
static_assert(sizeof(UINT32) == sizeof(*a->ColorTable), "a->ColorTable");
}
void Tracing::s_TraceApi(const CONSOLE_MODE_MSG* const a, const std::wstring_view handleType)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_GetConsoleMode",
TraceLoggingHexUInt32(a->Mode, "Mode"),
TraceLoggingCountedWideString(handleType.data(), gsl::narrow_cast<ULONG>(handleType.size()), "Handle type"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceApi(const CONSOLE_SETTEXTATTRIBUTE_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_SetConsoleTextAttribute",
TraceLoggingHexUInt16(a->Attributes, "Attributes"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceApi(const CONSOLE_WRITECONSOLEOUTPUTSTRING_MSG* const a)
{
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"API_WriteConsoleOutput",
TraceLoggingInt16(a->WriteCoord.X, "WriteCoord.X"),
TraceLoggingInt16(a->WriteCoord.Y, "WriteCoord.Y"),
TraceLoggingHexUInt32(a->StringType, "StringType"),
TraceLoggingUInt32(a->NumRecords, "NumRecords"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::API));
}
void Tracing::s_TraceWindowViewport(const Viewport& viewport)
{
TraceLoggingWrite(
@@ -413,10 +180,10 @@ void Tracing::s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcess
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"CookedRead",
TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"),
TraceLoggingCountedWideString(pwchCookedBuffer, cchCookedBufferLength, "ReadBuffer"),
TraceLoggingULong(cchCookedBufferLength, "ReadBufferLength"),
TraceLoggingUInt32(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"),
TraceLoggingUInt64(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"),
TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingKeyword(TraceKeywords::CookedRead));
}
@@ -431,8 +198,8 @@ void Tracing::s_TraceConsoleAttachDetach(_In_ ConsoleProcessHandle* const pConso
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ConsoleAttachDetach",
TraceLoggingUInt32(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"),
TraceLoggingUInt64(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"),
TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"),
TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"),
TraceLoggingBool(bIsAttach, "IsAttach"),
TraceLoggingBool(bIsUserInteractive, "IsUserInteractive"),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),

View File

@@ -35,25 +35,14 @@ Author(s):
#define DBGOUTPUT(_params_)
#endif
#define TraceLoggingConsoleCoord(value, name) \
TraceLoggingStruct(2, name), \
TraceLoggingInt32(value.X, "X"), \
TraceLoggingInt32(value.Y, "Y")
class Tracing
{
public:
~Tracing();
static Tracing s_TraceApiCall(const NTSTATUS result, PCSTR traceName);
static void s_TraceApi(const NTSTATUS status, const CONSOLE_GETLARGESTWINDOWSIZE_MSG* const a);
static void s_TraceApi(const NTSTATUS status, const CONSOLE_SCREENBUFFERINFO_MSG* const a, const bool fSet);
static void s_TraceApi(const NTSTATUS status, const CONSOLE_SETSCREENBUFFERSIZE_MSG* const a);
static void s_TraceApi(const NTSTATUS status, const CONSOLE_SETWINDOWINFO_MSG* const a);
static void s_TraceApi(_In_ const void* const buffer, const CONSOLE_WRITECONSOLE_MSG* const a);
static void s_TraceApi(const CONSOLE_SCREENBUFFERINFO_MSG* const a);
static void s_TraceApi(const CONSOLE_MODE_MSG* const a, const std::wstring_view handleType);
static void s_TraceApi(const CONSOLE_SETTEXTATTRIBUTE_MSG* const a);
static void s_TraceApi(const CONSOLE_WRITECONSOLEOUTPUTSTRING_MSG* const a);
static void s_TraceWindowViewport(const Microsoft::Console::Types::Viewport& viewport);
static void s_TraceChars(_In_z_ const char* pszMessage, ...);
@@ -69,8 +58,4 @@ public:
private:
static ULONG s_ulDebugFlag;
Tracing(std::function<void()> onExit);
std::function<void()> _onExit;
};

View File

@@ -12,6 +12,29 @@
#include "../host/telemetry.hpp"
#include "../host/cmdline.h"
// Assumes that it will find <m> in the calling environment.
#define TraceConsoleAPICallWithOrigin(ApiName, ...) \
TraceLoggingWrite( \
g_hConhostV2EventTraceProvider, \
"API_" ApiName, \
TraceLoggingPid(TraceGetProcessId(m), "OriginatingProcess"), \
TraceLoggingTid(TraceGetThreadId(m), "OriginatingThread"), \
__VA_ARGS__ __VA_OPT__(, ) \
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), \
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
static DWORD TraceGetProcessId(CONSOLE_API_MSG* const m)
{
const auto p = m->GetProcessHandle();
return p ? p->dwProcessId : 0;
}
static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m)
{
const auto p = m->GetProcessHandle();
return p ? p->dwThreadId : 0;
}
[[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleCP(_Inout_ CONSOLE_API_MSG* const m,
_Inout_ BOOL* const /*pbReplyPending*/)
{
@@ -35,35 +58,22 @@
{
Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode);
const auto a = &m->u.consoleMsgL1.GetConsoleMode;
std::wstring_view handleType = L"unknown";
TraceLoggingWrite(g_hConhostV2EventTraceProvider,
"API_GetConsoleMode",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingOpcode(WINEVENT_OPCODE_START));
auto tracing = wil::scope_exit([&]() {
Tracing::s_TraceApi(a, handleType);
TraceLoggingWrite(g_hConhostV2EventTraceProvider,
"API_GetConsoleMode",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
TraceLoggingOpcode(WINEVENT_OPCODE_STOP));
});
const auto pObjectHandle = m->GetObjectHandle();
RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle);
TraceConsoleAPICallWithOrigin(
"GetConsoleMode",
TraceLoggingBool(pObjectHandle->IsInputHandle(), "InputHandle"));
if (pObjectHandle->IsInputHandle())
{
handleType = L"input";
InputBuffer* pObj;
RETURN_IF_FAILED(pObjectHandle->GetInputBuffer(GENERIC_READ, &pObj));
m->_pApiRoutines->GetConsoleInputModeImpl(*pObj, a->Mode);
}
else
{
handleType = L"output";
SCREEN_INFORMATION* pObj;
RETURN_IF_FAILED(pObjectHandle->GetScreenBuffer(GENERIC_READ, &pObj));
m->_pApiRoutines->GetConsoleOutputModeImpl(*pObj, a->Mode);
@@ -79,6 +89,12 @@
const auto pObjectHandle = m->GetObjectHandle();
RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle);
TraceConsoleAPICallWithOrigin(
"SetConsoleMode",
TraceLoggingBool(pObjectHandle->IsInputHandle(), "InputHandle"),
TraceLoggingHexULong(a->Mode, "Mode"));
if (pObjectHandle->IsInputHandle())
{
InputBuffer* pObj;
@@ -365,9 +381,6 @@
// Get input parameter buffer
PVOID pvBuffer;
ULONG cbBufferSize;
auto tracing = wil::scope_exit([&]() {
Tracing::s_TraceApi(pvBuffer, a);
});
RETURN_IF_FAILED(m->GetInputBuffer(&pvBuffer, &cbBufferSize));
std::unique_ptr<IWaitRoutine> waiter;
@@ -385,6 +398,11 @@
const std::wstring_view buffer(reinterpret_cast<wchar_t*>(pvBuffer), cbBufferSize / sizeof(wchar_t));
size_t cchInputRead;
TraceConsoleAPICallWithOrigin(
"WriteConsoleW",
TraceLoggingUInt32(a->NumBytes, "NumBytes"),
TraceLoggingCountedWideString(buffer.data(), static_cast<ULONG>(buffer.size()), "Buffer"));
hr = m->_pApiRoutines->WriteConsoleWImpl(*pScreenInfo, buffer, cchInputRead, requiresVtQuirk, waiter);
// We must set the reply length in bytes. Convert back from characters.
@@ -395,6 +413,11 @@
const std::string_view buffer(reinterpret_cast<char*>(pvBuffer), cbBufferSize);
size_t cchInputRead;
TraceConsoleAPICallWithOrigin(
"WriteConsoleA",
TraceLoggingUInt32(a->NumBytes, "NumBytes"),
TraceLoggingCountedString(buffer.data(), static_cast<ULONG>(buffer.size()), "Buffer"));
hr = m->_pApiRoutines->WriteConsoleAImpl(*pScreenInfo, buffer, cchInputRead, requiresVtQuirk, waiter);
// Reply length is already in bytes (chars), don't need to convert.
@@ -581,10 +604,6 @@
Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleScreenBufferInfoEx);
const auto a = &m->u.consoleMsgL2.GetConsoleScreenBufferInfo;
auto tracing = wil::scope_exit([&]() {
Tracing::s_TraceApi(a);
});
CONSOLE_SCREEN_BUFFER_INFOEX ex = { 0 };
ex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
@@ -640,6 +659,12 @@
ex.wAttributes = a->Attributes;
ex.wPopupAttributes = a->PopupAttributes;
TraceConsoleAPICallWithOrigin(
"SetConsoleScreenBufferInfoEx",
TraceLoggingConsoleCoord(a->Size, "BufferSize"),
TraceLoggingConsoleCoord(a->CurrentWindowSize, "WindowSize"),
TraceLoggingConsoleCoord(a->MaximumWindowSize, "MaxWindowSize"));
return m->_pApiRoutines->SetConsoleScreenBufferInfoExImpl(*pObj, ex);
}
@@ -655,6 +680,10 @@
SCREEN_INFORMATION* pObj;
RETURN_IF_FAILED(pObjectHandle->GetScreenBuffer(GENERIC_WRITE, &pObj));
TraceConsoleAPICallWithOrigin(
"SetConsoleScreenBufferSize",
TraceLoggingConsoleCoord(a->Size, "BufferSize"));
return m->_pApiRoutines->SetConsoleScreenBufferSizeImpl(*pObj, til::wrap_coord_size(a->Size));
}
@@ -731,15 +760,16 @@
Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleTextAttribute);
const auto a = &m->u.consoleMsgL2.SetConsoleTextAttribute;
auto tracing = wil::scope_exit([&]() {
Tracing::s_TraceApi(a);
});
const auto pObjectHandle = m->GetObjectHandle();
RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle);
SCREEN_INFORMATION* pObj;
RETURN_IF_FAILED(pObjectHandle->GetScreenBuffer(GENERIC_WRITE, &pObj));
TraceConsoleAPICallWithOrigin(
"SetConsoleTextAttribute",
TraceLoggingHexUInt16(a->Attributes, "Attributes"));
RETURN_HR(m->_pApiRoutines->SetConsoleTextAttributeImpl(*pObj, a->Attributes));
}
@@ -755,6 +785,14 @@
SCREEN_INFORMATION* pObj;
RETURN_IF_FAILED(pObjectHandle->GetScreenBuffer(GENERIC_WRITE, &pObj));
TraceConsoleAPICallWithOrigin(
"SetConsoleWindowInfo",
TraceLoggingBool(a->Absolute, "IsWindowRectAbsolute"),
TraceLoggingInt32(a->Window.Left, "WindowRectLeft"),
TraceLoggingInt32(a->Window.Right, "WindowRectRight"),
TraceLoggingInt32(a->Window.Top, "WindowRectTop"),
TraceLoggingInt32(a->Window.Bottom, "WindowRectBottom"));
return m->_pApiRoutines->SetConsoleWindowInfoImpl(*pObj, a->Absolute, til::wrap_small_rect(a->Window));
}
@@ -911,10 +949,6 @@
{
const auto a = &m->u.consoleMsgL2.WriteConsoleOutputString;
auto tracing = wil::scope_exit([&]() {
Tracing::s_TraceApi(a);
});
switch (a->StringType)
{
case CONSOLE_ATTRIBUTE:
@@ -951,6 +985,11 @@
{
const std::string_view text(reinterpret_cast<char*>(pvBuffer), cbBufferSize);
TraceConsoleAPICallWithOrigin(
"WriteConsoleOutputCharacterA",
TraceLoggingConsoleCoord(a->WriteCoord, "WriteCoord"),
TraceLoggingUInt32(a->NumRecords, "NumRecords"));
hr = m->_pApiRoutines->WriteConsoleOutputCharacterAImpl(*pScreenInfo,
text,
til::wrap_coord(a->WriteCoord),
@@ -963,6 +1002,11 @@
{
const std::wstring_view text(reinterpret_cast<wchar_t*>(pvBuffer), cbBufferSize / sizeof(wchar_t));
TraceConsoleAPICallWithOrigin(
"WriteConsoleOutputCharacterW",
TraceLoggingConsoleCoord(a->WriteCoord, "WriteCoord"),
TraceLoggingUInt32(a->NumRecords, "NumRecords"));
hr = m->_pApiRoutines->WriteConsoleOutputCharacterWImpl(*pScreenInfo,
text,
til::wrap_coord(a->WriteCoord),
@@ -974,6 +1018,11 @@
{
const std::span<const WORD> text(reinterpret_cast<WORD*>(pvBuffer), cbBufferSize / sizeof(WORD));
TraceConsoleAPICallWithOrigin(
"WriteConsoleOutputAttribute",
TraceLoggingConsoleCoord(a->WriteCoord, "WriteCoord"),
TraceLoggingUInt32(a->NumRecords, "NumRecords"));
hr = m->_pApiRoutines->WriteConsoleOutputAttributeImpl(*pScreenInfo,
text,
til::wrap_coord(a->WriteCoord),

View File

@@ -174,7 +174,6 @@ PCONSOLE_API_MSG ApiSorter::ConsoleDispatchRequest(_Inout_ PCONSOLE_API_MSG Mess
// alias API.
NTSTATUS Status = S_OK;
{
const auto trace = Tracing::s_TraceApiCall(Status, Descriptor->TraceName);
Status = (*Descriptor->Routine)(Message, &ReplyPending);
}
if (Status != STATUS_BUFFER_TOO_SMALL)

View File

@@ -28,7 +28,7 @@ ConsoleProcessHandle::ConsoleProcessHandle(const DWORD dwProcessId,
dwProcessId))),
_policy(ConsoleProcessPolicy::s_CreateInstance(_hProcess.get())),
_shimPolicy(_hProcess.get()),
_processCreationTime(0)
_processCreationTime{}
{
if (nullptr != _hProcess.get())
{
@@ -77,24 +77,17 @@ const HANDLE ConsoleProcessHandle::GetRawHandle() const
// Routine Description:
// - Retrieves the process creation time (currently used in telemetry traces)
// - The creation time is lazily populated on first call
const ULONG64 ConsoleProcessHandle::GetProcessCreationTime() const
const FILETIME ConsoleProcessHandle::GetProcessCreationTime() const
{
if (_processCreationTime == 0 && _hProcess != nullptr)
if (_processCreationTime.dwHighDateTime == 0 && _processCreationTime.dwLowDateTime == 0 && _hProcess != nullptr)
{
FILETIME ftCreationTime, ftDummyTime = { 0 };
ULARGE_INTEGER creationTime = { 0 };
FILETIME ftDummyTime = { 0 };
if (::GetProcessTimes(_hProcess.get(),
&ftCreationTime,
&ftDummyTime,
&ftDummyTime,
&ftDummyTime))
{
creationTime.HighPart = ftCreationTime.dwHighDateTime;
creationTime.LowPart = ftCreationTime.dwLowDateTime;
}
_processCreationTime = creationTime.QuadPart;
::GetProcessTimes(_hProcess.get(),
&_processCreationTime,
&ftDummyTime,
&ftDummyTime,
&ftDummyTime);
}
return _processCreationTime;

View File

@@ -53,14 +53,14 @@ public:
CD_CONNECTION_INFORMATION GetConnectionInformation(IDeviceComm* deviceComm) const;
const ULONG64 GetProcessCreationTime() const;
const FILETIME GetProcessCreationTime() const;
private:
ULONG _ulTerminateCount;
ULONG const _ulProcessGroupId;
wil::unique_handle const _hProcess;
mutable ULONG64 _processCreationTime;
mutable FILETIME _processCreationTime;
const ConsoleProcessPolicy _policy;
const ConsoleShimPolicy _shimPolicy;

View File

@@ -166,7 +166,8 @@ namespace Microsoft::Console::VirtualTerminal
constexpr VTParameter at(const size_t index) const noexcept
{
return til::at(_subParams, index);
// If the index is out of range, we return a sub parameter with no value.
return index < _subParams.size() ? til::at(_subParams, index) : defaultParameter;
}
VTSubParameters subspan(const size_t offset, const size_t count) const noexcept
@@ -191,6 +192,8 @@ namespace Microsoft::Console::VirtualTerminal
}
private:
static constexpr VTParameter defaultParameter{};
std::span<const VTParameter> _subParams;
};

View File

@@ -4117,14 +4117,14 @@ void AdaptDispatch::_ReportSGRSetting() const
else if (color.IsIndex256())
{
const auto index = color.GetIndex();
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L";{};5;{}"), base + 8, index);
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L";{}:5:{}"), base + 8, index);
}
else if (color.IsRgb())
{
const auto r = GetRValue(color.GetRGB());
const auto g = GetGValue(color.GetRGB());
const auto b = GetBValue(color.GetRGB());
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L";{};2;{};{};{}"), base + 8, r, g, b);
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L";{}:2::{}:{}:{}"), base + 8, r, g, b);
}
};
addColor(30, attr.GetForeground());

View File

@@ -297,12 +297,15 @@ namespace Microsoft::Console::VirtualTerminal
size_t _SetRgbColorsHelper(const VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept;
void _SetRgbColorsHelperFromSubParams(const VTParameter colorItem,
const VTSubParameters options,
TextAttribute& attr) noexcept;
size_t _ApplyGraphicsOption(const VTParameters options,
const size_t optionIndex,
TextAttribute& attr) noexcept;
void _ApplyGraphicsOptionSubParam(const VTParameter option,
const VTSubParameters subParams,
TextAttribute& attr) noexcept;
void _ApplyGraphicsOptionWithSubParams(const VTParameter option,
const VTSubParameters subParams,
TextAttribute& attr) noexcept;
void _ApplyGraphicsOptions(const VTParameters options,
TextAttribute& attr) noexcept;

View File

@@ -35,7 +35,8 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,
const size_t red = options.at(1).value_or(0);
const size_t green = options.at(2).value_or(0);
const size_t blue = options.at(3).value_or(0);
// ensure that each value fits in a byte
// We only apply the color if the R, G, B values fit within a byte.
// This is to match XTerm's and VTE's behavior.
if (red <= 255 && green <= 255 && blue <= 255)
{
const auto rgbColor = RGB(red, green, blue);
@@ -46,6 +47,9 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,
{
optionsConsumed = 2;
const size_t tableIndex = options.at(1).value_or(0);
// We only apply the color if the index value fit within a byte.
// This is to match XTerm's and VTE's behavior.
if (tableIndex <= 255)
{
const auto adjustedIndex = gsl::narrow_cast<BYTE>(tableIndex);
@@ -62,6 +66,77 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,
return optionsConsumed;
}
// Routine Description:
// - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG)
// - These options are followed by either a 2 (RGB) or 5 (xterm index):
// - RGB sequences then take 4 MORE options to designate the ColorSpaceID, R, G, B parts
// of the color.
// - Xterm index will use the option that follows to use a color from the
// preset 256 color xterm color table.
// Arguments:
// - colorItem - One of FG(38) and BG(48), indicating which color we're setting.
// - options - An array of options that will be used to generate the RGB color
// - attr - The attribute that will be updated with the parsed color.
// Return Value:
// - <none>
void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem,
const VTSubParameters options,
TextAttribute& attr) noexcept
{
// This should be called for applying FG and BG colors only.
assert(colorItem == GraphicsOptions::ForegroundExtended ||
colorItem == GraphicsOptions::BackgroundExtended);
const bool isForeground = (colorItem == GraphicsOptions::ForegroundExtended);
const DispatchTypes::GraphicsOptions typeOpt = options.at(0);
if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint)
{
// sub params are in the order:
// :2:<color-space-id>:<r>:<g>:<b>
// We treat a color as invalid, if it has a color space ID, as some
// applications that support non-standard ODA color sequence may send
// the red value in its place.
const bool hasColorSpaceId = options.at(1).has_value();
// Skip color-space-id.
const size_t red = options.at(2).value_or(0);
const size_t green = options.at(3).value_or(0);
const size_t blue = options.at(4).value_or(0);
// We only apply the color if the R, G, B values fit within a byte.
// This is to match XTerm's and VTE's behavior.
if (!hasColorSpaceId && red <= 255 && green <= 255 && blue <= 255)
{
const auto rgbColor = RGB(red, green, blue);
attr.SetColor(rgbColor, isForeground);
}
}
else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index)
{
// sub params are in the order:
// :5:<n>
// where 'n' is the index into the xterm color table.
const size_t tableIndex = options.at(1).value_or(0);
// We only apply the color if the index value fit within a byte.
// This is to match XTerm's and VTE's behavior.
if (tableIndex <= 255)
{
const auto adjustedIndex = gsl::narrow_cast<BYTE>(tableIndex);
if (isForeground)
{
attr.SetIndexedForeground256(adjustedIndex);
}
else
{
attr.SetIndexedBackground256(adjustedIndex);
}
}
}
}
// Routine Description:
// - Helper to apply a single graphic rendition option to an attribute.
// - Calls appropriate helper to apply the option with sub parameters when necessary.
@@ -80,7 +155,7 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options,
if (options.hasSubParamsFor(optionIndex))
{
const auto subParams = options.subParamsFor(optionIndex);
_ApplyGraphicsOptionSubParam(opt, subParams, attr);
_ApplyGraphicsOptionWithSubParams(opt, subParams, attr);
return 1;
}
@@ -260,20 +335,30 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options,
}
// Routine Description:
// - This is a no-op until we have something meaningful to do with sub parameters.
// - Helper to apply a single graphic rendition option with sub parameters to an attribute.
// Arguments:
// - option - An option to apply.
// - subParams - Sub parameters associated with the option.
// - attr - The attribute that will be updated with the applied option.
// Return Value:
// - <None>
void AdaptDispatch::_ApplyGraphicsOptionSubParam(const VTParameter /* option */,
const VTSubParameters /* subParam */,
TextAttribute& /* attr */) noexcept
void AdaptDispatch::_ApplyGraphicsOptionWithSubParams(const VTParameter option,
const VTSubParameters subParams,
TextAttribute& attr) noexcept
{
// here, we apply our "best effort" rule, while handling sub params if we don't
// recognise the parameter substring (parameter and it's sub parameters) then
// we should just skip over them.
switch (option)
{
case ForegroundExtended:
case BackgroundExtended:
_SetRgbColorsHelperFromSubParams(option, subParams, attr);
break;
default:
/* do nothing */
break;
}
}
// Routine Description:

View File

@@ -1699,7 +1699,7 @@ public:
attribute.SetIndexedBackground256(45);
_testGetSet->_textBuffer->SetCurrentAttributes(attribute);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;5;123;48;5;45m\033\\");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38:5:123;48:5:45m\033\\");
Log::Comment(L"Requesting SGR attributes (ITU RGB colors).");
_testGetSet->PrepData();
@@ -1708,7 +1708,7 @@ public:
attribute.SetBackground(RGB(65, 43, 21));
_testGetSet->_textBuffer->SetCurrentAttributes(attribute);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;2;12;34;56;48;2;65;43;21m\033\\");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38:2::12:34:56;48:2::65:43:21m\033\\");
Log::Comment(L"Requesting DECSCA attributes (unprotected).");
_testGetSet->PrepData();
@@ -2464,6 +2464,119 @@ public:
rgOptions[4] = 123;
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 123));
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 5 }));
Log::Comment(L"Test 6: Ignore Rgb color when R, G or B is out of range (>255)");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgOptions[2] = 283;
rgOptions[3] = 182;
rgOptions[4] = 123;
// expect no change
_testGetSet->_expectedAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 5 }));
Log::Comment(L"Test 7: Ignore indexed color when index is out of range (>255)");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = 283;
// expect no change
_testGetSet->_expectedAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 3 }));
}
TEST_METHOD(XtermExtendedSubParameterColorTest)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[1];
VTParameter rgSubParamOpts[16];
std::pair<BYTE, BYTE> subParamRanges[1];
_testGetSet->_expectedAttribute = _testGetSet->_textBuffer->GetCurrentAttributes();
Log::Comment(L"Test 1: Change Indexed Foreground with missing index sub parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
subParamRanges[0] = { (BYTE)0, (BYTE)1 };
_testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::DARK_BLACK);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 2: Change Indexed Background with default index sub parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgSubParamOpts[1] = {};
subParamRanges[0] = { (BYTE)0, (BYTE)2 };
_testGetSet->_expectedAttribute.SetIndexedBackground256(TextColor::DARK_BLACK);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 3: Change RGB Foreground with all RGB sub parameters missing");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
subParamRanges[0] = { (BYTE)0, (BYTE)1 };
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 0));
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 4: Change RGB Background with some missing RGB sub parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgSubParamOpts[1] = {}; // color-space-id
rgSubParamOpts[2] = 123;
subParamRanges[0] = { (BYTE)0, (BYTE)3 };
_testGetSet->_expectedAttribute.SetBackground(RGB(123, 0, 0));
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 5: Change RGB Foreground with some default RGB sub parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgSubParamOpts[1] = {}; // color-space-id
rgSubParamOpts[2] = {};
rgSubParamOpts[3] = {};
rgSubParamOpts[4] = 123;
subParamRanges[0] = { (BYTE)0, (BYTE)5 };
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 123));
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 6: Ignore color when ColorSpaceID is not empty");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgSubParamOpts[1] = 7; // color-space-id
rgSubParamOpts[2] = 182;
rgSubParamOpts[3] = 182;
rgSubParamOpts[4] = 123;
subParamRanges[0] = { (BYTE)0, (BYTE)5 };
// expect no change
_testGetSet->_expectedAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 7: Ignore Rgb color when R, G or B is out of range (>255)");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgSubParamOpts[1] = {}; // color-space-id
// Ensure r, g and b set a color that is different from current color.
// Otherwise, the test will pass even if the color is not ignored.
rgSubParamOpts[2] = 128;
rgSubParamOpts[3] = 283;
rgSubParamOpts[4] = 155;
subParamRanges[0] = { (BYTE)0, (BYTE)5 };
// expect no change
_testGetSet->_expectedAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
Log::Comment(L"Test 8: Ignore indexed color when index is out of range (>255)");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgSubParamOpts[0] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgSubParamOpts[1] = 283;
subParamRanges[0] = { (BYTE)0, (BYTE)2 };
// expect no change
_testGetSet->_expectedAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, rgSubParamOpts, subParamRanges }));
}
TEST_METHOD(SetColorTableValue)