- Notifications
You must be signed in to change notification settings - Fork 8.5k
/
Copy pathCursorBlinker.cpp
184 lines (155 loc) · 6.78 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
179
180
181
182
183
184
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include"precomp.h"
#include"../host/scrolling.hpp"
#include"../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
usingnamespaceMicrosoft::Console;
usingnamespaceMicrosoft::Console::Interactivity;
usingnamespaceMicrosoft::Console::Render;
staticvoid CALLBACK CursorTimerRoutineWrapper(_Inout_ PTP_CALLBACK_INSTANCE /*Instance*/, _Inout_opt_ PVOID /*Context*/, _Inout_ PTP_TIMER /*Timer*/)
{
auto& 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();
}
voidCursorBlinker::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.
auto animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
auto& renderSettings = ServiceLocator::LocateGlobals().getConsoleInformation().GetRenderSettings();
renderSettings.SetRenderMode(RenderSettings::Mode::BlinkAllowed, animationsEnabled && _uCaretBlinkTime != INFINITE);
}
voidCursorBlinker::SettingsChanged() noexcept
{
constauto dwCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
if (dwCaretBlinkTime != _uCaretBlinkTime)
{
KillCaretTimer();
_uCaretBlinkTime = dwCaretBlinkTime;
SetCaretTimer();
}
}
voidCursorBlinker::FocusEnd() constnoexcept
{
KillCaretTimer();
}
voidCursorBlinker::FocusStart() constnoexcept
{
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>
voidCursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) constnoexcept
{
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.
constauto position = buffer.BufferToScreenPosition(cursor.GetPosition());
constauto viewport = ScreenInfo.GetViewport();
constauto fontSize = ScreenInfo.GetScreenFontSize();
cursor.SetHasMoved(false);
til::rect rc;
rc.left = (position.x - viewport.Left()) * fontSize.width;
rc.top = (position.y - viewport.Top()) * fontSize.height;
rc.right = rc.left + fontSize.width;
rc.bottom = rc.top + fontSize.height;
pAccessibilityNotifier->NotifyConsoleCaretEvent(rc);
// Send accessibility information
{
auto 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;
}
elseif (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.
voidCursorBlinker::SetCaretTimer() constnoexcept
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsInVtIoMode())
{
return;
}
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
staticconstexpr DWORD dwDefTimeout = 0x212;
constauto 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);
}
voidCursorBlinker::KillCaretTimer() constnoexcept
{
SetThreadpoolTimer(_timer.get(), nullptr, 0, 0);
}