From 97a9b4743e0a3699d238b79ec6d2bcd40fc40de5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 27 Dec 2015 15:51:28 +0800 Subject: [PATCH] Make sure only *W functions from Win32 API are called. After this commit, SolveSpace always uses UTF-8 strings internally (represented as char* and std::string) on every OS, for everything: UI labels, paths and user input. OS X always uses UTF-8; on Windows, strings are converted at boundary; and on Linux/BSD/etc SolveSpace refuses to start unless the current locale is UTF-8. This will negatively affect literally no one. This commit does not bring forth any user-visible improvement yet; in order to correctly handle Unicode in filenames, it is still necessary to change the fopen calls to _wfopen on Windows. --- CMakeLists.txt | 4 +- src/gtk/gtkmain.cpp | 19 ++- src/solvespace.h | 7 ++ src/win32/w32main.cpp | 263 ++++++++++++++++++++++-------------------- src/win32/w32util.cpp | 47 +++++++- 5 files changed, 209 insertions(+), 131 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f69566a7..6d4ef9ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,9 @@ if(WIN32) -D_WIN32_IE=_WIN32_WINNT -DISOLATION_AWARE_ENABLED=1 -DWIN32=1 - -DWIN32_LEAN_AND_MEAN=1) + -DWIN32_LEAN_AND_MEAN=1 + -DUNICODE + -D_UNICODE) endif() if(MINGW) diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index b8e23b6b..56d40a78 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -1470,7 +1470,21 @@ void ExitNow(void) { }; int main(int argc, char** argv) { - /* If we don't call this, gtk_init will set the C standard library + /* It would in principle be possible to judiciously use + Glib::filename_{from,to}_utf8, but it's not really worth + the effort. + The setlocale() call is necessary for Glib::get_charset() + to detect the system character set; otherwise it thinks + it is always ANSI_X3.4-1968. + We set it back to C after all. */ + setlocale(LC_ALL, ""); + if(!Glib::get_charset()) { + std::cerr << "Sorry, only UTF-8 locales are supported." << std::endl; + return 1; + } + setlocale(LC_ALL, "C"); + + /* If we don't do this, gtk_init will set the C standard library locale, and printf will format floats using ",". We will then fail to parse these. Also, many text window lines will become ambiguous. */ @@ -1500,7 +1514,8 @@ int main(int argc, char** argv) { << std::endl; } - SS.OpenFile(argv[1]); + /* Make sure the argument is valid UTF-8. */ + SS.OpenFile(Glib::ustring(argv[1])); } main.run(*GW); diff --git a/src/solvespace.h b/src/solvespace.h index 9fb0de44..8a5b0936 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -107,6 +107,13 @@ typedef UCHAR uint8_t; typedef CHAR int8_t; #endif +#if defined(WIN32) +std::string Narrow(const wchar_t *s); +std::wstring Widen(const char *s); +std::string Narrow(const std::wstring &s); +std::wstring Widen(const std::string &s); +#endif + inline double Random(double vmax) { return (vmax*rand()) / RAND_MAX; } diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 97de2b6c..fe9f64e3 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -90,9 +90,9 @@ static LRESULT CALLBACK MessageProc(HWND hwnd, UINT msg, WPARAM wParam, col = 0; row++; } else { - TextOut(hdc, col*SS.TW.CHAR_WIDTH + 10, - row*SS.TW.LINE_HEIGHT + 10, - &(MessageString[i]), 1); + TextOutW(hdc, col*SS.TW.CHAR_WIDTH + 10, + row*SS.TW.LINE_HEIGHT + 10, + Widen(&(MessageString[i])).c_str(), 1); col++; } } @@ -107,11 +107,11 @@ static LRESULT CALLBACK MessageProc(HWND hwnd, UINT msg, WPARAM wParam, return 1; } -HWND CreateWindowClient(DWORD exStyle, const char *className, const char *windowName, +HWND CreateWindowClient(DWORD exStyle, const wchar_t *className, const wchar_t *windowName, DWORD style, int x, int y, int width, int height, HWND parent, HMENU menu, HINSTANCE instance, void *param) { - HWND h = CreateWindowEx(exStyle, className, windowName, style, x, y, + HWND h = CreateWindowExW(exStyle, className, windowName, style, x, y, width, height, parent, menu, instance, param); RECT r; @@ -128,16 +128,15 @@ void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error) { EnableWindow(GraphicsWnd, false); EnableWindow(TextWnd, false); - //HWND h = GetForegroundWindow(); // Register the window class for our dialog. WNDCLASSEX wc = {}; - wc.cbSize = sizeof(wc); + wc.cbSize = sizeof(wc); wc.style = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC)MessageProc; wc.hInstance = Instance; wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; - wc.lpszClassName = "MessageWnd"; + wc.lpszClassName = L"MessageWnd"; wc.lpszMenuName = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), @@ -153,11 +152,11 @@ void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error) const char *title = error ? "SolveSpace - Error" : "SolveSpace - Message"; int width = cols*SS.TW.CHAR_WIDTH + 20, height = rows*SS.TW.LINE_HEIGHT + 60; - MessageWnd = CreateWindowClient(0, "MessageWnd", title, + MessageWnd = CreateWindowClient(0, L"MessageWnd", Widen(title).c_str(), WS_OVERLAPPED | WS_SYSMENU, r.left + 100, r.top + 100, width, height, NULL, NULL, Instance, NULL); - OkButton = CreateWindowEx(0, WC_BUTTON, "OK", + OkButton = CreateWindowExW(0, WC_BUTTON, L"OK", WS_CHILD | WS_TABSTOP | WS_CLIPSIBLINGS | WS_VISIBLE | BS_DEFPUSHBUTTON, (width - 70)/2, rows*SS.TW.LINE_HEIGHT + 20, 70, 25, MessageWnd, NULL, Instance, NULL); @@ -196,15 +195,15 @@ void SolveSpace::AddContextMenuItem(const char *label, int id) if(!ContextMenu) ContextMenu = CreatePopupMenu(); if(id == CONTEXT_SUBMENU) { - AppendMenu(ContextMenu, MF_STRING | MF_POPUP, - (UINT_PTR)ContextSubmenu, label); + AppendMenuW(ContextMenu, MF_STRING | MF_POPUP, + (UINT_PTR)ContextSubmenu, Widen(label).c_str()); ContextSubmenu = NULL; } else { HMENU m = ContextSubmenu ? ContextSubmenu : ContextMenu; if(id == CONTEXT_SEPARATOR) { - AppendMenu(m, MF_SEPARATOR, 0, ""); + AppendMenuW(m, MF_SEPARATOR, 0, L""); } else { - AppendMenu(m, MF_STRING, id, label); + AppendMenuW(m, MF_STRING, id, Widen(label).c_str()); } } } @@ -271,7 +270,7 @@ void SolveSpace::GetTextWindowSize(int *w, int *h) } void SolveSpace::OpenWebsite(const char *url) { - ShellExecute(GraphicsWnd, "open", url, NULL, NULL, SW_SHOWNORMAL); + ShellExecuteW(GraphicsWnd, L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL); } void SolveSpace::ExitNow(void) { @@ -292,12 +291,13 @@ inline int CLAMP(int v, int a, int b) { static HKEY GetRegistryKey() { HKEY Software; - if(RegOpenKeyEx(HKEY_CURRENT_USER, "Software", 0, KEY_ALL_ACCESS, &Software) != ERROR_SUCCESS) + if(RegOpenKeyExW(HKEY_CURRENT_USER, L"Software", 0, + KEY_ALL_ACCESS, &Software) != ERROR_SUCCESS) return NULL; HKEY SolveSpace; - if(RegCreateKeyEx(Software, "SolveSpace", 0, NULL, 0, - KEY_ALL_ACCESS, NULL, &SolveSpace, NULL) != ERROR_SUCCESS) + if(RegCreateKeyExW(Software, L"SolveSpace", 0, NULL, 0, + KEY_ALL_ACCESS, NULL, &SolveSpace, NULL) != ERROR_SUCCESS) return NULL; RegCloseKey(Software); @@ -308,8 +308,8 @@ static HKEY GetRegistryKey() void SolveSpace::CnfFreezeInt(uint32_t val, const std::string &name) { HKEY SolveSpace = GetRegistryKey(); - RegSetValueEx(SolveSpace, &name[0], 0, - REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); + RegSetValueExW(SolveSpace, &Widen(name)[0], 0, + REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); RegCloseKey(SolveSpace); } void SolveSpace::CnfFreezeFloat(float val, const std::string &name) @@ -317,15 +317,16 @@ void SolveSpace::CnfFreezeFloat(float val, const std::string &name) static_assert(sizeof(float) == sizeof(DWORD), "sizes of float and DWORD must match"); HKEY SolveSpace = GetRegistryKey(); - RegSetValueEx(SolveSpace, &name[0], 0, - REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); + RegSetValueExW(SolveSpace, &Widen(name)[0], 0, + REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); RegCloseKey(SolveSpace); } void SolveSpace::CnfFreezeString(const std::string &str, const std::string &name) { HKEY SolveSpace = GetRegistryKey(); - RegSetValueEx(SolveSpace, &name[0], 0, - REG_SZ, (const BYTE*) &str[0], str.length() + 1); + std::wstring strW = Widen(str); + RegSetValueExW(SolveSpace, &Widen(name)[0], 0, + REG_SZ, (const BYTE*) &strW[0], (strW.length() + 1) * 2); RegCloseKey(SolveSpace); } static void FreezeWindowPos(HWND hwnd, const std::string &name) @@ -344,7 +345,8 @@ uint32_t SolveSpace::CnfThawInt(uint32_t val, const std::string &name) { HKEY SolveSpace = GetRegistryKey(); DWORD type, newval, len = sizeof(DWORD); - LONG result = RegQueryValueEx(SolveSpace, &name[0], NULL, &type, (BYTE*) &newval, &len); + LONG result = RegQueryValueEx(SolveSpace, &Widen(name)[0], NULL, + &type, (BYTE*) &newval, &len); RegCloseKey(SolveSpace); if(result == ERROR_SUCCESS && type == REG_DWORD) @@ -357,7 +359,8 @@ float SolveSpace::CnfThawFloat(float val, const std::string &name) HKEY SolveSpace = GetRegistryKey(); DWORD type, len = sizeof(DWORD); float newval; - LONG result = RegQueryValueEx(SolveSpace, &name[0], NULL, &type, (BYTE*) &newval, &len); + LONG result = RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, + &type, (BYTE*) &newval, &len); RegCloseKey(SolveSpace); if(result == ERROR_SUCCESS && type == REG_DWORD) @@ -369,22 +372,22 @@ std::string SolveSpace::CnfThawString(const std::string &val, const std::string { HKEY SolveSpace = GetRegistryKey(); DWORD type, len; - if(RegQueryValueEx(SolveSpace, &name[0], NULL, - &type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) { + if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, + &type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) { RegCloseKey(SolveSpace); return val; } - std::string newval; - newval.resize(len - 1); - if(RegQueryValueEx(SolveSpace, &name[0], NULL, - NULL, (BYTE*) &newval[0], &len) != ERROR_SUCCESS) { + std::wstring newval; + newval.resize(len / 2 - 1); + if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, + NULL, (BYTE*) &newval[0], &len) != ERROR_SUCCESS) { RegCloseKey(SolveSpace); return val; } RegCloseKey(SolveSpace); - return newval; + return Narrow(newval); } static void ThawWindowPos(HWND hwnd, const std::string &name) { @@ -414,9 +417,9 @@ static void ThawWindowPos(HWND hwnd, const std::string &name) void SolveSpace::SetCurrentFilename(const std::string &filename) { if(!filename.empty()) { - SetWindowText(GraphicsWnd, ("SolveSpace - " + filename).c_str()); + SetWindowTextW(GraphicsWnd, Widen("SolveSpace - " + filename).c_str()); } else { - SetWindowText(GraphicsWnd, "SolveSpace - (not yet saved)"); + SetWindowTextW(GraphicsWnd, L"SolveSpace - (not yet saved)"); } } @@ -622,13 +625,19 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) return 1; } +static std::string EditControlText(HWND hwnd) +{ + std::wstring result; + result.resize(GetWindowTextLength(hwnd)); + GetWindowTextW(hwnd, &result[0], result.length() + 1); + return Narrow(result); +} + static bool ProcessKeyDown(WPARAM wParam) { if(GraphicsEditControlIsVisible() && wParam != VK_ESCAPE) { if(wParam == VK_RETURN) { - char s[1024] = {}; - SendMessage(GraphicsEditControl, WM_GETTEXT, 900, (LPARAM)s); - SS.GW.EditControlDone(s); + SS.GW.EditControlDone(EditControlText(GraphicsEditControl).c_str()); return true; } else { return false; @@ -636,9 +645,7 @@ static bool ProcessKeyDown(WPARAM wParam) } if(TextEditControlIsVisible() && wParam != VK_ESCAPE) { if(wParam == VK_RETURN) { - char s[1024] = {}; - SendMessage(TextEditControl, WM_GETTEXT, 900, (LPARAM)s); - SS.TW.EditControlDone(s); + SS.TW.EditControlDone(EditControlText(TextEditControl).c_str()); } else { return false; } @@ -796,12 +803,12 @@ void SolveSpace::InvalidateText(void) InvalidateRect(TextWnd, NULL, false); } -static void ShowEditControl(HWND h, int x, int y, char *s) { +static void ShowEditControl(HWND h, int x, int y, const std::wstring &s) { MoveWindow(h, x, y, EDIT_WIDTH, EDIT_HEIGHT, true); ShowWindow(h, SW_SHOW); - if(s) { - SendMessage(h, WM_SETTEXT, 0, (LPARAM)s); - SendMessage(h, EM_SETSEL, 0, strlen(s)); + if(!s.empty()) { + SendMessage(h, WM_SETTEXT, 0, (LPARAM)s.c_str()); + SendMessage(h, EM_SETSEL, 0, s.length()); SetFocus(h); } } @@ -809,7 +816,7 @@ void SolveSpace::ShowTextEditControl(int x, int y, char *s) { if(GraphicsEditControlIsVisible()) return; - ShowEditControl(TextEditControl, x, y, s); + ShowEditControl(TextEditControl, x, y, Widen(s)); } void SolveSpace::HideTextEditControl(void) { @@ -832,7 +839,7 @@ void SolveSpace::ShowGraphicsEditControl(int x, int y, char *s) // top left corner y -= 20; - ShowEditControl(GraphicsEditControl, x, y, s); + ShowEditControl(GraphicsEditControl, x, y, Widen(s)); } void SolveSpace::HideGraphicsEditControl(void) { @@ -958,67 +965,63 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, //----------------------------------------------------------------------------- // Common dialog routines, to open or save a file. //----------------------------------------------------------------------------- -bool SolveSpace::GetOpenFile(std::string &filename, - const char *defExtension, const char *selPattern) -{ - // UNC paths may be arbitrarily long. +static size_t strlen2(const char *p) { + const char *s = p; + while(*p || (!*p && *(p+1))) p++; + return p - s + 1; +} + +static bool OpenSaveFile(bool isOpen, std::string &filename, + const char *defExtension, const char *selPattern) { + // UNC paths may be as long as 32767 characters. // Unfortunately, the Get*FileName API does not provide any way to use it // except with a preallocated buffer of fixed size, so we use something // reasonably large. - char filenameC[0xFFFF] = {}; - strncpy(filenameC, filename.c_str(), sizeof(filenameC) - 1); + const int len = 32768; + wchar_t filenameC[len] = {}; + wcsncpy(filenameC, Widen(filename).c_str(), len - 1); + + std::wstring selPatternW = Widen(std::string(selPattern, strlen2(selPattern))); + std::wstring defExtensionW = Widen(defExtension); OPENFILENAME ofn = {}; ofn.lStructSize = sizeof(ofn); ofn.hInstance = Instance; ofn.hwndOwner = GraphicsWnd; - ofn.lpstrFilter = selPattern; - ofn.lpstrDefExt = defExtension; + ofn.lpstrFilter = selPatternW.c_str(); + ofn.lpstrDefExt = defExtensionW.c_str(); ofn.lpstrFile = filenameC; - ofn.nMaxFile = sizeof(filenameC); + ofn.nMaxFile = len; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; EnableWindow(GraphicsWnd, false); EnableWindow(TextWnd, false); - BOOL r = GetOpenFileName(&ofn); + BOOL r; + if(isOpen) { + r = GetOpenFileNameW(&ofn); + } else { + r = GetSaveFileNameW(&ofn); + } EnableWindow(TextWnd, true); EnableWindow(GraphicsWnd, true); SetForegroundWindow(GraphicsWnd); - if(r) filename = filenameC; + if(r) filename = Narrow(filenameC); return r ? true : false; } +bool SolveSpace::GetOpenFile(std::string &filename, + const char *defExtension, const char *selPattern) +{ + return OpenSaveFile(true, filename, defExtension, selPattern); +} + bool SolveSpace::GetSaveFile(std::string &filename, const char *defExtension, const char *selPattern) { - // See GetOpenFile - char filenameC[0xFFFF] = {}; - strncpy(filenameC, filename.c_str(), sizeof(filenameC) - 1); - - OPENFILENAME ofn = {}; - ofn.lStructSize = sizeof(ofn); - ofn.hInstance = Instance; - ofn.hwndOwner = GraphicsWnd; - ofn.lpstrFilter = selPattern; - ofn.lpstrDefExt = defExtension; - ofn.lpstrFile = filenameC; - ofn.nMaxFile = sizeof(filenameC); - ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; - - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - BOOL r = GetSaveFileName(&ofn); - - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - - if(r) filename = filenameC; - return r ? true : false; + return OpenSaveFile(false, filename, defExtension, selPattern); } int SolveSpace::SaveFileYesNoCancel(void) @@ -1026,9 +1029,9 @@ int SolveSpace::SaveFileYesNoCancel(void) EnableWindow(GraphicsWnd, false); EnableWindow(TextWnd, false); - int r = MessageBox(GraphicsWnd, - "The program has changed since it was last saved.\r\n\r\n" - "Do you want to save the changes?", "SolveSpace", + int r = MessageBoxW(GraphicsWnd, + L"The program has changed since it was last saved.\r\n\r\n" + L"Do you want to save the changes?", L"SolveSpace", MB_YESNOCANCEL | MB_ICONWARNING); EnableWindow(TextWnd, true); @@ -1050,9 +1053,9 @@ int SolveSpace::LoadAutosaveYesNo(void) EnableWindow(GraphicsWnd, false); EnableWindow(TextWnd, false); - int r = MessageBox(GraphicsWnd, - "An autosave file is availible for this project.\r\n\r\n" - "Do you want to load the autosave file instead?", "SolveSpace", + int r = MessageBoxW(GraphicsWnd, + L"An autosave file is availible for this project.\r\n\r\n" + L"Do you want to load the autosave file instead?", L"SolveSpace", MB_YESNO | MB_ICONWARNING); EnableWindow(TextWnd, true); @@ -1069,18 +1072,18 @@ int SolveSpace::LoadAutosaveYesNo(void) void SolveSpace::LoadAllFontFiles(void) { - std::string fontsDir(MAX_PATH, '\0'); - fontsDir.resize(GetWindowsDirectory(&fontsDir[0], fontsDir.length())); - fontsDir += "\\fonts\\"; + std::wstring fontsDir(MAX_PATH, '\0'); + fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); + fontsDir += L"\\fonts\\"; WIN32_FIND_DATA wfd; - HANDLE h = FindFirstFile((fontsDir + "*.ttf").c_str(), &wfd); + HANDLE h = FindFirstFileW((fontsDir + L"*.ttf").c_str(), &wfd); while(h != INVALID_HANDLE_VALUE) { TtfFont tf = {}; - tf.fontFile = fontsDir + wfd.cFileName; + tf.fontFile = Narrow(fontsDir) + Narrow(wfd.cFileName); SS.fonts.l.Add(&tf); - if(!FindNextFile(h, &wfd)) break; + if(!FindNextFileW(h, &wfd)) break; } } @@ -1128,11 +1131,11 @@ static void DoRecent(HMENU m, int base) int i, c = 0; for(i = 0; i < MAX_RECENT; i++) { if(!RecentFile[i].empty()) { - AppendMenu(m, MF_STRING, base + i, RecentFile[i].c_str()); + AppendMenuW(m, MF_STRING, base + i, Widen(RecentFile[i]).c_str()); c++; } } - if(c == 0) AppendMenu(m, MF_STRING | MF_GRAYED, 0, "(no recent files)"); + if(c == 0) AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"(no recent files)"); } void SolveSpace::RefreshRecentMenus(void) { @@ -1160,23 +1163,23 @@ HMENU CreateGraphicsWindowMenus(void) if(SS.GW.menu[i].level == 0) { m = CreateMenu(); - AppendMenu(top, MF_STRING | MF_POPUP, (UINT_PTR)m, label); + AppendMenuW(top, MF_STRING | MF_POPUP, (UINT_PTR)m, Widen(label).c_str()); if(subMenu >= (int)arraylen(SubMenus)) oops(); SubMenus[subMenu] = m; subMenu++; } else if(SS.GW.menu[i].level == 1) { if(SS.GW.menu[i].id == GraphicsWindow::MNU_OPEN_RECENT) { RecentOpenMenu = CreateMenu(); - AppendMenu(m, MF_STRING | MF_POPUP, - (UINT_PTR)RecentOpenMenu, label); + AppendMenuW(m, MF_STRING | MF_POPUP, + (UINT_PTR)RecentOpenMenu, Widen(label).c_str()); } else if(SS.GW.menu[i].id == GraphicsWindow::MNU_GROUP_RECENT) { RecentImportMenu = CreateMenu(); - AppendMenu(m, MF_STRING | MF_POPUP, - (UINT_PTR)RecentImportMenu, label); + AppendMenuW(m, MF_STRING | MF_POPUP, + (UINT_PTR)RecentImportMenu, Widen(label).c_str()); } else if(SS.GW.menu[i].label) { - AppendMenu(m, MF_STRING, SS.GW.menu[i].id, label); + AppendMenuW(m, MF_STRING, SS.GW.menu[i].id, Widen(label).c_str()); } else { - AppendMenu(m, MF_SEPARATOR, SS.GW.menu[i].id, ""); + AppendMenuW(m, MF_SEPARATOR, SS.GW.menu[i].id, L""); } } else oops(); } @@ -1196,7 +1199,7 @@ static void CreateMainWindows(void) CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC)GraphicsWndProc; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); - wc.lpszClassName = "GraphicsWnd"; + wc.lpszClassName = L"GraphicsWnd"; wc.lpszMenuName = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), @@ -1206,14 +1209,14 @@ static void CreateMainWindows(void) if(!RegisterClassEx(&wc)) oops(); HMENU top = CreateGraphicsWindowMenus(); - GraphicsWnd = CreateWindowEx(0, "GraphicsWnd", - "SolveSpace (not yet saved)", + GraphicsWnd = CreateWindowExW(0, L"GraphicsWnd", + L"SolveSpace (not yet saved)", WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX | WS_CLIPSIBLINGS, 50, 50, 900, 600, NULL, top, Instance, NULL); if(!GraphicsWnd) oops(); - GraphicsEditControl = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, "", + GraphicsEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, 50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL); SendMessage(GraphicsEditControl, WM_SETFONT, (WPARAM)FixedFont, true); @@ -1223,24 +1226,24 @@ static void CreateMainWindows(void) wc.style &= ~CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC)TextWndProc; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc.lpszClassName = "TextWnd"; + wc.lpszClassName = L"TextWnd"; wc.hCursor = NULL; if(!RegisterClassEx(&wc)) oops(); // We get the desired Alt+Tab behaviour by specifying that the text // window is a child of the graphics window. - TextWnd = CreateWindowEx(0, - "TextWnd", "SolveSpace - Browser", WS_THICKFRAME | WS_CLIPCHILDREN, + TextWnd = CreateWindowExW(0, + L"TextWnd", L"SolveSpace - Browser", WS_THICKFRAME | WS_CLIPCHILDREN, 650, 500, 420, 300, GraphicsWnd, (HMENU)NULL, Instance, NULL); if(!TextWnd) oops(); - TextWndScrollBar = CreateWindowEx(0, WC_SCROLLBAR, "", WS_CHILD | + TextWndScrollBar = CreateWindowExW(0, WC_SCROLLBAR, L"", WS_CHILD | SBS_VERT | SBS_LEFTALIGN | WS_VISIBLE | WS_CLIPSIBLINGS, 200, 100, 100, 100, TextWnd, NULL, Instance, NULL); // Force the scrollbar to get resized to the window, TextWndProc(TextWnd, WM_SIZE, 0, 0); - TextEditControl = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, "", + TextEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, 50, 50, 100, 21, TextWnd, NULL, Instance, NULL); SendMessage(TextEditControl, WM_SETFONT, (WPARAM)FixedFont, true); @@ -1304,10 +1307,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, InitCommonControls(); // A monospaced font - FixedFont = CreateFont(SS.TW.CHAR_HEIGHT, SS.TW.CHAR_WIDTH, 0, 0, + FixedFont = CreateFontW(SS.TW.CHAR_HEIGHT, SS.TW.CHAR_WIDTH, 0, 0, FW_REGULAR, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, - DEFAULT_QUALITY, FF_DONTCARE, "Lucida Console"); + DEFAULT_QUALITY, FF_DONTCARE, L"Lucida Console"); if(!FixedFont) FixedFont = (HFONT)GetStockObject(SYSTEM_FONT); @@ -1331,23 +1334,29 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, // Create the heaps for all dynamic memory (AllocTemporary, MemAlloc) InitHeaps(); + // Pull out the Unicode command line. + int argc; + LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc); + // A filename may have been specified on the command line; if so, then // strip any quotation marks, and make it absolute. - std::string filename = lpCmdLine; - size_t pos; - pos = filename.find_last_not_of("\""); - if(pos != std::string::npos) - filename.erase(pos + 1); - pos = filename.find_first_not_of(" \""); - if(pos != std::string::npos) - filename.erase(0, pos); - if(!filename.empty()) - filename = GetAbsoluteFilename(filename); + std::wstring filenameRel, filename; + if(argc >= 2) { + filenameRel = argv[1]; + if(filenameRel[0] == L'\"' && filenameRel[filenameRel.length() - 1] == L'\"') { + filenameRel.erase(0, 1); + filenameRel.erase(filenameRel.length() - 1, 1); + } + + DWORD len = GetFullPathNameW(&filenameRel[0], 0, NULL, NULL); + filename.resize(len - 1); + GetFullPathNameW(&filenameRel[0], len, &filename[0], NULL); + } #ifdef HAVE_SPACEWARE // Initialize the SpaceBall, if present. Test if the driver is running // first, to avoid a long timeout if it's not. - HWND swdc = FindWindow("SpaceWare Driver Class", NULL); + HWND swdc = FindWindowW(L"SpaceWare Driver Class", NULL); if(swdc != NULL) { SiOpenData sod; SiInitialize(); @@ -1361,7 +1370,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, // Call in to the platform-independent code, and let them do their init SS.Init(); if(!filename.empty()) - SS.OpenFile(filename); + SS.OpenFile(Narrow(filename)); // And now it's the message loop. All calls in to the rest of the code // will be from the wndprocs. diff --git a/src/win32/w32util.cpp b/src/win32/w32util.cpp index 50518f97..170c6a51 100644 --- a/src/win32/w32util.cpp +++ b/src/win32/w32util.cpp @@ -19,7 +19,52 @@ void dbp(const char *str, ...) _vsnprintf(buf, sizeof(buf), str, f); va_end(f); - OutputDebugString(buf); + // The native version of OutputDebugString, unlike most others, + // is OutputDebugStringA. + OutputDebugStringA(buf); +} + +std::string Narrow(const wchar_t *in) +{ + std::string out; + DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); + out.resize(len - 1); + if(!WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL)) + oops(); + return out; +} + +std::string Narrow(const std::wstring &in) +{ + if(in == L"") return ""; + + std::string out; + out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(), NULL, 0, NULL, NULL)); + if(!WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(), + &out[0], out.length(), NULL, NULL)) + oops(); + return out; +} + +std::wstring Widen(const char *in) +{ + std::wstring out; + DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); + out.resize(len - 1); + if(!MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len)) + oops(); + return out; +} + +std::wstring Widen(const std::string &in) +{ + if(in == "") return L""; + + std::wstring out; + out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), NULL, 0)); + if(!MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), &out[0], out.length())) + oops(); + return out; } //-----------------------------------------------------------------------------