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',