|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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: "); |
|
} |
|
|