#define Uses_TEvent
#define Uses_TKeys
#define Uses_THardwareInfo
#include <tvision/tv.h>
#include <internal/win32con.h>
#include <internal/conctl.h>
#include <internal/winwidth.h>
#include <internal/codepage.h>
#include <internal/termio.h>
#include <internal/utf8.h>
#include <locale.h>
namespace
tvision
{
#ifdef _WIN32
static
bool
isWine()
noexcept
{
return
GetProcAddress(GetModuleHandleW(L
"ntdll"
),
"wine_get_version"
);
}
Win32ConsoleStrategy &Win32ConsoleStrategy::create()
noexcept
{
auto
&con = ConsoleCtl::getInstance();
{
DWORD
consoleMode = 0;
GetConsoleMode(con.in(), &consoleMode);
consoleMode |= ENABLE_WINDOW_INPUT;
consoleMode &= ~ENABLE_PROCESSED_INPUT;
consoleMode |= ENABLE_EXTENDED_FLAGS;
consoleMode &= ~ENABLE_QUICK_EDIT_MODE;
SetConsoleMode(con.in(), consoleMode);
}
bool
supportsVT;
{
DWORD
consoleMode = 0;
GetConsoleMode(con.out(), &consoleMode);
consoleMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
SetConsoleMode(con.out(), consoleMode);
if
(isWine())
supportsVT =
false
;
else
{
consoleMode |= DISABLE_NEWLINE_AUTO_RETURN;
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(con.out(), consoleMode);
GetConsoleMode(con.out(), &consoleMode);
supportsVT = consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING;
}
}
UINT
cpInput = GetConsoleCP();
UINT
cpOutput = GetConsoleOutputCP();
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
setlocale
(LC_ALL,
".utf8"
);
if
(!supportsVT)
{
CONSOLE_FONT_INFOEX fontInfo {};
fontInfo.cbSize =
sizeof
(fontInfo);
auto
isBitmap = [](
UINT
family)
{
return
!(family & (TMPF_FIXED_PITCH | TMPF_VECTOR | TMPF_TRUETYPE | TMPF_DEVICE));
};
if
( GetCurrentConsoleFontEx(con.out(), FALSE, &fontInfo)
&& isBitmap(fontInfo.FontFamily) )
{
short
fontY = 2*min(fontInfo.dwFontSize.X, fontInfo.dwFontSize.Y);
for
(
auto
*name : {L
"Consolas"
, L
"Lucida Console"
})
{
fontInfo.nFont = 0;
fontInfo.FontFamily = FF_DONTCARE;
fontInfo.FontWeight = FW_NORMAL;
fontInfo.dwFontSize = {0, fontY};
wcscpy(fontInfo.FaceName, name);
SetCurrentConsoleFontEx(con.out(), FALSE, &fontInfo);
GetCurrentConsoleFontEx(con.out(), FALSE, &fontInfo);
if
(wcscmp(fontInfo.FaceName, name) == 0)
break
;
}
}
}
WinWidth::reset();
auto
&display = *
new
Win32Display(con, supportsVT);
auto
&input = *
new
Win32Input(con);
return
*
new
Win32ConsoleStrategy(con, cpInput, cpOutput, display, input);
}
Win32ConsoleStrategy::~Win32ConsoleStrategy()
{
delete
&display;
delete
&input;
SetConsoleCP(cpInput);
SetConsoleOutputCP(cpOutput);
}
bool
Win32ConsoleStrategy::isAlive()
noexcept
{
DWORD
events = 0;
return
GetNumberOfConsoleInputEvents(con.in(), &events);
}
static
bool
openClipboard()
noexcept
{
for
(
int
i = 0; i < 5; ++i)
{
if
(OpenClipboard(
nullptr
))
return
true
;
Sleep(5);
}
return
false
;
}
bool
Win32ConsoleStrategy::setClipboardText(TStringView text)
noexcept
{
bool
result =
false
;
if
(openClipboard())
{
HGLOBAL
hData = NULL;
wchar_t
*pData;
int
dataLen;
if
( EmptyClipboard() &&
!(result = text.empty()) &&
(dataLen = MultiByteToWideChar(CP_UTF8, 0, text.data(), text.size(),
nullptr
, 0)) &&
(hData = GlobalAlloc(GMEM_MOVEABLE, (dataLen + 1)*
sizeof
(
wchar_t
))) &&
(pData = (
wchar_t
*) GlobalLock(hData))
)
{
MultiByteToWideChar(CP_UTF8, 0, text.data(), text.size(), pData, dataLen);
pData[dataLen] = L
'\0'
;
GlobalUnlock(hData);
result = SetClipboardData(CF_UNICODETEXT, hData);
}
CloseClipboard();
if
(hData && !result)
GlobalFree(hData);
}
return
result;
}
bool
Win32ConsoleStrategy::requestClipboardText(
void
(&accept)(TStringView))
noexcept
{
bool
result =
false
;
if
(openClipboard())
{
HGLOBAL
hData;
wchar_t
*pData;
if
( (hData = GetClipboardData(CF_UNICODETEXT)) &&
(result = (pData = (
wchar_t
*) GlobalLock(hData))) )
{
size_t
dataLen = wcslen(pData);
int
textLen = WideCharToMultiByte(CP_UTF8, 0, pData, dataLen,
nullptr
, 0,
nullptr
,
nullptr
);
char
*text =
new
char
[textLen];
WideCharToMultiByte(CP_UTF8, 0, pData, dataLen, text, textLen,
nullptr
,
nullptr
);
GlobalUnlock(hData);
accept({text,
size_t
(textLen)});
delete
[] text;
}
CloseClipboard();
}
return
result;
}
Win32Input::Win32Input(ConsoleCtl &aCon)
noexcept
:
InputStrategy(aCon.in()),
con(aCon)
{
}
int
Win32Input::getButtonCount()
noexcept
{
DWORD
num;
GetNumberOfConsoleMouseButtons(&num);
return
num;
}
void
Win32Input::cursorOn()
noexcept
{
DWORD
consoleMode = 0;
GetConsoleMode(con.in(), &consoleMode);
SetConsoleMode(con.in(), consoleMode | ENABLE_MOUSE_INPUT);
}
void
Win32Input::cursorOff()
noexcept
{
DWORD
consoleMode = 0;
GetConsoleMode(con.in(), &consoleMode);
SetConsoleMode(con.in(), consoleMode & ~ENABLE_MOUSE_INPUT);
}
bool
Win32Input::getEvent(TEvent &ev)
noexcept
{
DWORD
events;
while
(GetNumberOfConsoleInputEvents(con.in(), &events) && events)
{
while
(events--)
{
INPUT_RECORD ir;
DWORD
ok;
if
(!ReadConsoleInputW(con.in(), &ir, 1, &ok) || !ok)
return
false
;
if
(getEvent(ir, ev))
return
true
;
}
}
return
false
;
}
bool
Win32Input::getEvent(
const
INPUT_RECORD &ir, TEvent &ev)
noexcept
{
switch
(ir.EventType)
{
case
KEY_EVENT:
if
( ir.Event.KeyEvent.bKeyDown ||
(ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU &&
ir.Event.KeyEvent.uChar.UnicodeChar) )
return
getWin32Key(ir.Event.KeyEvent, ev, state);
break
;
case
MOUSE_EVENT:
getWin32Mouse(ir.Event.MouseEvent, ev, state);
return
true
;
case
WINDOW_BUFFER_SIZE_EVENT:
ev.what = evCommand;
ev.message.command = cmScreenChanged;
ev.message.infoPtr = 0;
return
true
;
}
return
false
;
}
Win32Display::Win32Display(ConsoleCtl &aCon,
bool
useAnsi)
noexcept
:
TerminalDisplay(aCon),
ansiScreenWriter(useAnsi ?
new
AnsiScreenWriter(aCon) :
nullptr
)
{
initCapabilities();
}
Win32Display::~Win32Display()
{
delete
ansiScreenWriter;
}
void
Win32Display::reloadScreenInfo()
noexcept
{
size = con.getSize();
CONSOLE_SCREEN_BUFFER_INFO sbInfo {};
GetConsoleScreenBufferInfo(con.out(), &sbInfo);
auto
curPos = sbInfo.dwCursorPosition;
SetConsoleCursorPosition(con.out(), {0, 0});
SetConsoleScreenBufferSize(con.out(), {(
short
) size.x, (
short
) size.y});
SetConsoleCursorPosition(con.out(), curPos);
if
(ansiScreenWriter)
ansiScreenWriter->resetAttributes();
}
bool
Win32Display::screenChanged()
noexcept
{
bool
changed = TerminalDisplay::screenChanged();
CONSOLE_FONT_INFO fontInfo;
if
( GetCurrentConsoleFont(con.out(), FALSE, &fontInfo)
&&
memcmp
(&fontInfo, &lastFontInfo,
sizeof
(fontInfo)) != 0 )
{
changed =
true
;
WinWidth::reset();
lastFontInfo = fontInfo;
}
return
changed;
}
TPoint Win32Display::getScreenSize()
noexcept
{
return
size;
}
int
Win32Display::getCaretSize()
noexcept
{
CONSOLE_CURSOR_INFO crInfo {};
GetConsoleCursorInfo(con.out(), &crInfo);
return
crInfo.bVisible ? crInfo.dwSize : 0;
}
int
Win32Display::getColorCount()
noexcept
{
DWORD
consoleMode = 0;
GetConsoleMode(con.out(), &consoleMode);
if
(consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
return
256*256*256;
return
16;
}
void
Win32Display::lowlevelCursorSize(
int
size)
noexcept
{
CONSOLE_CURSOR_INFO crInfo;
if
(size) {
crInfo.bVisible = TRUE;
crInfo.dwSize = size;
}
else
{
crInfo.bVisible = FALSE;
crInfo.dwSize = 1;
}
SetConsoleCursorInfo(con.out(), &crInfo);
}
void
Win32Display::clearScreen()
noexcept
{
if
(ansiScreenWriter)
ansiScreenWriter->clearScreen();
else
{
COORD coord = {0, 0};
DWORD
length = size.x * size.y;
BYTE
attr = 0x07;
DWORD
read;
FillConsoleOutputAttribute(con.out(), attr, length, coord, &read);
FillConsoleOutputCharacterA(con.out(),
' '
, length, coord, &read);
lastAttr = attr;
}
}
void
Win32Display::lowlevelWriteChars(TStringView chars, TColorAttr attr)
noexcept
{
if
(ansiScreenWriter)
ansiScreenWriter->lowlevelWriteChars(chars, attr, termcap);
else
{
uchar bios = attr.toBIOS();
if
(bios != lastAttr)
{
lowlevelFlush();
SetConsoleTextAttribute(con.out(), bios);
lastAttr = bios;
}
buf.insert(buf.end(), chars.begin(), chars.end());
}
}
void
Win32Display::lowlevelMoveCursor(uint x, uint y)
noexcept
{
if
(ansiScreenWriter)
ansiScreenWriter->lowlevelMoveCursor(x, y);
else
{
lowlevelFlush();
SetConsoleCursorPosition(con.out(), {(
short
) x, (
short
) y});
}
}
void
Win32Display::lowlevelMoveCursorX(uint x, uint y)
noexcept
{
if
(ansiScreenWriter)
ansiScreenWriter->lowlevelMoveCursorX(x);
else
lowlevelMoveCursor(x, y);
}
void
Win32Display::lowlevelFlush()
noexcept
{
if
(ansiScreenWriter)
ansiScreenWriter->lowlevelFlush();
else
{
con.write(buf.data(), buf.size());
buf.resize(0);
}
}
#endif // _WIN32
static
bool
getWin32KeyText(
const
KEY_EVENT_RECORD &KeyEvent, TEvent &ev, InputState &state)
noexcept
{
uint32_t ch = KeyEvent.uChar.UnicodeChar;
ev.keyDown.textLength = 0;
if
(
' '
<= ch && ch != 0x7F)
{
#ifdef _WIN32
if
(0xD800 <= ch && ch <= 0xDBFF)
{
state.surrogate = ch;
return
false
;
}
wchar_t
utf16[2] = {(
wchar_t
) ch, 0};
if
(state.surrogate)
{
if
(0xDC00 <= ch && ch <= 0xDFFF)
{
utf16[1] = (
wchar_t
) ch;
utf16[0] = state.surrogate;
}
state.surrogate = 0;
}
ev.keyDown.textLength = WideCharToMultiByte(
CP_UTF8, 0,
utf16, utf16[1] ? 2 : 1,
ev.keyDown.text,
sizeof
(ev.keyDown.text),
nullptr
,
nullptr
);
#else
(
void
) state;
if
(ch < 0xD800 || (0xDFFF < ch && ch < 0x10FFFF))
ev.keyDown.textLength = (uchar) utf32To8(ch, ev.keyDown.text);
#endif // _WIN32
}
return
true
;
}
bool
getWin32Key(
const
KEY_EVENT_RECORD &KeyEvent, TEvent &ev, InputState &state)
noexcept
{
if
(!getWin32KeyText(KeyEvent, ev, state))
return
false
;
ev.what = evKeyDown;
ev.keyDown.charScan.scanCode = KeyEvent.wVirtualScanCode;
ev.keyDown.charScan.charCode = KeyEvent.uChar.AsciiChar;
ev.keyDown.controlKeyState = KeyEvent.dwControlKeyState & (
kbShift | kbCtrlShift | kbAltShift |
kbScrollState | kbNumState | kbCapsState | kbEnhanced
);
if
(ev.keyDown.textLength != 0)
{
ev.keyDown.charScan.charCode = CpTranslator::fromUtf8(ev.keyDown.getText());
if
(KeyEvent.wVirtualKeyCode == VK_MENU)
ev.keyDown.charScan.scanCode = 0;
if
(ev.keyDown.charScan.charCode ==
'\0'
|| ev.keyDown.keyCode <= kbCtrlZ)
ev.keyDown.keyCode = kbNoKey;
}
if
( ev.keyDown.keyCode == 0x2A00 || ev.keyDown.keyCode == 0x1D00 ||
ev.keyDown.keyCode == 0x3600 || ev.keyDown.keyCode == 0x3800 ||
ev.keyDown.keyCode == 0x3A00 || ev.keyDown.keyCode == 0x5B00 ||
ev.keyDown.keyCode == 0x5C00 )
ev.keyDown.keyCode = kbNoKey;
else
if
(ev.keyDown.controlKeyState & kbRightAlt)
{
if
(ev.keyDown.textLength == 0)
ev.keyDown.keyCode = kbNoKey;
else
ev.keyDown.controlKeyState &= ~kbLeftCtrl;
}
else
if
(KeyEvent.wVirtualScanCode < 89)
{
uchar index = KeyEvent.wVirtualScanCode;
ushort keyCode = 0;
if
((ev.keyDown.controlKeyState & kbAltShift) && THardwareInfo::AltCvt[index])
keyCode = THardwareInfo::AltCvt[index];
else
if
((ev.keyDown.controlKeyState & kbCtrlShift) && THardwareInfo::CtrlCvt[index])
keyCode = THardwareInfo::CtrlCvt[index];
else
if
((ev.keyDown.controlKeyState & kbShift) && THardwareInfo::ShiftCvt[index])
keyCode = THardwareInfo::ShiftCvt[index];
else
if
( !(ev.keyDown.controlKeyState & (kbShift | kbCtrlShift | kbAltShift)) &&
THardwareInfo::NormalCvt[index] )
keyCode = THardwareInfo::NormalCvt[index];
if
(keyCode != 0)
{
ev.keyDown.keyCode = keyCode;
if
(ev.keyDown.charScan.charCode <
' '
)
ev.keyDown.textLength = 0;
else
if
(ev.keyDown.charScan.charCode < 0x7F && !ev.keyDown.textLength)
{
ev.keyDown.text[0] = ev.keyDown.charScan.charCode;
ev.keyDown.textLength = 1;
}
}
}
return
ev.keyDown.keyCode != kbNoKey || ev.keyDown.textLength;
}
void
getWin32Mouse(
const
MOUSE_EVENT_RECORD &MouseEvent, TEvent &ev, InputState &state)
noexcept
{
ev.what = evMouse;
ev.mouse.where.x = MouseEvent.dwMousePosition.X;
ev.mouse.where.y = MouseEvent.dwMousePosition.Y;
ev.mouse.buttons = state.buttons = MouseEvent.dwButtonState;
ev.mouse.eventFlags = MouseEvent.dwEventFlags;
ev.mouse.controlKeyState = MouseEvent.dwControlKeyState & (
kbShift | kbCtrlShift | kbAltShift |
kbScrollState | kbNumState | kbCapsState | kbEnhanced
);
Boolean positive = !(MouseEvent.dwButtonState & 0x80000000);
if
( MouseEvent.dwEventFlags & MOUSE_WHEELED )
ev.mouse.wheel = positive ? mwUp : mwDown;
else
if
( MouseEvent.dwEventFlags & MOUSE_HWHEELED )
ev.mouse.wheel = positive ? mwRight : mwLeft;
else
ev.mouse.wheel = 0;
}
}