-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
CursorBlinker.cpp
178 lines (150 loc) · 6.68 KB
/
CursorBlinker.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../host/scrolling.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using namespace Microsoft::Console;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Render;
static void CALLBACK CursorTimerRoutineWrapper(_Inout_ PTP_CALLBACK_INSTANCE /*Instance*/, _Inout_opt_ PVOID /*Context*/, _Inout_ PTP_TIMER /*Timer*/)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// There's a slight race condition here.
// CreateThreadpoolTimer callbacks may be scheduled even after they were canceled.
// But I'm not too concerned that this will lead to issues at the time of writing,
// as CursorBlinker is allocated as a static variable through the Globals class.
// It'd be nice to fix this, but realistically it'll likely not lead to issues.
gci.LockConsole();
gci.GetCursorBlinker().TimerRoutine(gci.GetActiveOutputBuffer());
gci.UnlockConsole();
}
CursorBlinker::CursorBlinker() :
_timer(THROW_LAST_ERROR_IF_NULL(CreateThreadpoolTimer(&CursorTimerRoutineWrapper, nullptr, nullptr))),
_uCaretBlinkTime(INFINITE) // default to no blink
{
}
CursorBlinker::~CursorBlinker()
{
KillCaretTimer();
}
void CursorBlinker::UpdateSystemMetrics() noexcept
{
// This can be -1 in a TS session
_uCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
// If animations are disabled, or the blink rate is infinite, blinking is not allowed.
BOOL animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
auto& renderSettings = ServiceLocator::LocateGlobals().getConsoleInformation().GetRenderSettings();
renderSettings.SetRenderMode(RenderSettings::Mode::BlinkAllowed, animationsEnabled && _uCaretBlinkTime != INFINITE);
}
void CursorBlinker::SettingsChanged() noexcept
{
DWORD const dwCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
if (dwCaretBlinkTime != _uCaretBlinkTime)
{
KillCaretTimer();
_uCaretBlinkTime = dwCaretBlinkTime;
SetCaretTimer();
}
}
void CursorBlinker::FocusEnd() const noexcept
{
KillCaretTimer();
}
void CursorBlinker::FocusStart() const noexcept
{
SetCaretTimer();
}
// Routine Description:
// - This routine is called when the timer in the console with the focus goes off.
// It blinks the cursor and also toggles the rendition of any blinking attributes.
// Arguments:
// - ScreenInfo - reference to screen info structure.
// Return Value:
// - <none>
void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
{
auto& buffer = ScreenInfo.GetTextBuffer();
auto& cursor = buffer.GetCursor();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
{
goto DoScroll;
}
// Update the cursor pos in USER so accessibility will work.
// Don't do all this work or send events if we don't have a notifier target.
if (pAccessibilityNotifier && cursor.HasMoved())
{
// Convert the buffer position to the equivalent screen coordinates
// required by the notifier, taking line rendition into account.
const auto position = buffer.BufferToScreenPosition(cursor.GetPosition());
const auto viewport = ScreenInfo.GetViewport();
const auto fontSize = ScreenInfo.GetScreenFontSize();
cursor.SetHasMoved(false);
RECT rc;
rc.left = (position.X - viewport.Left()) * fontSize.X;
rc.top = (position.Y - viewport.Top()) * fontSize.Y;
rc.right = rc.left + fontSize.X;
rc.bottom = rc.top + fontSize.Y;
pAccessibilityNotifier->NotifyConsoleCaretEvent(rc);
// Send accessibility information
{
IAccessibilityNotifier::ConsoleCaretEventFlags flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretInvisible;
// Flags is expected to be 2, 1, or 0. 2 in selecting (whether or not visible), 1 if just visible, 0 if invisible/noselect.
if (WI_IsFlagSet(gci.Flags, CONSOLE_SELECTING))
{
flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection;
}
else if (cursor.IsVisible())
{
flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretVisible;
}
pAccessibilityNotifier->NotifyConsoleCaretEvent(flags, MAKELONG(position.X, position.Y));
}
}
// If the DelayCursor flag has been set, wait one more tick before toggle.
// This is used to guarantee the cursor is on for a finite period of time
// after a move and off for a finite period of time after a WriteString.
if (cursor.GetDelay())
{
cursor.SetDelay(false);
goto DoBlinkingRenditionAndScroll;
}
// Don't blink the cursor for remote sessions.
if ((!ServiceLocator::LocateSystemConfigurationProvider()->IsCaretBlinkingEnabled() ||
_uCaretBlinkTime == -1 ||
(!cursor.IsBlinkingAllowed())) &&
cursor.IsOn())
{
goto DoBlinkingRenditionAndScroll;
}
// Blink only if the cursor isn't turned off via the API
if (cursor.IsVisible())
{
cursor.SetIsOn(!cursor.IsOn());
}
DoBlinkingRenditionAndScroll:
gci.GetRenderSettings().ToggleBlinkRendition(buffer.GetRenderer());
DoScroll:
Scrolling::s_ScrollIfNecessary(ScreenInfo);
}
// Routine Description:
// - If guCaretBlinkTime is -1, we don't want to blink the caret. However, we
// need to make sure it gets drawn, so we'll set a short timer. When that
// goes off, we'll hit CursorTimerRoutine, and it'll do the right thing if
// guCaretBlinkTime is -1.
void CursorBlinker::SetCaretTimer() const noexcept
{
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
static constexpr DWORD dwDefTimeout = 0x212;
const auto periodInMS = _uCaretBlinkTime == -1 ? dwDefTimeout : _uCaretBlinkTime;
// The FILETIME struct measures time in 100ns steps. 10000 thus equals 1ms.
auto periodInFiletime = -static_cast<int64_t>(periodInMS) * 10000;
SetThreadpoolTimer(_timer.get(), reinterpret_cast<FILETIME*>(&periodInFiletime), periodInMS, 0);
}
void CursorBlinker::KillCaretTimer() const noexcept
{
SetThreadpoolTimer(_timer.get(), nullptr, 0, 0);
}