terminal/src/cascadia/TerminalControl/TermControl.cpp

3069 lines
129 KiB
C++
Raw Normal View History

2020-02-12 20:06:46 +01:00
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TermControl.h"
#include <argb.h>
#include <DefaultSettings.h>
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <WinUser.h>
#include <LibraryResources.h>
#include "..\..\types\inc\GlyphWidth.hpp"
#include "TermControl.g.cpp"
#include "TermControlAutomationPeer.h"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::VirtualTerminal;
2020-02-12 20:06:46 +01:00
using namespace ::Microsoft::Terminal::Core;
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
using namespace winrt::Windows::Graphics::Display;
2020-02-12 20:06:46 +01:00
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Input;
2020-02-12 20:06:46 +01:00
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::UI::Input;
2020-02-12 20:06:46 +01:00
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
// The minimum delay between updates to the scroll bar's values.
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);
DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::TerminalControl::CopyFormat);
2020-02-12 20:06:46 +01:00
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */)
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static bool initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
}
TermControl::TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection) :
2020-02-12 20:06:46 +01:00
_connection{ connection },
_initializedTerminal{ false },
_settings{ settings },
_closing{ false },
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
_isInternalScrollBarUpdate{ false },
2020-02-12 20:06:46 +01:00
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
_autoScrollTimer{},
_lastAutoScrollUpdateTime{ std::nullopt },
Add font weight options (#6048) ## Summary of the Pull Request Adds the ability to specify the font weight in the profiles, right next to the size and the font face. ## PR Checklist * [x] Closes #1751 * [x] I work here. * [x] Tested manually, see below. * [x] Added documentation to the schema * [x] Requires docs.microsoft.com update, filed as https://github.com/MicrosoftDocs/terminal/issues/26 * [x] I'm a core contributor. ## Detailed Description of the Pull Request / Additional comments - Weights can be specified according to the OpenType specification values. We accept either the friendly name or the numerical value that applies to each weight. - Weights are carried through per-profile and sent into the renderer. - Weights are carried through to the TSF/IME overlay. - The names are restricted to the set seen at https://docs.microsoft.com/en-us/uwp/api/windows.ui.text.fontweights. - There are alternate names at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_weight for the same values (ultra-black is just an alias for extra-black at 950). ## Validation Steps Performed - Cascadia Code normal ![image](https://user-images.githubusercontent.com/18221333/82480181-46117380-9a88-11ea-9436-a5fe4ccd4350.png) - Cascadia Code bold ![image](https://user-images.githubusercontent.com/18221333/82480202-4f9adb80-9a88-11ea-9e27-a113b41387f5.png) - Segoe UI Semilight ![image](https://user-images.githubusercontent.com/18221333/82480306-73f6b800-9a88-11ea-93f7-d773ab7ccce8.png) - Segoe UI Black ![image](https://user-images.githubusercontent.com/18221333/82480401-9688d100-9a88-11ea-957c-0c8e03a8cc29.png) - Segoe UI 900 (value for Black) ![image](https://user-images.githubusercontent.com/18221333/82480774-26c71600-9a89-11ea-8cf6-aaeab1fd0747.png)
2020-05-20 22:17:17 +02:00
_desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 },
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
2020-02-12 20:06:46 +01:00
_touchAnchor{ std::nullopt },
_cursorTimer{},
Add support for the "blink" graphic rendition attribute (#7490) This PR adds support for the _blink_ graphic rendition attribute. When a character is output with this attribute set, it "blinks" at a regular interval, by cycling its color between the normal rendition and a dimmer shade of that color. The majority of the blinking mechanism is encapsulated in a new `BlinkingState` class, which is shared between the Terminal and Conhost implementations. This class keeps track of the position in the blinking cycle, which determines whether characters are rendered as normal or faint. In Windows Terminal, the state is stored in the `Terminal` class, and in Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases, the `IsBlinkingFaint` method is used to determine the current blinking rendition, and that is passed on as a parameter to the `TextAttribute::CalculateRgbColors` method when these classes are looking up attribute colors. Prior to calculating the colors, the current attribute is also passed to the `RecordBlinkingUsage` method, which keeps track of whether there are actually any blink attributes in use. This is used to determine whether the screen needs to be refreshed when the blinking cycle toggles between the normal and faint renditions. The refresh itself is handled by the `ToggleBlinkingRendition` method, which is triggered by a timer. In Conhost this is just piggybacking on the existing cursor blink timer, but in Windows Terminal it needs to have its own separate timer, since the cursor timer is reset whenever a key is pressed, which is not something we want for attribute blinking. Although the `ToggleBlinkingRendition` is called at the same rate as the cursor blinking, we actually only want the cells to blink at half that frequency. We thus have a counter that cycles through four phases, and blinking is rendered as faint for two of those four. Then every two cycles - when the state changes - a redraw is triggered, but only if there are actually blinking attributes in use (as previously recorded). As mentioned earlier, the blinking frequency is based on the cursor blink rate, so that means it'll automatically be disabled if a user has set their cursor blink rate to none. It can also be disabled by turning off the _Show animations in Windows_ option. In Conhost these settings take effect immediately, but in Windows Terminal they only apply when a new tab is opened. This PR also adds partial support for the `SGR 6` _rapid blink_ attribute. This is not used by DEC terminals, but was defined in the ECMA/ANSI standards. It's not widely supported, but many terminals just it implement it as an alias for the regular `SGR 5` blink attribute, so that's what I've done here too. ## Validation Steps Performed I've checked the _Graphic rendition test pattern_ in Vttest, and compared our representation of the blink attribute to that of an actual DEC VT220 terminal as seen on [YouTube]. With the right color scheme it's a reasonably close match. [YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s Closes #7388
2020-09-22 01:21:33 +02:00
_blinkTimer{},
Ignore right-click copy when copy on select is enabled (#4819) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Right clicking on a focused tab while Copy On Select is active currently copies any active selection. This is because `PointerReleasedHandler` doesn't check for the mouse button that was released. During a mouse button release, only the left mouse button release should be doing anything. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4740 * [x] CLA signed. * [x] Tests added/passed <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed These are the scenarios I've tested. They're a combination of in focus/out of focus, Copy On Select on/off, left/right click pressed and their move and release variants. From Out of Focus: - Left Click = Focus - Left Click Move = Focus + Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Focus - Right Click - Focus - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing From In Focus - Left Click = Selection if CoS off - Left Click Move = Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Set Selection Anchor - Right Click - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing
2020-03-10 15:59:16 +01:00
_lastMouseClickTimestamp{},
2020-02-12 20:06:46 +01:00
_lastMouseClickPos{},
2020-03-25 22:09:49 +01:00
_selectionNeedsToBeCopied{ false },
_searchBox{ nullptr }
2020-02-12 20:06:46 +01:00
{
_EnsureStaticInitialization();
InitializeComponent();
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
auto pfnWarningBell = std::bind(&TermControl::_TerminalWarningBell, this);
_terminal->SetWarningBellCallback(pfnWarningBell);
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
_terminal->SetTitleChangedCallback(pfnTitleChanged);
auto pfnTabColorChanged = std::bind(&TermControl::_TerminalTabColorChanged, this, std::placeholders::_1);
_terminal->SetTabColorChangedCallback(pfnTabColorChanged);
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
auto pfnTerminalCursorPositionChanged = std::bind(&TermControl::_TerminalCursorPositionChanged, this);
_terminal->SetCursorPositionChangedCallback(pfnTerminalCursorPositionChanged);
auto pfnCopyToClipboard = std::bind(&TermControl::_CopyToClipboard, this, std::placeholders::_1);
_terminal->SetCopyToClipboardCallback(pfnCopyToClipboard);
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// This event is explicitly revoked in the destructor: does not need weak_ref
auto onReceiveOutputFn = [this](const hstring str) {
_terminal->Write(str);
_updatePatternLocations->Run();
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
};
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
_terminal->SetWriteInputCallback([this](std::wstring& wstr) {
_SendInputToConnection(wstr);
});
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_terminal->UpdateSettings(settings);
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Subscribe to the connection's disconnected event and call our connection closed handlers.
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
_ConnectionStateChangedHandlers(*this, nullptr);
});
2020-02-12 20:06:46 +01:00
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
2020-02-12 20:06:46 +01:00
// This event fires every time the layout changes, but it is always the last one to fire
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
// Any earlier than the last layout update and we may not know the terminal's starting size.
if (_InitializeTerminal())
{
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
}
});
_tsfTryRedrawCanvas = std::make_shared<ThrottledFunc<>>(
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() })
{
control->TSFInputControl().TryRedrawCanvas();
}
},
TsfRedrawInterval,
Dispatcher());
_updatePatternLocations = std::make_shared<ThrottledFunc<>>(
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() })
{
control->UpdatePatternLocations();
}
},
UpdatePatternLocationsInterval,
Dispatcher());
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() })
{
control->_isInternalScrollBarUpdate = true;
auto scrollBar = control->ScrollBar();
if (update.newValue.has_value())
{
scrollBar.Value(update.newValue.value());
}
scrollBar.Maximum(update.newMaximum);
scrollBar.Minimum(update.newMinimum);
scrollBar.ViewportSize(update.newViewportSize);
scrollBar.LargeChange(std::max(update.newViewportSize - 1, 0.)); // scroll one "screenful" at a time when the scroll bar is clicked
control->_isInternalScrollBarUpdate = false;
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
}
},
ScrollBarUpdateInterval,
Dispatcher());
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
2020-02-12 20:06:46 +01:00
_ApplyUISettings();
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Loads the search box from the xaml UI and focuses it.
2020-02-12 20:06:46 +01:00
void TermControl::CreateSearchBoxControl()
{
// Lazy load the search box control.
if (auto loadedSearchBox{ FindName(L"SearchBox") })
2020-02-12 20:06:46 +01:00
{
if (auto searchBox{ loadedSearchBox.try_as<::winrt::Microsoft::Terminal::TerminalControl::SearchBoxControl>() })
{
// get at its private implementation
_searchBox.copy_from(winrt::get_self<implementation::SearchBoxControl>(searchBox));
_searchBox->Visibility(Visibility::Visible);
_searchBox->SetFocusOnTextbox();
}
2020-02-12 20:06:46 +01:00
}
}
// Method Description:
// - Search text in text buffer. This is triggered if the user click
// search button or press enter.
// Arguments:
// - text: the text to search
// - goForward: boolean that represents if the current search direction is forward
// - caseSensitive: boolean that represents if the current search is case sensitive
// Return Value:
// - <none>
void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (text.size() == 0 || _closing)
2020-02-12 20:06:46 +01:00
{
return;
}
const Search::Direction direction = goForward ?
Search::Direction::Forward :
Search::Direction::Backward;
const Search::Sensitivity sensitivity = caseSensitive ?
Search::Sensitivity::CaseSensitive :
Search::Sensitivity::CaseInsensitive;
Search search(*GetUiaData(), text.c_str(), direction, sensitivity);
auto lock = _terminal->LockForWriting();
if (search.FindNext())
{
Move rect expansion to textbuffer; refactor selection code (#4560) - When performing chunk selection, the expansion now occurs at the time of the selection, not the rendering of the selection - `GetSelectionRects()` was moved to the `TextBuffer` and is now shared between ConHost and Windows Terminal - Some of the selection variables were renamed for clarity - Selection COORDs are now in the Text Buffer coordinate space - Fixes an issue with Shift+Click after performing a Multi-Click Selection ## References This also contributes to... - #4509: UIA Box Selection - #2447: UIA Signaling for Selection - #1354: UIA support for Wide Glyphs Now that the expansion occurs at before render-time, the selection anchors are an accurate representation of what is selected. We just need to move `GetText` to the `TextBuffer`. Then we can have those three issues just rely on code from the text buffer. This also means ConHost gets some of this stuff for free 😀 ### TextBuffer - `GetTextRects` is the abstracted form of `GetSelectionRects` - `_ExpandTextRow` is still needed to handle wide glyphs properly ### Terminal - Rename... - `_boxSelection` --> `_blockSelection` for consistency with ConHost - `_selectionAnchor` --> `_selectionStart` for consistency with UIA - `_endSelectionPosition` --> `_selectionEnd` for consistency with UIA - Selection anchors are in Text Buffer coordinates now - Really rely on `SetSelectionEnd` to accomplish appropriate chunk selection and shift+click actions ## Validation Steps Performed - Shift+Click - Multi-Click --> Shift+Click - Chunk Selection at... - top of buffer - bottom of buffer - random region in scrollback Closes #4465 Closes #4547
2020-02-28 01:42:26 +01:00
_terminal->SetBlockSelection(false);
2020-02-12 20:06:46 +01:00
search.Select();
_renderer->TriggerSelection();
}
}
// Method Description:
// - The handler for the close button or pressing "Esc" when focusing on the
// search dialog.
// Arguments:
// - IInspectable: not used
// - RoutedEventArgs: not used
// Return Value:
// - <none>
void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/)
{
_searchBox->Visibility(Visibility::Collapsed);
2020-02-12 20:06:46 +01:00
// Set focus back to terminal control
this->Focus(FocusState::Programmatic);
}
// Method Description:
// - Given new settings for this profile, applies the settings to the current terminal.
// Arguments:
// - newSettings: New settings values for the profile in this terminal.
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::UpdateSettings(IControlSettings newSettings)
2020-02-12 20:06:46 +01:00
{
_settings = newSettings;
auto weakThis{ get_weak() };
// Dispatch a call to the UI thread to apply the new settings to the
// terminal.
co_await winrt::resume_foreground(Dispatcher());
2020-02-12 20:06:46 +01:00
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
co_return;
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
}
2020-02-12 20:06:46 +01:00
// Update our control settings
_ApplyUISettings();
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
auto lock = _terminal->LockForWriting();
// Update DxEngine settings under the lock
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
_renderEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
2020-02-12 20:06:46 +01:00
// Refresh our font with the renderer
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto actualFontOldSize = _actualFont.GetSize();
2020-02-12 20:06:46 +01:00
_UpdateFont();
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto actualFontNewSize = _actualFont.GetSize();
if (actualFontNewSize != actualFontOldSize)
2020-02-12 20:06:46 +01:00
{
_RefreshSizeUnderLock();
2020-02-12 20:06:46 +01:00
}
}
}
// Method Description:
// - Writes the given sequence as input to the active terminal connection,
// Arguments:
// - wstr: the string of characters to write to the terminal connection.
// Return Value:
// - <none>
void TermControl::SendInput(const winrt::hstring& wstr)
{
_SendInputToConnection(wstr);
}
void TermControl::ToggleRetroEffect()
{
auto lock = _terminal->LockForWriting();
_renderEngine->SetRetroTerminalEffects(!_renderEngine->GetRetroTerminalEffects());
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Style our UI elements based on the values in our _settings, and set up
// other control-specific settings. This method will be called whenever
// the settings are reloaded.
// * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible
// for the control's background
// * Calls _BackgroundColorChanged to style the background of the control
// - Core settings will be passed to the terminal in _InitializeTerminal
// Arguments:
// - <none>
// Return Value:
// - <none>
void TermControl::_ApplyUISettings()
{
_InitializeBackgroundBrush();
COLORREF bg = _settings.DefaultBackground();
2020-02-12 20:06:46 +01:00
_BackgroundColorChanged(bg);
// Apply padding as swapChainPanel's margin
auto newMargin = _ParseThicknessFromPadding(_settings.Padding());
SwapChainPanel().Margin(newMargin);
2020-02-12 20:06:46 +01:00
// Initialize our font information.
const auto fontFace = _settings.FontFace();
const short fontHeight = gsl::narrow_cast<short>(_settings.FontSize());
Add font weight options (#6048) ## Summary of the Pull Request Adds the ability to specify the font weight in the profiles, right next to the size and the font face. ## PR Checklist * [x] Closes #1751 * [x] I work here. * [x] Tested manually, see below. * [x] Added documentation to the schema * [x] Requires docs.microsoft.com update, filed as https://github.com/MicrosoftDocs/terminal/issues/26 * [x] I'm a core contributor. ## Detailed Description of the Pull Request / Additional comments - Weights can be specified according to the OpenType specification values. We accept either the friendly name or the numerical value that applies to each weight. - Weights are carried through per-profile and sent into the renderer. - Weights are carried through to the TSF/IME overlay. - The names are restricted to the set seen at https://docs.microsoft.com/en-us/uwp/api/windows.ui.text.fontweights. - There are alternate names at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_weight for the same values (ultra-black is just an alias for extra-black at 950). ## Validation Steps Performed - Cascadia Code normal ![image](https://user-images.githubusercontent.com/18221333/82480181-46117380-9a88-11ea-9436-a5fe4ccd4350.png) - Cascadia Code bold ![image](https://user-images.githubusercontent.com/18221333/82480202-4f9adb80-9a88-11ea-9e27-a113b41387f5.png) - Segoe UI Semilight ![image](https://user-images.githubusercontent.com/18221333/82480306-73f6b800-9a88-11ea-93f7-d773ab7ccce8.png) - Segoe UI Black ![image](https://user-images.githubusercontent.com/18221333/82480401-9688d100-9a88-11ea-957c-0c8e03a8cc29.png) - Segoe UI 900 (value for Black) ![image](https://user-images.githubusercontent.com/18221333/82480774-26c71600-9a89-11ea-8cf6-aaeab1fd0747.png)
2020-05-20 22:17:17 +02:00
const auto fontWeight = _settings.FontWeight();
2020-02-12 20:06:46 +01:00
// The font width doesn't terribly matter, we'll only be using the
// height to look it up
// The other params here also largely don't matter.
// The family is only used to determine if the font is truetype or
// not, but DX doesn't use that info at all.
// The Codepage is additionally not actually used by the DX engine at all.
Add font weight options (#6048) ## Summary of the Pull Request Adds the ability to specify the font weight in the profiles, right next to the size and the font face. ## PR Checklist * [x] Closes #1751 * [x] I work here. * [x] Tested manually, see below. * [x] Added documentation to the schema * [x] Requires docs.microsoft.com update, filed as https://github.com/MicrosoftDocs/terminal/issues/26 * [x] I'm a core contributor. ## Detailed Description of the Pull Request / Additional comments - Weights can be specified according to the OpenType specification values. We accept either the friendly name or the numerical value that applies to each weight. - Weights are carried through per-profile and sent into the renderer. - Weights are carried through to the TSF/IME overlay. - The names are restricted to the set seen at https://docs.microsoft.com/en-us/uwp/api/windows.ui.text.fontweights. - There are alternate names at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_weight for the same values (ultra-black is just an alias for extra-black at 950). ## Validation Steps Performed - Cascadia Code normal ![image](https://user-images.githubusercontent.com/18221333/82480181-46117380-9a88-11ea-9436-a5fe4ccd4350.png) - Cascadia Code bold ![image](https://user-images.githubusercontent.com/18221333/82480202-4f9adb80-9a88-11ea-9e27-a113b41387f5.png) - Segoe UI Semilight ![image](https://user-images.githubusercontent.com/18221333/82480306-73f6b800-9a88-11ea-93f7-d773ab7ccce8.png) - Segoe UI Black ![image](https://user-images.githubusercontent.com/18221333/82480401-9688d100-9a88-11ea-957c-0c8e03a8cc29.png) - Segoe UI 900 (value for Black) ![image](https://user-images.githubusercontent.com/18221333/82480774-26c71600-9a89-11ea-8cf6-aaeab1fd0747.png)
2020-05-20 22:17:17 +02:00
_actualFont = { fontFace, 0, fontWeight.Weight, { 0, fontHeight }, CP_UTF8, false };
2020-02-12 20:06:46 +01:00
_desiredFont = { _actualFont };
// set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(static_cast<til::color>(_settings.DefaultForeground()));
TSFInputControl().Foreground(foregroundBrush);
TSFInputControl().Margin(newMargin);
// Apply settings for scrollbar
if (_settings.ScrollState() == ScrollbarState::Hidden)
{
// In the scenario where the user has turned off the OS setting to automatically hide scrollbars, the
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
// achieve the intended effect.
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
ScrollBar().Visibility(Visibility::Collapsed);
}
else // (default or Visible)
{
// Default behavior
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
ScrollBar().Visibility(Visibility::Visible);
}
2020-02-12 20:06:46 +01:00
_UpdateSystemParameterSettings();
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Set up each layer's brush used to display the control's background.
// - Respects the settings for acrylic, background image and opacity from
// _settings.
// * If acrylic is not enabled, setup a solid color background, otherwise
// use bgcolor as acrylic's tint
// - Avoids image flickering and acrylic brush redraw if settings are changed
// but the appropriate brush is still in place.
// - Does not apply background color outside of acrylic mode;
// _BackgroundColorChanged must be called to do so.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TermControl::_InitializeBackgroundBrush()
{
if (_settings.UseAcrylic())
{
// See if we've already got an acrylic background brush
// to avoid the flicker when setting up a new one
auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>();
2020-02-12 20:06:46 +01:00
// Instantiate a brush if there's not already one there
if (acrylic == nullptr)
{
acrylic = Media::AcrylicBrush{};
acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
}
// see GH#1082: Initialize background color so we don't get a
// fade/flash when _BackgroundColorChanged is called
uint32_t color = _settings.DefaultBackground();
winrt::Windows::UI::Color bgColor{};
bgColor.R = GetRValue(color);
bgColor.G = GetGValue(color);
bgColor.B = GetBValue(color);
bgColor.A = 255;
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
// Apply brush settings
acrylic.TintOpacity(_settings.TintOpacity());
// Apply brush to control if it's not already there
if (RootGrid().Background() != acrylic)
2020-02-12 20:06:46 +01:00
{
RootGrid().Background(acrylic);
2020-02-12 20:06:46 +01:00
}
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
// GH#5098: Inform the engine of the new opacity of the default text background.
if (_renderEngine)
{
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
}
2020-02-12 20:06:46 +01:00
}
else
{
Media::SolidColorBrush solidColor{};
RootGrid().Background(solidColor);
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
// GH#5098: Inform the engine of the new opacity of the default text background.
if (_renderEngine)
{
_renderEngine->SetDefaultTextBackgroundOpacity(1.0f);
}
2020-02-12 20:06:46 +01:00
}
if (!_settings.BackgroundImage().empty())
{
Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() };
// Check if the image brush is already pointing to the image
// in the modified settings; if it isn't (or isn't there),
// set a new image source for the brush
auto imageSource = BackgroundImage().Source().try_as<Media::Imaging::BitmapImage>();
2020-02-12 20:06:46 +01:00
if (imageSource == nullptr ||
imageSource.UriSource() == nullptr ||
imageSource.UriSource().RawUri() != imageUri.RawUri())
{
// Note that BitmapImage handles the image load asynchronously,
// which is especially important since the image
// may well be both large and somewhere out on the
// internet.
Media::Imaging::BitmapImage image(imageUri);
BackgroundImage().Source(image);
2020-02-12 20:06:46 +01:00
}
// Apply stretch, opacity and alignment settings
BackgroundImage().Stretch(_settings.BackgroundImageStretchMode());
BackgroundImage().Opacity(_settings.BackgroundImageOpacity());
BackgroundImage().HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment());
BackgroundImage().VerticalAlignment(_settings.BackgroundImageVerticalAlignment());
2020-02-12 20:06:46 +01:00
}
else
{
BackgroundImage().Source(nullptr);
2020-02-12 20:06:46 +01:00
}
}
// Method Description:
// - Style the background of the control with the provided background color
// Arguments:
// - color: The background color to use as a uint32 (aka DWORD COLORREF)
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::_BackgroundColorChanged(const COLORREF color)
2020-02-12 20:06:46 +01:00
{
til::color newBgColor{ color };
2020-02-12 20:06:46 +01:00
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
2020-02-12 20:06:46 +01:00
if (auto control{ weakThis.get() })
{
if (auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>())
2020-02-12 20:06:46 +01:00
{
acrylic.FallbackColor(newBgColor);
acrylic.TintColor(newBgColor);
2020-02-12 20:06:46 +01:00
}
else if (auto solidColor = RootGrid().Background().try_as<Media::SolidColorBrush>())
2020-02-12 20:06:46 +01:00
{
solidColor.Color(newBgColor);
2020-02-12 20:06:46 +01:00
}
// Set the default background as transparent to prevent the
// DX layer from overwriting the background image or acrylic effect
_settings.DefaultBackground(static_cast<COLORREF>(newBgColor.with_alpha(0)));
2020-02-12 20:06:46 +01:00
}
}
TermControl::~TermControl()
{
Close();
}
// Method Description:
// - Creates an automation peer for the Terminal Control, enabling accessibility on our control.
// Arguments:
// - None
// Return Value:
// - The automation peer for our control
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
try
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_initializedTerminal && !_closing) // only set up the automation peer if we're ready to go live
2020-02-12 20:06:46 +01:00
{
// create a custom automation peer with this code pattern:
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
auto autoPeer = winrt::make_self<winrt::Microsoft::Terminal::TerminalControl::implementation::TermControlAutomationPeer>(this);
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
_renderer->AddRenderEngine(_uiaEngine.get());
return *autoPeer;
}
return nullptr;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
::Microsoft::Console::Types::IUiaData* TermControl::GetUiaData() const
{
return _terminal.get();
}
const FontInfo TermControl::GetActualFont() const
{
return _actualFont;
}
const Windows::UI::Xaml::Thickness TermControl::GetPadding()
2020-02-12 20:06:46 +01:00
{
return SwapChainPanel().Margin();
2020-02-12 20:06:46 +01:00
}
TerminalConnection::ConnectionState TermControl::ConnectionState() const
{
return _connection.State();
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged()
2020-02-12 20:06:46 +01:00
{
// This event is only registered during terminal initialization,
// so we don't need to check _initializedTerminal.
// We also don't lock for things that come back from the renderer.
2020-02-12 20:06:46 +01:00
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
2020-02-12 20:06:46 +01:00
if (auto control{ weakThis.get() })
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_AttachDxgiSwapChainToXaml(chain.Get());
2020-02-12 20:06:46 +01:00
}
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain)
2020-02-12 20:06:46 +01:00
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(swapChain);
2020-02-12 20:06:46 +01:00
}
bool TermControl::_InitializeTerminal()
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
{ // scope for terminalLock
auto terminalLock = _terminal->LockForWriting();
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_initializedTerminal)
{
return false;
}
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto actualWidth = SwapChainPanel().ActualWidth();
const auto actualHeight = SwapChainPanel().ActualHeight();
const auto windowWidth = actualWidth * SwapChainPanel().CompositionScaleX(); // Width() and Height() are NaN?
const auto windowHeight = actualHeight * SwapChainPanel().CompositionScaleY();
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (windowWidth == 0 || windowHeight == 0)
{
return false;
}
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// First create the render thread.
// Then stash a local pointer to the render thread so we can initialize it and enable it
// to paint itself *after* we hand off its ownership to the renderer.
// We split up construction and initialization of the render thread object this way
// because the renderer and render thread have circular references to each other.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
auto* const localPointerToThread = renderThread.get();
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Now create the renderer and initialize the render thread.
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
2020-02-12 20:06:46 +01:00
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
if (auto strongThis{ weakThis.get() })
{
strongThis->_RendererEnteredErrorState();
}
});
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
_UpdateFont(true);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Fist set up the dx engine with the window size in pixels.
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
_connection.Resize(height, width);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Override the default width and height to match the size of the swapChainPanel
_settings.InitialCols(width);
_settings.InitialRows(height);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_terminal->CreateFromSettings(_settings, renderTarget);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
Add renderer settings to mitigate blurry text for some graphics devices ## Summary of the Pull Request Adds user settings to adjust rendering behavior to mitigate blurry text on some devices. ## References - #778 introduced this, almost certainly. ## PR Checklist * [x] Closes #5759, mostly * [x] I work here. * [ ] We need community verification that this will help. * [x] Updated schema and schema doc. * [x] Am core contributor. Discussed in Monday sync meeting and w/ @DHowett-MSFT. ## Detailed Description of the Pull Request / Additional comments When we switched from full-screen repaints to incremental rendering, it seems like we exposed a situation where some display drivers and hardware combinations do not handle scroll and/or dirty regions (from `IDXGISwapChain::Present1`) without blurring the data from the previous frame. As we're really close to ship, I'm offering two options to let people in this situation escape it on their own. We hope in the future to figure out what's actually going on here and mitigate it further in software, but until then, these escape hatches are available. 1. `experimental.rendering.forceFullRepaint` - This one restores the pre-778 behavior to the Terminal. On every single frame paint, we'll invalidate the entire screen and repaint it. 2. `experimental.rendering.software` - This one uses the software WARP renderer instead of using the hardware and display driver directly. The theory is that this will sidestep any driver bugs or hardware variations. One, the other, or both of these may be field-applied by users who are experiencing this behavior. Reverting #778 completely would also resolve this, but it would give back our largest performance win in the whole Terminal project. We don't believe that's acceptable when seemingly a majority of the users are experiencing the performance benefit with no detriment to graphical display. ## Validation Steps Performed - [x] Flipped them on and verified with the debugger that they are being applied to the rendering pipeline - [ ] Gave a private copy to community members in #5759 and had them try whether one, the other, or both resolved their issue.
2020-05-11 23:54:03 +02:00
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Update DxEngine's AntialiasingMode
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
2020-02-12 20:06:46 +01:00
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
// GH#5098: Inform the engine of the opacity of the default text background.
if (_settings.UseAcrylic())
{
dxEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChain().Get());
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
_renderEngine->SetCallback(std::bind(&TermControl::RenderEngineSwapChainChanged, this));
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
auto bottom = _terminal->GetViewport().BottomExclusive();
auto bufferHeight = bottom;
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
ScrollBar().Maximum(bufferHeight - bufferHeight);
ScrollBar().Minimum(0);
ScrollBar().Value(0);
ScrollBar().ViewportSize(bufferHeight);
ScrollBar().LargeChange(std::max<SHORT>(bufferHeight - 1, 0)); // scroll one "screenful" at a time when the scroll bar is clicked
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
localPointerToThread->EnablePainting();
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Set up blinking cursor
int blinkTime = GetCaretBlinkTime();
if (blinkTime != INFINITE)
{
// Create a timer
DispatcherTimer cursorTimer;
cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
cursorTimer.Start();
_cursorTimer.emplace(std::move(cursorTimer));
}
else
{
// The user has disabled cursor blinking
_cursorTimer = std::nullopt;
}
2020-02-12 20:06:46 +01:00
Add support for the "blink" graphic rendition attribute (#7490) This PR adds support for the _blink_ graphic rendition attribute. When a character is output with this attribute set, it "blinks" at a regular interval, by cycling its color between the normal rendition and a dimmer shade of that color. The majority of the blinking mechanism is encapsulated in a new `BlinkingState` class, which is shared between the Terminal and Conhost implementations. This class keeps track of the position in the blinking cycle, which determines whether characters are rendered as normal or faint. In Windows Terminal, the state is stored in the `Terminal` class, and in Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases, the `IsBlinkingFaint` method is used to determine the current blinking rendition, and that is passed on as a parameter to the `TextAttribute::CalculateRgbColors` method when these classes are looking up attribute colors. Prior to calculating the colors, the current attribute is also passed to the `RecordBlinkingUsage` method, which keeps track of whether there are actually any blink attributes in use. This is used to determine whether the screen needs to be refreshed when the blinking cycle toggles between the normal and faint renditions. The refresh itself is handled by the `ToggleBlinkingRendition` method, which is triggered by a timer. In Conhost this is just piggybacking on the existing cursor blink timer, but in Windows Terminal it needs to have its own separate timer, since the cursor timer is reset whenever a key is pressed, which is not something we want for attribute blinking. Although the `ToggleBlinkingRendition` is called at the same rate as the cursor blinking, we actually only want the cells to blink at half that frequency. We thus have a counter that cycles through four phases, and blinking is rendered as faint for two of those four. Then every two cycles - when the state changes - a redraw is triggered, but only if there are actually blinking attributes in use (as previously recorded). As mentioned earlier, the blinking frequency is based on the cursor blink rate, so that means it'll automatically be disabled if a user has set their cursor blink rate to none. It can also be disabled by turning off the _Show animations in Windows_ option. In Conhost these settings take effect immediately, but in Windows Terminal they only apply when a new tab is opened. This PR also adds partial support for the `SGR 6` _rapid blink_ attribute. This is not used by DEC terminals, but was defined in the ECMA/ANSI standards. It's not widely supported, but many terminals just it implement it as an alias for the regular `SGR 5` blink attribute, so that's what I've done here too. ## Validation Steps Performed I've checked the _Graphic rendition test pattern_ in Vttest, and compared our representation of the blink attribute to that of an actual DEC VT220 terminal as seen on [YouTube]. With the right color scheme it's a reasonably close match. [YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s Closes #7388
2020-09-22 01:21:33 +02:00
// Set up blinking attributes
BOOL animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
if (animationsEnabled && blinkTime != INFINITE)
{
// Create a timer
DispatcherTimer blinkTimer;
blinkTimer.Interval(std::chrono::milliseconds(blinkTime));
blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick });
blinkTimer.Start();
_blinkTimer.emplace(std::move(blinkTimer));
}
else
{
// The user has disabled blinking
_blinkTimer = std::nullopt;
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// import value from WinUser (convert from milli-seconds to micro-seconds)
_multiClickTimer = GetDoubleClickTime() * 1000;
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// Focus the control here. If we do it during control initialization, then
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
// becomes a no-op.
this->Focus(FocusState::Programmatic);
2020-02-12 20:06:46 +01:00
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_initializedTerminal = true;
} // scope for TerminalLock
2020-02-12 20:06:46 +01:00
// Start the connection outside of lock, because it could
// start writing output immediately.
_connection.Start();
// Likewise, run the event handlers outside of lock (they could
// be reentrant)
2020-02-12 20:06:46 +01:00
_InitializedHandlers(*this, nullptr);
return true;
}
void TermControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::CharacterReceivedRoutedEventArgs const& e)
{
if (_closing)
{
return;
}
const auto ch = e.Character();
Delegate all character input to the character event handler (#4192) My basic idea was that `WM_CHAR` is just the better `WM_KEYDOWN`. The latter fails to properly support common dead key sequences like in #3516. As such I added some logic to `Terminal::SendKeyEvent` to make it return false if the pressed key represents a printable character. This causes us to receive a character event with a (hopefully) correctly composed code unit, which then gets sent to `Terminal::SendCharEvent`. `Terminal::SendCharEvent` in turn had to be modified to support potentially pressed modifier keys, since `Terminal::SendKeyEvent` isn't doing that for us anymore. Lastly `TerminalInput` had to be modified heavily to support character events with modifier key states. In order to do so I merged its `HandleKey` and `HandleChar` methods into a single one, that now handles both cases. Since key events will now contain character data and character events key codes the decision logic in `TerminalInput::HandleKey` had to be rewritten. ## PR Checklist * [x] CLA signed * [x] Tests added/passed * [x] I've discussed this with core contributors already. ## Validation Steps Performed * See #3516. * I don't have any keyboard that generates surrogate characters. Due to this I modified `TermControl::_SendPastedTextToConnection` to send the data to `_terminal->SendCharEvent()` instead. I then pasted the test string ""𐐌𐐜𐐬" and ensured that the new `TerminalInput::_SendChar` method still correctly assembles surrogate pairs. Closes #3516 Closes #3554 (obsoleted by this PR) Potentially impacts #391, which sounds like a duplicate of #3516
2020-04-07 21:09:28 +02:00
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
auto modifiers = _GetPressedModifierKeys();
if (e.KeyStatus().IsExtendedKey)
{
modifiers |= ControlKeyStates::EnhancedKey;
}
Delegate all character input to the character event handler (#4192) My basic idea was that `WM_CHAR` is just the better `WM_KEYDOWN`. The latter fails to properly support common dead key sequences like in #3516. As such I added some logic to `Terminal::SendKeyEvent` to make it return false if the pressed key represents a printable character. This causes us to receive a character event with a (hopefully) correctly composed code unit, which then gets sent to `Terminal::SendCharEvent`. `Terminal::SendCharEvent` in turn had to be modified to support potentially pressed modifier keys, since `Terminal::SendKeyEvent` isn't doing that for us anymore. Lastly `TerminalInput` had to be modified heavily to support character events with modifier key states. In order to do so I merged its `HandleKey` and `HandleChar` methods into a single one, that now handles both cases. Since key events will now contain character data and character events key codes the decision logic in `TerminalInput::HandleKey` had to be rewritten. ## PR Checklist * [x] CLA signed * [x] Tests added/passed * [x] I've discussed this with core contributors already. ## Validation Steps Performed * See #3516. * I don't have any keyboard that generates surrogate characters. Due to this I modified `TermControl::_SendPastedTextToConnection` to send the data to `_terminal->SendCharEvent()` instead. I then pasted the test string ""𐐌𐐜𐐬" and ensured that the new `TerminalInput::_SendChar` method still correctly assembles surrogate pairs. Closes #3516 Closes #3554 (obsoleted by this PR) Potentially impacts #391, which sounds like a duplicate of #3516
2020-04-07 21:09:28 +02:00
const bool handled = _terminal->SendCharEvent(ch, scanCode, modifiers);
2020-02-12 20:06:46 +01:00
e.Handled(handled);
}
// Method Description:
// - Manually handles key events for certain keys that can't be passed to us
// normally. Namely, the keys we're concerned with are F7 down and Alt up.
// Return value:
// - Whether the key was handled.
bool TermControl::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
{
const auto modifiers{ _GetPressedModifierKeys() };
auto handled = false;
if (vkey == VK_MENU && !down)
{
// Manually generate an Alt KeyUp event into the key bindings or terminal.
// This is required as part of GH#6421.
(void)_TrySendKeyEvent(VK_MENU, scanCode, modifiers, false);
handled = true;
}
else if (vkey == VK_F7 && down)
{
// Manually generate an F7 event into the key bindings or terminal.
// This is required as part of GH#638.
auto bindings{ _settings.KeyBindings() };
if (bindings)
{
handled = bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
VK_F7,
});
}
if (!handled)
{
// _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason.
(void)_TrySendKeyEvent(VK_F7, scanCode, modifiers, true);
// GH#6438: Note that we're _not_ sending the key up here - that'll
// get passed through XAML to our KeyUp handler normally.
handled = true;
}
}
return handled;
}
2020-02-12 20:06:46 +01:00
void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::KeyRoutedEventArgs const& e)
{
_KeyHandler(e, true);
}
void TermControl::_KeyUpHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::KeyRoutedEventArgs const& e)
{
_KeyHandler(e, false);
}
void TermControl::_KeyHandler(Input::KeyRoutedEventArgs const& e, const bool keyDown)
2020-02-12 20:06:46 +01:00
{
// If the current focused element is a child element of searchbox,
// we do not send this event up to terminal
if (_searchBox && _searchBox->ContainsFocus())
{
return;
}
// Mark the event as handled and do nothing if we're closing, or the key
// was the Windows key.
//
// NOTE: for key combos like CTRL + C, two events are fired (one for
// CTRL, one for 'C'). Since it's possible the terminal is in
// win32-input-mode, then we'll send all these keystrokes to the
// terminal - it's smart enough to ignore the keys it doesn't care
// about.
2020-02-12 20:06:46 +01:00
if (_closing ||
e.OriginalKey() == VirtualKey::LeftWindows ||
e.OriginalKey() == VirtualKey::RightWindows)
{
e.Handled(true);
return;
}
auto modifiers = _GetPressedModifierKeys();
2020-02-12 20:06:46 +01:00
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
if (e.KeyStatus().IsExtendedKey)
{
modifiers |= ControlKeyStates::EnhancedKey;
}
2020-02-12 20:06:46 +01:00
// Alt-Numpad# input will send us a character once the user releases
// Alt, so we should be ignoring the individual keydowns. The character
// will be sent through the TSFInputControl. See GH#1401 for more
// details
if (modifiers.IsAltPressed() &&
(e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
{
e.Handled(true);
return;
}
// GH#2235: Terminal::Settings hasn't been modified to differentiate
// between AltGr and Ctrl+Alt yet.
2020-02-12 20:06:46 +01:00
// -> Don't check for key bindings if this is an AltGr key combination.
//
// GH#4999: Only process keybindings on the keydown. If we don't check
// this at all, we'll process the keybinding twice. If we only process
// keybindings on the keyUp, then we'll still send the keydown to the
// connected terminal application, and something like ctrl+shift+T will
// emit a ^T to the pipe.
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, scanCode, modifiers))
2020-02-12 20:06:46 +01:00
{
e.Handled(true);
return;
2020-02-12 20:06:46 +01:00
}
if (_TrySendKeyEvent(vkey, scanCode, modifiers, keyDown))
2020-02-12 20:06:46 +01:00
{
e.Handled(true);
return;
2020-02-12 20:06:46 +01:00
}
// Manually prevent keyboard navigation with tab. We want to send tab to
// the terminal, and we don't want to be able to escape focus of the
// control with tab.
e.Handled(e.OriginalKey() == VirtualKey::Tab);
}
// Method Description:
// - Attempt to handle this key combination as a key binding
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
// - modifiers: The ControlKeyStates representing the modifier key states.
bool TermControl::_TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
{
auto bindings = _settings.KeyBindings();
if (!bindings)
2020-02-12 20:06:46 +01:00
{
return false;
2020-02-12 20:06:46 +01:00
}
auto success = bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
vkey,
});
if (!success)
{
return false;
}
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
// If the user presses the two keys "^a" it'll produce "bâ", despite us marking the key event as handled.
// The following is used to manually "consume" such dead keys and clear them from the keyboard state.
_ClearKeyboardState(vkey, scanCode);
return true;
}
// Method Description:
// - Discards currently pressed dead keys.
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept
{
std::array<BYTE, 256> keyState;
if (!GetKeyboardState(keyState.data()))
{
return;
}
// As described in "Sometimes you *want* to interfere with the keyboard's state buffer":
// http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
// > "The key here is to keep trying to pass stuff to ToUnicode until -1 is not returned."
std::array<wchar_t, 16> buffer;
while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b1, nullptr) < 0)
{
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Send this particular key event to the terminal.
// See Terminal::SendKeyEvent for more information.
// - Clears the current selection.
// - Makes the cursor briefly visible during typing.
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
2020-02-12 20:06:46 +01:00
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// - keyDown: If true, the key was pressed, otherwise the key was released.
bool TermControl::_TrySendKeyEvent(const WORD vkey,
const WORD scanCode,
const ControlKeyStates modifiers,
const bool keyDown)
2020-02-12 20:06:46 +01:00
{
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
// flow through to the terminal.
// GH#6423 - don't dismiss selection if the key that was pressed was a
// modifier key. We'll wait for a real keystroke to dismiss the
// GH #7395 - don't dismiss selection when taking PrintScreen
// selection.
if (_terminal->IsSelectionActive() && !KeyEvent::IsModifierKey(vkey) && vkey != VK_SNAPSHOT)
2020-02-12 20:06:46 +01:00
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
2020-02-12 20:06:46 +01:00
if (vkey == VK_ESCAPE)
{
return true;
}
}
Make Korean IME input more consistent (#4796) ## Summary of the Pull Request Korean IME was not working correctly due to way we were clearing the input buffer inside of `TSFInputControl`. We wanted to clear our input buffer and tell TSF to clear its input buffer as well when we receive a `CompositionCompleted` event. This works fine in some IME languages such as Chinese and Japanese. However, Korean IME composes characters differently in such a way where we can't tell TSF to clear their buffer during a `CompositionCompleted` event because it would clear the character that triggered the `CompositionCompleted` event in the first place. The solution in this PR is to keep our `_inputBuffer` intact until the user presses <kbd>Enter</kbd> or <kbd>Esc</kbd>, in which case we clear our buffer and the TSF buffer. I've chosen these two keys because it seems to make sense to clear the buffer after text is sent to the terminal with <kbd>Enter</kbd>, and <kbd>Esc</kbd> usually means to cancel a current composition anyway. This means we need to keep track of our last known "Composition Start Point", which is represented by `_activeTextStart`. Whenever we complete a composition, we'll send the portion of the input buffer between `_activeTextStart` and the end of the input buffer to the terminal. Then, we'll update `_activeTextStart` to be the end of the input buffer so that the next time we send text to the terminal, we'll only send the portion of our buffer that's "active". ## PR Checklist * [x] Closes #4226 * [x] CLA signed * [x] Tests added/passed ## Validation Steps Performed Manual testing with Chinese, Japanese, and Korean IME.
2020-03-04 21:01:01 +01:00
if (vkey == VK_ESCAPE ||
vkey == VK_RETURN)
{
TSFInputControl().ClearBuffer();
}
2020-02-12 20:06:46 +01:00
// If the terminal translated the key, mark the event as handled.
// This will prevent the system from trying to get the character out
// of it and sending us a CharacterReceived event.
const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown) : true;
2020-02-12 20:06:46 +01:00
if (_cursorTimer.has_value())
{
// Manually show the cursor when a key is pressed. Restarting
// the timer prevents flickering.
_terminal->SetCursorOn(true);
2020-02-12 20:06:46 +01:00
_cursorTimer.value().Start();
}
return handled;
}
// Method Description:
// - handle a tap event by taking focus
// Arguments:
// - sender: the XAML element responding to the tap event
// - args: event data
void TermControl::_TappedHandler(const IInspectable& /*sender*/, const TappedRoutedEventArgs& e)
{
Focus(FocusState::Pointer);
e.Handled(true);
}
// Method Description:
// - Send this particular mouse event to the terminal.
// See Terminal::SendMouseEvent for more information.
// Arguments:
// - point: the PointerPoint object representing a mouse event from our XAML input handler
bool TermControl::_TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point)
{
const auto props = point.Properties();
// Get the terminal position relative to the viewport
const auto terminalPosition = _GetTerminalPosition(point.Position());
// Which mouse button changed state (and how)
unsigned int uiButton{};
switch (props.PointerUpdateKind())
{
case PointerUpdateKind::LeftButtonPressed:
uiButton = WM_LBUTTONDOWN;
break;
case PointerUpdateKind::LeftButtonReleased:
uiButton = WM_LBUTTONUP;
break;
case PointerUpdateKind::MiddleButtonPressed:
uiButton = WM_MBUTTONDOWN;
break;
case PointerUpdateKind::MiddleButtonReleased:
uiButton = WM_MBUTTONUP;
break;
case PointerUpdateKind::RightButtonPressed:
uiButton = WM_RBUTTONDOWN;
break;
case PointerUpdateKind::RightButtonReleased:
uiButton = WM_RBUTTONUP;
break;
default:
uiButton = WM_MOUSEMOVE;
}
// Mouse wheel data
const short sWheelDelta = ::base::saturated_cast<short>(props.MouseWheelDelta());
if (sWheelDelta != 0 && !props.IsHorizontalMouseWheel())
{
// if we have a mouse wheel delta and it wasn't a horizontal wheel motion
uiButton = WM_MOUSEWHEEL;
}
const auto modifiers = _GetPressedModifierKeys();
const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() };
return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta, state);
}
// Method Description:
// - Checks if we can send vt mouse input.
// Arguments:
// - point: the PointerPoint object representing a mouse event from our XAML input handler
bool TermControl::_CanSendVTMouseInput()
{
Process actions sync. on startup; don't dupe nonexistent profile (#5090) This PR has evolved to encapsulate two related fixes that I can't really untie anymore. #2455 - Duplicating a tab that doesn't exist anymore This was the bug I was originally fixing in #4429. When the user tries to `duplicateTab` with a profile that doesn't exist anymore (like might happen after a settings reload), don't crash. As I was going about adding tests for this, got blocked by the fact that the Terminal couldn't open _any_ panes while the `TerminalPage` was size 0x0. This had two theoretical solutions: * Fake the `TerminalPage` into thinking it had a real size in the test - probably possible, though I'm unsure how it would work in practice. * Change `Pane`s to not require an `ActualWidth`, `ActualHeight` on initialization. Fortuately, the second option was something else that was already on my backlog of bugs. #4618 - `wt` command-line can't consistently parse more than one arg Presently, the Terminal just arbitrarily dispatches a bunch of handlers to try and handle all the commands provided on the commandline. That's lead to a bunch of reports that not all the commands will always get executed, nor will they all get executed in the same order. This PR also changes the `TerminalPage` to be able to dispatch all the commands sequentially, all at once in the startup. No longer will there be a hot second where the commands seem to execute themselves in from of the user - they'll all happen behind the scenes on startup. This involved a couple other changes areound the `TerminalPage` * I had to make sure that panes could be opened at a 0x0 size. Now they use a star sizing based off the percentage of the parent they're supposed to consume, so that when the parent _does_ get laid out, they'll take the appropriate size of that parent. * I had to do some math ahead of time to try and calculate what a `SplitState::Automatic` would be evaluated as, despite the fact that we don't actually know how big the pane will be. * I had to ensure that `focus-tab` commands appropriately mark a single tab as focused while we're in startup, without roundtripping to the Dispatcher thread and back ## References #4429 - the original PR for #2455 #5047 - a follow-up task from discussion in #4429 #4953 - a PR for making panes use star sizing, which was immensly helpful for this PR. ## Detailed Description of the Pull Request / Additional comments `CascadiaSettings::BuildSettings` can throw if the GUID doesn't exist. This wraps those calls up with a try/catch. It also adds a couple tests - a few `SettingsTests` for try/catching this state. It also adds a XAML-y test in `TabTests` that creates a `TerminalPage` and then performs som UI-like actions on it. This test required a minor change to how we generate the new tab dropdown - in the tests, `Application::Current()` is _not_ a `TerminalApp::App`, so it doesn't have a `Logic()` to query. So wrap that in a try/catch as well. While working on these tests, I found that we'd crash pretty agressively for mysterious reasons if the TestHostApp became focused while the test was running. This was due to a call in `TSFInputControl::NotifyFocusEnter` that would callback to `TSFInputControl::_layoutRequested`, which would crash on setting the `MaxSize` of the canvas to a negative value. This PR includes a hotfix for that bug as well. ## Validation Steps Performed * Manual testing with a _lot_ of commands in a commandline * run the tests * Team tested in selfhost Closes #2455 Closes #4618
2020-03-26 01:03:32 +01:00
if (!_terminal)
{
return false;
}
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
const auto modifiers = _GetPressedModifierKeys();
if (modifiers.IsShiftPressed())
{
return false;
}
return _terminal->IsTrackingMouseInput();
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - handle a mouse click event. Begin selection process.
// Arguments:
// - sender: the XAML element responding to the pointer input
// - args: event data
void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
_CapturePointer(sender, args);
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(*this);
2020-02-12 20:06:46 +01:00
// We also TryShow in GotFocusHandler, but this call is specifically
// for the case where the Terminal is in focus but the user closed the
// on-screen keyboard. This lets the user simply tap on the terminal
// again to bring it up.
InputPane::GetForCurrentView().TryShow();
if (!_focused)
2020-02-12 20:06:46 +01:00
{
Focus(FocusState::Pointer);
}
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
2020-02-12 20:06:46 +01:00
const auto modifiers = static_cast<uint32_t>(args.KeyModifiers());
// static_cast to a uint32_t because we can't use the WI_IsFlagSet
// macro directly with a VirtualKeyModifiers
const auto altEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Menu));
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
OSC 8 support for conhost and terminal (#7251) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Conhost can now support OSC8 sequences (as specified [here](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)). Terminal also supports those sequences and additionally hyperlinks can be opened by Ctrl+LeftClicking on them. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #204 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [X] Closes #204 * [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Added support to: - parse OSC8 sequences and extract URIs from them (conhost and terminal) - add hyperlink uri data to textbuffer/screeninformation, associated with a hyperlink id (conhost and terminal) - attach hyperlink ids to text to allow for uri extraction from the textbuffer/screeninformation (conhost and terminal) - process ctrl+leftclick to open a hyperlink in the clicked region if present <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Open up a PowerShell tab and type ```PowerShell ${ESC}=[char]27 Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\" ``` Ctrl+LeftClick on the link correctly brings you to the terminal page on github ![hyperlink](https://user-images.githubusercontent.com/26824113/89953536-45a6f580-dbfd-11ea-8e0d-8a3cd25c634a.gif)
2020-09-03 19:52:39 +02:00
const auto ctrlEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Control));
2020-02-12 20:06:46 +01:00
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}
2020-02-12 20:06:46 +01:00
if (point.Properties().IsLeftButtonPressed())
{
Render row-by-row instead of invalidating entire screen (#5185) ## Summary of the Pull Request Adjusts DirectX renderer to use `til::bitmap` to track invalidation regions. Uses special modification to invalidate a row-at-a-time to ensure ligatures and NxM glyphs continue to work. ## References Likely helps #1064 ## PR Checklist * [x] Closes #778 * [x] I work here. * [x] Manual testing performed. See Performance traces in #778. * [x] Automated tests for `til` changes. * [x] Am core contributor. And discussed with @DHowett-MSFT. ## Detailed Description of the Pull Request / Additional comments - Applies `til::bitmap` as the new invalidation scheme inside the DirectX renderer and updates all entrypoints for collecting invalidation data to coalesce into this structure. - Semi-permanently routes all invalidations through a helper method `_InvalidateRectangle` that will expand any invalidation to cover the entire line. This ensures that ligatures and NxM glyphs will continue to render appropriately while still allowing us to dramatically reduce the number of lines drawn overall. In the future, we may come up with a tighter solution than line-by-line invalidation and can modify this helper method appropriately at that later date to further scope the invalid region. - Ensures that the `experimental.retroTerminalEffects` feature continues to invalidate the entire display on start of frame as the shader is applied at the end of the frame composition and will stack on itself in an amusing fashion when we only redraw part of the display. - Moves many member variables inside the DirectX renderer into the new `til::size`, `til::point`, and `til::rectangle` methods to facilitate easier management and mathematical operations. Consequently adds `try/catch` blocks around many of the already-existing `noexcept` methods to deal with mathematical or casting failures now detected by using the support classes. - Corrects `TerminalCore` redraw triggers to appropriately communicate scrolling circumstances to the renderer so it can optimize the draw regions appropriately. - Fixes an issue in the base `Renderer` that was causing overlapping scroll regions due to behavior of `Viewport::TrimToViewport` modifying the local. This fix is "good enough" for now and should go away when `Viewport` is fully migrated to `til::rectangle`. - Adds multiplication and division operators to `til::rectangle` and supporting tests. These operates will help scale back and forth between a cell-based rectangle and a pixel-based rectangle. They take special care to ensure that a pixel rectangle being divided downward back to cells will expand (with the ceiling division methods) to cover a full cell when even one pixel inside the cell is touched (as is how a redraw would have to occur). - Blocks off trace logging of invalid regions if no one is listening to optimize performance. - Restores full usage of `IDXGISwapChain1::Present1` to accurately and fully communicate dirty and scroll regions to the underlying DirectX framework. This additional information allows the framework to optimize drawing between frames by eliminating data transfer of regions that aren't modified and shuffling frames in place. See [Remarks](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgiswapchain1-present1#remarks) for more details. - Updates `til::bitmap` set methods to use more optimized versions of the setters on the `dynamic_bitset<>` that can bulk fill bits as the existing algorithm was noticeably slow after applying the "expand-to-row" helper to the DirectX renderer invalidation. - All `til` import hierarchy is now handled in the parent `til.h` file and not in the child files to prevent circular imports from happening. We don't expect the import of any individual library file, only the base one. So this should be OK for now. ## Validation Steps Performed - Ran `cmatrix`, `cmatrix -u0`, and `cacafire` after changes were made. - Made a bunch of ligatures with `Cascadia Code` in the Terminal before/after the changes and confirmed they still ligate. - Ran `dir` in Powershell and fixed the scrolling issues - Clicked all over the place and dragged to make sure selection works. - Checked retro terminal effect manually with Powershell.
2020-04-13 22:09:02 +02:00
auto lock = _terminal->LockForWriting();
2020-02-12 20:06:46 +01:00
const auto cursorPosition = point.Position();
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// handle ALT key
Move rect expansion to textbuffer; refactor selection code (#4560) - When performing chunk selection, the expansion now occurs at the time of the selection, not the rendering of the selection - `GetSelectionRects()` was moved to the `TextBuffer` and is now shared between ConHost and Windows Terminal - Some of the selection variables were renamed for clarity - Selection COORDs are now in the Text Buffer coordinate space - Fixes an issue with Shift+Click after performing a Multi-Click Selection ## References This also contributes to... - #4509: UIA Box Selection - #2447: UIA Signaling for Selection - #1354: UIA support for Wide Glyphs Now that the expansion occurs at before render-time, the selection anchors are an accurate representation of what is selected. We just need to move `GetText` to the `TextBuffer`. Then we can have those three issues just rely on code from the text buffer. This also means ConHost gets some of this stuff for free 😀 ### TextBuffer - `GetTextRects` is the abstracted form of `GetSelectionRects` - `_ExpandTextRow` is still needed to handle wide glyphs properly ### Terminal - Rename... - `_boxSelection` --> `_blockSelection` for consistency with ConHost - `_selectionAnchor` --> `_selectionStart` for consistency with UIA - `_endSelectionPosition` --> `_selectionEnd` for consistency with UIA - Selection anchors are in Text Buffer coordinates now - Really rely on `SetSelectionEnd` to accomplish appropriate chunk selection and shift+click actions ## Validation Steps Performed - Shift+Click - Multi-Click --> Shift+Click - Chunk Selection at... - top of buffer - bottom of buffer - random region in scrollback Closes #4465 Closes #4547
2020-02-28 01:42:26 +01:00
_terminal->SetBlockSelection(altEnabled);
2020-02-12 20:06:46 +01:00
auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp());
// This formula enables the number of clicks to cycle properly between single-, double-, and triple-click.
// To increase the number of acceptable click states, simply increment MAX_CLICK_COUNT and add another if-statement
const unsigned int MAX_CLICK_COUNT = 3;
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
if (multiClickMapper == 1)
2020-02-12 20:06:46 +01:00
{
mode = ::Terminal::SelectionExpansionMode::Cell;
2020-02-12 20:06:46 +01:00
}
else if (multiClickMapper == 2)
{
mode = ::Terminal::SelectionExpansionMode::Word;
}
else if (multiClickMapper == 3)
{
mode = ::Terminal::SelectionExpansionMode::Line;
}
// Update the selection appropriately
OSC 8 support for conhost and terminal (#7251) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Conhost can now support OSC8 sequences (as specified [here](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)). Terminal also supports those sequences and additionally hyperlinks can be opened by Ctrl+LeftClicking on them. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #204 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [X] Closes #204 * [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Added support to: - parse OSC8 sequences and extract URIs from them (conhost and terminal) - add hyperlink uri data to textbuffer/screeninformation, associated with a hyperlink id (conhost and terminal) - attach hyperlink ids to text to allow for uri extraction from the textbuffer/screeninformation (conhost and terminal) - process ctrl+leftclick to open a hyperlink in the clicked region if present <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Open up a PowerShell tab and type ```PowerShell ${ESC}=[char]27 Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\" ``` Ctrl+LeftClick on the link correctly brings you to the terminal page on github ![hyperlink](https://user-images.githubusercontent.com/26824113/89953536-45a6f580-dbfd-11ea-8e0d-8a3cd25c634a.gif)
2020-09-03 19:52:39 +02:00
if (ctrlEnabled && multiClickMapper == 1 &&
!(_terminal->GetHyperlinkAtPosition(terminalPosition).empty()))
{
_HyperlinkHandler(_terminal->GetHyperlinkAtPosition(terminalPosition));
}
else if (shiftEnabled && _terminal->IsSelectionActive())
{
// Shift+Click: only set expand on the "end" selection point
_terminal->SetSelectionEnd(terminalPosition, mode);
2020-03-25 22:09:49 +01:00
_selectionNeedsToBeCopied = true;
2020-02-12 20:06:46 +01:00
}
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
{
// Single Click: reset the selection and begin a new one
_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
}
2020-02-12 20:06:46 +01:00
else
{
// Multi-Click Selection: expand both "start" and "end" selection points
_terminal->MultiClickSelection(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
2020-02-12 20:06:46 +01:00
}
Render row-by-row instead of invalidating entire screen (#5185) ## Summary of the Pull Request Adjusts DirectX renderer to use `til::bitmap` to track invalidation regions. Uses special modification to invalidate a row-at-a-time to ensure ligatures and NxM glyphs continue to work. ## References Likely helps #1064 ## PR Checklist * [x] Closes #778 * [x] I work here. * [x] Manual testing performed. See Performance traces in #778. * [x] Automated tests for `til` changes. * [x] Am core contributor. And discussed with @DHowett-MSFT. ## Detailed Description of the Pull Request / Additional comments - Applies `til::bitmap` as the new invalidation scheme inside the DirectX renderer and updates all entrypoints for collecting invalidation data to coalesce into this structure. - Semi-permanently routes all invalidations through a helper method `_InvalidateRectangle` that will expand any invalidation to cover the entire line. This ensures that ligatures and NxM glyphs will continue to render appropriately while still allowing us to dramatically reduce the number of lines drawn overall. In the future, we may come up with a tighter solution than line-by-line invalidation and can modify this helper method appropriately at that later date to further scope the invalid region. - Ensures that the `experimental.retroTerminalEffects` feature continues to invalidate the entire display on start of frame as the shader is applied at the end of the frame composition and will stack on itself in an amusing fashion when we only redraw part of the display. - Moves many member variables inside the DirectX renderer into the new `til::size`, `til::point`, and `til::rectangle` methods to facilitate easier management and mathematical operations. Consequently adds `try/catch` blocks around many of the already-existing `noexcept` methods to deal with mathematical or casting failures now detected by using the support classes. - Corrects `TerminalCore` redraw triggers to appropriately communicate scrolling circumstances to the renderer so it can optimize the draw regions appropriately. - Fixes an issue in the base `Renderer` that was causing overlapping scroll regions due to behavior of `Viewport::TrimToViewport` modifying the local. This fix is "good enough" for now and should go away when `Viewport` is fully migrated to `til::rectangle`. - Adds multiplication and division operators to `til::rectangle` and supporting tests. These operates will help scale back and forth between a cell-based rectangle and a pixel-based rectangle. They take special care to ensure that a pixel rectangle being divided downward back to cells will expand (with the ceiling division methods) to cover a full cell when even one pixel inside the cell is touched (as is how a redraw would have to occur). - Blocks off trace logging of invalid regions if no one is listening to optimize performance. - Restores full usage of `IDXGISwapChain1::Present1` to accurately and fully communicate dirty and scroll regions to the underlying DirectX framework. This additional information allows the framework to optimize drawing between frames by eliminating data transfer of regions that aren't modified and shuffling frames in place. See [Remarks](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgiswapchain1-present1#remarks) for more details. - Updates `til::bitmap` set methods to use more optimized versions of the setters on the `dynamic_bitset<>` that can bulk fill bits as the existing algorithm was noticeably slow after applying the "expand-to-row" helper to the DirectX renderer invalidation. - All `til` import hierarchy is now handled in the parent `til.h` file and not in the child files to prevent circular imports from happening. We don't expect the import of any individual library file, only the base one. So this should be OK for now. ## Validation Steps Performed - Ran `cmatrix`, `cmatrix -u0`, and `cacafire` after changes were made. - Made a bunch of ligatures with `Cascadia Code` in the Terminal before/after the changes and confirmed they still ligate. - Ran `dir` in Powershell and fixed the scrolling issues - Clicked all over the place and dragged to make sure selection works. - Checked retro terminal effect manually with Powershell.
2020-04-13 22:09:02 +02:00
_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
2020-02-12 20:06:46 +01:00
_renderer->TriggerSelection();
}
else if (point.Properties().IsRightButtonPressed())
{
Ignore right-click copy when copy on select is enabled (#4819) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Right clicking on a focused tab while Copy On Select is active currently copies any active selection. This is because `PointerReleasedHandler` doesn't check for the mouse button that was released. During a mouse button release, only the left mouse button release should be doing anything. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4740 * [x] CLA signed. * [x] Tests added/passed <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed These are the scenarios I've tested. They're a combination of in focus/out of focus, Copy On Select on/off, left/right click pressed and their move and release variants. From Out of Focus: - Left Click = Focus - Left Click Move = Focus + Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Focus - Right Click - Focus - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing From In Focus - Left Click = Selection if CoS off - Left Click Move = Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Set Selection Anchor - Right Click - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing
2020-03-10 15:59:16 +01:00
// CopyOnSelect right click always pastes
2020-03-25 22:09:49 +01:00
if (_settings.CopyOnSelect() || !_terminal->IsSelectionActive())
2020-02-12 20:06:46 +01:00
{
PasteTextFromClipboard();
}
else
{
CopySelectionToClipboard(shiftEnabled, nullptr);
2020-02-12 20:06:46 +01:00
}
}
}
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch)
{
const auto contactRect = point.Properties().ContactRect();
// Set our touch rect, to start a pan.
_touchAnchor = winrt::Windows::Foundation::Point{ contactRect.X, contactRect.Y };
}
args.Handled(true);
}
// Method Description:
// - handle a mouse moved event. Specifically handling mouse drag to update selection process.
// Arguments:
// - sender: not used
// - args: event data
void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(*this);
2020-02-12 20:06:46 +01:00
if (!_focused)
{
args.Handled(true);
return;
}
2020-02-12 20:06:46 +01:00
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}
2020-02-12 20:06:46 +01:00
if (point.Properties().IsLeftButtonPressed())
{
Render row-by-row instead of invalidating entire screen (#5185) ## Summary of the Pull Request Adjusts DirectX renderer to use `til::bitmap` to track invalidation regions. Uses special modification to invalidate a row-at-a-time to ensure ligatures and NxM glyphs continue to work. ## References Likely helps #1064 ## PR Checklist * [x] Closes #778 * [x] I work here. * [x] Manual testing performed. See Performance traces in #778. * [x] Automated tests for `til` changes. * [x] Am core contributor. And discussed with @DHowett-MSFT. ## Detailed Description of the Pull Request / Additional comments - Applies `til::bitmap` as the new invalidation scheme inside the DirectX renderer and updates all entrypoints for collecting invalidation data to coalesce into this structure. - Semi-permanently routes all invalidations through a helper method `_InvalidateRectangle` that will expand any invalidation to cover the entire line. This ensures that ligatures and NxM glyphs will continue to render appropriately while still allowing us to dramatically reduce the number of lines drawn overall. In the future, we may come up with a tighter solution than line-by-line invalidation and can modify this helper method appropriately at that later date to further scope the invalid region. - Ensures that the `experimental.retroTerminalEffects` feature continues to invalidate the entire display on start of frame as the shader is applied at the end of the frame composition and will stack on itself in an amusing fashion when we only redraw part of the display. - Moves many member variables inside the DirectX renderer into the new `til::size`, `til::point`, and `til::rectangle` methods to facilitate easier management and mathematical operations. Consequently adds `try/catch` blocks around many of the already-existing `noexcept` methods to deal with mathematical or casting failures now detected by using the support classes. - Corrects `TerminalCore` redraw triggers to appropriately communicate scrolling circumstances to the renderer so it can optimize the draw regions appropriately. - Fixes an issue in the base `Renderer` that was causing overlapping scroll regions due to behavior of `Viewport::TrimToViewport` modifying the local. This fix is "good enough" for now and should go away when `Viewport` is fully migrated to `til::rectangle`. - Adds multiplication and division operators to `til::rectangle` and supporting tests. These operates will help scale back and forth between a cell-based rectangle and a pixel-based rectangle. They take special care to ensure that a pixel rectangle being divided downward back to cells will expand (with the ceiling division methods) to cover a full cell when even one pixel inside the cell is touched (as is how a redraw would have to occur). - Blocks off trace logging of invalid regions if no one is listening to optimize performance. - Restores full usage of `IDXGISwapChain1::Present1` to accurately and fully communicate dirty and scroll regions to the underlying DirectX framework. This additional information allows the framework to optimize drawing between frames by eliminating data transfer of regions that aren't modified and shuffling frames in place. See [Remarks](https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgiswapchain1-present1#remarks) for more details. - Updates `til::bitmap` set methods to use more optimized versions of the setters on the `dynamic_bitset<>` that can bulk fill bits as the existing algorithm was noticeably slow after applying the "expand-to-row" helper to the DirectX renderer invalidation. - All `til` import hierarchy is now handled in the parent `til.h` file and not in the child files to prevent circular imports from happening. We don't expect the import of any individual library file, only the base one. So this should be OK for now. ## Validation Steps Performed - Ran `cmatrix`, `cmatrix -u0`, and `cacafire` after changes were made. - Made a bunch of ligatures with `Cascadia Code` in the Terminal before/after the changes and confirmed they still ligate. - Ran `dir` in Powershell and fixed the scrolling issues - Clicked all over the place and dragged to make sure selection works. - Checked retro terminal effect manually with Powershell.
2020-04-13 22:09:02 +02:00
auto lock = _terminal->LockForWriting();
2020-03-25 22:09:49 +01:00
const auto cursorPosition = point.Position();
2020-03-25 22:09:49 +01:00
if (_singleClickTouchdownPos)
{
2020-03-25 22:09:49 +01:00
// Figure out if the user's moved a quarter of a cell's smaller axis away from the clickdown point
auto& touchdownPoint{ *_singleClickTouchdownPos };
auto distance{ std::sqrtf(std::powf(cursorPosition.X - touchdownPoint.X, 2) + std::powf(cursorPosition.Y - touchdownPoint.Y, 2)) };
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const til::size fontSize{ _actualFont.GetSize() };
const auto fontSizeInDips = fontSize.scale(til::math::rounding, 1.0f / _renderEngine->GetScaling());
if (distance >= (std::min(fontSizeInDips.width(), fontSizeInDips.height()) / 4.f))
2020-03-25 22:09:49 +01:00
{
_terminal->SetSelectionAnchor(_GetTerminalPosition(touchdownPoint));
// stop tracking the touchdown point
_singleClickTouchdownPos = std::nullopt;
}
}
2020-02-12 20:06:46 +01:00
_SetEndSelectionPointAtCursor(cursorPosition);
const double cursorBelowBottomDist = cursorPosition.Y - SwapChainPanel().Margin().Top - SwapChainPanel().ActualHeight();
const double cursorAboveTopDist = -1 * cursorPosition.Y + SwapChainPanel().Margin().Top;
2020-02-12 20:06:46 +01:00
constexpr double MinAutoScrollDist = 2.0; // Arbitrary value
double newAutoScrollVelocity = 0.0;
if (cursorBelowBottomDist > MinAutoScrollDist)
{
newAutoScrollVelocity = _GetAutoScrollSpeed(cursorBelowBottomDist);
}
else if (cursorAboveTopDist > MinAutoScrollDist)
{
newAutoScrollVelocity = -1.0 * _GetAutoScrollSpeed(cursorAboveTopDist);
}
if (newAutoScrollVelocity != 0)
{
_TryStartAutoScroll(point, newAutoScrollVelocity);
}
else
{
_TryStopAutoScroll(ptr.PointerId());
}
}
const auto terminalPos = _GetTerminalPosition(point.Position());
if (terminalPos != _lastHoveredCell)
{
const auto uri = _terminal->GetHyperlinkAtPosition(terminalPos);
if (!uri.empty())
{
// Update the tooltip with the URI
HoveredUri().Text(uri);
// Set the border thickness so it covers the entire cell
const auto charSizeInPixels = CharacterDimensions();
const auto htInDips = charSizeInPixels.Height / SwapChainPanel().CompositionScaleY();
const auto wtInDips = charSizeInPixels.Width / SwapChainPanel().CompositionScaleX();
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::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
const til::point startPos{ terminalPos.X, terminalPos.Y };
const til::size fontSize{ _actualFont.GetSize() };
const til::point posInPixels{ startPos * fontSize };
const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() };
const til::point locationInDIPs{ posInDIPs + marginsInDips };
// Move the border to the top left corner of the cell
OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), (locationInDIPs.x() - SwapChainPanel().ActualOffset().x));
OverlayCanvas().SetTop(HyperlinkTooltipBorder(), (locationInDIPs.y() - SwapChainPanel().ActualOffset().y));
}
_lastHoveredCell = terminalPos;
const auto newId = _terminal->GetHyperlinkIdAtPosition(terminalPos);
const auto newInterval = _terminal->GetHyperlinkIntervalFromPosition(terminalPos);
// If the hyperlink ID changed or the interval changed, trigger a redraw all
// (so this will happen both when we move onto a link and when we move off a link)
if (newId != _lastHoveredId || (newInterval != _lastHoveredInterval))
{
_lastHoveredId = newId;
_lastHoveredInterval = newInterval;
_renderEngine->UpdateHyperlinkHoveredId(newId);
_renderer->UpdateLastHoveredInterval(newInterval);
_renderer->TriggerRedrawAll();
}
}
2020-02-12 20:06:46 +01:00
}
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch && _touchAnchor)
{
const auto contactRect = point.Properties().ContactRect();
winrt::Windows::Foundation::Point newTouchPoint{ contactRect.X, contactRect.Y };
const auto anchor = _touchAnchor.value();
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Our _actualFont's size is in pixels, convert to DIPs, which the
// rest of the Points here are in.
const til::size fontSize{ _actualFont.GetSize() };
const auto fontSizeInDips = fontSize.scale(til::math::rounding, 1.0f / _renderEngine->GetScaling());
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Get the difference between the point we've dragged to and the start of the touch.
2020-02-12 20:06:46 +01:00
const float dy = newTouchPoint.Y - anchor.Y;
// Start viewport scroll after we've moved more than a half row of text
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
if (std::abs(dy) > (fontSizeInDips.height<float>() / 2.0f))
2020-02-12 20:06:46 +01:00
{
// Multiply by -1, because moving the touch point down will
// create a positive delta, but we want the viewport to move up,
// so we'll need a negative scroll amount (and the inverse for
// panning down)
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const float numRows = -1.0f * (dy / fontSizeInDips.height<float>());
2020-02-12 20:06:46 +01:00
const auto currentOffset = ::base::ClampedNumeric<double>(ScrollBar().Value());
const auto newValue = numRows + currentOffset;
2020-02-12 20:06:46 +01:00
ScrollBar().Value(newValue);
2020-02-12 20:06:46 +01:00
// Use this point as our new scroll anchor.
_touchAnchor = newTouchPoint;
}
}
args.Handled(true);
}
// Method Description:
// - Event handler for the PointerReleased event. We use this to de-anchor
// touch events, to stop scrolling via touch.
// Arguments:
// - sender: the XAML element responding to the pointer input
// - args: event data
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
const auto ptr = args.Pointer();
Ignore right-click copy when copy on select is enabled (#4819) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Right clicking on a focused tab while Copy On Select is active currently copies any active selection. This is because `PointerReleasedHandler` doesn't check for the mouse button that was released. During a mouse button release, only the left mouse button release should be doing anything. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4740 * [x] CLA signed. * [x] Tests added/passed <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed These are the scenarios I've tested. They're a combination of in focus/out of focus, Copy On Select on/off, left/right click pressed and their move and release variants. From Out of Focus: - Left Click = Focus - Left Click Move = Focus + Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Focus - Right Click - Focus - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing From In Focus - Left Click = Selection if CoS off - Left Click Move = Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Set Selection Anchor - Right Click - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing
2020-03-10 15:59:16 +01:00
const auto point = args.GetCurrentPoint(*this);
_ReleasePointerCapture(sender, args);
2020-02-12 20:06:46 +01:00
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}
Ignore right-click copy when copy on select is enabled (#4819) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Right clicking on a focused tab while Copy On Select is active currently copies any active selection. This is because `PointerReleasedHandler` doesn't check for the mouse button that was released. During a mouse button release, only the left mouse button release should be doing anything. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4740 * [x] CLA signed. * [x] Tests added/passed <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed These are the scenarios I've tested. They're a combination of in focus/out of focus, Copy On Select on/off, left/right click pressed and their move and release variants. From Out of Focus: - Left Click = Focus - Left Click Move = Focus + Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Focus - Right Click - Focus - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing From In Focus - Left Click = Selection if CoS off - Left Click Move = Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Set Selection Anchor - Right Click - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing
2020-03-10 15:59:16 +01:00
// Only a left click release when copy on select is active should perform a copy.
// Right clicks and middle clicks should not need to do anything when released.
if (_settings.CopyOnSelect() && point.Properties().PointerUpdateKind() == Windows::UI::Input::PointerUpdateKind::LeftButtonReleased && _selectionNeedsToBeCopied)
2020-02-12 20:06:46 +01:00
{
CopySelectionToClipboard(false, nullptr);
2020-02-12 20:06:46 +01:00
}
}
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch)
{
_touchAnchor = std::nullopt;
}
2020-03-25 22:09:49 +01:00
_singleClickTouchdownPos = std::nullopt;
2020-02-12 20:06:46 +01:00
_TryStopAutoScroll(ptr.PointerId());
args.Handled(true);
}
// Method Description:
// - Event handler for the PointerWheelChanged event. This is raised in
// response to mouse wheel changes. Depending upon what modifier keys are
// pressed, different actions will take place.
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
// - Primarily just takes the data from the PointerRoutedEventArgs and uses
// it to call _DoMouseWheel, see _DoMouseWheel for more details.
2020-02-12 20:06:46 +01:00
// Arguments:
// - args: the event args containing information about t`he mouse wheel event.
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
const auto point = args.GetCurrentPoint(*this);
const auto props = point.Properties();
const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() };
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
auto result = _DoMouseWheel(point.Position(),
ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(),
state);
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
if (result)
{
args.Handled(true);
}
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
}
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
// Method Description:
// - Actually handle a scrolling event, whether from a mouse wheel or a
// touchpad scroll. Depending upon what modifier keys are pressed,
// different actions will take place.
// * Attempts to first dispatch the mouse scroll as a VT event
// * If Ctrl+Shift are pressed, then attempts to change our opacity
// * If just Ctrl is pressed, we'll attempt to "zoom" by changing our font size
// * Otherwise, just scrolls the content of the viewport
// Arguments:
// - point: the location of the mouse during this event
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event.
bool TermControl::_DoMouseWheel(const Windows::Foundation::Point point,
const ControlKeyStates modifiers,
const int32_t delta,
const TerminalInput::MouseButtonState state)
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
{
if (_CanSendVTMouseInput())
{
// Most mouse event handlers call
// _TrySendMouseEvent(point);
// here with a PointerPoint. However, as of #979, we don't have a
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _terminal->SendMouseEvent(_GetTerminalPosition(point),
WM_MOUSEWHEEL,
_GetPressedModifierKeys(),
::base::saturated_cast<short>(delta),
state);
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
}
2020-02-12 20:06:46 +01:00
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
const auto ctrlPressed = modifiers.IsCtrlPressed();
const auto shiftPressed = modifiers.IsShiftPressed();
2020-02-12 20:06:46 +01:00
if (ctrlPressed && shiftPressed)
{
_MouseTransparencyHandler(delta);
}
else if (ctrlPressed)
{
_MouseZoomHandler(delta);
}
else
{
_MouseScrollHandler(delta, point, state.isLeftButtonDown);
2020-02-12 20:06:46 +01:00
}
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
return false;
}
// Method Description:
// - This is part of the solution to GH#979
// - Manually handle a scrolling event. This is used to help support
// scrolling on devices where the touchpad doesn't correctly handle
// scrolling inactive windows.
// Arguments:
// - location: the location of the mouse during this event. This location is
// relative to the origin of the control
// - delta: the mouse wheel delta that triggered this event.
// - state: the state for each of the mouse buttons individually (pressed/unpressed)
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
const int32_t delta,
const bool leftButtonDown,
const bool midButtonDown,
const bool rightButtonDown)
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
{
const auto modifiers = _GetPressedModifierKeys();
TerminalInput::MouseButtonState state{ leftButtonDown, midButtonDown, rightButtonDown };
return _DoMouseWheel(location, modifiers, delta, state);
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Tell TerminalCore to update its knowledge about the locations of visible regex patterns
// - We should call this (through the throttled function) when something causes the visible
// region to change, such as when new text enters the buffer or the viewport is scrolled
void TermControl::UpdatePatternLocations()
{
_terminal->UpdatePatterns();
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Adjust the opacity of the acrylic background in response to a mouse
// scrolling event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
void TermControl::_MouseTransparencyHandler(const double mouseDelta)
{
// Transparency is on a scale of [0.0,1.0], so only increment by .01.
const auto effectiveDelta = mouseDelta < 0 ? -.01 : .01;
if (_settings.UseAcrylic())
{
try
{
auto acrylicBrush = RootGrid().Background().as<Media::AcrylicBrush>();
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
_settings.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta);
acrylicBrush.TintOpacity(_settings.TintOpacity());
if (acrylicBrush.TintOpacity() == 1.0)
{
_settings.UseAcrylic(false);
_InitializeBackgroundBrush();
COLORREF bg = _settings.DefaultBackground();
_BackgroundColorChanged(bg);
}
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
else
{
// GH#5098: Inform the engine of the new opacity of the default text background.
if (_renderEngine)
{
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
}
}
2020-02-12 20:06:46 +01:00
}
CATCH_LOG();
}
else if (mouseDelta < 0)
{
_settings.UseAcrylic(true);
//Setting initial opacity set to 1 to ensure smooth transition to acrylic during mouse scroll
_settings.TintOpacity(1.0);
_InitializeBackgroundBrush();
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Adjust the font size of the terminal in response to a mouse scrolling
// event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
void TermControl::_MouseZoomHandler(const double mouseDelta)
{
const auto fontDelta = mouseDelta < 0 ? -1 : 1;
AdjustFontSize(fontDelta);
}
// Method Description:
// - Reset the font size of the terminal to its default size.
// Arguments:
// - none
void TermControl::ResetFontSize()
{
_SetFontSize(_settings.FontSize());
}
// Method Description:
// - Adjust the font size of the terminal control.
// Arguments:
// - fontSizeDelta: The amount to increase or decrease the font size by.
void TermControl::AdjustFontSize(int fontSizeDelta)
{
const auto newSize = _desiredFont.GetEngineSize().Y + fontSizeDelta;
_SetFontSize(newSize);
}
// Method Description:
// - Scroll the visible viewport in response to a mouse wheel event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
// - point: the location of the mouse during this event
// - isLeftButtonPressed: true iff the left mouse button was pressed during this event.
void TermControl::_MouseScrollHandler(const double mouseDelta,
const Windows::Foundation::Point point,
const bool isLeftButtonPressed)
2020-02-12 20:06:46 +01:00
{
const auto currentOffset = ScrollBar().Value();
2020-02-12 20:06:46 +01:00
// negative = down, positive = up
// However, for us, the signs are flipped.
// With one of the precision mice, one click is always a multiple of 120 (WHEEL_DELTA),
2020-02-12 20:06:46 +01:00
// but the "smooth scrolling" mode results in non-int values
const auto rowDelta = mouseDelta / (-1.0 * WHEEL_DELTA);
2020-02-12 20:06:46 +01:00
// WHEEL_PAGESCROLL is a Win32 constant that represents the "scroll one page
// at a time" setting. If we ignore it, we will scroll a truly absurd number
// of rows.
const auto rowsToScroll{ _rowsToScroll == WHEEL_PAGESCROLL ? GetViewHeight() : _rowsToScroll };
double newValue = (rowsToScroll * rowDelta) + (currentOffset);
2020-02-12 20:06:46 +01:00
// The scroll bar's ValueChanged handler will actually move the viewport
// for us.
ScrollBar().Value(newValue);
2020-02-12 20:06:46 +01:00
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
if (_terminal->IsSelectionActive() && isLeftButtonPressed)
2020-02-12 20:06:46 +01:00
{
Lock when changing selection endpoint on wheel/auto-scroll (#5551) Takes the lock inside two routines in `TermControl` that were changing the selection endpoint while a rendering frame was still drawing, resulting in several variants of graphical glitches from double-struck selection boxes to duplicated line text. ## References - Introduced with #5185 ## PR Checklist * [x] Closes #5471 * [x] Already signed life away to company. * [x] Manual tests passed since it's visual. * [x] No extra doc besides the comments. * [x] Am core contributor: Roar. The renderer base and specific renderer engine do a lot of work to remember the previous selection and compensate for scrolling regions and deltas between frames. However, all that work doesn't quite match up when the endpoints are changed out from under it. Unfortunately, `TermControl` doesn't have a robust history of locking correctly in step with the renderer nor does the renderer's `IRenderData` currently provide any way of 'snapping' state at the beginning of a frame so it could work without a full lock. So the solution for now is for the methods that scroll the display in `TermControl` to take the lock that is shared with the renderer's frame painter so they can't change out of sync. ## Validation Steps Performed - Opened terminal with Powershell core. Did ls a bunch of times. Clicked to make selection and held mouse button while wheeling around. - Opened terminal with Powershell core.; Did ls a bunch of times. Clicked to make selection and dragged mouse outside the window to make auto scroll happen. - Opened terminal with Powershell core. Did ls a bunch of times. Clicked to make selection and released. Wheeled around like a crazy person to make sure I didn't regress that.
2020-04-25 00:14:43 +02:00
// Have to take the lock or we could change the endpoints out from under the renderer actively rendering.
auto lock = _terminal->LockForWriting();
2020-02-12 20:06:46 +01:00
// If user is mouse selecting and scrolls, they then point at new character.
// Make sure selection reflects that immediately.
Manually pass mouse wheel messages to TermControls (#5131) ## Summary of the Pull Request As we've learned in #979, not all touchpads are created equal. Some of them have bad drivers that makes scrolling inactive windows not work. For whatever reason, these devices think the Terminal is all one giant inactive window, so we don't get the mouse wheel events through the XAML stack. We do however get the event as a `WM_MOUSEWHEEL` on those devices (a message we don't get on devices with normally functioning trackpads). This PR attempts to take that `WM_MOUSEWHEEL` and manually dispatch it to the `TermControl`, so we can at least scroll the terminal content. Unfortunately, this solution is not very general purpose. This only works to scroll controls that manually implement our own `IMouseWheelListener` interface. As we add more controls, we'll need to continue manually implementing this interface, until the underlying XAML Islands bug is fixed. **I don't love this**. I'd rather have a better solution, but it seems that we can't synthesize a more general-purpose `PointerWheeled` event that could get routed through the XAML tree as normal. ## References * #2606 and microsoft/microsoft-ui-xaml#2101 - these bugs are also tracking a similar "inactive windows" / "scaled mouse events" issue in XAML ## PR Checklist * [x] Closes #979 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I've also added a `til::point` conversion _to_ `winrt::Windows::Foundation::Point`, and some scaling operators for `point` ## Validation Steps Performed * It works on my HP Spectre 2017 with a synaptics trackpad - I also made sure to test that `tmux` works in panes on this laptop * It works on my slaptop, and DOESN'T follow this hack codepath on this machine.
2020-04-01 18:58:16 +02:00
_SetEndSelectionPointAtCursor(point);
2020-02-12 20:06:46 +01:00
}
}
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/,
Controls::Primitives::RangeBaseValueChangedEventArgs const& args)
{
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
if (_isInternalScrollBarUpdate || _closing)
2020-02-12 20:06:46 +01:00
{
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
// The update comes from ourselves, more specifically from the
// terminal. So we don't have to update the terminal because it
// already knows.
2020-02-12 20:06:46 +01:00
return;
}
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
_terminal->ClearPatternTree();
2020-02-12 20:06:46 +01:00
const auto newValue = static_cast<int>(args.NewValue());
// This is a scroll event that wasn't initiated by the terminal
// itself - it was initiated by the mouse wheel, or the scrollbar.
_terminal->UserScrollViewport(newValue);
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
// User input takes priority over terminal events so cancel
// any pending scroll bar update if the user scrolls.
_updateScrollBar->ModifyPending([](auto& update) {
update.newValue.reset();
});
_updatePatternLocations->Run();
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - captures the pointer so that none of the other XAML elements respond to pointer events
// Arguments:
// - sender: XAML element that is interacting with pointer
// - args: pointer data (i.e.: mouse, touch)
// Return Value:
// - true if we successfully capture the pointer, false otherwise.
bool TermControl::_CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args)
{
IUIElement uielem;
if (sender.try_as(uielem))
{
uielem.CapturePointer(args.Pointer());
return true;
}
return false;
}
// Method Description:
// - releases the captured pointer because we're done responding to XAML pointer events
// Arguments:
// - sender: XAML element that is interacting with pointer
// - args: pointer data (i.e.: mouse, touch)
// Return Value:
// - true if we release capture of the pointer, false otherwise.
bool TermControl::_ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args)
{
IUIElement uielem;
if (sender.try_as(uielem))
{
uielem.ReleasePointerCapture(args.Pointer());
return true;
}
return false;
}
// Method Description:
// - Starts new pointer related auto scroll behavior, or continues existing one.
// Does nothing when there is already auto scroll associated with another pointer.
// Arguments:
// - pointerPoint: info about pointer that causes auto scroll. Pointer's position
// is later used to update selection.
// - scrollVelocity: target velocity of scrolling in characters / sec
void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity)
{
// Allow only one pointer at the time
if (!_autoScrollingPointerPoint.has_value() || _autoScrollingPointerPoint.value().PointerId() == pointerPoint.PointerId())
{
_autoScrollingPointerPoint = pointerPoint;
_autoScrollVelocity = scrollVelocity;
// If this is first time the auto scroll update is about to be called,
// kick-start it by initializing its time delta as if it started now
if (!_lastAutoScrollUpdateTime.has_value())
{
_lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now();
}
// Apparently this check is not necessary but greatly improves performance
if (!_autoScrollTimer.IsEnabled())
{
_autoScrollTimer.Start();
}
}
}
// Method Description:
// - Stops auto scroll if it's active and is associated with supplied pointer id.
// Arguments:
// - pointerId: id of pointer for which to stop auto scroll
void TermControl::_TryStopAutoScroll(const uint32_t pointerId)
{
if (_autoScrollingPointerPoint.has_value() && pointerId == _autoScrollingPointerPoint.value().PointerId())
{
_autoScrollingPointerPoint = std::nullopt;
_autoScrollVelocity = 0;
_lastAutoScrollUpdateTime = std::nullopt;
// Apparently this check is not necessary but greatly improves performance
if (_autoScrollTimer.IsEnabled())
{
_autoScrollTimer.Stop();
}
}
}
// Method Description:
// - Called continuously to gradually scroll viewport when user is
// mouse selecting outside it (to 'follow' the cursor).
// Arguments:
// - none
void TermControl::_UpdateAutoScroll(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
{
if (_autoScrollVelocity != 0)
{
const auto timeNow = std::chrono::high_resolution_clock::now();
if (_lastAutoScrollUpdateTime.has_value())
{
static constexpr double microSecPerSec = 1000000.0;
const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec;
ScrollBar().Value(ScrollBar().Value() + _autoScrollVelocity * deltaTime);
2020-02-12 20:06:46 +01:00
if (_autoScrollingPointerPoint.has_value())
{
Lock when changing selection endpoint on wheel/auto-scroll (#5551) Takes the lock inside two routines in `TermControl` that were changing the selection endpoint while a rendering frame was still drawing, resulting in several variants of graphical glitches from double-struck selection boxes to duplicated line text. ## References - Introduced with #5185 ## PR Checklist * [x] Closes #5471 * [x] Already signed life away to company. * [x] Manual tests passed since it's visual. * [x] No extra doc besides the comments. * [x] Am core contributor: Roar. The renderer base and specific renderer engine do a lot of work to remember the previous selection and compensate for scrolling regions and deltas between frames. However, all that work doesn't quite match up when the endpoints are changed out from under it. Unfortunately, `TermControl` doesn't have a robust history of locking correctly in step with the renderer nor does the renderer's `IRenderData` currently provide any way of 'snapping' state at the beginning of a frame so it could work without a full lock. So the solution for now is for the methods that scroll the display in `TermControl` to take the lock that is shared with the renderer's frame painter so they can't change out of sync. ## Validation Steps Performed - Opened terminal with Powershell core. Did ls a bunch of times. Clicked to make selection and held mouse button while wheeling around. - Opened terminal with Powershell core.; Did ls a bunch of times. Clicked to make selection and dragged mouse outside the window to make auto scroll happen. - Opened terminal with Powershell core. Did ls a bunch of times. Clicked to make selection and released. Wheeled around like a crazy person to make sure I didn't regress that.
2020-04-25 00:14:43 +02:00
// Have to take the lock because the renderer will not draw correctly if you move its endpoints while it is generating a frame.
auto lock = _terminal->LockForWriting();
2020-02-12 20:06:46 +01:00
_SetEndSelectionPointAtCursor(_autoScrollingPointerPoint.value().Position());
}
}
_lastAutoScrollUpdateTime = timeNow;
}
}
// Method Description:
// - Event handler for the GotFocus event. This is used to...
// - enable accessibility notifications for this TermControl
// - start blinking the cursor when the window is focused
// - update the number of lines to scroll to the value set in the system
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
_focused = true;
InputPane::GetForCurrentView().TryShow();
// GH#5421: Enable the UiaEngine before checking for the SearchBox
// That way, new selections are notified to automation clients.
if (_uiaEngine.get())
{
THROW_IF_FAILED(_uiaEngine->Enable());
}
// If the searchbox is focused, we don't want TSFInputControl to think
// it has focus so it doesn't intercept IME input. We also don't want the
// terminal's cursor to start blinking. So, we'll just return quickly here.
if (_searchBox && _searchBox->ContainsFocus())
{
return;
}
if (TSFInputControl() != nullptr)
2020-02-12 20:06:46 +01:00
{
TSFInputControl().NotifyFocusEnter();
2020-02-12 20:06:46 +01:00
}
if (_cursorTimer.has_value())
{
// When the terminal focuses, show the cursor immediately
_terminal->SetCursorOn(true);
2020-02-12 20:06:46 +01:00
_cursorTimer.value().Start();
}
Add support for the "blink" graphic rendition attribute (#7490) This PR adds support for the _blink_ graphic rendition attribute. When a character is output with this attribute set, it "blinks" at a regular interval, by cycling its color between the normal rendition and a dimmer shade of that color. The majority of the blinking mechanism is encapsulated in a new `BlinkingState` class, which is shared between the Terminal and Conhost implementations. This class keeps track of the position in the blinking cycle, which determines whether characters are rendered as normal or faint. In Windows Terminal, the state is stored in the `Terminal` class, and in Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases, the `IsBlinkingFaint` method is used to determine the current blinking rendition, and that is passed on as a parameter to the `TextAttribute::CalculateRgbColors` method when these classes are looking up attribute colors. Prior to calculating the colors, the current attribute is also passed to the `RecordBlinkingUsage` method, which keeps track of whether there are actually any blink attributes in use. This is used to determine whether the screen needs to be refreshed when the blinking cycle toggles between the normal and faint renditions. The refresh itself is handled by the `ToggleBlinkingRendition` method, which is triggered by a timer. In Conhost this is just piggybacking on the existing cursor blink timer, but in Windows Terminal it needs to have its own separate timer, since the cursor timer is reset whenever a key is pressed, which is not something we want for attribute blinking. Although the `ToggleBlinkingRendition` is called at the same rate as the cursor blinking, we actually only want the cells to blink at half that frequency. We thus have a counter that cycles through four phases, and blinking is rendered as faint for two of those four. Then every two cycles - when the state changes - a redraw is triggered, but only if there are actually blinking attributes in use (as previously recorded). As mentioned earlier, the blinking frequency is based on the cursor blink rate, so that means it'll automatically be disabled if a user has set their cursor blink rate to none. It can also be disabled by turning off the _Show animations in Windows_ option. In Conhost these settings take effect immediately, but in Windows Terminal they only apply when a new tab is opened. This PR also adds partial support for the `SGR 6` _rapid blink_ attribute. This is not used by DEC terminals, but was defined in the ECMA/ANSI standards. It's not widely supported, but many terminals just it implement it as an alias for the regular `SGR 5` blink attribute, so that's what I've done here too. ## Validation Steps Performed I've checked the _Graphic rendition test pattern_ in Vttest, and compared our representation of the blink attribute to that of an actual DEC VT220 terminal as seen on [YouTube]. With the right color scheme it's a reasonably close match. [YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s Closes #7388
2020-09-22 01:21:33 +02:00
if (_blinkTimer.has_value())
{
_blinkTimer.value().Start();
}
_UpdateSystemParameterSettings();
}
// Method Description
// - Updates internal params based on system parameters
void TermControl::_UpdateSystemParameterSettings() noexcept
{
if (!SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &_rowsToScroll, 0))
{
LOG_LAST_ERROR();
// If SystemParametersInfoW fails, which it shouldn't, fall back to
// Windows' default value.
_rowsToScroll = 3;
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Event handler for the LostFocus event. This is used to...
// - disable accessibility notifications for this TermControl
// - hide and stop blinking the cursor when the window loses focus.
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
if (_closing)
{
return;
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
2020-02-12 20:06:46 +01:00
_focused = false;
if (_uiaEngine.get())
{
THROW_IF_FAILED(_uiaEngine->Disable());
}
if (TSFInputControl() != nullptr)
2020-02-12 20:06:46 +01:00
{
TSFInputControl().NotifyFocusLeave();
2020-02-12 20:06:46 +01:00
}
if (_cursorTimer.has_value())
{
_cursorTimer.value().Stop();
_terminal->SetCursorOn(false);
2020-02-12 20:06:46 +01:00
}
Add support for the "blink" graphic rendition attribute (#7490) This PR adds support for the _blink_ graphic rendition attribute. When a character is output with this attribute set, it "blinks" at a regular interval, by cycling its color between the normal rendition and a dimmer shade of that color. The majority of the blinking mechanism is encapsulated in a new `BlinkingState` class, which is shared between the Terminal and Conhost implementations. This class keeps track of the position in the blinking cycle, which determines whether characters are rendered as normal or faint. In Windows Terminal, the state is stored in the `Terminal` class, and in Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases, the `IsBlinkingFaint` method is used to determine the current blinking rendition, and that is passed on as a parameter to the `TextAttribute::CalculateRgbColors` method when these classes are looking up attribute colors. Prior to calculating the colors, the current attribute is also passed to the `RecordBlinkingUsage` method, which keeps track of whether there are actually any blink attributes in use. This is used to determine whether the screen needs to be refreshed when the blinking cycle toggles between the normal and faint renditions. The refresh itself is handled by the `ToggleBlinkingRendition` method, which is triggered by a timer. In Conhost this is just piggybacking on the existing cursor blink timer, but in Windows Terminal it needs to have its own separate timer, since the cursor timer is reset whenever a key is pressed, which is not something we want for attribute blinking. Although the `ToggleBlinkingRendition` is called at the same rate as the cursor blinking, we actually only want the cells to blink at half that frequency. We thus have a counter that cycles through four phases, and blinking is rendered as faint for two of those four. Then every two cycles - when the state changes - a redraw is triggered, but only if there are actually blinking attributes in use (as previously recorded). As mentioned earlier, the blinking frequency is based on the cursor blink rate, so that means it'll automatically be disabled if a user has set their cursor blink rate to none. It can also be disabled by turning off the _Show animations in Windows_ option. In Conhost these settings take effect immediately, but in Windows Terminal they only apply when a new tab is opened. This PR also adds partial support for the `SGR 6` _rapid blink_ attribute. This is not used by DEC terminals, but was defined in the ECMA/ANSI standards. It's not widely supported, but many terminals just it implement it as an alias for the regular `SGR 5` blink attribute, so that's what I've done here too. ## Validation Steps Performed I've checked the _Graphic rendition test pattern_ in Vttest, and compared our representation of the blink attribute to that of an actual DEC VT220 terminal as seen on [YouTube]. With the right color scheme it's a reasonably close match. [YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s Closes #7388
2020-09-22 01:21:33 +02:00
if (_blinkTimer.has_value())
{
_blinkTimer.value().Stop();
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Writes the given sequence as input to the active terminal connection.
// - This method has been overloaded to allow zero-copy winrt::param::hstring optimizations.
2020-02-12 20:06:46 +01:00
// Arguments:
// - wstr: the string of characters to write to the terminal connection.
// Return Value:
// - <none>
void TermControl::_SendInputToConnection(const winrt::hstring& wstr)
{
_connection.WriteInput(wstr);
}
void TermControl::_SendInputToConnection(std::wstring_view wstr)
2020-02-12 20:06:46 +01:00
{
_connection.WriteInput(wstr);
}
// Method Description:
// - Pre-process text pasted (presumably from the clipboard)
// before sending it over the terminal's connection, converting
// Windows-space \r\n line-endings to \r line-endings
void TermControl::_SendPastedTextToConnection(const std::wstring& wstr)
{
// Some notes on this implementation:
//
// - std::regex can do this in a single line, but is somewhat
// overkill for a simple search/replace operation (and its
// performance guarantees aren't exactly stellar)
// - The STL doesn't have a simple string search/replace method.
// This fact is lamentable.
// - This line-ending conversion is intentionally fairly
// conservative, to avoid stripping out lone \n characters
// where they could conceivably be intentional.
std::wstring stripped{ wstr };
std::wstring::size_type pos = 0;
while ((pos = stripped.find(L"\r\n", pos)) != std::wstring::npos)
{
stripped.replace(pos, 2, L"\r");
}
_connection.WriteInput(stripped);
_terminal->TrySnapOnInput();
}
// Method Description:
// - Update the font with the renderer. This will be called either when the
// font changes or the DPI changes, as DPI changes will necessitate a
// font change. This method will *not* change the buffer/viewport size
// to account for the new glyph dimensions. Callers should make sure to
// appropriately call _DoResizeUnderLock after this method is called.
// - The write lock should be held when calling this method.
2020-02-12 20:06:46 +01:00
// Arguments:
// - initialUpdate: whether this font update should be considered as being
// concerned with initialization process. Value forwarded to event handler.
void TermControl::_UpdateFont(const bool initialUpdate)
{
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
2020-02-12 20:06:46 +01:00
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
const auto actualNewSize = _actualFont.GetSize();
_fontSizeChangedHandlers(actualNewSize.X, actualNewSize.Y, initialUpdate);
}
// Method Description:
// - Set the font size of the terminal control.
// Arguments:
// - fontSize: The size of the font.
void TermControl::_SetFontSize(int fontSize)
{
try
{
// Make sure we have a non-zero font size
const auto newSize = std::max<short>(gsl::narrow_cast<short>(fontSize), 1);
2020-02-12 20:06:46 +01:00
const auto fontFace = _settings.FontFace();
Add font weight options (#6048) ## Summary of the Pull Request Adds the ability to specify the font weight in the profiles, right next to the size and the font face. ## PR Checklist * [x] Closes #1751 * [x] I work here. * [x] Tested manually, see below. * [x] Added documentation to the schema * [x] Requires docs.microsoft.com update, filed as https://github.com/MicrosoftDocs/terminal/issues/26 * [x] I'm a core contributor. ## Detailed Description of the Pull Request / Additional comments - Weights can be specified according to the OpenType specification values. We accept either the friendly name or the numerical value that applies to each weight. - Weights are carried through per-profile and sent into the renderer. - Weights are carried through to the TSF/IME overlay. - The names are restricted to the set seen at https://docs.microsoft.com/en-us/uwp/api/windows.ui.text.fontweights. - There are alternate names at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_weight for the same values (ultra-black is just an alias for extra-black at 950). ## Validation Steps Performed - Cascadia Code normal ![image](https://user-images.githubusercontent.com/18221333/82480181-46117380-9a88-11ea-9436-a5fe4ccd4350.png) - Cascadia Code bold ![image](https://user-images.githubusercontent.com/18221333/82480202-4f9adb80-9a88-11ea-9e27-a113b41387f5.png) - Segoe UI Semilight ![image](https://user-images.githubusercontent.com/18221333/82480306-73f6b800-9a88-11ea-93f7-d773ab7ccce8.png) - Segoe UI Black ![image](https://user-images.githubusercontent.com/18221333/82480401-9688d100-9a88-11ea-957c-0c8e03a8cc29.png) - Segoe UI 900 (value for Black) ![image](https://user-images.githubusercontent.com/18221333/82480774-26c71600-9a89-11ea-8cf6-aaeab1fd0747.png)
2020-05-20 22:17:17 +02:00
const auto fontWeight = _settings.FontWeight();
_actualFont = { fontFace, 0, fontWeight.Weight, { 0, newSize }, CP_UTF8, false };
2020-02-12 20:06:46 +01:00
_desiredFont = { _actualFont };
auto lock = _terminal->LockForWriting();
2020-02-12 20:06:46 +01:00
// Refresh our font with the renderer
_UpdateFont();
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
2020-02-12 20:06:46 +01:00
// Resize the terminal's BUFFER to match the new font size. This does
// NOT change the size of the window, because that can lead to more
// problems (like what happens when you change the font size while the
// window is maximized?)
_RefreshSizeUnderLock();
2020-02-12 20:06:46 +01:00
}
CATCH_LOG();
}
// Method Description:
// - Triggered when the swapchain changes size. We use this to resize the
// terminal buffers to match the new visible size.
// Arguments:
// - e: a SizeChangedEventArgs with the new dimensions of the SwapChainPanel
void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/,
SizeChangedEventArgs const& e)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (!_initializedTerminal || _closing)
2020-02-12 20:06:46 +01:00
{
return;
}
auto lock = _terminal->LockForWriting();
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto newSize = e.NewSize();
const auto currentScaleX = SwapChainPanel().CompositionScaleX();
const auto currentEngineScale = _renderEngine->GetScaling();
auto foundationSize = newSize;
// A strange thing can happen here. If you have two tabs open, and drag
// across a DPI boundary, then switch to the other tab, that tab will
// receive two events: First, a SizeChanged, then a ScaleChanged. In the
// SizeChanged event handler, the SwapChainPanel's CompositionScale will
// _already_ be the new scaling, but the engine won't have that value
// yet. If we scale by the CompositionScale here, we'll end up in a
// weird torn state. I'm not totally sure why.
//
// Fortunately we will be getting that following ScaleChanged event, and
// we'll end up resizing again, so we don't terribly need to worry about
// this.
foundationSize.Width *= currentEngineScale;
foundationSize.Height *= currentEngineScale;
2020-02-12 20:06:46 +01:00
_DoResizeUnderLock(foundationSize.Width, foundationSize.Height);
2020-02-12 20:06:46 +01:00
}
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Method Description:
// - Triggered when the swapchain changes DPI. When this happens, we're
// going to receive 3 events:
// - 1. First, a CompositionScaleChanged _for the original scale_. I don't
// know why this event happens first. **It also doesn't always happen.**
// However, when it does happen, it doesn't give us any useful
// information.
// - 2. Then, a SizeChanged. During that SizeChanged, either:
// - the CompositionScale will still be the original DPI. This happens
// when the control is visible as the DPI changes.
// - The CompositionScale will be the new DPI. This happens when the
// control wasn't focused as the window's DPI changed, so it only got
// these messages after XAML updated it's scaling.
// - 3. Finally, a CompositionScaleChanged with the _new_ DPI.
// - 4. We'll usually get another SizeChanged some time after this last
// ScaleChanged. This usually seems to happen after something triggers
// the UI to re-layout, like hovering over the scrollbar. This event
// doesn't reliably happen immediately after a scale change, so we can't
// depend on it (despite the fact that both the scale and size state is
// definitely correct in it)
// - In the 3rd event, we're going to update our font size for the new DPI.
// At that point, we know how big the font should be for the new DPI, and
// how big the SwapChainPanel will be. If these sizes are different, we'll
// need to resize the buffer to fit in the new window.
// Arguments:
// - sender: The SwapChainPanel who's DPI changed. This is our _swapchainPanel.
// - args: This param is unused in the CompositionScaleChanged event.
2020-02-12 20:06:46 +01:00
void TermControl::_SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender,
Windows::Foundation::IInspectable const& /*args*/)
{
if (_renderEngine)
{
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto scaleX = sender.CompositionScaleX();
const auto scaleY = sender.CompositionScaleY();
const auto dpi = (float)(scaleX * USER_DEFAULT_SCREEN_DPI);
const auto currentEngineScale = _renderEngine->GetScaling();
// If we're getting a notification to change to the DPI we already
// have, then we're probably just beginning the DPI change. Since
// we'll get _another_ event with the real DPI, do nothing here for
// now. We'll also skip the next resize in _SwapChainSizeChanged.
const bool dpiWasUnchanged = currentEngineScale == scaleX;
if (dpiWasUnchanged)
{
return;
}
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto actualFontOldSize = _actualFont.GetSize();
auto lock = _terminal->LockForWriting();
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
_renderer->TriggerFontChange(::base::saturated_cast<int>(dpi), _desiredFont, _actualFont);
const auto actualFontNewSize = _actualFont.GetSize();
if (actualFontNewSize != actualFontOldSize)
{
_RefreshSizeUnderLock();
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
}
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Toggle the cursor on and off when called by the cursor blink timer.
// Arguments:
// - sender: not used
// - e: not used
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
2020-02-12 20:06:46 +01:00
{
if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible()))
{
return;
}
_terminal->SetCursorOn(!_terminal->IsCursorOn());
2020-02-12 20:06:46 +01:00
}
Add support for the "blink" graphic rendition attribute (#7490) This PR adds support for the _blink_ graphic rendition attribute. When a character is output with this attribute set, it "blinks" at a regular interval, by cycling its color between the normal rendition and a dimmer shade of that color. The majority of the blinking mechanism is encapsulated in a new `BlinkingState` class, which is shared between the Terminal and Conhost implementations. This class keeps track of the position in the blinking cycle, which determines whether characters are rendered as normal or faint. In Windows Terminal, the state is stored in the `Terminal` class, and in Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases, the `IsBlinkingFaint` method is used to determine the current blinking rendition, and that is passed on as a parameter to the `TextAttribute::CalculateRgbColors` method when these classes are looking up attribute colors. Prior to calculating the colors, the current attribute is also passed to the `RecordBlinkingUsage` method, which keeps track of whether there are actually any blink attributes in use. This is used to determine whether the screen needs to be refreshed when the blinking cycle toggles between the normal and faint renditions. The refresh itself is handled by the `ToggleBlinkingRendition` method, which is triggered by a timer. In Conhost this is just piggybacking on the existing cursor blink timer, but in Windows Terminal it needs to have its own separate timer, since the cursor timer is reset whenever a key is pressed, which is not something we want for attribute blinking. Although the `ToggleBlinkingRendition` is called at the same rate as the cursor blinking, we actually only want the cells to blink at half that frequency. We thus have a counter that cycles through four phases, and blinking is rendered as faint for two of those four. Then every two cycles - when the state changes - a redraw is triggered, but only if there are actually blinking attributes in use (as previously recorded). As mentioned earlier, the blinking frequency is based on the cursor blink rate, so that means it'll automatically be disabled if a user has set their cursor blink rate to none. It can also be disabled by turning off the _Show animations in Windows_ option. In Conhost these settings take effect immediately, but in Windows Terminal they only apply when a new tab is opened. This PR also adds partial support for the `SGR 6` _rapid blink_ attribute. This is not used by DEC terminals, but was defined in the ECMA/ANSI standards. It's not widely supported, but many terminals just it implement it as an alias for the regular `SGR 5` blink attribute, so that's what I've done here too. ## Validation Steps Performed I've checked the _Graphic rendition test pattern_ in Vttest, and compared our representation of the blink attribute to that of an actual DEC VT220 terminal as seen on [YouTube]. With the right color scheme it's a reasonably close match. [YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s Closes #7388
2020-09-22 01:21:33 +02:00
// Method Description:
// - Toggle the blinking rendition state when called by the blink timer.
// Arguments:
// - sender: not used
// - e: not used
void TermControl::_BlinkTimerTick(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
{
if (!_closing)
{
auto& renderTarget = *_renderer;
auto& blinkingState = _terminal->GetBlinkingState();
blinkingState.ToggleBlinkingRendition(renderTarget);
}
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
// - cursorPosition: in pixels, relative to the origin of the control
void TermControl::_SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition)
{
if (!_terminal->IsSelectionActive())
{
return;
}
2020-02-12 20:06:46 +01:00
auto terminalPosition = _GetTerminalPosition(cursorPosition);
const short lastVisibleRow = std::max<short>(_terminal->GetViewport().Height() - 1, 0);
const short lastVisibleCol = std::max<short>(_terminal->GetViewport().Width() - 1, 0);
terminalPosition.Y = std::clamp<short>(terminalPosition.Y, 0, lastVisibleRow);
terminalPosition.X = std::clamp<short>(terminalPosition.X, 0, lastVisibleCol);
// save location (for rendering) + render
Move rect expansion to textbuffer; refactor selection code (#4560) - When performing chunk selection, the expansion now occurs at the time of the selection, not the rendering of the selection - `GetSelectionRects()` was moved to the `TextBuffer` and is now shared between ConHost and Windows Terminal - Some of the selection variables were renamed for clarity - Selection COORDs are now in the Text Buffer coordinate space - Fixes an issue with Shift+Click after performing a Multi-Click Selection ## References This also contributes to... - #4509: UIA Box Selection - #2447: UIA Signaling for Selection - #1354: UIA support for Wide Glyphs Now that the expansion occurs at before render-time, the selection anchors are an accurate representation of what is selected. We just need to move `GetText` to the `TextBuffer`. Then we can have those three issues just rely on code from the text buffer. This also means ConHost gets some of this stuff for free 😀 ### TextBuffer - `GetTextRects` is the abstracted form of `GetSelectionRects` - `_ExpandTextRow` is still needed to handle wide glyphs properly ### Terminal - Rename... - `_boxSelection` --> `_blockSelection` for consistency with ConHost - `_selectionAnchor` --> `_selectionStart` for consistency with UIA - `_endSelectionPosition` --> `_selectionEnd` for consistency with UIA - Selection anchors are in Text Buffer coordinates now - Really rely on `SetSelectionEnd` to accomplish appropriate chunk selection and shift+click actions ## Validation Steps Performed - Shift+Click - Multi-Click --> Shift+Click - Chunk Selection at... - top of buffer - bottom of buffer - random region in scrollback Closes #4465 Closes #4547
2020-02-28 01:42:26 +01:00
_terminal->SetSelectionEnd(terminalPosition);
2020-02-12 20:06:46 +01:00
_renderer->TriggerSelection();
2020-03-25 22:09:49 +01:00
_selectionNeedsToBeCopied = true;
2020-02-12 20:06:46 +01:00
}
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Method Description:
// - Perform a resize for the current size of the swapchainpanel. If the
// font size changed, we'll need to resize the buffer to fit the existing
// swapchain size. This helper will call _DoResizeUnderLock with the current size
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// of the swapchain, accounting for scaling due to DPI.
// - Note that a DPI change will also trigger a font size change, and will call into here.
// - The write lock should be held when calling this method, we might be changing the buffer size in _DoResizeUnderLock.
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Arguments:
// - <none>
// Return Value:
// - <none>
void TermControl::_RefreshSizeUnderLock()
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
{
const auto currentScaleX = SwapChainPanel().CompositionScaleX();
const auto currentScaleY = SwapChainPanel().CompositionScaleY();
const auto actualWidth = SwapChainPanel().ActualWidth();
const auto actualHeight = SwapChainPanel().ActualHeight();
const auto widthInPixels = actualWidth * currentScaleX;
const auto heightInPixels = actualHeight * currentScaleY;
_DoResizeUnderLock(widthInPixels, heightInPixels);
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Process a resize event that was initiated by the user. This can either
// be due to the user resizing the window (causing the swapchain to
// resize) or due to the DPI changing (causing us to need to resize the
// buffer to match)
// Arguments:
// - newWidth: the new width of the swapchain, in pixels.
// - newHeight: the new height of the swapchain, in pixels.
void TermControl::_DoResizeUnderLock(const double newWidth, const double newHeight)
2020-02-12 20:06:46 +01:00
{
SIZE size;
size.cx = static_cast<long>(newWidth);
size.cy = static_cast<long>(newHeight);
// Don't actually resize so small that a single character wouldn't fit
// in either dimension. The buffer really doesn't like being size 0.
if (size.cx < _actualFont.GetSize().X || size.cy < _actualFont.GetSize().Y)
{
return;
}
_terminal->ClearSelection();
2020-02-12 20:06:46 +01:00
// Tell the dx engine that our window is now the new size.
THROW_IF_FAILED(_renderEngine->SetWindowSize(size));
// Invalidate everything
_renderer->TriggerRedrawAll();
// Convert our new dimensions to characters
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 },
{ static_cast<short>(size.cx), static_cast<short>(size.cy) });
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
// If this function succeeds with S_FALSE, then the terminal didn't
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// actually change size. No need to notify the connection of this no-op.
2020-02-12 20:06:46 +01:00
const HRESULT hr = _terminal->UserResize({ vp.Width(), vp.Height() });
if (SUCCEEDED(hr) && hr != S_FALSE)
{
_connection.Resize(vp.Height(), vp.Width());
}
}
void TermControl::_TerminalWarningBell()
{
_WarningBellHandlers(*this, nullptr);
}
2020-02-12 20:06:46 +01:00
void TermControl::_TerminalTitleChanged(const std::wstring_view& wstr)
{
_titleChangedHandlers(winrt::hstring{ wstr });
}
void TermControl::_TerminalTabColorChanged(const std::optional<til::color> /*color*/)
{
_TabColorChangedHandlers(*this, nullptr);
}
2020-02-12 20:06:46 +01:00
void TermControl::_CopyToClipboard(const std::wstring_view& wstr)
{
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(wstr));
_clipboardCopyHandlers(*this, *copyArgs);
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Update the position and size of the scrollbar to match the given
// viewport top, viewport height, and buffer size.
// Additionally fires a ScrollPositionChanged event for anyone who's
// registered an event handler for us.
// Arguments:
// - viewTop: the top of the visible viewport, in rows. 0 indicates the top
// of the buffer.
// - viewHeight: the height of the viewport in rows.
// - bufferSize: the length of the buffer, in rows
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
void TermControl::_TerminalScrollPositionChanged(const int viewTop,
const int viewHeight,
const int bufferSize)
2020-02-12 20:06:46 +01:00
{
// Since this callback fires from non-UI thread, we might be already
// closed/closing.
if (_closing.load())
{
return;
}
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
_terminal->ClearPatternTree();
2020-02-12 20:06:46 +01:00
_scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize);
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
ScrollBarUpdate update;
const auto hiddenContent = bufferSize - viewHeight;
update.newMaximum = hiddenContent;
update.newMinimum = 0;
update.newViewportSize = viewHeight;
update.newValue = viewTop;
2020-02-12 20:06:46 +01:00
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608) In addition to the below (original) description, this commit introduces a ThrottledFunc template that can throttle _any_ function. It applies that type to muffle updates to the scrollbar. --- Redo #3531 but without the bug that it caused (#3622) which is why it was reverted. I'm sorry if I explain this badly. If you don't understand a part, make sure to let me know and I will explain it better. ### Explanation How it worked before: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it dispatches work for later to be ran the UI thread to updates the scrollbar's values Why it's bad: * If we have many viewport changes, it will create a long stack of operations to run. Instead, we should just update the scroll bar with the most recent information that we know. * Imagine if the rate that the work gets pushed on the UI thread is greater than the rate that it can handle: it might freeze? * No need to be real time, we can wait just a little bit (8ms) to accumulate viewport changes before we actually change the scroll bar's value because it appears to be expensive (see perf below). Now: `Terminal` signals that viewport changed -> `TermControl::_TerminalScrollPositionChanged` gets called on the terminal thread -> it tells the `ScrollBarUpdater` about a new update -> the `ScrollBarUpdater` only runs one job (I don't know if that's the right term) on the UI thread at a time. If a job is already running but hasn't updated the scroll bar yet, it changes the setting in the already existing job to update the scroll bar with the new values. A job "waits" some time before doing the update to throttle updates because we don't need real time scroll bar updates. -> eventually, it updates the scroll bar If the user scrolls when a scroll bar update is pending, we keep the scroll bar's Maximum and Minimum but let the user choose its new Value with the `CancelPendingValueChange` method. ### Note Also I changed a little bit the code from the Terminal to notify the TermControl less often when possible. I tried to scroll with the scroll bar, with the mouse wheel. I tried to scroll while content is being outputted. I tried to reproduce the crash from #2248 without success (good). Co-authored-by: Leonard Hecker <leonard@hecker.io> Closes #3622
2020-06-12 21:51:37 +02:00
_updateScrollBar->Run(update);
_updatePatternLocations->Run();
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Tells TSFInputControl to redraw the Canvas/TextBlock so it'll update
// to be where the current cursor position is.
// Arguments:
// - N/A
void TermControl::_TerminalCursorPositionChanged()
{
_tsfTryRedrawCanvas->Run();
}
2020-02-12 20:06:46 +01:00
hstring TermControl::Title()
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
hstring hstr{ _terminal->GetConsoleTitle() };
2020-02-12 20:06:46 +01:00
return hstr;
}
Define Automation Properties for TermControl (#4732) Defines the following automation properties for a Terminal Control: - [**Orientation**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getorientationcore): - The orientation of the control - None --> Vertical - [**Name**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getnamecore): - The name as used by assistive technology and other Microsoft UI Automation clients. Generally presented by automation clients as the primary way to identify an element (along with the control type) - "" --> <profile name> - [**HelpText**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.gethelptextcore): - The help text. Generally presented by automation clients if requested by the user. This would be something that you would normally expect to appear from tooltips. - "" --> <tab title> - [**LiveSetting**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getlivesettingcore): - reports the live setting notification behavior. A representation of how assertive this control should be when content changes. - none --> Polite ## Detailed Description of the Pull Request / Additional comments ProfileName had to be added to the TerminalSettings (IControlSettings) to pass that information along to the automation peer. In the rare event that somebody purposefully decided to make their ProfileName empty, we fallback to the tab title. ## Validation Steps Performed Verified using Accessibility Insights and inspect.exe This is are some examples of the information a general user can expect to receive about a Terminal Control. - Type: Terminal Control - Name: Command Prompt - Help Text (if requested): Command Prompt - ping bing.com - Type: Terminal Control - Name: Ubuntu - Help Text (if requested): cazamor@PC-cazamor:/mnt/c/Users/cazamor$ Note, it is generally read by an automation client as follows: "<type>, <name>" References #2099 - Automation Properties for TerminalControl, Search Box References #2142 - Localization Closes #2142
2020-02-28 01:37:56 +01:00
hstring TermControl::GetProfileName() const
{
return _settings.ProfileName();
}
2020-02-12 20:06:46 +01:00
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
// - CopyOnSelect does NOT clear the selection
// Arguments:
// - singleLine: collapse all of the text to one line
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool TermControl::CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
2020-02-12 20:06:46 +01:00
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return false;
}
2020-02-12 20:06:46 +01:00
// no selection --> nothing to copy
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (!_terminal->IsSelectionActive())
2020-02-12 20:06:46 +01:00
{
return false;
}
2020-03-25 22:09:49 +01:00
// Mark the current selection as copied
_selectionNeedsToBeCopied = false;
2020-02-12 20:06:46 +01:00
// extract text from buffer
const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(singleLine);
2020-02-12 20:06:46 +01:00
// convert text: vector<string> --> string
std::wstring textData;
for (const auto& text : bufferData.text)
{
textData += text;
}
// convert text to HTML format
// GH#5347 - Don't provide a title for the generated HTML, as many
// web applications will paste the title first, followed by the HTML
// content, which is unexpected.
const auto htmlData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::HTML) ?
TextBuffer::GenHTML(bufferData,
_actualFont.GetUnscaledSize().Y,
_actualFont.GetFaceName(),
_settings.DefaultBackground()) :
"";
2020-02-12 20:06:46 +01:00
// convert to RTF format
const auto rtfData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::RTF) ?
TextBuffer::GenRTF(bufferData,
_actualFont.GetUnscaledSize().Y,
_actualFont.GetFaceName(),
_settings.DefaultBackground()) :
"";
2020-02-12 20:06:46 +01:00
2020-03-25 22:09:49 +01:00
if (!_settings.CopyOnSelect())
2020-02-12 20:06:46 +01:00
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
2020-02-12 20:06:46 +01:00
}
// send data up for clipboard
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(textData),
winrt::to_hstring(htmlData),
winrt::to_hstring(rtfData),
formats);
2020-02-12 20:06:46 +01:00
_clipboardCopyHandlers(*this, *copyArgs);
return true;
}
// Method Description:
// - Initiate a paste operation.
void TermControl::PasteTextFromClipboard()
{
// attach TermControl::_SendInputToConnection() as the clipboardDataHandler.
// This is called when the clipboard data is loaded.
auto clipboardDataHandler = std::bind(&TermControl::_SendPastedTextToConnection, this, std::placeholders::_1);
auto pasteArgs = winrt::make_self<PasteFromClipboardEventArgs>(clipboardDataHandler);
// send paste event up to TermApp
_clipboardPasteHandlers(*this, *pasteArgs);
}
Don't wait for a connection to finish when a control is closed (#5303) ## Summary of the Pull Request When a pane is closed by a connection, we want to wait until the connection is actually `Closed` before we fire the actual `Closed` event. If the connection didn't close gracefully, there are scenarios where we want to print a message to the screen. However, when a pane is closed by the UI, we don't really care to wait for the connection to be completely closed. We can just do it whenever. So I've moved that call to be on a background thread. ## PR Checklist * [x] Closes #1996 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Previously we'd wait for the connection to close synchronously when closing tabs or panes. For misbehaving applications like `ssh.exe`, that could result in the `Close` needing to `WaitForSingleObject` _on the UI thread_. If the user closed the tab / pane either with a keybinding or with some other UI element, they don't really care to see the error message anymore. They just want the pane closed. So there's no need to wait for the actual connection to close - the app can just continue on with whatever it was doing. ## Validation Steps Performed Messed around with closing tabs, panes, tabs with many panes, the entire window. Did this with keybindings, or by clicking on the 'x' on the tab, the 'x' on the window, or using middle-click. I'm always scared of things like this, so there's a 50% chance this makes things horribly worse.
2020-04-10 01:27:56 +02:00
// Method Description:
// - Asynchronously close our connection. The Connection will likely wait
// until the attached process terminates before Close returns. If that's
// the case, we don't want to block the UI thread waiting on that process
// handle.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::_AsyncCloseConnection()
{
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
// Close the connection on the background thread.
co_await winrt::resume_background();
localConnection.Close();
// connection is destroyed.
}
}
2020-02-12 20:06:46 +01:00
void TermControl::Close()
{
if (!_closing.exchange(true))
{
// Stop accepting new output and state changes before we disconnect everything.
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
_autoScrollTimer.Stop();
2020-02-12 20:06:46 +01:00
Don't wait for a connection to finish when a control is closed (#5303) ## Summary of the Pull Request When a pane is closed by a connection, we want to wait until the connection is actually `Closed` before we fire the actual `Closed` event. If the connection didn't close gracefully, there are scenarios where we want to print a message to the screen. However, when a pane is closed by the UI, we don't really care to wait for the connection to be completely closed. We can just do it whenever. So I've moved that call to be on a background thread. ## PR Checklist * [x] Closes #1996 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Previously we'd wait for the connection to close synchronously when closing tabs or panes. For misbehaving applications like `ssh.exe`, that could result in the `Close` needing to `WaitForSingleObject` _on the UI thread_. If the user closed the tab / pane either with a keybinding or with some other UI element, they don't really care to see the error message anymore. They just want the pane closed. So there's no need to wait for the actual connection to close - the app can just continue on with whatever it was doing. ## Validation Steps Performed Messed around with closing tabs, panes, tabs with many panes, the entire window. Did this with keybindings, or by clicking on the 'x' on the tab, the 'x' on the window, or using middle-click. I'm always scared of things like this, so there's a 50% chance this makes things horribly worse.
2020-04-10 01:27:56 +02:00
// GH#1996 - Close the connection asynchronously on a background
// thread.
// Since TermControl::Close is only ever triggered by the UI, we
// don't really care to wait for the connection to be completely
// closed. We can just do it whenever.
_AsyncCloseConnection();
2020-02-12 20:06:46 +01:00
if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
{
if (auto localRenderer{ std::exchange(_renderer, nullptr) })
{
localRenderer->TriggerTeardown();
// renderer is destroyed
}
// renderEngine is destroyed
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
// we don't destroy _terminal here; it now has the same lifetime as the
// control.
2020-02-12 20:06:46 +01:00
}
}
// Method Description:
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly
// Arguments:
// - viewTop: the viewTop to scroll to
void TermControl::ScrollViewport(int viewTop)
{
ScrollBar().Value(viewTop);
2020-02-12 20:06:46 +01:00
}
int TermControl::GetScrollOffset()
{
return _terminal->GetScrollOffset();
}
// Function Description:
// - Gets the height of the terminal in lines of text
// Return Value:
// - The height of the terminal in lines of text
int TermControl::GetViewHeight() const
{
const auto viewPort = _terminal->GetViewport();
return viewPort.Height();
}
// Function Description:
// - Determines how much space (in pixels) an app would need to reserve to
// create a control with the settings stored in the settings param. This
// accounts for things like the font size and face, the initialRows and
// initialCols, and scrollbar visibility. The returned sized is based upon
// the provided DPI value
// Arguments:
// - settings: A IControlSettings with the settings to get the pixel size of.
// - dpi: The DPI we should create the terminal at. This affects things such
// as font size, scrollbar and other control scaling, etc. Make sure the
// caller knows what monitor the control is about to appear on.
// Return Value:
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
// - a size containing the requested dimensions in pixels.
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi)
2020-02-12 20:06:46 +01:00
{
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
// If the settings have negative or zero row or column counts, ignore those counts.
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
// we may eventually impose different ones depending on how many pixels we can address.)
const auto cols = ::base::saturated_cast<float>(std::max(settings.InitialCols(), 1));
const auto rows = ::base::saturated_cast<float>(std::max(settings.InitialRows(), 1));
const winrt::Windows::Foundation::Size initialSize{ cols, rows };
return GetProposedDimensions(initialSize,
settings.FontSize(),
settings.FontWeight(),
settings.FontFace(),
settings.ScrollState(),
settings.Padding(),
dpi);
}
// Function Description:
// - Determines how much space (in pixels) an app would need to reserve to
// create a control with the settings stored in the settings param. This
// accounts for things like the font size and face, the initialRows and
// initialCols, and scrollbar visibility. The returned sized is based upon
// the provided DPI value
// Arguments:
// - initialSizeInChars: The size to get the proposed dimensions for.
// - fontHeight: The font height to use to calculate the proposed size for.
// - fontWeight: The font weight to use to calculate the proposed size for.
// - fontFace: The font name to use to calculate the proposed size for.
// - scrollState: The ScrollbarState to use to calculate the proposed size for.
// - padding: The padding to use to calculate the proposed size for.
// - dpi: The DPI we should create the terminal at. This affects things such
// as font size, scrollbar and other control scaling, etc. Make sure the
// caller knows what monitor the control is about to appear on.
// Return Value:
// - a size containing the requested dimensions in pixels.
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
const int32_t& fontHeight,
const winrt::Windows::UI::Text::FontWeight& fontWeight,
const winrt::hstring& fontFace,
const ScrollbarState& scrollState,
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
const winrt::hstring& padding,
const uint32_t dpi)
{
const auto cols = ::base::saturated_cast<int>(initialSizeInChars.Width);
const auto rows = ::base::saturated_cast<int>(initialSizeInChars.Height);
2020-02-12 20:06:46 +01:00
// Initialize our font information.
// The font width doesn't terribly matter, we'll only be using the
// height to look it up
// The other params here also largely don't matter.
// The family is only used to determine if the font is truetype or
// not, but DX doesn't use that info at all.
// The Codepage is additionally not actually used by the DX engine at all.
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontHeight) }, CP_UTF8, false };
2020-02-12 20:06:46 +01:00
FontInfoDesired desiredFont = { actualFont };
// Create a DX engine and initialize it with our font and DPI. We'll
// then use it to measure how much space the requested rows and columns
// will take up.
// TODO: MSFT:21254947 - use a static function to do this instead of
// instantiating a DxEngine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
THROW_IF_FAILED(dxEngine->UpdateDpi(dpi));
THROW_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont));
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
const auto scale = dxEngine->GetScaling();
2020-02-12 20:06:46 +01:00
const auto fontSize = actualFont.GetSize();
// UWP XAML scrollbars aren't guaranteed to be the same size as the
// ComCtl scrollbars, but it's certainly close enough.
const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
double width = cols * fontSize.X;
2020-02-12 20:06:46 +01:00
// Reserve additional space if scrollbar is intended to be visible
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
if (scrollState == ScrollbarState::Visible)
2020-02-12 20:06:46 +01:00
{
width += scrollbarSize;
}
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
double height = rows * fontSize.Y;
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
auto thickness = _ParseThicknessFromPadding(padding);
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
width += scale * (thickness.Left + thickness.Right);
height += scale * (thickness.Top + thickness.Bottom);
2020-02-12 20:06:46 +01:00
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}
// Method Description:
// - Get the size of a single character of this control. The size is in
// DIPs. If you need it in _pixels_, you'll need to multiply by the
// current display scaling.
// Arguments:
// - <none>
// Return Value:
// - The dimensions of a single character of this control, in DIPs
winrt::Windows::Foundation::Size TermControl::CharacterDimensions() const
{
const auto fontSize = _actualFont.GetSize();
return { gsl::narrow_cast<float>(fontSize.X), gsl::narrow_cast<float>(fontSize.Y) };
}
// Method Description:
// - Get the absolute minimum size that this control can be resized to and
// still have 1x1 character visible. This includes the space needed for
// the scrollbar and the padding.
// Arguments:
// - <none>
// Return Value:
// - The minimum size that this terminal control can be resized to and still
// have a visible character.
winrt::Windows::Foundation::Size TermControl::MinimumSize()
2020-02-12 20:06:46 +01:00
{
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
if (_initializedTerminal)
2020-02-12 20:06:46 +01:00
{
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
const auto fontSize = _actualFont.GetSize();
double width = fontSize.X;
double height = fontSize.Y;
// Reserve additional space if scrollbar is intended to be visible
if (_settings.ScrollState() == ScrollbarState::Visible)
{
width += ScrollBar().ActualWidth();
}
2020-02-12 20:06:46 +01:00
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
// Account for the size of any padding
const auto padding = GetPadding();
width += padding.Left + padding.Right;
height += padding.Top + padding.Bottom;
2020-02-12 20:06:46 +01:00
Add support for running a `wt` commandline in the curent window WITH A KEYBINDING (#6537) ## Summary of the Pull Request Adds a execute commandline action (`wt`), which lets a user bind a key to a specific `wt` commandline. This commandline will get parsed and run _in the current window_. ## References * Related to #4472 * Related to #5400 - I need this for the commandline mode of the Command Palette * Related to #5970 ## PR Checklist * [x] Closes oh, there's not actually an issue for this. * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - yes it does ## Detailed Description of the Pull Request / Additional comments One important part of this change concerns how panes are initialized at runtime. We've had some persistent trouble with initializing multiple panes, because they rely on knowing how big they'll actually be, to be able to determine if they can split again. We previously worked around this by ignoring the size check when we were in "startup", processing an initial commandline. This PR however requires us to be able to know the initial size of a pane at runtime, but before the parents have necessarily been added to the tree, or had their renderer's set up. This led to the development of `Pane::PreCalculateCanSplit`, which is very highly similar to `Pane::PreCalculateAutoSplit`. This method attempts to figure out how big a pane _will_ take, before the parent has necessarily laid out. This also involves a small change to `TermControl`, because if its renderer hasn't been set up yet, it'll always think the font is `{0, fontHeight}`, which will let the Terminal keep splitting in the x direction. This change also makes the TermControl set up a renderer to get the real font size when it hasn't yet been initialized. ## Validation Steps Performed This was what the json blob I was using for testing evolved into ```json { "command": { "action":"wt", "commandline": "new-tab cmd.exe /k #work 15 ; split-pane cmd.exe /k #work 15 ; split-pane cmd.exe /k media-commandline ; new-tab powershell dev\\symbols.ps1 ; new-tab -p \"Ubuntu\" ; new-tab -p \"haunter.gif\" ; focus-tab -t 0", }, "keys": ["ctrl+shift+n"] } ``` I also added some tests. # TODO * [x] Creating a `{ "command": "wt" }` action without a commandline will spawn a new `wt.exe` process? - Probably should just do nothing for the empty string
2020-07-17 23:05:29 +02:00
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}
else
{
// If the terminal hasn't been initialized yet, then the font size will
// have dimensions {1, fontSize.Y}, which can mess with consumers of
// this method. In that case, we'll need to pre-calculate the font
// width, before we actually have a renderer or swapchain.
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
return GetProposedDimensions(minSize,
_settings.FontSize(),
_settings.FontWeight(),
_settings.FontFace(),
_settings.ScrollState(),
_settings.Padding(),
dpi);
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Adjusts given dimension (width or height) so that it aligns to the character grid.
// The snap is always downward.
// Arguments:
// - widthOrHeight: if true operates on width, otherwise on height
// - dimension: a dimension (width or height) to be snapped
// Return Value:
// - A dimension that would be aligned to the character grid.
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension)
2020-02-12 20:06:46 +01:00
{
const auto fontSize = _actualFont.GetSize();
const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y;
const auto padding = GetPadding();
2020-02-12 20:06:46 +01:00
auto nonTerminalArea = gsl::narrow_cast<float>(widthOrHeight ?
padding.Left + padding.Right :
padding.Top + padding.Bottom);
if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible)
{
nonTerminalArea += gsl::narrow_cast<float>(ScrollBar().ActualWidth());
2020-02-12 20:06:46 +01:00
}
const auto gridSize = dimension - nonTerminalArea;
const int cells = static_cast<int>(gridSize / fontDimension);
return cells * fontDimension + nonTerminalArea;
}
// Method Description:
// - Create XAML Thickness object based on padding props provided.
// Used for controlling the TermControl XAML Grid container's Padding prop.
// Arguments:
// - padding: 2D padding values
// Single Double value provides uniform padding
// Two Double values provide isometric horizontal & vertical padding
// Four Double values provide independent padding for 4 sides of the bounding rectangle
// Return Value:
// - Windows::UI::Xaml::Thickness object
Windows::UI::Xaml::Thickness TermControl::_ParseThicknessFromPadding(const hstring padding)
{
const wchar_t singleCharDelim = L',';
std::wstringstream tokenStream(padding.c_str());
std::wstring token;
uint8_t paddingPropIndex = 0;
std::array<double, 4> thicknessArr = {};
size_t* idx = nullptr;
// Get padding values till we run out of delimiter separated values in the stream
// or we hit max number of allowable values (= 4) for the bounding rectangle
// Non-numeral values detected will default to 0
// std::getline will not throw exception unless flags are set on the wstringstream
// std::stod will throw invalid_argument exception if the input is an invalid double value
// std::stod will throw out_of_range exception if the input value is more than DBL_MAX
try
{
for (; std::getline(tokenStream, token, singleCharDelim) && (paddingPropIndex < thicknessArr.size()); paddingPropIndex++)
{
// std::stod internally calls wcstod which handles whitespace prefix (which is ignored)
// & stops the scan when first char outside the range of radix is encountered
// We'll be permissive till the extent that stod function allows us to be by default
// Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail
thicknessArr[paddingPropIndex] = std::stod(token, idx);
}
}
catch (...)
{
// If something goes wrong, even if due to a single bad padding value, we'll reset the index & return default 0 padding
paddingPropIndex = 0;
LOG_CAUGHT_EXCEPTION();
}
switch (paddingPropIndex)
{
case 1:
return ThicknessHelper::FromUniformLength(thicknessArr[0]);
case 2:
return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[0], thicknessArr[1]);
// No case for paddingPropIndex = 3, since it's not a norm to provide just Left, Top & Right padding values leaving out Bottom
case 4:
return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[2], thicknessArr[3]);
default:
return Thickness();
}
}
// Method Description:
// - Get the modifier keys that are currently pressed. This can be used to
// find out which modifiers (ctrl, alt, shift) are pressed in events that
// don't necessarily include that state.
// Return Value:
// - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
ControlKeyStates TermControl::_GetPressedModifierKeys() const
{
CoreWindow window = CoreWindow::GetForCurrentThread();
// DONT USE
// != CoreVirtualKeyStates::None
// OR
// == CoreVirtualKeyStates::Down
// Sometimes with the key down, the state is Down | Locked.
// Sometimes with the key up, the state is Locked.
// IsFlagSet(Down) is the only correct solution.
struct KeyModifier
{
VirtualKey vkey;
ControlKeyStates flags;
};
constexpr std::array<KeyModifier, 5> modifiers{ {
{ VirtualKey::RightMenu, ControlKeyStates::RightAltPressed },
{ VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed },
{ VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed },
{ VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed },
{ VirtualKey::Shift, ControlKeyStates::ShiftPressed },
} };
ControlKeyStates flags;
for (const auto& mod : modifiers)
{
const auto state = window.GetKeyState(mod.vkey);
const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down);
if (isDown)
{
flags |= mod.flags;
}
}
return flags;
}
// Method Description:
// - Gets the corresponding viewport terminal position for the cursor
// by excluding the padding and normalizing with the font size.
// This is used for selection.
// Arguments:
// - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// Return Value:
// - the corresponding viewport terminal position for the given Point parameter
const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition)
{
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// cursorPosition is DIPs, relative to SwapChainPanel origin
const til::point cursorPosInDIPs{ til::math::rounding, cursorPosition };
const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top };
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// This point is the location of the cursor within the actual grid of characters, in DIPs
const til::point relativeToMarginInDIPs = cursorPosInDIPs - marginsInDips;
// Convert it to pixels
const til::point relativeToMarginInPixels{ relativeToMarginInDIPs * SwapChainPanel().CompositionScaleX() };
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Get the size of the font, which is in pixels
const til::size fontSize{ _actualFont.GetSize() };
2020-02-12 20:06:46 +01:00
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// Convert the location in pixels to characters within the current viewport.
return til::point{ relativeToMarginInPixels / fontSize };
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Composition Completion handler for the TSFInputControl that
// handles writing text out to TerminalConnection
// Arguments:
// - text: the text to write to TerminalConnection
// Return Value:
// - <none>
void TermControl::_CompositionCompleted(winrt::hstring text)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
_connection.WriteInput(text);
}
// Method Description:
// - CurrentCursorPosition handler for the TSFInputControl that
// handles returning current cursor position.
// Arguments:
// - eventArgs: event for storing the current cursor position
// Return Value:
// - <none>
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
auto lock = _terminal->LockForReading();
if (!_initializedTerminal)
{
Process actions sync. on startup; don't dupe nonexistent profile (#5090) This PR has evolved to encapsulate two related fixes that I can't really untie anymore. #2455 - Duplicating a tab that doesn't exist anymore This was the bug I was originally fixing in #4429. When the user tries to `duplicateTab` with a profile that doesn't exist anymore (like might happen after a settings reload), don't crash. As I was going about adding tests for this, got blocked by the fact that the Terminal couldn't open _any_ panes while the `TerminalPage` was size 0x0. This had two theoretical solutions: * Fake the `TerminalPage` into thinking it had a real size in the test - probably possible, though I'm unsure how it would work in practice. * Change `Pane`s to not require an `ActualWidth`, `ActualHeight` on initialization. Fortuately, the second option was something else that was already on my backlog of bugs. #4618 - `wt` command-line can't consistently parse more than one arg Presently, the Terminal just arbitrarily dispatches a bunch of handlers to try and handle all the commands provided on the commandline. That's lead to a bunch of reports that not all the commands will always get executed, nor will they all get executed in the same order. This PR also changes the `TerminalPage` to be able to dispatch all the commands sequentially, all at once in the startup. No longer will there be a hot second where the commands seem to execute themselves in from of the user - they'll all happen behind the scenes on startup. This involved a couple other changes areound the `TerminalPage` * I had to make sure that panes could be opened at a 0x0 size. Now they use a star sizing based off the percentage of the parent they're supposed to consume, so that when the parent _does_ get laid out, they'll take the appropriate size of that parent. * I had to do some math ahead of time to try and calculate what a `SplitState::Automatic` would be evaluated as, despite the fact that we don't actually know how big the pane will be. * I had to ensure that `focus-tab` commands appropriately mark a single tab as focused while we're in startup, without roundtripping to the Dispatcher thread and back ## References #4429 - the original PR for #2455 #5047 - a follow-up task from discussion in #4429 #4953 - a PR for making panes use star sizing, which was immensly helpful for this PR. ## Detailed Description of the Pull Request / Additional comments `CascadiaSettings::BuildSettings` can throw if the GUID doesn't exist. This wraps those calls up with a try/catch. It also adds a couple tests - a few `SettingsTests` for try/catching this state. It also adds a XAML-y test in `TabTests` that creates a `TerminalPage` and then performs som UI-like actions on it. This test required a minor change to how we generate the new tab dropdown - in the tests, `Application::Current()` is _not_ a `TerminalApp::App`, so it doesn't have a `Logic()` to query. So wrap that in a try/catch as well. While working on these tests, I found that we'd crash pretty agressively for mysterious reasons if the TestHostApp became focused while the test was running. This was due to a call in `TSFInputControl::NotifyFocusEnter` that would callback to `TSFInputControl::_layoutRequested`, which would crash on setting the `MaxSize` of the canvas to a negative value. This PR includes a hotfix for that bug as well. ## Validation Steps Performed * Manual testing with a _lot_ of commands in a commandline * run the tests * Team tested in selfhost Closes #2455 Closes #4618
2020-03-26 01:03:32 +01:00
// fake it
eventArgs.CurrentPosition({ 0, 0 });
return;
}
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
const til::point cursorPos = _terminal->GetCursorPosition();
Windows::Foundation::Point p = { ::base::ClampedNumeric<float>(cursorPos.x()), ::base::ClampedNumeric<float>(cursorPos.y()) };
2020-02-12 20:06:46 +01:00
eventArgs.CurrentPosition(p);
}
// Method Description:
// - FontInfo handler for the TSFInputControl that
// handles returning current font information
// Arguments:
// - eventArgs: event for storing the current font information
// Return Value:
// - <none>
void TermControl::_FontInfoHandler(const IInspectable& /*sender*/, const FontInfoEventArgs& eventArgs)
{
eventArgs.FontSize(CharacterDimensions());
eventArgs.FontFace(_actualFont.GetFaceName());
Add font weight options (#6048) ## Summary of the Pull Request Adds the ability to specify the font weight in the profiles, right next to the size and the font face. ## PR Checklist * [x] Closes #1751 * [x] I work here. * [x] Tested manually, see below. * [x] Added documentation to the schema * [x] Requires docs.microsoft.com update, filed as https://github.com/MicrosoftDocs/terminal/issues/26 * [x] I'm a core contributor. ## Detailed Description of the Pull Request / Additional comments - Weights can be specified according to the OpenType specification values. We accept either the friendly name or the numerical value that applies to each weight. - Weights are carried through per-profile and sent into the renderer. - Weights are carried through to the TSF/IME overlay. - The names are restricted to the set seen at https://docs.microsoft.com/en-us/uwp/api/windows.ui.text.fontweights. - There are alternate names at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_weight for the same values (ultra-black is just an alias for extra-black at 950). ## Validation Steps Performed - Cascadia Code normal ![image](https://user-images.githubusercontent.com/18221333/82480181-46117380-9a88-11ea-9436-a5fe4ccd4350.png) - Cascadia Code bold ![image](https://user-images.githubusercontent.com/18221333/82480202-4f9adb80-9a88-11ea-9e27-a113b41387f5.png) - Segoe UI Semilight ![image](https://user-images.githubusercontent.com/18221333/82480306-73f6b800-9a88-11ea-93f7-d773ab7ccce8.png) - Segoe UI Black ![image](https://user-images.githubusercontent.com/18221333/82480401-9688d100-9a88-11ea-957c-0c8e03a8cc29.png) - Segoe UI 900 (value for Black) ![image](https://user-images.githubusercontent.com/18221333/82480774-26c71600-9a89-11ea-8cf6-aaeab1fd0747.png)
2020-05-20 22:17:17 +02:00
::winrt::Windows::UI::Text::FontWeight weight;
weight.Weight = static_cast<uint16_t>(_actualFont.GetWeight());
eventArgs.FontWeight(weight);
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Returns the number of clicks that occurred (double and triple click support)
// Arguments:
// - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// - clickTime: the timestamp that the click occurred
// Return Value:
// - if the click is in the same position as the last click and within the timeout, the number of clicks within that time window
// - otherwise, 1
const unsigned int TermControl::_NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime)
{
// if click occurred at a different location or past the multiClickTimer...
Timestamp delta;
Ignore right-click copy when copy on select is enabled (#4819) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Right clicking on a focused tab while Copy On Select is active currently copies any active selection. This is because `PointerReleasedHandler` doesn't check for the mouse button that was released. During a mouse button release, only the left mouse button release should be doing anything. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4740 * [x] CLA signed. * [x] Tests added/passed <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed These are the scenarios I've tested. They're a combination of in focus/out of focus, Copy On Select on/off, left/right click pressed and their move and release variants. From Out of Focus: - Left Click = Focus - Left Click Move = Focus + Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Focus - Right Click - Focus - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing From In Focus - Left Click = Selection if CoS off - Left Click Move = Selection - Left Click Release - CoS on = Copy - CoS off = Nothing - Shift Left Click = Set Selection Anchor - Right Click - CoS on = Paste - CoS off = Copy if Active Selection, Paste if not. - Right Click Move = Nothing - Right Click Release = Nothing
2020-03-10 15:59:16 +01:00
THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClickTimestamp, &delta));
2020-02-12 20:06:46 +01:00
if (clickPos != _lastMouseClickPos || delta > _multiClickTimer)
{
// exit early. This is a single click.
_multiClickCounter = 1;
}
else
{
_multiClickCounter++;
}
return _multiClickCounter;
}
// Method Description:
// - Calculates speed of single axis of auto scrolling. It has to allow for both
// fast and precise selection.
// Arguments:
// - cursorDistanceFromBorder: distance from viewport border to cursor, in pixels. Must be non-negative.
// Return Value:
// - positive speed in characters / sec
double TermControl::_GetAutoScrollSpeed(double cursorDistanceFromBorder) const
{
// The numbers below just feel well, feel free to change.
// TODO: Maybe account for space beyond border that user has available
return std::pow(cursorDistanceFromBorder, 2.0) / 25.0 + 2.0;
}
// Method Description:
// - Async handler for the "Drop" event. If a file was dropped onto our
// root, we'll try to get the path of the file dropped onto us, and write
// the full path of the file to our terminal connection. Like conhost, if
// the path contains a space, we'll wrap the path in quotes.
// - Unlike conhost, if multiple files are dropped onto the terminal, we'll
// write all the paths to the terminal, separated by spaces.
// Arguments:
// - e: The DragEventArgs from the Drop event
// Return Value:
// - <none>
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const e)
2020-02-12 20:06:46 +01:00
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
2020-02-12 20:06:46 +01:00
if (e.DataView().Contains(StandardDataFormats::StorageItems()))
{
auto items = co_await e.DataView().GetStorageItemsAsync();
if (items.Size() > 0)
{
std::wstring allPaths;
for (auto item : items)
{
// Join the paths with spaces
if (!allPaths.empty())
{
allPaths += L" ";
}
std::wstring fullPath{ item.Path() };
const auto containsSpaces = std::find(fullPath.begin(),
fullPath.end(),
L' ') != fullPath.end();
auto lock = _terminal->LockForWriting();
if (containsSpaces)
{
fullPath.insert(0, L"\"");
fullPath += L"\"";
}
allPaths += fullPath;
}
_SendInputToConnection(allPaths);
}
}
else if (e.DataView().Contains(StandardDataFormats::Text()))
{
try
{
std::wstring text{ co_await e.DataView().GetTextAsync() };
_SendPastedTextToConnection(text);
}
CATCH_LOG();
}
2020-02-12 20:06:46 +01:00
}
// Method Description:
// - Handle the DragOver event. We'll signal that the drag operation we
// support is the "copy" operation, and we'll also customize the
// appearance of the drag-drop UI, by removing the preview and setting a
// custom caption. For more information, see
// https://docs.microsoft.com/en-us/windows/uwp/design/input/drag-and-drop#customize-the-ui
// Arguments:
// - e: The DragEventArgs from the DragOver event
// Return Value:
// - <none>
void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const& e)
{
Rework TermControl's initialization (#5051) This commit rewrites a large swath of TermControl's initialization code. * `TermControl` now _always_ has a `_terminal`; it will never be null * Event registration for `_terminal` and any other available-at-init fixtures has been moved into the constructor. * Event handlers how more uniformly check `_closing` if they interact with the _terminal. * Swap chain attachment has been cleaned up and no longer uses a coroutine when it's spawned from the UI thread. * We have to register the renderer's swapchain change notification handler after we set the swap chain, otherwise it'll call us back when it initializes itself. * `InitializeTerminal` now happens under the `_terminal`'s write lock * Certain things that InitializeTerminal were calling themselves attempted to take the lock. They no longer do so. * TermControlAutomationPeer cannot take the read lock, because setting the scrollbar's `Maximum` during `InitializeTerminal` will trigger vivification of the automation peer tree; if it attempts to take the lock it will deadlock during initialization. * `BlinkCursor` was renamed to `CursorTimerTick` because it's the "Tick" handler for the "CursorTimer". * `DragDropHandler` was converted into a coroutine instead of just _calling_ a coroutine. Caveats: Terminal may not have a `_buffer` until InitializeTerminal happens. There's a nasty coupling between RenderTarget and TextBuffer that means that we need to have a renderer before we have a buffer. There's a second nasty coupling between RenderThread and Renderer: we can't create a RenderThread during construction because it needs to be given a renderer, and we can't create a Renderer during construction because it needs a RenderThread. We don't want to kick off a thread during construction. Testing: I wailed on this by opening and closing and resizing terminals and panes and tabs, up to a hundred open tabs and one tab with 51 panes. I set one tab to update the title as fast as it possibly could and tested teardown, zoom, resize, mouse movement, etc. while this was all happening. Closes #4613.
2020-03-27 00:25:11 +01:00
if (_closing)
{
return;
}
// We can only handle drag/dropping StorageItems (files) and plain Text
// currently. If the format on the clipboard is anything else, returning
// early here will prevent the drag/drop from doing anything.
if (!(e.DataView().Contains(StandardDataFormats::StorageItems()) ||
e.DataView().Contains(StandardDataFormats::Text())))
2020-02-12 20:06:46 +01:00
{
return;
}
// Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event
e.AcceptedOperation(DataPackageOperation::Copy);
// Sets custom UI text
if (e.DataView().Contains(StandardDataFormats::StorageItems()))
{
e.DragUIOverride().Caption(RS_(L"DragFileCaption"));
}
else if (e.DataView().Contains(StandardDataFormats::Text()))
{
e.DragUIOverride().Caption(RS_(L"DragTextCaption"));
}
2020-02-12 20:06:46 +01:00
// Sets if the caption is visible
e.DragUIOverride().IsCaptionVisible(true);
// Sets if the dragged content is visible
e.DragUIOverride().IsContentVisible(false);
// Sets if the glyph is visible
2020-02-12 20:06:46 +01:00
e.DragUIOverride().IsGlyphVisible(false);
}
OSC 8 support for conhost and terminal (#7251) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Conhost can now support OSC8 sequences (as specified [here](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)). Terminal also supports those sequences and additionally hyperlinks can be opened by Ctrl+LeftClicking on them. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #204 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [X] Closes #204 * [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Added support to: - parse OSC8 sequences and extract URIs from them (conhost and terminal) - add hyperlink uri data to textbuffer/screeninformation, associated with a hyperlink id (conhost and terminal) - attach hyperlink ids to text to allow for uri extraction from the textbuffer/screeninformation (conhost and terminal) - process ctrl+leftclick to open a hyperlink in the clicked region if present <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Open up a PowerShell tab and type ```PowerShell ${ESC}=[char]27 Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\" ``` Ctrl+LeftClick on the link correctly brings you to the terminal page on github ![hyperlink](https://user-images.githubusercontent.com/26824113/89953536-45a6f580-dbfd-11ea-8e0d-8a3cd25c634a.gif)
2020-09-03 19:52:39 +02:00
// Method description:
// - Checks if the uri is valid and sends an event if so
// Arguments:
// - The uri
void TermControl::_HyperlinkHandler(const std::wstring_view uri)
{
auto hyperlinkArgs = winrt::make_self<OpenHyperlinkEventArgs>(winrt::hstring{ uri });
_openHyperlinkHandlers(*this, *hyperlinkArgs);
}
// Method Description:
// - Produces the error dialog that notifies the user that rendering cannot proceed.
winrt::fire_and_forget TermControl::_RendererEnteredErrorState()
{
auto strongThis{ get_strong() };
co_await Dispatcher(); // pop up onto the UI thread
if (auto loadedUiElement{ FindName(L"RendererFailedNotice") })
{
if (auto uiElement{ loadedUiElement.try_as<::winrt::Windows::UI::Xaml::UIElement>() })
{
uiElement.Visibility(Visibility::Visible);
}
}
}
// Method Description:
// - Responds to the Click event on the button that will re-enable the renderer.
void TermControl::_RenderRetryButton_Click(IInspectable const& /*sender*/, IInspectable const& /*args*/)
{
// It's already loaded if we get here, so just hide it.
RendererFailedNotice().Visibility(Visibility::Collapsed);
_renderer->ResetErrorStateAndResume();
}
IControlSettings TermControl::Settings() const
{
return _settings;
}
Windows::Foundation::IReference<winrt::Windows::UI::Color> TermControl::TabColor() noexcept
{
auto coreColor = _terminal->GetTabColor();
return coreColor.has_value() ? Windows::Foundation::IReference<winrt::Windows::UI::Color>(coreColor.value()) : nullptr;
}
2020-02-12 20:06:46 +01:00
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(TermControl, FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs);
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs);
OSC 8 support for conhost and terminal (#7251) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Conhost can now support OSC8 sequences (as specified [here](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)). Terminal also supports those sequences and additionally hyperlinks can be opened by Ctrl+LeftClicking on them. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #204 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [X] Closes #204 * [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Added support to: - parse OSC8 sequences and extract URIs from them (conhost and terminal) - add hyperlink uri data to textbuffer/screeninformation, associated with a hyperlink id (conhost and terminal) - attach hyperlink ids to text to allow for uri extraction from the textbuffer/screeninformation (conhost and terminal) - process ctrl+leftclick to open a hyperlink in the clicked region if present <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Open up a PowerShell tab and type ```PowerShell ${ESC}=[char]27 Write-Host "${ESC}]8;;https://github.com/microsoft/terminal${ESC}\This is a link!${ESC}]8;;${ESC}\" ``` Ctrl+LeftClick on the link correctly brings you to the terminal page on github ![hyperlink](https://user-images.githubusercontent.com/26824113/89953536-45a6f580-dbfd-11ea-8e0d-8a3cd25c634a.gif)
2020-09-03 19:52:39 +02:00
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, OpenHyperlink, _openHyperlinkHandlers, TerminalControl::TermControl, TerminalControl::OpenHyperlinkEventArgs);
2020-02-12 20:06:46 +01:00
// clang-format on
}