| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "ConsoleFont.h" |
| |
|
| | #include <windows.h> |
| | #include <stdio.h> |
| | #include <string.h> |
| | #include <wchar.h> |
| |
|
| | #include <algorithm> |
| | #include <string> |
| | #include <tuple> |
| | #include <utility> |
| | #include <vector> |
| |
|
| | #include "../shared/DebugClient.h" |
| | #include "../shared/OsModule.h" |
| | #include "../shared/StringUtil.h" |
| | #include "../shared/WindowsVersion.h" |
| | #include "../shared/WinptyAssert.h" |
| | #include "../shared/winpty_snprintf.h" |
| |
|
| | namespace { |
| |
|
| | #define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) |
| |
|
| | |
| | const wchar_t kLucidaConsole[] = L"Lucida Console"; |
| | const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; |
| | const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; |
| | const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; |
| | const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; |
| |
|
| | struct FontSize { |
| | short size; |
| | int width; |
| | }; |
| |
|
| | struct Font { |
| | const wchar_t *faceName; |
| | unsigned int family; |
| | short size; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | const FontSize kLucidaFontSizes[] = { |
| | { 5, 3 }, |
| | { 6, 4 }, |
| | { 8, 5 }, |
| | { 10, 6 }, |
| | { 12, 7 }, |
| | { 14, 8 }, |
| | { 16, 10 }, |
| | { 18, 11 }, |
| | { 20, 12 }, |
| | { 36, 22 }, |
| | { 48, 29 }, |
| | { 60, 36 }, |
| | { 72, 43 }, |
| | }; |
| |
|
| | |
| | const FontSize k932GothicVista[] = { |
| | { 6, 3 }, |
| | { 8, 4 }, |
| | { 10, 5 }, |
| | { 12, 6 }, |
| | { 13, 7 }, |
| | { 15, 8 }, |
| | { 17, 9 }, |
| | { 19, 10 }, |
| | { 21, 11 }, |
| | |
| | }; |
| |
|
| | |
| | const FontSize k932GothicWin8[] = { |
| | |
| | |
| | { 5, 3 }, |
| | { 7, 4 }, |
| | { 9, 5 }, |
| | { 11, 6 }, |
| | { 13, 7 }, |
| | { 15, 8 }, |
| | { 17, 9 }, |
| | { 20, 10 }, |
| | { 22, 11 }, |
| | { 24, 12 }, |
| | |
| | { 36, 18 }, |
| | { 48, 24 }, |
| | { 60, 30 }, |
| | { 72, 36 }, |
| | }; |
| |
|
| | |
| | const FontSize k932GothicWin10[] = { |
| | { 6, 3 }, |
| | { 8, 4 }, |
| | { 10, 5 }, |
| | { 12, 6 }, |
| | { 14, 7 }, |
| | { 16, 8 }, |
| | { 18, 9 }, |
| | { 20, 10 }, |
| | { 22, 11 }, |
| | { 24, 12 }, |
| | |
| | { 36, 18 }, |
| | { 48, 24 }, |
| | { 60, 30 }, |
| | { 72, 36 }, |
| | }; |
| |
|
| | |
| | const FontSize k936SimSun[] = { |
| | { 6, 3 }, |
| | { 8, 4 }, |
| | { 10, 5 }, |
| | { 12, 6 }, |
| | { 14, 7 }, |
| | { 16, 8 }, |
| | { 18, 9 }, |
| | { 20, 10 }, |
| | { 22, 11 }, |
| | { 24, 12 }, |
| | |
| | { 36, 18 }, |
| | { 48, 24 }, |
| | { 60, 30 }, |
| | { 72, 36 }, |
| | }; |
| |
|
| | |
| | const FontSize k949GulimChe[] = { |
| | { 6, 3 }, |
| | { 8, 4 }, |
| | { 10, 5 }, |
| | { 12, 6 }, |
| | { 14, 7 }, |
| | { 16, 8 }, |
| | { 18, 9 }, |
| | { 20, 10 }, |
| | { 22, 11 }, |
| | { 24, 12 }, |
| | |
| | { 36, 18 }, |
| | { 48, 24 }, |
| | { 60, 30 }, |
| | { 72, 36 }, |
| | }; |
| |
|
| | |
| | const FontSize k950MingLight[] = { |
| | { 6, 3 }, |
| | { 8, 4 }, |
| | { 10, 5 }, |
| | { 12, 6 }, |
| | { 14, 7 }, |
| | { 16, 8 }, |
| | { 18, 9 }, |
| | { 20, 10 }, |
| | { 22, 11 }, |
| | { 24, 12 }, |
| | |
| | { 36, 18 }, |
| | { 48, 24 }, |
| | { 60, 30 }, |
| | { 72, 36 }, |
| | }; |
| |
|
| | |
| | |
| |
|
| | 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]; |
| | }; |
| |
|
| | |
| | typedef BOOL WINAPI SetConsoleFont_t( |
| | HANDLE hOutput, |
| | DWORD dwFontIndex); |
| |
|
| | |
| | typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); |
| |
|
| | |
| | typedef BOOL WINAPI GetCurrentConsoleFont_t( |
| | HANDLE hOutput, |
| | BOOL bMaximumWindow, |
| | AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); |
| |
|
| | |
| | typedef COORD WINAPI GetConsoleFontSize_t( |
| | HANDLE hConsoleOutput, |
| | DWORD nFont); |
| |
|
| | |
| | typedef BOOL WINAPI GetCurrentConsoleFontEx_t( |
| | HANDLE hConsoleOutput, |
| | BOOL bMaximumWindow, |
| | AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); |
| |
|
| | |
| | typedef BOOL WINAPI SetCurrentConsoleFontEx_t( |
| | HANDLE hConsoleOutput, |
| | BOOL bMaximumWindow, |
| | AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); |
| |
|
| | #define GET_MODULE_PROC(mod, funcName) \ |
| | m_##funcName = reinterpret_cast<funcName##_t*>((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); |
| | GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); |
| | } |
| |
|
| | bool valid() const { |
| | return this->XPFontAPI::valid() && |
| | m_SetConsoleFont != NULL && |
| | m_GetNumberOfConsoleFonts != NULL; |
| | } |
| |
|
| | DEFINE_ACCESSOR(SetConsoleFont) |
| | DEFINE_ACCESSOR(GetNumberOfConsoleFonts) |
| |
|
| | private: |
| | OsModule m_kernel32; |
| | SetConsoleFont_t *m_SetConsoleFont; |
| | GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; |
| | }; |
| |
|
| | 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<std::pair<DWORD, COORD> > readFontTable( |
| | XPFontAPI &api, HANDLE conout, DWORD maxCount) { |
| | std::vector<std::pair<DWORD, COORD> > ret; |
| | for (DWORD i = 0; i < maxCount; ++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) { |
| | const int kMaxCount = 1000; |
| | if (!isTracingEnabled()) { |
| | return; |
| | } |
| | XPFontAPI api; |
| | if (!api.valid()) { |
| | trace("dumpFontTable: cannot dump font table -- missing APIs"); |
| | return; |
| | } |
| | std::vector<std::pair<DWORD, COORD> > table = |
| | readFontTable(api, conout, kMaxCount); |
| | 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); |
| | winpty_snprintf(tmp, "%sfonts %02u-%02u:", |
| | prefix, static_cast<unsigned>(first), static_cast<unsigned>(last)); |
| | line = tmp; |
| | for (size_t i = first; i <= last; ++i) { |
| | if (i % 10 == 5) { |
| | line += " - "; |
| | } |
| | winpty_snprintf(tmp, " %2dx%-2d", |
| | table[i].second.X, table[i].second.Y); |
| | line += tmp; |
| | } |
| | trace("%s", line.c_str()); |
| | first = last + 1; |
| | } |
| | if (table.size() == kMaxCount) { |
| | trace("%sfonts: ... stopped reading at %d fonts ...", |
| | prefix, kMaxCount); |
| | } |
| | } |
| |
|
| | static std::string stringToCodePoints(const std::wstring &str) { |
| | std::string ret = "("; |
| | for (size_t i = 0; i < str.size(); ++i) { |
| | char tmp[32]; |
| | winpty_snprintf(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<unsigned>(infoex.nFont), |
| | infoex.dwFontSize.X, infoex.dwFontSize.Y, |
| | infoex.FontFamily, infoex.FontWeight, utf8FromWide(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<unsigned>(info.nFont), |
| | info.dwFontSize.X, info.dwFontSize.Y); |
| | } |
| |
|
| | static bool setFontVista( |
| | VistaFontAPI &api, |
| | HANDLE conout, |
| | const Font &font) { |
| | AGENT_CONSOLE_FONT_INFOEX infoex = {}; |
| | infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX); |
| | infoex.dwFontSize.Y = font.size; |
| | infoex.FontFamily = font.family; |
| | infoex.FontWeight = 400; |
| | winpty_wcsncpy_nul(infoex.FaceName, font.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, font.faceName, |
| | COUNT_OF(infoex.FaceName)) != 0) { |
| | trace("setFontVista: face name was not set"); |
| | dumpFontInfoEx(infoex, "setFontVista: post-call font: "); |
| | return false; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | return true; |
| | } |
| |
|
| | static Font selectSmallFont(int codePage, int columns, bool isNewW10) { |
| | |
| | |
| |
|
| | const wchar_t *faceName = nullptr; |
| | unsigned int fontFamily = 0; |
| | const FontSize *table = nullptr; |
| | size_t tableSize = 0; |
| |
|
| | switch (codePage) { |
| | case 932: |
| | faceName = kMSGothic; |
| | fontFamily = 0x36; |
| | if (isNewW10) { |
| | table = k932GothicWin10; |
| | tableSize = COUNT_OF(k932GothicWin10); |
| | } else if (isAtLeastWindows8()) { |
| | table = k932GothicWin8; |
| | tableSize = COUNT_OF(k932GothicWin8); |
| | } else { |
| | table = k932GothicVista; |
| | tableSize = COUNT_OF(k932GothicVista); |
| | } |
| | break; |
| | case 936: |
| | faceName = kNSimSun; |
| | fontFamily = 0x36; |
| | table = k936SimSun; |
| | tableSize = COUNT_OF(k936SimSun); |
| | break; |
| | case 949: |
| | faceName = kGulimChe; |
| | fontFamily = 0x36; |
| | table = k949GulimChe; |
| | tableSize = COUNT_OF(k949GulimChe); |
| | break; |
| | case 950: |
| | faceName = kMingLight; |
| | fontFamily = 0x36; |
| | table = k950MingLight; |
| | tableSize = COUNT_OF(k950MingLight); |
| | break; |
| | default: |
| | faceName = kLucidaConsole; |
| | fontFamily = 0x36; |
| | table = kLucidaFontSizes; |
| | tableSize = COUNT_OF(kLucidaFontSizes); |
| | break; |
| | } |
| |
|
| | size_t bestIndex = static_cast<size_t>(-1); |
| | std::tuple<int, int> bestScore = std::make_tuple(-1, -1); |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | for (size_t i = 0; i < tableSize; ++i) { |
| | const int width = table[i].width * columns; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const int halfColumns = std::min(columns, std::max(40, columns / 2)); |
| | const int halfWidth = table[i].width * halfColumns; |
| |
|
| | std::tuple<int, int> thisScore = std::make_tuple(-1, -1); |
| | if (width >= 160 && halfWidth >= 160) { |
| | |
| | thisScore = std::make_tuple(2, -width); |
| | } else if (width >= 160) { |
| | |
| | thisScore = std::make_tuple(1, -width); |
| | } else { |
| | |
| | thisScore = std::make_tuple(0, width); |
| | } |
| | if (thisScore > bestScore) { |
| | bestIndex = i; |
| | bestScore = thisScore; |
| | } |
| | } |
| |
|
| | ASSERT(bestIndex != static_cast<size_t>(-1)); |
| | return Font { faceName, fontFamily, table[bestIndex].size }; |
| | } |
| |
|
| | static void setSmallFontVista(VistaFontAPI &api, HANDLE conout, |
| | int columns, bool isNewW10) { |
| | int codePage = GetConsoleOutputCP(); |
| | const auto font = selectSmallFont(codePage, columns, isNewW10); |
| | if (setFontVista(api, conout, font)) { |
| | trace("setSmallFontVista: success"); |
| | return; |
| | } |
| | if (codePage == 932 || codePage == 936 || |
| | codePage == 949 || codePage == 950) { |
| | trace("setSmallFontVista: falling back to default codepage font instead"); |
| | const auto fontFB = selectSmallFont(0, columns, isNewW10); |
| | if (setFontVista(api, conout, fontFB)) { |
| | trace("setSmallFontVista: fallback was successful"); |
| | return; |
| | } |
| | } |
| | trace("setSmallFontVista: failure"); |
| | } |
| |
|
| | struct FontSizeComparator { |
| | bool operator()(const std::pair<DWORD, COORD> &obj1, |
| | const std::pair<DWORD, COORD> &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) { |
| | |
| | const DWORD fontCount = api.GetNumberOfConsoleFonts()(); |
| | trace("setSmallFontXP: number of console fonts: %u", |
| | static_cast<unsigned>(fontCount)); |
| | std::vector<std::pair<DWORD, COORD> > table = |
| | readFontTable(api, conout, fontCount); |
| | std::sort(table.begin(), table.end(), FontSizeComparator()); |
| | for (size_t i = 0; i < table.size(); ++i) { |
| | |
| | if (table[i].second.X < 4) { |
| | continue; |
| | } |
| | trace("setSmallFontXP: setting font to %u", |
| | static_cast<unsigned>(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"); |
| | } |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| | void setSmallFont(HANDLE conout, int columns, bool isNewW10) { |
| | trace("setSmallFont: attempting to set a small font for %d columns " |
| | "(CP=%u OutputCP=%u)", |
| | columns, |
| | static_cast<unsigned>(GetConsoleCP()), |
| | static_cast<unsigned>(GetConsoleOutputCP())); |
| | VistaFontAPI vista; |
| | if (vista.valid()) { |
| | dumpVistaFont(vista, conout, "previous font: "); |
| | dumpFontTable(conout, "previous font table: "); |
| | setSmallFontVista(vista, conout, columns, isNewW10); |
| | 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: "); |
| | } |
| |
|