2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file win32.cpp Implementation of MS Windows system calls */
10 #include "../../stdafx.h"
11 #include "../../debug.h"
12 #include "../../gfx_func.h"
13 #include "../../textbuf_gui.h"
14 #include "../../fileio_func.h"
19 #define NO_SHOBJIDL_SORTDIRECTION // Avoid multiple definition of SORT_ASCENDING
20 #include <shlobj.h> /* SHGetFolderPath */
24 #include "../../fios.h"
25 #include "../../core/alloc_func.hpp"
26 #include "../../string_func.h"
28 #include "../../language.h"
29 #include "../../thread.h"
30 #include "../../library_loader.h"
32 #include "../../safeguards.h"
34 static bool _has_console
;
35 static bool _cursor_disable
= true;
36 static bool _cursor_visible
= true;
38 bool MyShowCursor(bool show
, bool toggle
)
40 if (toggle
) _cursor_disable
= !_cursor_disable
;
41 if (_cursor_disable
) return show
;
42 if (_cursor_visible
== show
) return show
;
44 _cursor_visible
= show
;
50 void ShowOSErrorBox(const char *buf
, bool)
53 MessageBox(GetActiveWindow(), OTTD2FS(buf
).c_str(), L
"Error!", MB_ICONSTOP
| MB_TASKMODAL
);
56 void OSOpenBrowser(const std::string
&url
)
58 ShellExecute(GetActiveWindow(), L
"open", OTTD2FS(url
).c_str(), nullptr, nullptr, SW_SHOWNORMAL
);
61 bool FiosIsRoot(const std::string
&file
)
63 return file
.size() == 3; // C:\...
66 void FiosGetDrives(FileList
&file_list
)
71 GetLogicalDriveStrings(static_cast<DWORD
>(std::size(drives
)), drives
);
72 for (s
= drives
; *s
!= '\0';) {
73 FiosItem
*fios
= &file_list
.emplace_back();
74 fios
->type
= FIOS_TYPE_DRIVE
;
76 fios
->name
+= (char)(s
[0] & 0xFF);
78 fios
->title
= fios
->name
;
79 while (*s
++ != '\0') { /* Nothing */ }
83 bool FiosIsHiddenFile(const std::filesystem::path
&path
)
85 UINT sem
= SetErrorMode(SEM_FAILCRITICALERRORS
); // Disable 'no-disk' message box.
87 DWORD attributes
= GetFileAttributes(path
.c_str());
89 SetErrorMode(sem
); // Restore previous setting.
91 return (attributes
& (FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_SYSTEM
)) != 0;
94 std::optional
<uint64_t> FiosGetDiskFreeSpace(const std::string
&path
)
96 UINT sem
= SetErrorMode(SEM_FAILCRITICALERRORS
); // disable 'no-disk' message box
98 ULARGE_INTEGER bytes_free
;
99 bool retval
= GetDiskFreeSpaceEx(OTTD2FS(path
).c_str(), &bytes_free
, nullptr, nullptr);
101 SetErrorMode(sem
); // reset previous setting
103 if (retval
) return bytes_free
.QuadPart
;
110 CONSOLE_SCREEN_BUFFER_INFO coninfo
;
112 if (_has_console
) return;
115 if (!AllocConsole()) return;
117 hand
= GetStdHandle(STD_OUTPUT_HANDLE
);
118 GetConsoleScreenBufferInfo(hand
, &coninfo
);
119 coninfo
.dwSize
.Y
= 500;
120 SetConsoleScreenBufferSize(hand
, coninfo
.dwSize
);
122 /* redirect unbuffered STDIN, STDOUT, STDERR to the console */
123 #if !defined(__CYGWIN__)
125 /* Check if we can open a handle to STDOUT. */
126 int fd
= _open_osfhandle((intptr_t)hand
, _O_TEXT
);
128 /* Free everything related to the console. */
130 _has_console
= false;
134 ShowInfo("Unable to open an output handle to the console. Check known-bugs.txt for details.");
138 #if defined(_MSC_VER)
139 freopen("CONOUT$", "a", stdout
);
140 freopen("CONIN$", "r", stdin
);
141 freopen("CONOUT$", "a", stderr
);
143 *stdout
= *_fdopen(fd
, "w");
144 *stdin
= *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE
), _O_TEXT
), "r" );
145 *stderr
= *_fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE
), _O_TEXT
), "w" );
149 /* open_osfhandle is not in cygwin */
150 *stdout
= *fdopen(1, "w" );
151 *stdin
= *fdopen(0, "r" );
152 *stderr
= *fdopen(2, "w" );
155 setvbuf(stdin
, nullptr, _IONBF
, 0);
156 setvbuf(stdout
, nullptr, _IONBF
, 0);
157 setvbuf(stderr
, nullptr, _IONBF
, 0);
161 * Replace linefeeds with carriage-return and linefeed.
162 * @param msg string with LF linefeeds.
163 * @return String with Lf linefeeds converted to CrLf linefeeds.
165 static std::string
ConvertLfToCrLf(std::string_view msg
)
171 while ((next
= msg
.find('\n', last
)) != std::string_view::npos
) {
172 output
+= msg
.substr(last
, next
- last
);
176 output
+= msg
.substr(last
);
181 /** Callback function to handle the window */
182 static INT_PTR CALLBACK
HelpDialogFunc(HWND wnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
185 case WM_INITDIALOG
: {
186 std::wstring
&msg
= *reinterpret_cast<std::wstring
*>(lParam
);
187 SetDlgItemText(wnd
, 11, msg
.c_str());
188 SendDlgItemMessage(wnd
, 11, WM_SETFONT
, (WPARAM
)GetStockObject(ANSI_FIXED_FONT
), FALSE
);
192 if (wParam
== 12) ExitProcess(0);
201 void ShowInfoI(const std::string
&str
)
204 fmt::print(stderr
, "{}\n", str
);
208 _left_button_clicked
= _left_button_down
= false;
210 old
= MyShowCursor(true);
211 std::wstring native_str
= OTTD2FS(ConvertLfToCrLf(str
));
212 if (native_str
.size() > 2048) {
213 /* The minimum length of the help message is 2048. Other messages sent via
214 * ShowInfo are much shorter, or so long they need this way of displaying
216 DialogBoxParam(GetModuleHandle(nullptr), MAKEINTRESOURCE(101), nullptr, HelpDialogFunc
, reinterpret_cast<LPARAM
>(&native_str
));
218 MessageBox(GetActiveWindow(), native_str
.c_str(), L
"OpenTTD", MB_ICONINFORMATION
| MB_OK
);
224 char *getcwd(char *buf
, size_t size
)
226 wchar_t path
[MAX_PATH
];
227 GetCurrentDirectory(MAX_PATH
- 1, path
);
228 convert_from_fs(path
, {buf
, size
});
232 extern std::string _config_file
;
234 void DetermineBasePaths(const char *exe
)
236 extern std::array
<std::string
, NUM_SEARCHPATHS
> _searchpaths
;
238 wchar_t path
[MAX_PATH
];
239 #ifdef WITH_PERSONAL_DIR
240 if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PERSONAL
, nullptr, SHGFP_TYPE_CURRENT
, path
))) {
241 std::string
tmp(FS2OTTD(path
));
242 AppendPathSeparator(tmp
);
244 AppendPathSeparator(tmp
);
245 _searchpaths
[SP_PERSONAL_DIR
] = tmp
;
247 tmp
+= "content_download";
248 AppendPathSeparator(tmp
);
249 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
] = tmp
;
251 _searchpaths
[SP_PERSONAL_DIR
].clear();
254 if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_DOCUMENTS
, nullptr, SHGFP_TYPE_CURRENT
, path
))) {
255 std::string
tmp(FS2OTTD(path
));
256 AppendPathSeparator(tmp
);
258 AppendPathSeparator(tmp
);
259 _searchpaths
[SP_SHARED_DIR
] = tmp
;
261 _searchpaths
[SP_SHARED_DIR
].clear();
264 _searchpaths
[SP_PERSONAL_DIR
].clear();
265 _searchpaths
[SP_SHARED_DIR
].clear();
268 if (_config_file
.empty()) {
270 getcwd(cwd
, lengthof(cwd
));
271 std::string
cwd_s(cwd
);
272 AppendPathSeparator(cwd_s
);
273 _searchpaths
[SP_WORKING_DIR
] = cwd_s
;
275 /* Use the folder of the config file as working directory. */
276 wchar_t config_dir
[MAX_PATH
];
277 convert_to_fs(_config_file
, path
);
278 if (!GetFullPathName(path
, static_cast<DWORD
>(std::size(config_dir
)), config_dir
, nullptr)) {
279 Debug(misc
, 0, "GetFullPathName failed ({})", GetLastError());
280 _searchpaths
[SP_WORKING_DIR
].clear();
282 std::string
tmp(FS2OTTD(config_dir
));
283 auto pos
= tmp
.find_last_of(PATHSEPCHAR
);
284 if (pos
!= std::string::npos
) tmp
.erase(pos
+ 1);
286 _searchpaths
[SP_WORKING_DIR
] = tmp
;
290 if (!GetModuleFileName(nullptr, path
, static_cast<DWORD
>(std::size(path
)))) {
291 Debug(misc
, 0, "GetModuleFileName failed ({})", GetLastError());
292 _searchpaths
[SP_BINARY_DIR
].clear();
294 wchar_t exec_dir
[MAX_PATH
];
295 convert_to_fs(exe
, path
);
296 if (!GetFullPathName(path
, static_cast<DWORD
>(std::size(exec_dir
)), exec_dir
, nullptr)) {
297 Debug(misc
, 0, "GetFullPathName failed ({})", GetLastError());
298 _searchpaths
[SP_BINARY_DIR
].clear();
300 std::string
tmp(FS2OTTD(exec_dir
));
301 auto pos
= tmp
.find_last_of(PATHSEPCHAR
);
302 if (pos
!= std::string::npos
) tmp
.erase(pos
+ 1);
304 _searchpaths
[SP_BINARY_DIR
] = tmp
;
308 _searchpaths
[SP_INSTALLATION_DIR
].clear();
309 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
].clear();
313 std::optional
<std::string
> GetClipboardContents()
315 if (!IsClipboardFormatAvailable(CF_UNICODETEXT
)) return std::nullopt
;
317 OpenClipboard(nullptr);
318 HGLOBAL cbuf
= GetClipboardData(CF_UNICODETEXT
);
320 std::string result
= FS2OTTD(static_cast<LPCWSTR
>(GlobalLock(cbuf
)));
324 if (result
.empty()) return std::nullopt
;
330 * Convert to OpenTTD's encoding from a wide string.
331 * OpenTTD internal encoding is UTF8.
332 * @param name valid string that will be converted (local, or wide)
333 * @return converted string; if failed string is of zero-length
334 * @see the current code-page comes from video\win32_v.cpp, event-notification
337 std::string
FS2OTTD(const std::wstring
&name
)
339 int name_len
= (name
.length() >= INT_MAX
) ? INT_MAX
: (int)name
.length();
340 int len
= WideCharToMultiByte(CP_UTF8
, 0, name
.c_str(), name_len
, nullptr, 0, nullptr, nullptr);
341 if (len
<= 0) return std::string();
342 std::string
utf8_buf(len
, '\0'); // len includes terminating null
343 WideCharToMultiByte(CP_UTF8
, 0, name
.c_str(), name_len
, utf8_buf
.data(), len
, nullptr, nullptr);
348 * Convert from OpenTTD's encoding to a wide string.
349 * OpenTTD internal encoding is UTF8.
350 * @param name valid string that will be converted (UTF8)
351 * @param console_cp convert to the console encoding instead of the normal system encoding.
352 * @return converted string; if failed string is of zero-length
354 std::wstring
OTTD2FS(const std::string
&name
)
356 int name_len
= (name
.length() >= INT_MAX
) ? INT_MAX
: (int)name
.length();
357 int len
= MultiByteToWideChar(CP_UTF8
, 0, name
.c_str(), name_len
, nullptr, 0);
358 if (len
<= 0) return std::wstring();
359 std::wstring
system_buf(len
, L
'\0'); // len includes terminating null
360 MultiByteToWideChar(CP_UTF8
, 0, name
.c_str(), name_len
, system_buf
.data(), len
);
366 * Convert to OpenTTD's encoding from that of the environment in
367 * UNICODE. OpenTTD encoding is UTF8, local is wide.
368 * @param src wide string that will be converted
369 * @param dst_buf span of valid char buffer that will receive the converted string
370 * @return pointer to dst_buf. If conversion fails the string is of zero-length
372 char *convert_from_fs(const std::wstring_view src
, std::span
<char> dst_buf
)
374 /* Convert UTF-16 string to UTF-8. */
375 int len
= WideCharToMultiByte(CP_UTF8
, 0, src
.data(), static_cast<int>(src
.size()), dst_buf
.data(), static_cast<int>(dst_buf
.size() - 1U), nullptr, nullptr);
378 return dst_buf
.data();
383 * Convert from OpenTTD's encoding to that of the environment in
384 * UNICODE. OpenTTD encoding is UTF8, local is wide.
385 * @param src string that will be converted
386 * @param dst_buf span of valid wide-char buffer that will receive the converted string
387 * @return pointer to dst_buf. If conversion fails the string is of zero-length
389 wchar_t *convert_to_fs(const std::string_view src
, std::span
<wchar_t> dst_buf
)
391 int len
= MultiByteToWideChar(CP_UTF8
, 0, src
.data(), static_cast<int>(src
.size()), dst_buf
.data(), static_cast<int>(dst_buf
.size() - 1U));
394 return dst_buf
.data();
397 /** Determine the current user's locale. */
398 const char *GetCurrentLocale(const char *)
400 const LANGID userUiLang
= GetUserDefaultUILanguage();
401 const LCID userUiLocale
= MAKELCID(userUiLang
, SORT_DEFAULT
);
403 char lang
[9], country
[9];
404 if (GetLocaleInfoA(userUiLocale
, LOCALE_SISO639LANGNAME
, lang
, static_cast<int>(std::size(lang
))) == 0 ||
405 GetLocaleInfoA(userUiLocale
, LOCALE_SISO3166CTRYNAME
, country
, static_cast<int>(std::size(country
))) == 0) {
406 /* Unable to retrieve the locale. */
409 /* Format it as 'en_us'. */
410 static char retbuf
[6] = {lang
[0], lang
[1], '_', country
[0], country
[1], 0};
415 static WCHAR _cur_iso_locale
[16] = L
"";
417 void Win32SetCurrentLocaleName(std::string iso_code
)
419 /* Convert the iso code into the format that windows expects. */
420 if (iso_code
== "zh_TW") {
421 iso_code
= "zh-Hant";
422 } else if (iso_code
== "zh_CN") {
423 iso_code
= "zh-Hans";
425 /* Windows expects a '-' between language and country code, but we use a '_'. */
426 for (char &c
: iso_code
) {
427 if (c
== '_') c
= '-';
431 MultiByteToWideChar(CP_UTF8
, 0, iso_code
.c_str(), -1, _cur_iso_locale
, static_cast<int>(std::size(_cur_iso_locale
)));
434 int OTTDStringCompare(std::string_view s1
, std::string_view s2
)
436 typedef int (WINAPI
*PFNCOMPARESTRINGEX
)(LPCWSTR
, DWORD
, LPCWCH
, int, LPCWCH
, int, LPVOID
, LPVOID
, LPARAM
);
437 static PFNCOMPARESTRINGEX _CompareStringEx
= nullptr;
438 static bool first_time
= true;
440 #ifndef SORT_DIGITSASNUMBERS
441 # define SORT_DIGITSASNUMBERS 0x00000008 // use digits as numbers sort method
443 #ifndef LINGUISTIC_IGNORECASE
444 # define LINGUISTIC_IGNORECASE 0x00000010 // linguistically appropriate 'ignore case'
448 static LibraryLoader
_kernel32("Kernel32.dll");
449 _CompareStringEx
= _kernel32
.GetFunction("CompareStringEx");
453 if (_CompareStringEx
!= nullptr) {
454 /* CompareStringEx takes UTF-16 strings, even in ANSI-builds. */
455 int len_s1
= MultiByteToWideChar(CP_UTF8
, 0, s1
.data(), (int)s1
.size(), nullptr, 0);
456 int len_s2
= MultiByteToWideChar(CP_UTF8
, 0, s2
.data(), (int)s2
.size(), nullptr, 0);
458 if (len_s1
!= 0 && len_s2
!= 0) {
459 std::wstring
str_s1(len_s1
, L
'\0'); // len includes terminating null
460 std::wstring
str_s2(len_s2
, L
'\0');
462 MultiByteToWideChar(CP_UTF8
, 0, s1
.data(), (int)s1
.size(), str_s1
.data(), len_s1
);
463 MultiByteToWideChar(CP_UTF8
, 0, s2
.data(), (int)s2
.size(), str_s2
.data(), len_s2
);
465 int result
= _CompareStringEx(_cur_iso_locale
, LINGUISTIC_IGNORECASE
| SORT_DIGITSASNUMBERS
, str_s1
.c_str(), -1, str_s2
.c_str(), -1, nullptr, nullptr, 0);
466 if (result
!= 0) return result
;
470 wchar_t s1_buf
[512], s2_buf
[512];
471 convert_to_fs(s1
, s1_buf
);
472 convert_to_fs(s2
, s2_buf
);
474 return CompareString(MAKELCID(_current_language
->winlangid
, SORT_DEFAULT
), NORM_IGNORECASE
, s1_buf
, -1, s2_buf
, -1);
478 * Search if a string is contained in another string using the current locale.
480 * @param str String to search in.
481 * @param value String to search for.
482 * @param case_insensitive Search case-insensitive.
483 * @return 1 if value was found, 0 if it was not found, or -1 if not supported by the OS.
485 int Win32StringContains(const std::string_view str
, const std::string_view value
, bool case_insensitive
)
487 typedef int (WINAPI
*PFNFINDNLSSTRINGEX
)(LPCWSTR
, DWORD
, LPCWSTR
, int, LPCWSTR
, int, LPINT
, LPNLSVERSIONINFO
, LPVOID
, LPARAM
);
488 static PFNFINDNLSSTRINGEX _FindNLSStringEx
= nullptr;
489 static bool first_time
= true;
492 static LibraryLoader
_kernel32("Kernel32.dll");
493 _FindNLSStringEx
= _kernel32
.GetFunction("FindNLSStringEx");
497 if (_FindNLSStringEx
!= nullptr) {
498 int len_str
= MultiByteToWideChar(CP_UTF8
, 0, str
.data(), (int)str
.size(), nullptr, 0);
499 int len_value
= MultiByteToWideChar(CP_UTF8
, 0, value
.data(), (int)value
.size(), nullptr, 0);
501 if (len_str
!= 0 && len_value
!= 0) {
502 std::wstring
str_str(len_str
, L
'\0'); // len includes terminating null
503 std::wstring
str_value(len_value
, L
'\0');
505 MultiByteToWideChar(CP_UTF8
, 0, str
.data(), (int)str
.size(), str_str
.data(), len_str
);
506 MultiByteToWideChar(CP_UTF8
, 0, value
.data(), (int)value
.size(), str_value
.data(), len_value
);
508 return _FindNLSStringEx(_cur_iso_locale
, FIND_FROMSTART
| (case_insensitive
? LINGUISTIC_IGNORECASE
: 0), str_str
.data(), -1, str_value
.data(), -1, nullptr, nullptr, nullptr, 0) >= 0 ? 1 : 0;
512 return -1; // Failure indication.
516 /* Based on code from MSDN: https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx */
517 const DWORD MS_VC_EXCEPTION
= 0x406D1388;
519 PACK_N(struct THREADNAME_INFO
{
520 DWORD dwType
; ///< Must be 0x1000.
521 LPCSTR szName
; ///< Pointer to name (in user addr space).
522 DWORD dwThreadID
; ///< Thread ID (-1=caller thread).
523 DWORD dwFlags
; ///< Reserved for future use, must be zero.
527 * Signal thread name to any attached debuggers.
529 void SetCurrentThreadName(const char *threadName
)
531 THREADNAME_INFO info
;
532 info
.dwType
= 0x1000;
533 info
.szName
= threadName
;
534 info
.dwThreadID
= -1;
537 #pragma warning(push)
538 #pragma warning(disable: 6320 6322)
540 RaiseException(MS_VC_EXCEPTION
, 0, sizeof(info
) / sizeof(ULONG_PTR
), (ULONG_PTR
*)&info
);
541 } __except (EXCEPTION_EXECUTE_HANDLER
) {
546 void SetCurrentThreadName(const char *) {}