From c3999b5043b7d6c2d1b2dd5ac95fc37e778e4647 Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Sun, 11 Oct 2015 01:06:54 -0500 Subject: [PATCH] Rewrite console font changing. * Remove the old "Vista" code path in favor of two code paths: - setSmallFontVista: sets a TrueType font to match the code page - setSmallFontXP: uses SetConsoleFont to activate the smallest viable font in the console's font table * Selecting a CJK-specific TrueType font is important so that double-width characters occupy two cells in the console, which is important for maintaining consistency between the console and the Unix terminal. (Perfect consistency here might be impossible, but using the appropriate font improves things.) winpty currently duplicates double-width characters, but that will be fixed soon. --- agent/Agent.cc | 3 +- agent/ConsoleFont.cc | 531 ++++++++++++++++++++++++++++++++++++++++ agent/ConsoleFont.h | 28 +++ agent/Makefile | 2 + agent/Win32Console.cc | 255 ------------------- agent/Win32Console.h | 7 - agent/winpty_wcsnlen.cc | 15 ++ agent/winpty_wcsnlen.h | 9 + winpty.gyp | 4 + 9 files changed, 591 insertions(+), 263 deletions(-) create mode 100644 agent/ConsoleFont.cc create mode 100644 agent/ConsoleFont.h create mode 100644 agent/winpty_wcsnlen.cc create mode 100644 agent/winpty_wcsnlen.h diff --git a/agent/Agent.cc b/agent/Agent.cc index 79503339..00df5a69 100644 --- a/agent/Agent.cc +++ b/agent/Agent.cc @@ -24,6 +24,7 @@ #include "Terminal.h" #include "NamedPipe.h" #include "AgentAssert.h" +#include "ConsoleFont.h" #include "../shared/DebugClient.h" #include "../shared/AgentMsg.h" #include "../shared/Buffer.h" @@ -82,7 +83,7 @@ Agent::Agent(LPCWSTR controlPipeName, m_bufferData.resize(BUFFER_LINE_COUNT); m_console = new Win32Console; - m_console->setSmallFont(); + setSmallFont(m_console->conout()); m_console->moveWindow(SmallRect(0, 0, 1, 1)); m_console->resizeBuffer(Coord(initialCols, BUFFER_LINE_COUNT)); m_console->moveWindow(SmallRect(0, 0, initialCols, initialRows)); diff --git a/agent/ConsoleFont.cc b/agent/ConsoleFont.cc new file mode 100644 index 00000000..7609dc07 --- /dev/null +++ b/agent/ConsoleFont.cc @@ -0,0 +1,531 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleFont.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "AgentAssert.h" +#include "../shared/DebugClient.h" +#include "winpty_wcsnlen.h" + +namespace { + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +struct Font { + int codePage; + const wchar_t *faceName; + int pxSize; +}; + +const Font kFonts[] = { + // MS Gothic double-width handling seems to be broken with console versions + // prior to Windows 10 (including Windows 10's legacy mode), and it's + // especially broken in Windows 8 and 8.1. AFAICT, MS Gothic at size 9 + // avoids problems in Windows 7 and minimizes problems in 8/8.1. + // + // Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 + // + // The first three codepoints are always rendered as half-width with the + // Windows Japanese fonts. (Of these, the first two must be half-width, + // but U+2014 could be either.) The last three are rendered as full-width, + // and they are East_Asian_Width=Wide. + // + // Windows 7 fails by modeling all codepoints as full-width with font + // sizes 14 and above. + // + // Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but + // using a point size not listed in the console properties dialog + // (e.g. "9") is less wrong: + // + // | code point | + // font | 00A2 00A3 2014 3044 30FC 4000 | cell size + // ------------+---------------------------------+---------- + // 8 | F F F F H H | 4x8 + // 9 | F F F F F F | 5x9 + // 16 | F F F F H H | 8x16 + // raster 6x13 | H H H F F H(*) | 6x13 + // + // (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported + // character). + // + { 932, kMSGothic, 9 }, + + // kNSimSun: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` + // did something sensible with Windows 8. It *did* with the listed font + // sizes, but not with unlisted sizes. Listed sizes: + // - 6 ==> 3x7px + // - 8 ==> 4x9px + // - 10 ==> 5x11px + // - 12 ==> 6x14px + // - 14 ==> 7x16px + // - 16 ==> 8x18px + // ... + // - 36 ==> 18x41px + // - 72 ==> 36x82px + // U+2014 is modeled and rendered as full-width. + { 936, kNSimSun, 8 }, + + // kMingLight: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` + // did something sensible with Windows 8. It *did* with the listed font + // sizes, but not with unlisted sizes. Listed sizes: + // - 6 => 3x7px + // - 8 => 4x10px + // - 10 => 5x12px + // - 12 => 6x14px + // - 14 => 7x17px + // - 16 => 8x19px + // ... + // - 36 => 18x43px + // - 72 => 36x86px + // U+2014 is modeled and rendered as full-width. + { 950, kMingLight, 8 }, + + // kGulimChe: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` + // did something sensible with Windows 8. It *did* with the listed font + // sizes, but not with unlisted sizes. Listed sizes: + // - 6 ==> 3x7px + // - 8 ==> 4x9px + // - 10 ==> 5x11px + // - 12 ==> 6x14px + // - 14 ==> 7x16px + // - 16 ==> 8x18px + // ... + // - 36 ==> 18x41px + // - 72 ==> 36x83px + { 949, kGulimChe, 8 }, + + // Listed sizes: + // - 5 ==> 2x5px + // - 6 ==> 3x6px + // - 7 ==> 3x6px + // - 8 ==> 4x8px + // - 10 ==> 5x10px + // - 12 ==> 6x12px + // - 14 ==> 7x14px + // - 16 ==> 8x16px + // ... + // - 36 ==> 17x36px + // - 72 ==> 34x72px + { 0, L"Consolas", 8 }, + + // Listed sizes: + // - 5 ==> 3x5px + // - 6 ==> 4x6px + // - 7 ==> 4x7px + // - 8 ==> 5x8px + // - 10 ==> 6x10px + // - 12 ==> 7x12px + // - 14 ==> 8x14px + // - 16 ==> 10x16px + // ... + // - 36 ==> 22x36px + // - 72 ==> 43x72px + { 0, L"Lucida Console", 6 }, +}; + +class OsModule { + HMODULE m_module; +public: + OsModule(const wchar_t *fileName) { + m_module = LoadLibraryW(fileName); + ASSERT(m_module != NULL); + } + ~OsModule() { + FreeLibrary(m_module); + } + HMODULE handle() const { return m_module; } + FARPROC proc(const char *funcName) { + FARPROC ret = GetProcAddress(m_module, funcName); + if (ret == NULL) { + trace("GetProcAddress: %s is missing", funcName); + } + return ret; + } +}; + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable(XPFontAPI &api, HANDLE conout) { + std::vector > ret; + for (DWORD i = 0;; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + XPFontAPI api; + if (!api.valid()) { + trace("dumpFontTable: cannot dump font table -- missing APIs"); + return; + } + std::vector > table = readFontTable(api, conout); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + sprintf(tmp, "%sfonts %02u-%02u:", + prefix, static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + sprintf(tmp, " %2dx%-2d", table[i].second.X, table[i].second.Y); + line += tmp; + } + trace("%s", line.c_str()); + first = last + 1; + } +} + +static std::string narrowString(const std::wstring &input) +{ + int mblen = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + NULL, 0, NULL, NULL); + if (mblen <= 0) { + return std::string(); + } + std::vector tmp(mblen); + int mblen2 = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + tmp.data(), tmp.size(), + NULL, NULL); + ASSERT(mblen2 == mblen); + return std::string(tmp.data(), tmp.size()); +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + sprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex, + const char *prefix) { + if (!isTracingEnabled()) { + return; + } + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + trace("%snFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%s %s", + prefix, + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, narrowString(faceName).c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("GetCurrentConsoleFontEx call failed"); + return; + } + dumpFontInfoEx(infoex, prefix); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("GetCurrentConsoleFont call failed"); + return; + } + trace("%snFont=%u dwFontSize=(%d,%d)", + prefix, + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static bool setFontVista( + VistaFontAPI &api, + HANDLE conout, + const wchar_t *faceName, + int pxSize) { + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX); + infoex.dwFontSize.Y = pxSize; + infoex.FontWeight = 400; + wcsncpy(infoex.FaceName, faceName, COUNT_OF(infoex.FaceName)); + dumpFontInfoEx(infoex, "setFontVista: setting font to: "); + if (!api.SetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: SetCurrentConsoleFontEx call failed"); + return false; + } + memset(&infoex, 0, sizeof(infoex)); + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: GetCurrentConsoleFontEx call failed"); + return false; + } + if (wcsncmp(infoex.FaceName, faceName, COUNT_OF(infoex.FaceName)) != 0) { + trace("setFontVista: face name was not set"); + dumpFontInfoEx(infoex, "setFontVista: post-call font: "); + return false; + } + // We'd like to verify that the new font size is correct, but we can't + // predict what it will be, even though we just set it to `pxSize` through + // an apprently symmetric interface. For the Chinese and Korean fonts, the + // new `infoex.dwFontSize.Y` value can be slightly larger than the height + // we specified. + return true; +} + +static void setSmallFontVista(VistaFontAPI &api, HANDLE conout) { + int codePage = GetConsoleOutputCP(); + for (size_t i = 0; i < COUNT_OF(kFonts); ++i) { + if (kFonts[i].codePage == 0 || kFonts[i].codePage == codePage) { + if (setFontVista(api, conout, + kFonts[i].faceName, kFonts[i].pxSize)) { + trace("setSmallFontVista: success"); + return; + } + } + } + trace("setSmallFontVista: failure"); +} + +struct FontSizeComparator { + bool operator()(const std::pair &obj1, + const std::pair &obj2) const { + int score1 = obj1.second.X + obj1.second.Y; + int score2 = obj2.second.X + obj2.second.Y; + return score1 < score2; + } +}; + +static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) { + // Read the console font table and sort it from smallest to largest. + std::vector > table = readFontTable(api, conout); + std::sort(table.begin(), table.end(), FontSizeComparator()); + for (size_t i = 0; i < table.size(); ++i) { + // Skip especially narrow fonts to permit narrower terminals. + if (table[i].second.X < 4) { + continue; + } + trace("setSmallFontXP: setting font to %u", + static_cast(table[i].first)); + if (!api.SetConsoleFont()(conout, table[i].first)) { + trace("setSmallFontXP: SetConsoleFont call failed"); + continue; + } + AGENT_CONSOLE_FONT_INFO info; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("setSmallFontXP: GetCurrentConsoleFont call failed"); + return; + } + if (info.nFont != table[i].first) { + trace("setSmallFontXP: font was not set"); + dumpXPFont(api, conout, "setSmallFontXP: post-call font: "); + continue; + } + trace("setSmallFontXP: success"); + return; + } + trace("setSmallFontXP: failure"); +} + +} // anonymous namespace + +// A Windows console window can never be larger than the desktop window. To +// maximize the possible size of the console in rows*cols, try to configure +// the console with a small font. Unfortunately, we cannot make the font *too* +// small, because there is also a minimum window size in pixels. +void setSmallFont(HANDLE conout) { + trace("setSmallFont: attempting to set a small font (CP=%u OutputCP=%u)", + static_cast(GetConsoleCP()), + static_cast(GetConsoleOutputCP())); + VistaFontAPI vista; + if (vista.valid()) { + dumpVistaFont(vista, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontVista(vista, conout); + dumpVistaFont(vista, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + dumpXPFont(xp, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontXP(xp, conout); + dumpXPFont(xp, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + trace("setSmallFont: neither Vista nor XP APIs detected -- giving up"); + dumpFontTable(conout, "font table: "); +} diff --git a/agent/ConsoleFont.h b/agent/ConsoleFont.h new file mode 100644 index 00000000..f8769a5b --- /dev/null +++ b/agent/ConsoleFont.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLEFONT_H +#define CONSOLEFONT_H + +#include + +void setSmallFont(HANDLE conout); + +#endif // CONSOLEFONT_H diff --git a/agent/Makefile b/agent/Makefile index d09ccf49..5c8d28d8 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -35,6 +35,8 @@ OBJECTS = \ Coord.o \ SmallRect.o \ ConsoleLine.o \ + ConsoleFont.o \ + winpty_wcsnlen.o \ main.o CXXFLAGS += \ diff --git a/agent/Win32Console.cc b/agent/Win32Console.cc index 5c008a96..b4de1890 100644 --- a/agent/Win32Console.cc +++ b/agent/Win32Console.cc @@ -25,33 +25,6 @@ #include #include -namespace { - class OsModule { - HMODULE m_module; - public: - OsModule(const wchar_t *fileName) { - m_module = LoadLibraryW(fileName); - ASSERT(m_module != NULL); - } - ~OsModule() { - FreeLibrary(m_module); - } - HMODULE handle() const { return m_module; } - FARPROC proc(const char *funcName) { - FARPROC ret = GetProcAddress(m_module, funcName); - if (ret == NULL) { - trace("GetProcAddress: %s is missing", funcName); - } - return ret; - } - }; -} - -#define GET_MODULE_PROC(mod, funcName) \ - funcName##Type *p##funcName = reinterpret_cast((mod).proc(#funcName)); \ - -#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) - Win32Console::Win32Console() : m_titleWorkBuf(16) { m_conin = GetStdHandle(STD_INPUT_HANDLE); @@ -89,234 +62,6 @@ void Win32Console::postCloseMessage() PostMessage(h, WM_CLOSE, 0, 0); } -// A Windows console window can never be larger than the desktop window. To -// maximize the possible size of the console in rows*cols, try to configure -// the console with a small font. Unfortunately, we cannot make the font *too* -// small, because there is also a minimum window size in pixels. -void Win32Console::setSmallFont() -{ - // I measured the minimum window size in Windows 7 and Windows 10 VMs on - // a typical-DPI monitor. The minimum row count was always 1, but the - // minimum width varied. - // - // XXX: High-DPI monitors might break this code. The minimum console - // window might be much larger when measured in pixels. - // - // XXX: We could resize the console font when the terminal is resized. - - // Lucida Console 6px: cells are 4x6 pixels. Minimum window is 24x1. - if (setConsoleFont(L"Lucida Console", 6)) - return; - // Consolas 8px: cells are 4x8 pixels. Minimum window is 24x1. - if (setConsoleFont(L"Consolas", 8)) - return; - // Smallest raster font is 4x6. Minimum window is 24x1. - if (setSmallConsoleFontVista()) - return; - if (setSmallConsoleFontXP()) - return; - trace("Error: could not configure console font -- continuing anyway"); - dumpConsoleFont("setSmallFont: final font: "); -} - -// Some of these types and functions are missing from the MinGW headers. -// Others are undocumented. - -struct AGENT_CONSOLE_FONT_INFO { - DWORD nFont; - COORD dwFontSize; -}; - -struct AGENT_CONSOLE_FONT_INFOEX { - ULONG cbSize; - DWORD nFont; - COORD dwFontSize; - UINT FontFamily; - UINT FontWeight; - WCHAR FaceName[LF_FACESIZE]; -}; - -// undocumented XP API -typedef BOOL WINAPI SetConsoleFontType( - HANDLE hOutput, - DWORD dwFontIndex); - -// XP and up -typedef BOOL WINAPI GetCurrentConsoleFontType( - HANDLE hOutput, - BOOL bMaximize, - AGENT_CONSOLE_FONT_INFO *pFontInfo); - -// XP and up -typedef COORD WINAPI GetConsoleFontSizeType( - HANDLE hConsoleOutput, - DWORD nFont); - -// Vista and up -typedef BOOL WINAPI GetCurrentConsoleFontExType( - HANDLE hConsoleOutput, - BOOL bMaximumWindow, - AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); - -// Vista and up -typedef BOOL WINAPI SetCurrentConsoleFontExType( - HANDLE hConsoleOutput, - BOOL bMaximumWindow, - AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); - -// Attempt to set the console font to the given facename and pixel size. -// These APIs should exist on Vista and up. -bool Win32Console::setConsoleFont(const wchar_t *faceName, int pixelSize) -{ - trace("setConsoleFont: attempting to set console font to %dpx %ls", pixelSize, faceName); - - OsModule dll(L"kernel32.dll"); - GET_MODULE_PROC(dll, GetCurrentConsoleFontEx); - GET_MODULE_PROC(dll, SetCurrentConsoleFontEx); - if (pGetCurrentConsoleFontEx == NULL || pSetCurrentConsoleFontEx == NULL) { - trace("setConsoleFont failed: missing API(s)"); - return false; - } - dumpConsoleFont("setConsoleFont: cur font: "); - - AGENT_CONSOLE_FONT_INFOEX fontex = {0}; - fontex.cbSize = sizeof(fontex); - fontex.FontWeight = 400; - fontex.dwFontSize.Y = pixelSize; - wcsncpy(fontex.FaceName, faceName, COUNT_OF(fontex.FaceName)); - if (!pSetCurrentConsoleFontEx(m_conout, FALSE, &fontex)) { - trace("setConsoleFont failed: SetCurrentConsoleFontEx call failed"); - return false; - } - - memset(&fontex, 0, sizeof(fontex)); - fontex.cbSize = sizeof(fontex); - if (!pGetCurrentConsoleFontEx(m_conout, FALSE, &fontex)) { - trace("setConsoleFont failed: GetCurrentConsoleFontEx call failed"); - return false; - } - if (wcsncmp(fontex.FaceName, faceName, COUNT_OF(fontex.FaceName)) != 0) { - wchar_t curFace[COUNT_OF(fontex.FaceName) + 1]; - wcsncpy(curFace, fontex.FaceName, COUNT_OF(curFace)); - trace("setConsoleFont failed: new facename is %ls", curFace); - return false; - } - // XXX: Will the post-call font size always have *exactly* the same Y - // value? - if (fontex.dwFontSize.Y != pixelSize) { - trace("setConsoleFont failed: new font size is %d,%d", - fontex.dwFontSize.X, fontex.dwFontSize.Y); - return false; - } - - trace("setConsoleFont succeeded"); - dumpConsoleFont("setConsoleFont: final font: "); - return true; -} - -// Attempt to set the console font using the size of the 0-index font. This -// seems to select a raster font. -// -// Perhaps this behavior should be removed in favor of assuming the computer -// has one of the hard-coded fonts? -// -bool Win32Console::setSmallConsoleFontVista() -{ - trace("setSmallConsoleFontVista was called"); - - OsModule dll(L"kernel32.dll"); - GET_MODULE_PROC(dll, GetConsoleFontSize); - GET_MODULE_PROC(dll, SetCurrentConsoleFontEx); - if (pGetConsoleFontSize == NULL || pSetCurrentConsoleFontEx == NULL) { - trace("setSmallConsoleFontVista failed: missing API(s)"); - return false; - } - dumpConsoleFont("setSmallConsoleFontVista: cur font: "); - COORD smallest = pGetConsoleFontSize(m_conout, 0); - trace("setSmallConsoleFontVista: smallest=%d,%d", smallest.X, smallest.Y); - if (smallest.X == 0 || smallest.Y == 0) { - trace("setSmallConsoleFontVista failed: GetConsoleFontSize call failed"); - return false; - } - AGENT_CONSOLE_FONT_INFOEX fontex = {0}; - fontex.cbSize = sizeof(fontex); - fontex.nFont = 0; - fontex.dwFontSize = smallest; - if (!pSetCurrentConsoleFontEx(m_conout, FALSE, &fontex)) { - trace("setSmallConsoleFontVista failed: SetCurrentConsoleFontEx call failed"); - return false; - } - - trace("setSmallConsoleFontVista succeeded"); - dumpConsoleFont("setSmallConsoleFontVista: final font: "); - return true; -} - -// Use undocumented APIs to set a small console font on XP. -// -// Somewhat described here: -// http://blogs.microsoft.co.il/blogs/pavely/archive/2009/07/23/changing-console-fonts.aspx -// -bool Win32Console::setSmallConsoleFontXP() -{ - trace("setSmallConsoleFontXP: attempting to use undocumented XP API"); - - OsModule dll(L"kernel32.dll"); - GET_MODULE_PROC(dll, SetConsoleFont); - if (pSetConsoleFont == NULL) { - trace("setSmallConsoleFontXP failed: missing API"); - return false; - } - - // The undocumented GetNumberOfConsoleFonts API reports that my Windows 7 - // system has 12 fonts on it. Each font is really just a differently-sized - // raster/Terminal font. Font index 0 is the smallest font, so we want to - // choose it. - - dumpConsoleFont("setSmallConsoleFontXP: cur font: "); - if (!pSetConsoleFont(m_conout, 0)) { - trace("setSmallConsoleFontXP failed: SetConsoleFont failed"); - return false; - } - - trace("setSmallConsoleFontXP succeeded"); - dumpConsoleFont("setSmallConsoleFontXP: final font: "); - return true; -} - -void Win32Console::dumpConsoleFont(const char *prefix) -{ - OsModule dll(L"kernel32.dll"); - - GET_MODULE_PROC(dll, GetCurrentConsoleFontEx); - if (pGetCurrentConsoleFontEx != NULL) { - AGENT_CONSOLE_FONT_INFOEX fontex = {0}; - fontex.cbSize = sizeof(fontex); - if (!pGetCurrentConsoleFontEx(m_conout, FALSE, &fontex)) { - trace("GetCurrentConsoleFontEx call failed"); - return; - } - wchar_t curFace[COUNT_OF(fontex.FaceName) + 1]; - wcsncpy(curFace, fontex.FaceName, COUNT_OF(curFace)); - trace("%sfontex.nFont=%d", prefix, fontex.nFont); - trace("%sfontex.dwFontSize=%d,%d", - prefix, fontex.dwFontSize.X, fontex.dwFontSize.Y); - trace("%sfontex.FontFamily=%d", prefix, fontex.FontFamily); - trace("%sfontex.FontWeight=%d", prefix, fontex.FontWeight); - trace("%sfontex.FaceName=%ls", prefix, curFace); - return; - } - - GET_MODULE_PROC(dll, GetCurrentConsoleFont); - if (pGetCurrentConsoleFont != NULL) { - AGENT_CONSOLE_FONT_INFO font = {0}; - pGetCurrentConsoleFont(m_conout, FALSE, &font); - trace("%sfont.nFont=%d", prefix, font.nFont); - trace("%sfont.dwFontSize=%d,%d", - prefix, font.dwFontSize.X, font.dwFontSize.Y); - } -} - void Win32Console::clearLines( int row, int count, diff --git a/agent/Win32Console.h b/agent/Win32Console.h index e250bd84..41f7e703 100644 --- a/agent/Win32Console.h +++ b/agent/Win32Console.h @@ -50,13 +50,6 @@ class Win32Console HANDLE conout(); HWND hwnd(); void postCloseMessage(); - void setSmallFont(); - bool setConsoleFont(const wchar_t *faceName, int pointSize); -private: - bool setSmallConsoleFontXP(); - bool setSmallConsoleFontVista(); - void dumpConsoleFont(const char *prefix); -public: void clearLines(int row, int count, const ConsoleScreenBufferInfo &info); // Buffer and window sizes. diff --git a/agent/winpty_wcsnlen.cc b/agent/winpty_wcsnlen.cc new file mode 100644 index 00000000..964220e9 --- /dev/null +++ b/agent/winpty_wcsnlen.cc @@ -0,0 +1,15 @@ +#include "winpty_wcsnlen.h" + +#include "AgentAssert.h" + +// Workaround. MinGW (from mingw.org) does not have wcsnlen. MinGW-w64 *does* +// have wcsnlen, but use this function for consistency. +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen) { + ASSERT(s != NULL); + for (size_t i = 0; i < maxlen; ++i) { + if (s[i] == L'\0') { + return i; + } + } + return maxlen; +} diff --git a/agent/winpty_wcsnlen.h b/agent/winpty_wcsnlen.h new file mode 100644 index 00000000..92723b45 --- /dev/null +++ b/agent/winpty_wcsnlen.h @@ -0,0 +1,9 @@ +#ifndef WINPTY_WCSNLEN_H +#define WINPTY_WCSNLEN_H + +#include +#include + +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen); + +#endif // WINPTY_WCSNLEN_H diff --git a/winpty.gyp b/winpty.gyp index 5b03ec01..ebf89cc4 100644 --- a/winpty.gyp +++ b/winpty.gyp @@ -17,6 +17,8 @@ 'agent/Agent.cc', 'agent/AgentAssert.h', 'agent/AgentAssert.cc', + 'agent/ConsoleFont.cc', + 'agent/ConsoleFont.h', 'agent/ConsoleInput.cc', 'agent/ConsoleInput.h', 'agent/ConsoleLine.cc', @@ -34,6 +36,8 @@ 'agent/Win32Console.cc', 'agent/Win32Console.h', 'agent/main.cc', + 'agent/winpty_wcsnlen.cc', + 'agent/winpty_wcsnlen.h', 'shared/AgentMsg.h', 'shared/Buffer.h', 'shared/DebugClient.h',