diff options
Diffstat (limited to 'platform/windows')
28 files changed, 1821 insertions, 807 deletions
diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 76234c3065..7e412b140f 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -13,6 +13,7 @@ common_win = [ "display_server_windows.cpp", "key_mapping_windows.cpp", "joypad_windows.cpp", + "tts_windows.cpp", "windows_terminal_logger.cpp", "vulkan_context_win.cpp", "gl_manager_windows.cpp", diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 1b4dae207f..6ce10e6f0f 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,8 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/string/print_string.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -81,8 +81,9 @@ public: std::string name() { return std::string(sym->Name); } std::string undecorated_name() { - if (*sym->Name == '\0') + if (*sym->Name == '\0') { return "<couldn't map PC to fn name>"; + } std::vector<char> und_name(max_name_len); UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE); return std::string(&und_name[0], strlen(&und_name[0])); @@ -129,15 +130,32 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { return EXCEPTION_CONTINUE_SEARCH; } - fprintf(stderr, "\n================================================================\n"); - fprintf(stderr, "%s: Program crashed\n", __FUNCTION__); + String msg; + const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + if (proj_settings) { + msg = proj_settings->get("debug/settings/crash_handler/message"); + } - if (OS::get_singleton()->get_main_loop()) + // Tell MainLoop about the crash. This can be handled by users too in Node. + if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + } + + print_error("\n================================================================"); + print_error(vformat("%s: Program crashed", __FUNCTION__)); + + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).is_empty()) { + print_error(vformat("Engine version: %s", VERSION_FULL_NAME)); + } else { + print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH)); + } + print_error(vformat("Dumping the backtrace. %s", msg)); // Load the symbols: - if (!SymInitialize(process, nullptr, false)) + if (!SymInitialize(process, nullptr, false)) { return EXCEPTION_CONTINUE_SEARCH; + } SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); @@ -172,20 +190,6 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { IMAGE_NT_HEADERS *h = ImageNtHeader(base); DWORD image_type = h->FileHeader.Machine; - String msg; - const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); - if (proj_settings) { - msg = proj_settings->get("debug/settings/crash_handler/message"); - } - - // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); - } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); - } - fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); - int n = 0; do { if (skip_first) { @@ -194,22 +198,25 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { if (frame.AddrPC.Offset != 0) { std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name(); - if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line)) - fprintf(stderr, "[%d] %s (%s:%d)\n", n, fnName.c_str(), line.FileName, line.LineNumber); - else - fprintf(stderr, "[%d] %s\n", n, fnName.c_str()); - } else - fprintf(stderr, "[%d] ???\n", n); + if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line)) { + print_error(vformat("[%d] %s (%s:%d)", n, fnName.c_str(), (char *)line.FileName, (int)line.LineNumber)); + } else { + print_error(vformat("[%d] %s", n, fnName.c_str())); + } + } else { + print_error(vformat("[%d] ???", n)); + } n++; } - if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) + if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { break; + } } while (frame.AddrReturn.Offset != 0 && n < 256); - fprintf(stderr, "-- END OF BACKTRACE --\n"); - fprintf(stderr, "================================================================\n"); + print_error("-- END OF BACKTRACE --"); + print_error("================================================================"); SymCleanup(process); @@ -226,8 +233,9 @@ CrashHandler::~CrashHandler() { } void CrashHandler::disable() { - if (disabled) + if (disabled) { return; + } disabled = true; } diff --git a/platform/windows/crash_handler_windows.h b/platform/windows/crash_handler_windows.h index 5cdc6d3e05..ec8de3ffab 100644 --- a/platform/windows/crash_handler_windows.h +++ b/platform/windows/crash_handler_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/detect.py b/platform/windows/detect.py index e9ecc99ef5..0b18fb74fb 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -65,7 +65,7 @@ def get_opts(): # Vista support dropped after EOL due to GH-10243 ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"), BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), - EnumVariable("windows_subsystem", "Windows subsystem", "default", ("default", "console", "gui")), + EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None), BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False), @@ -178,15 +178,6 @@ def configure_msvc(env, manual_msvc_config): # Build type - if env["tests"]: - env["windows_subsystem"] = "console" - elif env["windows_subsystem"] == "default": - # Default means we use console for debug, gui for release. - if "debug" in env["target"]: - env["windows_subsystem"] = "console" - else: - env["windows_subsystem"] = "gui" - if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) @@ -261,6 +252,7 @@ def configure_msvc(env, manual_msvc_config): "kernel32", "ole32", "oleaut32", + "sapi", "user32", "gdi32", "IPHLPAPI", @@ -326,15 +318,6 @@ def configure_mingw(env): ## Build type - if env["tests"]: - env["windows_subsystem"] = "console" - elif env["windows_subsystem"] == "default": - # Default means we use console for debug, gui for release. - if "debug" in env["target"]: - env["windows_subsystem"] = "console" - else: - env["windows_subsystem"] = "gui" - if env["target"] == "release": env.Append(CCFLAGS=["-msse2"]) @@ -444,6 +427,7 @@ def configure_mingw(env): "ws2_32", "kernel32", "oleaut32", + "sapi", "dinput8", "dxguid", "ksuser", diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 899cf5dfad..93cab85441 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -59,6 +59,15 @@ static String format_error_message(DWORD id) { return msg; } +static void track_mouse_leave_event(HWND hWnd) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&tme); +} + bool DisplayServerWindows::has_feature(Feature p_feature) const { switch (p_feature) { case FEATURE_SUBWINDOWS: @@ -68,7 +77,6 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_CLIPBOARD: case FEATURE_CURSOR_SHAPE: case FEATURE_CUSTOM_CURSOR_SHAPE: - case FEATURE_CONSOLE_WINDOW: case FEATURE_IME: case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_HIDPI: @@ -76,6 +84,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_NATIVE_ICON: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: + case FEATURE_TEXT_TO_SPEECH: return true; default: return false; @@ -89,7 +98,10 @@ String DisplayServerWindows::get_name() const { void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). - WindowData &wd = windows[MAIN_WINDOW_ID]; + + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + + WindowData &wd = windows[window_id]; RECT clipRect; GetClientRect(wd.hWnd, &clipRect); @@ -122,6 +134,41 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { } } +bool DisplayServerWindows::tts_is_speaking() const { + ERR_FAIL_COND_V(!tts, false); + return tts->is_speaking(); +} + +bool DisplayServerWindows::tts_is_paused() const { + ERR_FAIL_COND_V(!tts, false); + return tts->is_paused(); +} + +Array DisplayServerWindows::tts_get_voices() const { + ERR_FAIL_COND_V(!tts, Array()); + return tts->get_voices(); +} + +void DisplayServerWindows::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!tts); + tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt); +} + +void DisplayServerWindows::tts_pause() { + ERR_FAIL_COND(!tts); + tts->pause(); +} + +void DisplayServerWindows::tts_resume() { + ERR_FAIL_COND(!tts); + tts->resume(); +} + +void DisplayServerWindows::tts_stop() { + ERR_FAIL_COND(!tts); + tts->stop(); +} + void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ @@ -139,7 +186,7 @@ DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const { return mouse_mode; } -void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { +void DisplayServerWindows::warp_mouse(const Point2i &p_position) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { @@ -147,12 +194,12 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { } if (mouse_mode == MOUSE_MODE_CAPTURED) { - old_x = p_to.x; - old_y = p_to.y; + old_x = p_position.x; + old_y = p_position.y; } else { POINT p; - p.x = p_to.x; - p.y = p_to.y; + p.x = p_position.x; + p.y = p_position.y; ClientToScreen(windows[last_focused_window].hWnd, &p); SetCursorPos(p.x, p.y); @@ -220,7 +267,7 @@ String DisplayServerWindows::clipboard_get() const { String ret; if (!OpenClipboard(windows[last_focused_window].hWnd)) { ERR_FAIL_V_MSG("", "Unable to open clipboard."); - }; + } if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); @@ -229,8 +276,8 @@ String DisplayServerWindows::clipboard_get() const { if (ptr != nullptr) { ret = String::utf16((const char16_t *)ptr); GlobalUnlock(mem); - }; - }; + } + } } else if (IsClipboardFormatAvailable(CF_TEXT)) { HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); @@ -239,9 +286,9 @@ String DisplayServerWindows::clipboard_get() const { if (ptr != nullptr) { ret.parse_utf8((const char *)ptr); GlobalUnlock(mem); - }; - }; - }; + } + } + } CloseClipboard(); @@ -315,6 +362,12 @@ typedef struct { Rect2i rect; } EnumRectData; +typedef struct { + int count; + int screen; + float rate; +} EnumRefreshRateData; + static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumSizeData *data = (EnumSizeData *)dwData; if (data->count == data->screen) { @@ -352,6 +405,26 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito return TRUE; } +static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + EnumRefreshRateData *data = (EnumRefreshRateData *)dwData; + if (data->count == data->screen) { + MONITORINFOEXW minfo; + memset(&minfo, 0, sizeof(minfo)); + minfo.cbSize = sizeof(minfo); + GetMonitorInfoW(hMonitor, &minfo); + + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + + data->rate = dm.dmDisplayFrequency; + } + + data->count++; + return TRUE; +} + Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -385,16 +458,16 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : nullptr; if ((Shcore == nullptr) || (getDPIForMonitor == nullptr)) { - if (Shcore) + if (Shcore) { FreeLibrary(Shcore); + } Shcore = (HMODULE)INVALID_HANDLE_VALUE; } } UINT x = 0, y = 0; - HRESULT hr = E_FAIL; if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) { - hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y); + HRESULT hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y); if (SUCCEEDED(hr) && (x > 0) && (y > 0)) { dpiX = (int)x; dpiY = (int)y; @@ -435,6 +508,13 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const { EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; } +float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + EnumRefreshRateData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); + return data.rate; +} bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { #ifndef _MSC_VER @@ -495,13 +575,25 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { wd.borderless = true; } - if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.always_on_top = true; } if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { wd.no_focus = true; } + if (p_flags & WINDOW_FLAG_POPUP_BIT) { + wd.is_popup = true; + } + // Inherit icons from MAIN_WINDOW for all sub windows. + HICON mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_SMALL, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon); + } + mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_BIG, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon); + } return window_id; } @@ -509,13 +601,14 @@ void DisplayServerWindows::show_window(WindowID p_id) { ERR_FAIL_COND(!windows.has(p_id)); WindowData &wd = windows[p_id]; + popup_open(p_id); if (p_id != MAIN_WINDOW_ID) { _update_window_style(p_id); } - ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. - if (!wd.no_focus) { + ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. + if (!wd.no_focus && !wd.is_popup) { SetForegroundWindow(wd.hWnd); // Slightly higher priority. SetFocus(wd.hWnd); // Set keyboard focus. } @@ -527,6 +620,8 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted."); + popup_close(p_window); + WindowData &wd = windows[p_window]; while (wd.transient_children.size()) { @@ -562,6 +657,24 @@ void DisplayServerWindows::gl_window_make_current(DisplayServer::WindowID p_wind #endif } +int64_t DisplayServerWindows::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].hWnd; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -666,8 +779,29 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_INDEX(p_screen, get_screen_count()); - Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); - window_set_position(ofs + screen_get_position(p_screen), p_window); + const WindowData &wd = windows[p_window]; + if (wd.fullscreen) { + int cs = window_get_current_screen(p_window); + if (cs == p_screen) { + return; + } + Point2 pos = screen_get_position(p_screen); + Size2 size = screen_get_size(p_screen); + + MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); + } else { + Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); + window_set_position(ofs + screen_get_position(p_screen), p_window); + } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { @@ -738,6 +872,23 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window _update_real_mouse_position(p_window); } +void DisplayServerWindows::window_set_exclusive(WindowID p_window, bool p_exclusive) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + if (wd.exclusive != p_exclusive) { + wd.exclusive = p_exclusive; + if (wd.transient_parent != INVALID_WINDOW_ID) { + if (wd.exclusive) { + WindowData &wd_parent = windows[wd.transient_parent]; + SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd); + } else { + SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr); + } + } + } +} + void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_parent) { _THREAD_SAFE_METHOD_ @@ -760,7 +911,9 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); - SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr); + if (wd_window.exclusive) { + SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr); + } } else { ERR_FAIL_COND(!windows.has(p_parent)); ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); @@ -769,7 +922,9 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa wd_window.transient_parent = p_parent; wd_parent.transient_children.insert(p_window); - SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd); + if (wd_window.exclusive) { + SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd); + } } } @@ -896,7 +1051,7 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -905,10 +1060,14 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { r_style_ex |= WS_EX_APPWINDOW; + r_style |= WS_VISIBLE; } if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. + if (p_fullscreen && p_multiwindow_fs) { + r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen. + } } else { if (p_resizable) { if (p_maximized) { @@ -920,14 +1079,17 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU; } } - if (!p_borderless) { - r_style |= WS_VISIBLE; - } if (p_no_activate_focus) { r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; } + + if (!p_borderless && !p_no_activate_focus) { + r_style |= WS_VISIBLE; + } + r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + r_style_ex |= WS_EX_ACCEPTFILES; } void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint) { @@ -939,12 +1101,16 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus || wd.is_popup, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); - SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | (wd.no_focus ? SWP_NOACTIVATE : 0)); + if (icon.is_valid()) { + set_icon(icon); + } + + SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | ((wd.no_focus || wd.is_popup) ? SWP_NOACTIVATE : 0)); if (p_repaint) { RECT rect; @@ -959,10 +1125,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) { + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { RECT rect; wd.fullscreen = false; + wd.multiwindow_fs = false; wd.maximized = wd.was_maximized; if (wd.pre_fs_valid) { @@ -1001,7 +1168,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.minimized = true; } - if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) { + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + wd.multiwindow_fs = false; + _update_window_style(p_window, false); + } else { + wd.multiwindow_fs = true; + _update_window_style(p_window, false); + } + + if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) { if (wd.minimized) { ShowWindow(wd.hWnd, SW_RESTORE); } @@ -1019,7 +1194,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.maximized = false; wd.minimized = false; - _update_window_style(false); + _update_window_style(p_window, false); MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); @@ -1030,6 +1205,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0); } } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const { @@ -1039,7 +1223,11 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo const WindowData &wd = windows[p_window]; if (wd.fullscreen) { - return WINDOW_MODE_FULLSCREEN; + if (wd.multiwindow_fs) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } } else if (wd.minimized) { return WINDOW_MODE_MINIMIZED; } else if (wd.maximized) { @@ -1073,6 +1261,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W wd.borderless = p_enabled; _update_window_style(p_window); _update_window_mouse_passthrough(p_window); + ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top"); @@ -1086,6 +1275,11 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W wd.no_focus = p_enabled; _update_window_style(p_window); } break; + case WINDOW_FLAG_POPUP: { + ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); + ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); + wd.is_popup = p_enabled; + } break; case WINDOW_FLAG_MAX: break; } @@ -1112,6 +1306,9 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; } break; + case WINDOW_FLAG_POPUP: { + return wd.is_popup; + } break; case WINDOW_FLAG_MAX: break; } @@ -1123,7 +1320,7 @@ void DisplayServerWindows::window_request_attention(WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; + const WindowData &wd = windows[p_window]; FLASHWINFO info; info.cbSize = sizeof(FLASHWINFO); @@ -1140,7 +1337,9 @@ void DisplayServerWindows::window_move_to_foreground(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - SetForegroundWindow(wd.hWnd); + if (!wd.no_focus && !wd.is_popup) { + SetForegroundWindow(wd.hWnd); + } } bool DisplayServerWindows::window_can_draw(WindowID p_window) const { @@ -1187,8 +1386,9 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI wd.im_position = p_pos; HIMC himc = ImmGetContext(wd.hWnd); - if (himc == (HIMC)0) + if (himc == (HIMC)0) { return; + } COMPOSITIONFORM cps; cps.dwStyle = CFS_FORCE_POSITION; @@ -1198,23 +1398,6 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI ImmReleaseContext(wd.hWnd, himc); } -void DisplayServerWindows::console_set_visible(bool p_enabled) { - _THREAD_SAFE_METHOD_ - - if (console_visible == p_enabled) { - return; - } - if (!((OS_Windows *)OS::get_singleton())->_is_win11_terminal()) { - // GetConsoleWindow is not supported by the Windows Terminal. - ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); - console_visible = p_enabled; - } -} - -bool DisplayServerWindows::is_console_visible() const { - return console_visible; -} - void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { _THREAD_SAFE_METHOD_ @@ -1311,7 +1494,7 @@ void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTra DeleteDC(hMainDC); } -void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { +void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { _THREAD_SAFE_METHOD_ if (p_cursor.is_valid()) { @@ -1418,13 +1601,8 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh } } - if (hAndMask != nullptr) { - DeleteObject(hAndMask); - } - - if (hXorMask != nullptr) { - DeleteObject(hXorMask); - } + DeleteObject(hAndMask); + DeleteObject(hXorMask); memfree(buffer); DeleteObject(bitmap); @@ -1627,8 +1805,8 @@ void DisplayServerWindows::swap_buffers() { void DisplayServerWindows::set_native_icon(const String &p_filename) { _THREAD_SAFE_METHOD_ - FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'."); + Ref<FileAccess> f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file with icon '" + p_filename + "'."); ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR)); int pos = 0; @@ -1714,7 +1892,6 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { err = GetLastError(); ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + "."); - memdelete(f); memdelete(icon_dir); } @@ -1722,9 +1899,11 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!p_icon.is_valid()); - Ref<Image> icon = p_icon->duplicate(); - if (icon->get_format() != Image::FORMAT_RGBA8) { - icon->convert(Image::FORMAT_RGBA8); + if (icon != p_icon) { + icon = p_icon->duplicate(); + if (icon->get_format() != Image::FORMAT_RGBA8) { + icon->convert(Image::FORMAT_RGBA8); + } } int w = icon->get_width(); int h = icon->get_height(); @@ -1773,16 +1952,14 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { _THREAD_SAFE_METHOD_ #if defined(VULKAN_ENABLED) - // TODO disabling for now - //context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); #endif } DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_window) const { _THREAD_SAFE_METHOD_ #if defined(VULKAN_ENABLED) - //TODO disabling for now - //return context_vulkan->get_vsync_mode(p_window); + return context_vulkan->get_vsync_mode(p_window); #endif return DisplayServer::VSYNC_ENABLED; } @@ -1852,7 +2029,7 @@ void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent } void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event) { - ((DisplayServerWindows *)(get_singleton()))->_dispatch_input_event(p_event); + static_cast<DisplayServerWindows *>(get_singleton())->_dispatch_input_event(p_event); } void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) { @@ -1867,33 +2044,155 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) Variant ret; Callable::CallError ce; + { + List<WindowID>::Element *E = popup_list.back(); + if (E && Object::cast_to<InputEventKey>(*p_event)) { + // Redirect keyboard input to active popup. + if (windows.has(E->get())) { + Callable callable = windows[E->get()].input_event_callback; + if (callable.is_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); + } + } + in_dispatch_input_event = false; + return; + } + } + Ref<InputEventFromWindow> event_from_window = p_event; if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { // Send to a single window. - if (!windows.has(event_from_window->get_window_id())) { - in_dispatch_input_event = false; - ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event."); - } - Callable callable = windows[event_from_window->get_window_id()].input_event_callback; - if (callable.is_null()) { - in_dispatch_input_event = false; - return; + if (windows.has(event_from_window->get_window_id())) { + Callable callable = windows[event_from_window->get_window_id()].input_event_callback; + if (callable.is_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); + } } - callable.call((const Variant **)&evp, 1, ret, ce); } else { // Send to all windows. for (const KeyValue<WindowID, WindowData> &E : windows) { const Callable callable = E.value.input_event_callback; - if (callable.is_null()) { - continue; + if (callable.is_valid()) { + callable.call((const Variant **)&evp, 1, ret, ce); } - callable.call((const Variant **)&evp, 1, ret, ce); } } in_dispatch_input_event = false; } +LRESULT CALLBACK MouseProc(int code, WPARAM wParam, LPARAM lParam) { + DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton()); + if (ds_win) { + return ds_win->MouseProc(code, wParam, lParam); + } else { + return ::CallNextHookEx(nullptr, code, wParam, lParam); + } +} + +DisplayServer::WindowID DisplayServerWindows::window_get_active_popup() const { + const List<WindowID>::Element *E = popup_list.back(); + if (E) { + return E->get(); + } else { + return INVALID_WINDOW_ID; + } +} + +void DisplayServerWindows::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + wd.parent_safe_rect = p_rect; +} + +Rect2i DisplayServerWindows::window_get_popup_safe_rect(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Rect2i()); + const WindowData &wd = windows[p_window]; + return wd.parent_safe_rect; +} + +void DisplayServerWindows::popup_open(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + const WindowData &wd = windows[p_window]; + if (wd.is_popup) { + // Find current popup parent, or root popup if new window is not transient. + List<WindowID>::Element *C = nullptr; + List<WindowID>::Element *E = popup_list.back(); + while (E) { + if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) { + C = E; + E = E->prev(); + } else { + break; + } + } + if (C) { + _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST); + } + + time_since_popup = OS::get_singleton()->get_ticks_msec(); + popup_list.push_back(p_window); + } +} + +void DisplayServerWindows::popup_close(WindowID p_window) { + _THREAD_SAFE_METHOD_ + + List<WindowID>::Element *E = popup_list.find(p_window); + while (E) { + List<WindowID>::Element *F = E->next(); + WindowID win_id = E->get(); + popup_list.erase(E); + + _send_window_event(windows[win_id], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST); + E = F; + } +} + +LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam) { + _THREAD_SAFE_METHOD_ + + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; + if (delta > 250) { + switch (wParam) { + case WM_NCLBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: { + MOUSEHOOKSTRUCT *ms = (MOUSEHOOKSTRUCT *)lParam; + Point2i pos = Point2i(ms->pt.x, ms->pt.y); + List<WindowID>::Element *C = nullptr; + List<WindowID>::Element *E = popup_list.back(); + // Find top popup to close. + while (E) { + // Popup window area. + Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); + // Area of the parent window, which responsible for opening sub-menu. + Rect2i safe_rect = window_get_popup_safe_rect(E->get()); + if (win_rect.has_point(pos)) { + break; + } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { + break; + } else { + C = E; + E = E->prev(); + } + } + if (C) { + _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST); + } + } break; + } + } + return ::CallNextHookEx(mouse_monitor, code, wParam, lParam); +} + // Our default window procedure to handle processing of window-related system messages/events. // Also known as DefProc or DefWindowProc. // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures @@ -1904,7 +2203,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } else { return DefWindowProcW(hWnd, uMsg, wParam, lParam); } - }; + } WindowID window_id = INVALID_WINDOW_ID; bool window_created = false; @@ -1926,6 +2225,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_MOUSEACTIVATE: { + if (windows[window_id].no_focus) { + return MA_NOACTIVATEANDEAT; // Do not activate, and discard mouse messages. + } else if (windows[window_id].is_popup) { + return MA_NOACTIVATE; // Do not activate, but process mouse messages. + } + } break; case WM_SETFOCUS: { windows[window_id].window_has_focus = true; last_focused_window = window_id; @@ -1976,6 +2282,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Run a timer to prevent event catching warning if the focused window is closing. windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } + if (wParam != WA_INACTIVE) { + track_mouse_leave_event(hWnd); + } return 0; // Return to the message loop. } break; case WM_GETMINMAXINFO: { @@ -2006,8 +2315,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case SC_MONITORPOWER: // Monitor trying to enter powersave? return 0; // Prevent from happening. case SC_KEYMENU: - if ((lParam >> 16) <= 0) + if ((lParam >> 16) <= 0) { return 0; + } } } break; case WM_CLOSE: // Did we receive a close message? @@ -2039,8 +2349,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; } - if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) { OutputDebugString(TEXT("GetRawInputData does not return correct size !\n")); + } RAWINPUT *raw = (RAWINPUT *)lpb; @@ -2066,8 +2377,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(c); mm->set_global_position(c); - Input::get_singleton()->set_mouse_position(c); - mm->set_speed(Vector2(0, 0)); + mm->set_velocity(Vector2(0, 0)); if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); @@ -2136,8 +2446,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ScreenToClient(windows[window_id].hWnd, &coords); // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) + if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) { break; + } Ref<InputEventMouseMotion> mm; mm.instantiate(); @@ -2171,8 +2482,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); - mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -2183,8 +2493,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); old_x = mm->get_position().x; old_y = mm->get_position().y; - if (windows[window_id].window_has_focus) + if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); + } } return 0; } @@ -2260,12 +2571,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA outside = false; // Once-off notification, must call again. - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); + track_mouse_leave_event(hWnd); } // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. @@ -2318,8 +2624,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); - mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -2330,7 +2635,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); old_x = mm->get_position().x; old_y = mm->get_position().y; - if (windows[window_id].window_has_focus) { + if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); } @@ -2366,12 +2671,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA outside = false; // Once-off notification, must call again. - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); + track_mouse_leave_event(hWnd); } // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. @@ -2424,8 +2724,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); - mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -2436,8 +2735,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); old_x = mm->get_position().x; old_y = mm->get_position().y; - if (windows[window_id].window_has_focus) + if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); + } } break; case WM_LBUTTONDOWN: @@ -2583,8 +2883,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) { if (mb->is_pressed()) { - if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) + if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) { SetCapture(hWnd); + } } else { if (--pressrc <= 0) { if (mouse_mode != MOUSE_MODE_CAPTURED) { @@ -2594,7 +2895,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } else { - // For reasons unknown to mankind, wheel comes in screen coordinates. + // For reasons unknown to humanity, wheel comes in screen coordinates. POINT coords; coords.x = mb->get_position().x; coords.y = mb->get_position().y; @@ -2618,98 +2919,78 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; - case WM_MOVE: { - if (!IsIconic(windows[window_id].hWnd)) { - int x = int16_t(LOWORD(lParam)); - int y = int16_t(HIWORD(lParam)); - windows[window_id].last_pos = Point2(x, y); - - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + + case WM_WINDOWPOSCHANGED: { + Rect2i window_client_rect; + Rect2i window_rect; + { + RECT rect; + GetClientRect(hWnd, &rect); + ClientToScreen(hWnd, (POINT *)&rect.left); + ClientToScreen(hWnd, (POINT *)&rect.right); + window_client_rect = Rect2i(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + + RECT wrect; + GetWindowRect(hWnd, &wrect); + window_rect = Rect2i(wrect.left, wrect.top, wrect.right - wrect.left, wrect.bottom - wrect.top); + } + + WINDOWPOS *window_pos_params = (WINDOWPOS *)lParam; + WindowData &window = windows[window_id]; + + bool rect_changed = false; + if (!(window_pos_params->flags & SWP_NOSIZE) || window_pos_params->flags & SWP_FRAMECHANGED) { + int screen_id = window_get_current_screen(window_id); + Size2i screen_size = screen_get_size(screen_id); + Point2i screen_position = screen_get_position(screen_id); + + window.maximized = false; + window.minimized = false; + window.fullscreen = false; + + if (IsIconic(hWnd)) { + window.minimized = true; + } else if (IsZoomed(hWnd)) { + window.maximized = true; + } else if (window_rect.position == screen_position && window_rect.size == screen_size) { + window.fullscreen = true; } - } - } break; - case WM_SIZE: { - // Ignore window size change when a SIZE_MINIMIZED event is triggered. - if (wParam != SIZE_MINIMIZED) { - // The new width and height of the client area. - int window_w = LOWORD(lParam); - int window_h = HIWORD(lParam); - - // Set new value to the size if it isn't preserved. - if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { - windows[window_id].width = window_w; - windows[window_id].height = window_h; + if (!window.minimized) { + window.width = window_client_rect.size.width; + window.height = window_client_rect.size.height; + + rect_changed = true; + } #if defined(VULKAN_ENABLED) - if (context_vulkan && window_created) { - context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); - } + if (context_vulkan && window_created) { + // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed. + context_vulkan->window_resize(window_id, window.width, window.height); + } #endif + } - } else { // If the size is preserved. - windows[window_id].preserve_window_size = false; + if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) { + window.last_pos = window_client_rect.position; + rect_changed = true; + } - // Restore the old size. - window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); + if (rect_changed) { + if (!window.rect_changed_callback.is_null()) { + Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height); + const Variant *args[] = { &size }; + Variant ret; + Callable::CallError ce; + window.rect_changed_callback.call(args, 1, ret, ce); } - } else { // When the window has been minimized, preserve its size. - windows[window_id].preserve_window_size = true; } - // Call windows rect change callback. - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *size_ptr = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce); - } - - // The window has been maximized. - if (wParam == SIZE_MAXIMIZED) { - windows[window_id].maximized = true; - windows[window_id].minimized = false; - } - // The window has been minimized. - else if (wParam == SIZE_MINIMIZED) { - windows[window_id].maximized = false; - windows[window_id].minimized = true; - windows[window_id].preserve_window_size = false; - } - // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies. - else if (wParam == SIZE_RESTORED) { - windows[window_id].maximized = false; - windows[window_id].minimized = false; - } -#if 0 - if (is_layered_allowed() && layered_window) { - DeleteObject(hBitmap); - - RECT r; - GetWindowRect(hWnd, &r); - dib_size = Size2i(r.right - r.left, r.bottom - r.top); - - BITMAPINFO bmi; - ZeroMemory(&bmi, sizeof(BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = dib_size.x; - bmi.bmiHeader.biHeight = dib_size.y; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; - hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0); - SelectObject(hDC_dib, hBitmap); - - ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); - } -#endif + // Return here to prevent WM_MOVE and WM_SIZE from being sent + // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks + return 0; + } break; + case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); @@ -2733,14 +3014,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_SYSKEYUP: case WM_KEYUP: case WM_KEYDOWN: { - if (wParam == VK_SHIFT) + if (wParam == VK_SHIFT) { shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); - if (wParam == VK_CONTROL) + } + if (wParam == VK_CONTROL) { control_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); + } if (wParam == VK_MENU) { alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); - if (lParam & (1 << 24)) + if (lParam & (1 << 24)) { gr_mem = alt_mem; + } } if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2749,10 +3033,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); } } - /* - if (wParam==VK_WIN) TODO wtf is this? - meta_mem=uMsg==WM_KEYDOWN; - */ [[fallthrough]]; } case WM_CHAR: { @@ -2767,10 +3047,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ke.uMsg = uMsg; ke.window_id = window_id; - if (ke.uMsg == WM_SYSKEYDOWN) + if (ke.uMsg == WM_SYSKEYDOWN) { ke.uMsg = WM_KEYDOWN; - if (ke.uMsg == WM_SYSKEYUP) + } + if (ke.uMsg == WM_SYSKEYUP) { ke.uMsg = WM_KEYUP; + } ke.wParam = wParam; ke.lParam = lParam; @@ -2798,7 +3080,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { _touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID); - }; + } } bHandled = TRUE; } else { @@ -2811,12 +3093,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (bHandled) { CloseTouchInputHandle((HTOUCHINPUT)lParam); return 0; - }; + } } break; case WM_DEVICECHANGE: { joypad->probe_joypads(); } break; + case WM_DESTROY: { + Input::get_singleton()->flush_buffered_events(); + } break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) { @@ -2862,8 +3147,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA default: { if (user_proc) { return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); - }; - }; + } + } } return DefWindowProcW(hWnd, uMsg, wParam, lParam); @@ -2871,10 +3156,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton()); - if (ds_win) + if (ds_win) { return ds_win->WndProc(hWnd, uMsg, wParam, lParam); - else + } else { return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } } void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) { @@ -2884,6 +3170,9 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM alt_mem = false; control_mem = false; shift_mem = false; + + // Restore mouse mode. + _set_mouse_mode_impl(mouse_mode); } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); @@ -2938,8 +3227,9 @@ void DisplayServerWindows::_process_key_events() { k->set_ctrl_pressed(false); } - if (k->get_unicode() < 32) + if (k->get_unicode() < 32) { k->set_unicode(0); + } Input::get_singleton()->parse_input_event(k); } else { @@ -2994,8 +3284,9 @@ void DisplayServerWindows::_process_key_events() { k->set_ctrl_pressed(false); } - if (k->get_unicode() < 32) + if (k->get_unicode() < 32) { k->set_unicode(0); + } k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); @@ -3051,7 +3342,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); RECT WindowRect; @@ -3060,7 +3351,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, WindowRect.top = p_rect.position.y; WindowRect.bottom = p_rect.position.y + p_rect.size.y; - if (p_mode == WINDOW_MODE_FULLSCREEN) { + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { int nearest_area = 0; Rect2i screen_rect; for (int i = 0; i < get_screen_count(); i++) { @@ -3103,7 +3394,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); return INVALID_WINDOW_ID; } - if (p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.pre_fs_valid = true; } @@ -3126,14 +3417,6 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, #endif RegisterTouchWindow(wd.hWnd, 0); - - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = wd.hWnd; - tme.dwHoverTime = HOVER_DEFAULT; - TrackMouseEvent(&tme); - DragAcceptFiles(wd.hWnd, true); if ((tablet_get_current_driver() == "wintab") && wintab_available) { @@ -3246,8 +3529,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win shift_mem = false; control_mem = false; meta_mem = false; - console_visible = IsWindowVisible(GetConsoleWindow()); - hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance(); + hInstance = static_cast<OS_Windows *>(OS::get_singleton())->get_hinstance(); pressrc = 0; old_invalid = true; @@ -3257,6 +3539,9 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win rendering_driver = p_rendering_driver; + // Init TTS + tts = memnew(TTS_Windows); + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { @@ -3308,7 +3593,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win wc.cbWndExtra = 0; wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr); wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO); - wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW); + wc.hCursor = nullptr; wc.hbrBackground = nullptr; wc.lpszMenuName = nullptr; wc.lpszClassName = L"Engine"; @@ -3359,11 +3644,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win return; } - // gl_manager->set_use_vsync(current_videomode.use_vsync); RasterizerGLES3::make_current(); } #endif + mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId()); + Point2i window_position( (screen_get_size(0).width - p_resolution.width) / 2, (screen_get_size(0).height - p_resolution.height) / 2); @@ -3389,14 +3675,17 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } #endif - //set_ime_active(false); - - if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { + if (!Engine::get_singleton()->is_editor_hint() && !OS::get_singleton()->is_in_low_processor_usage_mode()) { + // Increase priority for projects that are not in low-processor mode (typically games) + // to reduce the risk of frame stuttering. + // This is not done for the editor to prevent importers or resource bakers + // from making the system unresponsive. SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); DWORD index = 0; HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); - if (handle) + if (handle) { AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); + } // This is needed to make sure that background work does not starve the main thread. // This is only setting the priority of this thread, not the whole process. @@ -3411,7 +3700,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win r_error = OK; - ((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd); + static_cast<OS_Windows *>(OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); } @@ -3448,12 +3737,16 @@ DisplayServerWindows::~DisplayServerWindows() { cursors_cache.clear(); + if (mouse_monitor) { + UnhookWindowsHookEx(mouse_monitor); + } + if (user_proc) { SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc); - }; + } #ifdef GLES3_ENABLED - // destroy windows .. NYI? + // destroy windows .. NYI? #endif if (windows.has(MAIN_WINDOW_ID)) { @@ -3491,4 +3784,8 @@ DisplayServerWindows::~DisplayServerWindows() { gl_manager = nullptr; } #endif + if (tts) { + memdelete(tts); + } + CoUninitialize(); } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index fec449ebce..febc8a2043 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -46,6 +46,7 @@ #include "servers/rendering/renderer_compositor.h" #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #include "servers/rendering_server.h" +#include "tts_windows.h" #ifdef XAUDIO2_ENABLED #include "drivers/xaudio2/audio_driver_xaudio2.h" @@ -320,18 +321,20 @@ class DisplayServerWindows : public DisplayServer { String rendering_driver; bool app_focused = false; + TTS_Windows *tts = nullptr; + struct WindowData { HWND hWnd; //layered window Vector<Vector2> mpath; - bool preserve_window_size = false; bool pre_fs_valid = false; RECT pre_fs_rect; bool maximized = false; bool minimized = false; bool fullscreen = false; + bool multiwindow_fs = false; bool borderless = false; bool resizable = true; bool window_focused = false; @@ -339,6 +342,7 @@ class DisplayServerWindows : public DisplayServer { bool always_on_top = false; bool no_focus = false; bool window_has_focus = false; + bool exclusive = false; // Used to transfer data between events using timer. WPARAM saved_wparam; @@ -386,9 +390,16 @@ class DisplayServerWindows : public DisplayServer { WindowID transient_parent = INVALID_WINDOW_ID; Set<WindowID> transient_children; + + bool is_popup = false; + Rect2i parent_safe_rect; }; - JoypadWindows *joypad; + JoypadWindows *joypad = nullptr; + HHOOK mouse_monitor = nullptr; + List<WindowID> popup_list; + uint64_t time_since_popup = 0; + Ref<Image> icon; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); WindowID window_id_counter = MAIN_WINDOW_ID; @@ -401,7 +412,7 @@ class DisplayServerWindows : public DisplayServer { WNDPROC user_proc = nullptr; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -414,7 +425,6 @@ class DisplayServerWindows : public DisplayServer { bool use_raw_input = false; bool drop_events = false; bool in_dispatch_input_event = false; - bool console_visible = false; WNDCLASSEXW wc; @@ -440,14 +450,27 @@ class DisplayServerWindows : public DisplayServer { public: LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam); + + void popup_open(WindowID p_window); + void popup_close(WindowID p_window); virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual Array tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; - virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual void warp_mouse(const Point2i &p_position) override; virtual Point2i mouse_get_position() const override; virtual MouseButton mouse_get_button_state() const override; @@ -459,6 +482,7 @@ public: virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override; @@ -473,11 +497,17 @@ public: virtual void show_window(WindowID p_window) override; virtual void delete_sub_window(WindowID p_window) override; + virtual WindowID window_get_active_popup() const override; + virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override; + virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override; + + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void gl_window_make_current(DisplayServer::WindowID p_window_id); + virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override; virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; @@ -497,6 +527,7 @@ public: virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + virtual void window_set_exclusive(WindowID p_window, bool p_exclusive) override; virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; @@ -529,12 +560,9 @@ public: virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - virtual void console_set_visible(bool p_enabled) override; - virtual bool is_console_visible() const override; - virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; - virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; + virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; virtual bool get_swap_cancel_ok() override; diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 4ff42f3f62..0fa2913218 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,8 +32,6 @@ #include "export_plugin.h" -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); - void register_windows_exporter() { EDITOR_DEF("export/windows/rcedit", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); @@ -57,84 +55,7 @@ void register_windows_exporter() { logo->create_from_image(img); platform->set_logo(logo); platform->set_name("Windows Desktop"); - platform->set_extension("exe"); - platform->set_release_32("windows_32_release.exe"); - platform->set_debug_32("windows_32_debug.exe"); - platform->set_release_64("windows_64_release.exe"); - platform->set_debug_64("windows_64_debug.exe"); platform->set_os_name("Windows"); - platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); EditorExport::get_singleton()->add_export_platform(platform); } - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data - - FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); - if (!f) { - return ERR_CANT_OPEN; - } - - // Jump to the PE header and check the magic number - { - f->seek(0x3c); - uint32_t pe_pos = f->get_32(); - - f->seek(pe_pos); - uint32_t magic = f->get_32(); - if (magic != 0x00004550) { - f->close(); - return ERR_FILE_CORRUPT; - } - } - - // Process header - - int num_sections; - { - int64_t header_pos = f->get_position(); - - f->seek(header_pos + 2); - num_sections = f->get_16(); - f->seek(header_pos + 16); - uint16_t opt_header_size = f->get_16(); - - // Skip rest of header + optional header to go to the section headers - f->seek(f->get_position() + 2 + opt_header_size); - } - - // Search for the "pck" section - - int64_t section_table_pos = f->get_position(); - - bool found = false; - for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * 40; - f->seek(section_header_pos); - - uint8_t section_name[9]; - f->get_buffer(section_name, 8); - section_name[8] = '\0'; - - if (strcmp((char *)section_name, "pck") == 0) { - // "pck" section found, let's patch! - - // Set virtual size to a little to avoid it taking memory (zero would give issues) - f->seek(section_header_pos + 8); - f->store_32(8); - - f->seek(section_header_pos + 16); - f->store_32(p_embedded_size); - f->seek(section_header_pos + 20); - f->store_32(p_embedded_start); - - found = true; - break; - } - } - - f->close(); - - return found ? OK : ERR_FILE_CORRUPT; -} diff --git a/platform/windows/export/export.h b/platform/windows/export/export.h index 110e1439e2..09399f2bee 100644 --- a/platform/windows/export/export.h +++ b/platform/windows/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 5a1cdb0962..7627a3cba3 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,6 +30,9 @@ #include "export_plugin.h" +#include "core/config/project_settings.h" +#include "editor/editor_node.h" + Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { if (p_preset->get("codesign/enable")) { return _code_sign(p_preset, p_path); @@ -38,29 +41,84 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres } } -Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { - Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags); +Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE); - if (err != OK) { - return err; - } + f->store_line("@echo off"); + f->store_line("title \"" + p_app_name + "\""); + f->store_line("\"%~dp0" + p_pkg_name + "\" \"%*\""); + f->store_line("pause > nul"); - _rcedit_add_data(p_preset, p_path); + return OK; +} +Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { + if (p_preset->get("application/modify_resources")) { + return _rcedit_add_data(p_preset, p_path); + } else { + return OK; + } +} + +Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { + String pck_path = p_path; + if (p_preset->get("binary_format/embed_pck")) { + pck_path = p_path.get_basename() + ".tmp"; + } + Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags); if (p_preset->get("codesign/enable") && err == OK) { - err = _code_sign(p_preset, p_path); + err = _code_sign(p_preset, pck_path); + } + + if (p_preset->get("binary_format/embed_pck") && err == OK) { + Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); + err = tmp_dir->rename(pck_path, p_path); + } + + String app_name; + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + app_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + } else { + app_name = "Unnamed"; + } + app_name = OS::get_singleton()->get_safe_dir_name(app_name); + + // Save console script. + if (err == OK) { + int con_scr = p_preset->get("debug/export_console_script"); + if ((con_scr == 1 && p_debug) || (con_scr == 2)) { + String scr_path = p_path.get_basename() + ".cmd"; + err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path); + } } return err; } +String EditorExportPlatformWindows::get_template_file_name(const String &p_target, const String &p_arch) const { + return "windows_" + p_arch + "_" + p_target + ".exe"; +} + +List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> list; + list.push_back("exe"); + return list; +} + +bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { + // This option is not supported by "osslsigncode", used on non-Windows host. + if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") { + return false; + } + return true; +} + void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) { EditorExportPlatformPC::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); -#ifdef WINDOWS_ENABLED r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0)); -#endif r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); @@ -69,9 +127,10 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), "")); @@ -79,16 +138,16 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), "")); } -void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) { String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); - if (rcedit_path.is_empty()) { - return; + if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) { + ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", aborting."); + return ERR_FILE_NOT_FOUND; } - if (!FileAccess::exists(rcedit_path)) { - ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", no icon or app information data will be included."); - return; + if (rcedit_path == String()) { + rcedit_path = "rcedit"; // try to run rcedit from PATH } #ifndef WINDOWS_ENABLED @@ -96,8 +155,8 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> String wine_path = EditorSettings::get_singleton()->get("export/windows/wine"); if (!wine_path.is_empty() && !FileAccess::exists(wine_path)) { - ERR_PRINT("Could not find wine executable at " + wine_path + ", no icon or app information data will be included."); - return; + ERR_PRINT("Could not find wine executable at " + wine_path + ", aborting."); + return ERR_FILE_NOT_FOUND; } if (wine_path.is_empty()) { @@ -155,13 +214,22 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> args.push_back(trademarks); } -#ifdef WINDOWS_ENABLED - OS::get_singleton()->execute(rcedit_path, args); -#else +#ifndef WINDOWS_ENABLED // On non-Windows we need WINE to run rcedit args.push_front(rcedit_path); - OS::get_singleton()->execute(wine_path, args); + rcedit_path = wine_path; #endif + + String str; + Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit)."); + print_line("rcedit (" + p_path + "): " + str); + + if (str.find("Fatal error") != -1) { + return FAILED; + } + + return OK; } Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { @@ -298,7 +366,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p String str; Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true); - ERR_FAIL_COND_V(err != OK, err); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool)."); print_line("codesign (" + p_path + "): " + str); #ifndef WINDOWS_ENABLED @@ -310,7 +378,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p } #ifndef WINDOWS_ENABLED - DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); + Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); err = tmp_dir->remove(p_path); ERR_FAIL_COND_V(err != OK, err); @@ -321,3 +389,118 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p return OK; } + +bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { + String err = ""; + bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates); + + String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); + if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) { + err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n"; + } + + String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); + if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { + err += TTR("Invalid icon path:") + " " + icon_path + "\n"; + } + + // Only non-negative integers can exist in the version string. + + String file_version = p_preset->get("application/file_version"); + if (!file_version.is_empty()) { + PackedStringArray version_array = file_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || file_version.find("-") > -1) { + err += TTR("Invalid file version:") + " " + file_version + "\n"; + } + } + + String product_version = p_preset->get("application/product_version"); + if (!product_version.is_empty()) { + PackedStringArray version_array = product_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || product_version.find("-") > -1) { + err += TTR("Invalid product version:") + " " + product_version + "\n"; + } + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} + +Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const { + // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data + + if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Windows executables cannot be >= 4 GiB."); + } + + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE); + if (f.is_null()) { + return ERR_CANT_OPEN; + } + + // Jump to the PE header and check the magic number + { + f->seek(0x3c); + uint32_t pe_pos = f->get_32(); + + f->seek(pe_pos); + uint32_t magic = f->get_32(); + if (magic != 0x00004550) { + return ERR_FILE_CORRUPT; + } + } + + // Process header + + int num_sections; + { + int64_t header_pos = f->get_position(); + + f->seek(header_pos + 2); + num_sections = f->get_16(); + f->seek(header_pos + 16); + uint16_t opt_header_size = f->get_16(); + + // Skip rest of header + optional header to go to the section headers + f->seek(f->get_position() + 2 + opt_header_size); + } + + // Search for the "pck" section + + int64_t section_table_pos = f->get_position(); + + bool found = false; + for (int i = 0; i < num_sections; ++i) { + int64_t section_header_pos = section_table_pos + i * 40; + f->seek(section_header_pos); + + uint8_t section_name[9]; + f->get_buffer(section_name, 8); + section_name[8] = '\0'; + + if (strcmp((char *)section_name, "pck") == 0) { + // "pck" section found, let's patch! + + // Set virtual size to a little to avoid it taking memory (zero would give issues) + f->seek(section_header_pos + 8); + f->store_32(8); + + f->seek(section_header_pos + 16); + f->store_32(p_embedded_size); + f->seek(section_header_pos + 20); + f->store_32(p_embedded_start); + + found = true; + break; + } + } + + return found ? OK : ERR_FILE_CORRUPT; +} diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 11d3826410..b48ee7c985 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,18 +34,24 @@ #include "core/io/file_access.h" #include "core/os/os.h" #include "editor/editor_export.h" -#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "platform/windows/logo.gen.h" class EditorExportPlatformWindows : public EditorExportPlatformPC { - void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path); + Error _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); + Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); public: - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); - virtual void get_export_options(List<ExportOption> *r_options); + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override; + virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override; + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + virtual void get_export_options(List<ExportOption> *r_options) override; + virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; + virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const override; }; #endif diff --git a/platform/windows/gl_manager_windows.cpp b/platform/windows/gl_manager_windows.cpp index fe98f8b0ba..a97fa99d7f 100644 --- a/platform/windows/gl_manager_windows.cpp +++ b/platform/windows/gl_manager_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -56,14 +56,9 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int int GLManager_Windows::_find_or_create_display(GLWindow &win) { // find display NYI, only 1 supported so far - if (_displays.size()) + if (_displays.size()) { return 0; - - // for (unsigned int n = 0; n < _displays.size(); n++) { - // const GLDisplay &d = _displays[n]; - // if (d.x11_display == p_x11_display) - // return n; - // } + } // create GLDisplay d_temp = {}; @@ -230,23 +225,27 @@ void GLManager_Windows::window_destroy(DisplayServer::WindowID p_window_id) { } void GLManager_Windows::release_current() { - if (!_current_window) + if (!_current_window) { return; + } wglMakeCurrent(_current_window->hDC, nullptr); } void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) + if (p_window_id == -1) { return; + } GLWindow &win = _windows[p_window_id]; - if (!win.in_use) + if (!win.in_use) { return; + } // noop - if (&win == _current_window) + if (&win == _current_window) { return; + } const GLDisplay &disp = get_display(win.gldisplay_id); wglMakeCurrent(win.hDC, disp.hRC); @@ -255,8 +254,9 @@ void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id) } void GLManager_Windows::make_current() { - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -269,8 +269,9 @@ void GLManager_Windows::swap_buffers() { // NO NEED TO CALL SWAP BUFFERS for each window... // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -304,12 +305,15 @@ void GLManager_Windows::set_use_vsync(bool p_use) { if (!setup) { setup = true; String extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display)); - if (extensions.find("GLX_EXT_swap_control") != -1) + if (extensions.find("GLX_EXT_swap_control") != -1) { glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); - if (extensions.find("GLX_MESA_swap_control") != -1) + } + if (extensions.find("GLX_MESA_swap_control") != -1) { glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA"); - if (extensions.find("GLX_SGI_swap_control") != -1) + } + if (extensions.find("GLX_SGI_swap_control") != -1) { glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); + } } int val = p_use ? 1 : 0; if (glXSwapIntervalMESA) { @@ -319,8 +323,9 @@ void GLManager_Windows::set_use_vsync(bool p_use) { } else if (glXSwapIntervalEXT) { GLXDrawable drawable = glXGetCurrentDrawable(); glXSwapIntervalEXT(x11_display, drawable, val); - } else + } else { return; + } use_vsync = p_use; */ } diff --git a/platform/windows/gl_manager_windows.h b/platform/windows/gl_manager_windows.h index 9733a57420..dc411983e8 100644 --- a/platform/windows/gl_manager_windows.h +++ b/platform/windows/gl_manager_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -52,19 +52,18 @@ public: private: // any data specific to the window struct GLWindow { - GLWindow() { in_use = false; } - bool in_use; + bool in_use = false; // the external ID .. should match the GL window number .. unused I think - DisplayServer::WindowID window_id; - int width; - int height; + DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID; + int width = 0; + int height = 0; // windows specific HDC hDC; HWND hwnd; - int gldisplay_id; + int gldisplay_id = 0; }; struct GLDisplay { @@ -75,7 +74,7 @@ private: LocalVector<GLWindow> _windows; LocalVector<GLDisplay> _displays; - GLWindow *_current_window; + GLWindow *_current_window = nullptr; PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT; diff --git a/platform/windows/godot.ico b/platform/windows/godot.ico Binary files differindex dd611e07da..25830ffdc6 100644 --- a/platform/windows/godot.ico +++ b/platform/windows/godot.ico diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 22e2e5f7e5..8de3ef294a 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,6 +40,17 @@ #if defined _MSC_VER #pragma section("pck", read) __declspec(allocate("pck")) static char dummy[8] = { 0 }; + +// Dummy function to prevent LTO from discarding "pck" section. +extern "C" char *__cdecl pck_section_dummy_call() { + return &dummy[0]; +}; +#if defined _AMD64_ +#pragma comment(linker, "/include:pck_section_dummy_call") +#elif defined _X86_ +#pragma comment(linker, "/include:_pck_section_dummy_call") +#endif + #elif defined __GNUC__ static const char dummy[8] __attribute__((section("pck"), used)) = { 0 }; #endif @@ -158,8 +169,9 @@ int widechar_main(int argc, wchar_t **argv) { return 255; } - if (Main::start()) + if (Main::start()) { os.run(); + } Main::cleanup(); for (int i = 0; i < argc; ++i) { @@ -168,7 +180,7 @@ int widechar_main(int argc, wchar_t **argv) { delete[] argv_utf8; return os.get_exit_code(); -}; +} int _main() { LPWSTR *wc_argv; diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index f76749ec54..d039fd13a7 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -60,8 +60,9 @@ JoypadWindows::JoypadWindows(HWND *hwnd) { load_xinput(); - for (int i = 0; i < JOYPADS_MAX; i++) + for (int i = 0; i < JOYPADS_MAX; i++) { attached_joypads[i] = false; + } HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr); if (result == DI_OK) { @@ -144,8 +145,9 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { HRESULT hr; int num = input->get_unused_joy_id(); - if (have_device(instance->guidInstance) || num == -1) + if (have_device(instance->guidInstance) || num == -1) { return false; + } d_joypads[num] = dinput_gamepad(); dinput_gamepad *joy = &d_joypads[num]; @@ -196,27 +198,28 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ DIPROPRANGE prop_range; DIPROPDWORD dilong; LONG ofs; - if (ob->guidType == GUID_XAxis) + if (ob->guidType == GUID_XAxis) { ofs = DIJOFS_X; - else if (ob->guidType == GUID_YAxis) + } else if (ob->guidType == GUID_YAxis) { ofs = DIJOFS_Y; - else if (ob->guidType == GUID_ZAxis) + } else if (ob->guidType == GUID_ZAxis) { ofs = DIJOFS_Z; - else if (ob->guidType == GUID_RxAxis) + } else if (ob->guidType == GUID_RxAxis) { ofs = DIJOFS_RX; - else if (ob->guidType == GUID_RyAxis) + } else if (ob->guidType == GUID_RyAxis) { ofs = DIJOFS_RY; - else if (ob->guidType == GUID_RzAxis) + } else if (ob->guidType == GUID_RzAxis) { ofs = DIJOFS_RZ; - else if (ob->guidType == GUID_Slider) { + } else if (ob->guidType == GUID_Slider) { if (slider_count < 2) { ofs = DIJOFS_SLIDER(slider_count); slider_count++; } else { return; } - } else + } else { return; + } prop_range.diph.dwSize = sizeof(DIPROPRANGE); prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER); prop_range.diph.dwObj = ob->dwType; @@ -227,8 +230,9 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ dinput_gamepad &joy = d_joypads[p_joy_id]; res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_RANGE, &prop_range.diph); - if (FAILED(res)) + if (FAILED(res)) { return; + } dilong.diph.dwSize = sizeof(dilong); dilong.diph.dwHeaderSize = sizeof(dilong.diph); @@ -237,15 +241,16 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ dilong.dwData = 0; res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_DEADZONE, &dilong.diph); - if (FAILED(res)) + if (FAILED(res)) { return; + } joy.joy_axis.push_back(ofs); } } BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) { - JoypadWindows *self = (JoypadWindows *)p_context; + JoypadWindows *self = static_cast<JoypadWindows *>(p_context); if (self->is_xinput_device(&p_instance->guidProduct)) { return DIENUM_CONTINUE; } @@ -253,9 +258,9 @@ BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, vo return DIENUM_CONTINUE; } -BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context) { - JoypadWindows *self = (JoypadWindows *)context; - self->setup_joypad_object(instance, self->id_to_change); +BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_instance, void *p_context) { + JoypadWindows *self = static_cast<JoypadWindows *>(p_context); + self->setup_joypad_object(p_instance, self->id_to_change); return DIENUM_CONTINUE; } @@ -268,8 +273,9 @@ void JoypadWindows::close_joypad(int id) { return; } - if (!d_joypads[id].attached) + if (!d_joypads[id].attached) { return; + } d_joypads[id].di_joy->Unacquire(); d_joypads[id].di_joy->Release(); @@ -355,16 +361,18 @@ void JoypadWindows::process_joypads() { } } else if (joy.vibrating && joy.ff_end_timestamp != 0) { uint64_t current_time = OS::get_singleton()->get_ticks_usec(); - if (current_time >= joy.ff_end_timestamp) + if (current_time >= joy.ff_end_timestamp) { joypad_vibration_stop_xinput(i, current_time); + } } } for (int i = 0; i < JOYPADS_MAX; i++) { dinput_gamepad *joy = &d_joypads[i]; - if (!joy->attached) + if (!joy->attached) { continue; + } DIJOYSTATE2 js; hr = joy->di_joy->Poll(); @@ -396,7 +404,7 @@ void JoypadWindows::process_joypads() { // on mingw, these constants are not constants int count = 8; - LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) }; + const LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) }; int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] }; for (int j = 0; j < joy->joy_axis.size(); j++) { @@ -404,9 +412,9 @@ void JoypadWindows::process_joypads() { if (joy->joy_axis[j] == axes[k]) { input->joy_axis(joy->id, (JoyAxis)j, axis_correct(values[k])); break; - }; - }; - }; + } + } + } } return; } @@ -446,35 +454,29 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { dpad_val = (HatMask)(HatMask::LEFT | HatMask::UP); } input->joy_hat(p_device, dpad_val); -}; +} -Input::JoyAxisValue JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { - Input::JoyAxisValue jx; +float JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { if (Math::abs(p_val) < MIN_JOY_AXIS) { - jx.min = p_trigger ? 0 : -1; - jx.value = 0.0f; - return jx; - } - if (p_xinput) { - if (p_trigger) { - jx.min = 0; - jx.value = (float)p_val / MAX_TRIGGER; - return jx; - } - jx.min = -1; - if (p_val < 0) { - jx.value = (float)p_val / MAX_JOY_AXIS; - } else { - jx.value = (float)p_val / (MAX_JOY_AXIS - 1); - } - if (p_negate) { - jx.value = -jx.value; - } - return jx; + return p_trigger ? -1.0f : 0.0f; + } + if (!p_xinput) { + return (float)p_val / MAX_JOY_AXIS; + } + if (p_trigger) { + // Convert to a value between -1.0f and 1.0f. + return 2.0f * p_val / MAX_TRIGGER - 1.0f; + } + float value; + if (p_val < 0) { + value = (float)p_val / MAX_JOY_AXIS; + } else { + value = (float)p_val / (MAX_JOY_AXIS - 1); + } + if (p_negate) { + value = -value; } - jx.min = -1; - jx.value = (float)p_val / MAX_JOY_AXIS; - return jx; + return value; } void JoypadWindows::joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 757fb54fb3..d239471a5c 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -86,8 +86,9 @@ private: attached = false; confirmed = false; - for (int i = 0; i < MAX_JOY_BUTTONS; i++) + for (int i = 0; i < MAX_JOY_BUTTONS; i++) { last_buttons[i] = false; + } } }; @@ -104,10 +105,10 @@ private: typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState); typedef DWORD(WINAPI *XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); - HWND *hWnd; + HWND *hWnd = nullptr; HANDLE xinput_dll; LPDIRECTINPUT8 dinput; - Input *input; + Input *input = nullptr; int id_to_change; int slider_count; @@ -132,7 +133,7 @@ private: void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp); - Input::JoyAxisValue axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + float axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; XInputGetState_t xinput_get_state; XInputSetState_t xinput_set_state; }; diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index 65ee5dd46b..e32dc0d1a6 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,117 +32,134 @@ #include <stdio.h> +// This provides translation from Windows virtual key codes to Godot and back. +// See WinUser.h and the below for documentation: +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + struct _WinTranslatePair { Key keysym; unsigned int keycode; }; static _WinTranslatePair _vk_to_keycode[] = { - { Key::BACKSPACE, VK_BACK }, // (0x08) // backspace - { Key::TAB, VK_TAB }, //(0x09) - - //VK_CLEAR (0x0C) - - { Key::ENTER, VK_RETURN }, //(0x0D) - - { Key::SHIFT, VK_SHIFT }, //(0x10) - - { Key::CTRL, VK_CONTROL }, //(0x11) - - { Key::ALT, VK_MENU }, //(0x12) - - { Key::PAUSE, VK_PAUSE }, //(0x13) + // VK_LBUTTON (0x01) + // VK_RBUTTON (0x02) + // VK_CANCEL (0x03) + // VK_MBUTTON (0x04) + // VK_XBUTTON1 (0x05) + // VK_XBUTTON2 (0x06) + // We have no mappings for the above, as we only map keyboard buttons here. - { Key::CAPSLOCK, VK_CAPITAL }, //(0x14) + // 0x07 is undefined. - { Key::ESCAPE, VK_ESCAPE }, //(0x1B) + { Key::BACKSPACE, VK_BACK }, // (0x08) + { Key::TAB, VK_TAB }, // (0x09) - { Key::SPACE, VK_SPACE }, //(0x20) + // 0x0A-0B are reserved. - { Key::PAGEUP, VK_PRIOR }, //(0x21) + { Key::CLEAR, VK_CLEAR }, // (0x0C) + { Key::ENTER, VK_RETURN }, // (0x0D) - { Key::PAGEDOWN, VK_NEXT }, //(0x22) + // 0x0E-0F are undefined. - { Key::END, VK_END }, //(0x23) + { Key::SHIFT, VK_SHIFT }, // (0x10) + { Key::CTRL, VK_CONTROL }, // (0x11) + { Key::ALT, VK_MENU }, // (0x12) + { Key::PAUSE, VK_PAUSE }, // (0x13) + { Key::CAPSLOCK, VK_CAPITAL }, // (0x14) - { Key::HOME, VK_HOME }, //(0x24) + // 0x15-1A are IME keys. We have no mapping. - { Key::LEFT, VK_LEFT }, //(0x25) + { Key::ESCAPE, VK_ESCAPE }, // (0x1B) - { Key::UP, VK_UP }, //(0x26) - - { Key::RIGHT, VK_RIGHT }, //(0x27) + // 0x1C-1F are IME keys. We have no mapping. + { Key::SPACE, VK_SPACE }, // (0x20) + { Key::PAGEUP, VK_PRIOR }, // (0x21) + { Key::PAGEDOWN, VK_NEXT }, // (0x22) + { Key::END, VK_END }, // (0x23) + { Key::HOME, VK_HOME }, // (0x24) + { Key::LEFT, VK_LEFT }, // (0x25) + { Key::UP, VK_UP }, // (0x26) + { Key::RIGHT, VK_RIGHT }, // (0x27) { Key::DOWN, VK_DOWN }, // (0x28) - //VK_SELECT (0x29) + // VK_SELECT (0x29) + // Old select key, e.g. on Digital Equipment Corporation keyboards. + // Old and uncommon, we have no mapping. { Key::PRINT, VK_PRINT }, // (0x2A) + // Old IBM key, modern keyboards use VK_SNAPSHOT. Map to VK_SNAPSHOT. - //VK_EXECUTE (0x2B) + // VK_EXECUTE (0x2B) + // Old and uncommon, we have no mapping. { Key::PRINT, VK_SNAPSHOT }, // (0x2C) - { Key::INSERT, VK_INSERT }, // (0x2D) - { Key::KEY_DELETE, VK_DELETE }, // (0x2E) { Key::HELP, VK_HELP }, // (0x2F) - - { Key::KEY_0, (0x30) }, ////0 key - { Key::KEY_1, (0x31) }, ////1 key - { Key::KEY_2, (0x32) }, ////2 key - { Key::KEY_3, (0x33) }, ////3 key - { Key::KEY_4, (0x34) }, ////4 key - { Key::KEY_5, (0x35) }, ////5 key - { Key::KEY_6, (0x36) }, ////6 key - { Key::KEY_7, (0x37) }, ////7 key - { Key::KEY_8, (0x38) }, ////8 key - { Key::KEY_9, (0x39) }, ////9 key - { Key::A, (0x41) }, ////A key - { Key::B, (0x42) }, ////B key - { Key::C, (0x43) }, ////C key - { Key::D, (0x44) }, ////D key - { Key::E, (0x45) }, ////E key - { Key::F, (0x46) }, ////F key - { Key::G, (0x47) }, ////G key - { Key::H, (0x48) }, ////H key - { Key::I, (0x49) }, ////I key - { Key::J, (0x4A) }, ////J key - { Key::K, (0x4B) }, ////K key - { Key::L, (0x4C) }, ////L key - { Key::M, (0x4D) }, ////M key - { Key::N, (0x4E) }, ////N key - { Key::O, (0x4F) }, ////O key - { Key::P, (0x50) }, ////P key - { Key::Q, (0x51) }, ////Q key - { Key::R, (0x52) }, ////R key - { Key::S, (0x53) }, ////S key - { Key::T, (0x54) }, ////T key - { Key::U, (0x55) }, ////U key - { Key::V, (0x56) }, ////V key - { Key::W, (0x57) }, ////W key - { Key::X, (0x58) }, ////X key - { Key::Y, (0x59) }, ////Y key - { Key::Z, (0x5A) }, ////Z key - - { (Key)KeyModifierMask::META, VK_LWIN }, //(0x5B) - { (Key)KeyModifierMask::META, VK_RWIN }, //(0x5C) - { Key::MENU, VK_APPS }, //(0x5D) - { Key::STANDBY, VK_SLEEP }, //(0x5F) - { Key::KP_0, VK_NUMPAD0 }, //(0x60) - { Key::KP_1, VK_NUMPAD1 }, //(0x61) - { Key::KP_2, VK_NUMPAD2 }, //(0x62) - { Key::KP_3, VK_NUMPAD3 }, //(0x63) - { Key::KP_4, VK_NUMPAD4 }, //(0x64) - { Key::KP_5, VK_NUMPAD5 }, //(0x65) - { Key::KP_6, VK_NUMPAD6 }, //(0x66) - { Key::KP_7, VK_NUMPAD7 }, //(0x67) - { Key::KP_8, VK_NUMPAD8 }, //(0x68) - { Key::KP_9, VK_NUMPAD9 }, //(0x69) + // Old and uncommon, but we have a mapping. + + { Key::KEY_0, (0x30) }, // 0 key. + { Key::KEY_1, (0x31) }, // 1 key. + { Key::KEY_2, (0x32) }, // 2 key. + { Key::KEY_3, (0x33) }, // 3 key. + { Key::KEY_4, (0x34) }, // 4 key. + { Key::KEY_5, (0x35) }, // 5 key. + { Key::KEY_6, (0x36) }, // 6 key. + { Key::KEY_7, (0x37) }, // 7 key. + { Key::KEY_8, (0x38) }, // 8 key. + { Key::KEY_9, (0x39) }, // 9 key. + // 0x3A-40 are undefined. + { Key::A, (0x41) }, // A key. + { Key::B, (0x42) }, // B key. + { Key::C, (0x43) }, // C key. + { Key::D, (0x44) }, // D key. + { Key::E, (0x45) }, // E key. + { Key::F, (0x46) }, // F key. + { Key::G, (0x47) }, // G key. + { Key::H, (0x48) }, // H key. + { Key::I, (0x49) }, // I key + { Key::J, (0x4A) }, // J key. + { Key::K, (0x4B) }, // K key. + { Key::L, (0x4C) }, // L key. + { Key::M, (0x4D) }, // M key. + { Key::N, (0x4E) }, // N key. + { Key::O, (0x4F) }, // O key. + { Key::P, (0x50) }, // P key. + { Key::Q, (0x51) }, // Q key. + { Key::R, (0x52) }, // R key. + { Key::S, (0x53) }, // S key. + { Key::T, (0x54) }, // T key. + { Key::U, (0x55) }, // U key. + { Key::V, (0x56) }, // V key. + { Key::W, (0x57) }, // W key. + { Key::X, (0x58) }, // X key. + { Key::Y, (0x59) }, // Y key. + { Key::Z, (0x5A) }, // Z key. + + { (Key)KeyModifierMask::META, VK_LWIN }, // (0x5B) + { (Key)KeyModifierMask::META, VK_RWIN }, // (0x5C) + { Key::MENU, VK_APPS }, // (0x5D) + // 0x5E is reserved. + { Key::STANDBY, VK_SLEEP }, // (0x5F) + { Key::KP_0, VK_NUMPAD0 }, // (0x60) + { Key::KP_1, VK_NUMPAD1 }, // (0x61) + { Key::KP_2, VK_NUMPAD2 }, // (0x62) + { Key::KP_3, VK_NUMPAD3 }, // (0x63) + { Key::KP_4, VK_NUMPAD4 }, // (0x64) + { Key::KP_5, VK_NUMPAD5 }, // (0x65) + { Key::KP_6, VK_NUMPAD6 }, // (0x66) + { Key::KP_7, VK_NUMPAD7 }, // (0x67) + { Key::KP_8, VK_NUMPAD8 }, // (0x68) + { Key::KP_9, VK_NUMPAD9 }, // (0x69) { Key::KP_MULTIPLY, VK_MULTIPLY }, // (0x6A) { Key::KP_ADD, VK_ADD }, // (0x6B) - //VK_SEPARATOR (0x6C) + { Key::KP_PERIOD, VK_SEPARATOR }, // (0x6C) + // VK_SEPERATOR (key 0x6C) is not found on US keyboards. + // It is used on some Brazilian and Far East keyboards. + // We don't have a direct mapping, map to period. { Key::KP_SUBTRACT, VK_SUBTRACT }, // (0x6D) { Key::KP_PERIOD, VK_DECIMAL }, // (0x6E) { Key::KP_DIVIDE, VK_DIVIDE }, // (0x6F) @@ -162,8 +179,17 @@ static _WinTranslatePair _vk_to_keycode[] = { { Key::F14, VK_F14 }, // (0x7D) { Key::F15, VK_F15 }, // (0x7E) { Key::F16, VK_F16 }, // (0x7F) + // We have no mappings for F17-F24. (0x80-87) + // 0x88-8F are reserved for UI navigation. { Key::NUMLOCK, VK_NUMLOCK }, // (0x90) { Key::SCROLLLOCK, VK_SCROLL }, // (0x91) + + { Key::EQUAL, VK_OEM_NEC_EQUAL }, // (0x92) + // OEM NEC PC-9800 numpad '=' key. + + // 0x93-96 are OEM specific (e.g. used by Fujitsu/OASYS), we have no mappings. + // 0x97-9F are unassigned. + { Key::SHIFT, VK_LSHIFT }, // (0xA0) { Key::SHIFT, VK_RSHIFT }, // (0xA1) { Key::CTRL, VK_LCONTROL }, // (0xA2) @@ -172,70 +198,124 @@ static _WinTranslatePair _vk_to_keycode[] = { { Key::MENU, VK_RMENU }, // (0xA5) { Key::BACK, VK_BROWSER_BACK }, // (0xA6) - { Key::FORWARD, VK_BROWSER_FORWARD }, // (0xA7) - { Key::REFRESH, VK_BROWSER_REFRESH }, // (0xA8) - { Key::STOP, VK_BROWSER_STOP }, // (0xA9) - { Key::SEARCH, VK_BROWSER_SEARCH }, // (0xAA) - { Key::FAVORITES, VK_BROWSER_FAVORITES }, // (0xAB) - { Key::HOMEPAGE, VK_BROWSER_HOME }, // (0xAC) - { Key::VOLUMEMUTE, VK_VOLUME_MUTE }, // (0xAD) - { Key::VOLUMEDOWN, VK_VOLUME_DOWN }, // (0xAE) - { Key::VOLUMEUP, VK_VOLUME_UP }, // (0xAF) - { Key::MEDIANEXT, VK_MEDIA_NEXT_TRACK }, // (0xB0) - { Key::MEDIAPREVIOUS, VK_MEDIA_PREV_TRACK }, // (0xB1) - { Key::MEDIASTOP, VK_MEDIA_STOP }, // (0xB2) - //VK_MEDIA_PLAY_PAUSE (0xB3) + { Key::MEDIAPLAY, VK_MEDIA_PLAY_PAUSE }, // (0xB3) + // Media button play/pause toggle. + // Map to media play (there is no other 'play' mapping on Windows). { Key::LAUNCHMAIL, VK_LAUNCH_MAIL }, // (0xB4) - { Key::LAUNCHMEDIA, VK_LAUNCH_MEDIA_SELECT }, // (0xB5) - { Key::LAUNCH0, VK_LAUNCH_APP1 }, // (0xB6) - { Key::LAUNCH1, VK_LAUNCH_APP2 }, // (0xB7) + // 0xB8-B9 are reserved. + { Key::SEMICOLON, VK_OEM_1 }, // (0xBA) + // Misc. character, can vary by keyboard/region. + // Windows 2000/XP: For US standard keyboards, the ';:' key. - { Key::EQUAL, VK_OEM_PLUS }, // (0xBB) // Windows 2000/XP: For any country/region, the '+' key - { Key::COMMA, VK_OEM_COMMA }, // (0xBC) // Windows 2000/XP: For any country/region, the ',' key - { Key::MINUS, VK_OEM_MINUS }, // (0xBD) // Windows 2000/XP: For any country/region, the '-' key - { Key::PERIOD, VK_OEM_PERIOD }, // (0xBE) // Windows 2000/XP: For any country/region, the '.' key - { Key::SLASH, VK_OEM_2 }, // (0xBF) //Windows 2000/XP: For the US standard keyboard, the '/?' key + { Key::EQUAL, VK_OEM_PLUS }, // (0xBB) + // Windows 2000/XP: For any country/region, the '+' key. + { Key::COMMA, VK_OEM_COMMA }, // (0xBC) + // Windows 2000/XP: For any country/region, the ',' key. + { Key::MINUS, VK_OEM_MINUS }, // (0xBD) + // Windows 2000/XP: For any country/region, the '-' key. + { Key::PERIOD, VK_OEM_PERIOD }, // (0xBE) + // Windows 2000/XP: For any country/region, the '.' key. + + { Key::SLASH, VK_OEM_2 }, // (0xBF) + // Windows 2000/XP: For US standard keyboards, the '/?' key. { Key::QUOTELEFT, VK_OEM_3 }, // (0xC0) - { Key::BRACELEFT, VK_OEM_4 }, // (0xDB) + // Windows 2000/XP: For US standard keyboards, the '`~' key. + + // 0xC1-D7 are reserved. 0xD8-DA are unassigned. + // TODO: 0xC3-DA may be used for old gamepads? Maybe we want to support this? See WinUser.h. + + { Key::BRACKETLEFT, VK_OEM_4 }, // (0xDB) + // Misc. character, can vary by keyboard/region. + // Windows 2000/XP: For US standard keyboards, the '[{' key. + { Key::BACKSLASH, VK_OEM_5 }, // (0xDC) - { Key::BRACERIGHT, VK_OEM_6 }, // (0xDD) + // Misc. character, can vary by keyboard/region. + // Windows 2000/XP: For US standard keyboards, the '\|' key. + + { Key::BRACKETRIGHT, VK_OEM_6 }, // (0xDD) + // Misc. character, can vary by keyboard/region. + // Windows 2000/XP: For US standard keyboards, the ']}' key. + { Key::APOSTROPHE, VK_OEM_7 }, // (0xDE) - /* -{VK_OEM_8 (0xDF) -{VK_OEM_102 (0xE2) // Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard -*/ - //{ Key::PLAY, VK_PLAY},// (0xFA) + // Misc. character, can vary by keyboard/region. + // Windows 2000/XP: For US standard keyboards, single quote/double quote. + + // VK_OEM_8 (0xDF) + // Misc. character, can vary by keyboard/region. We have no mapping. + + // 0xE0 is reserved. 0xE1 is OEM specific, we have no mapping. + + // VK_OEM_102 (0xE2) + // Either angle bracket or backslash key on the RT 102-key keyboard. + // Old and uncommon, we have no mapping. + + { Key::HELP, VK_ICO_HELP }, // (0xE3) + // OEM (ICO) help key. Map to help. + + // 0xE4 is OEM (e.g. ICO) specific, we have no mapping. + + // VK_PROCESSKEY (0xE5) + // For IME, we have no mapping. + + { Key::CLEAR, VK_ICO_CLEAR }, // (0xE6) + // OEM (ICO) clear key. Map to clear. + + // VK_PACKET (0xE7) + // Used to pass Unicode characters as if they were keystrokes. + // See Win32 API docs. We have no mapping. + + // 0xE8 is unassigned, 0xE9-F5 are OEM (Nokia/Ericsson) specific, we have no mappings. + + { Key::ESCAPE, VK_ATTN }, // (0xF6) + // Old IBM 'ATTN' key used on midrange computers, e.g. AS/400, map to Escape. + + { Key::TAB, VK_CRSEL }, // (0xF7) + // Old IBM 3270 'CrSel' (cursor select) key, used to select data fields, map to Tab. + + // VK_EXSEL (0xF7) + // Old IBM 3270 extended selection key. No mapping. + + // VK_EREOF (0xF8) + // Old IBM 3270 erase to end of field key. No mapping. + + { Key::MEDIAPLAY, VK_PLAY }, // (0xFA) + // Old IBM 3270 'Play' key. Map to media play. + + // VK_ZOOM (0xFB) + // Old IBM 3290 'Zoom' key. No mapping. + + // VK_NONAME (0xFC) + // Reserved. No mapping. + + // VK_PA1 (0xFD) + // Old IBM 3270 PA1 key. No mapping. + + { Key::CLEAR, VK_OEM_CLEAR }, // (0xFE) + // OEM specific clear key. Unclear how it differs from normal clear. Map to clear. { Key::UNKNOWN, 0 } }; -/* -VK_ZOOM (0xFB) -VK_NONAME (0xFC) -VK_PA1 (0xFD) -VK_OEM_CLEAR (0xFE) -*/ - static _WinTranslatePair _scancode_to_keycode[] = { { Key::ESCAPE, 0x01 }, { Key::KEY_1, 0x02 }, @@ -320,7 +400,6 @@ static _WinTranslatePair _scancode_to_keycode[] = { { Key::PAGEDOWN, 0x51 }, { Key::INSERT, 0x52 }, { Key::KEY_DELETE, 0x53 }, - //{ Key::???, 0x56 }, //NON US BACKSLASH { Key::F11, 0x57 }, { Key::F12, 0x58 }, { Key::META, 0x5B }, @@ -336,8 +415,6 @@ static _WinTranslatePair _scancode_to_keycode[] = { Key KeyMappingWindows::get_keysym(unsigned int p_code) { for (int i = 0; _vk_to_keycode[i].keysym != Key::UNKNOWN; i++) { if (_vk_to_keycode[i].keycode == p_code) { - //printf("outcode: %x\n",_vk_to_keycode[i].keysym); - return _vk_to_keycode[i].keysym; } } diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index 0454be7310..393432fa39 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/lang_table.h b/platform/windows/lang_table.h index 51583cc11e..5b022853e8 100644 --- a/platform/windows/lang_table.h +++ b/platform/windows/lang_table.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index bb6a077a5d..68e188bbed 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -46,14 +46,13 @@ #include "windows_terminal_logger.h" #include <avrt.h> +#include <bcrypt.h> #include <direct.h> #include <knownfolders.h> #include <process.h> #include <regstr.h> #include <shlobj.h> -static const WORD MAX_CONSOLE_LINES = 1500; - extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 1; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; @@ -85,71 +84,32 @@ static String format_error_message(DWORD id) { return msg; } -void RedirectIOToConsole() { - int hConHandle; - - intptr_t lStdHandle; - - CONSOLE_SCREEN_BUFFER_INFO coninfo; - - FILE *fp; - - // allocate a console for this app - - AllocConsole(); - - // set the screen buffer to be big enough to let us scroll text - - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); - - coninfo.dwSize.Y = MAX_CONSOLE_LINES; - - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); - - // redirect unbuffered STDOUT to the console - - lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE); - - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - - fp = _fdopen(hConHandle, "w"); - - *stdout = *fp; - - setvbuf(stdout, nullptr, _IONBF, 0); - - // redirect unbuffered STDIN to the console - - lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE); - - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - - fp = _fdopen(hConHandle, "r"); - - *stdin = *fp; - - setvbuf(stdin, nullptr, _IONBF, 0); - - // redirect unbuffered STDERR to the console - - lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE); - - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - - fp = _fdopen(hConHandle, "w"); - - *stderr = *fp; - - setvbuf(stderr, nullptr, _IONBF, 0); +void RedirectStream(const char *p_file_name, const char *p_mode, FILE *p_cpp_stream, const DWORD p_std_handle) { + const HANDLE h_existing = GetStdHandle(p_std_handle); + if (h_existing != INVALID_HANDLE_VALUE) { // Redirect only if attached console have a valid handle. + const HANDLE h_cpp = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(p_cpp_stream))); + if (h_cpp == INVALID_HANDLE_VALUE) { // Redirect only if it's not already redirected to the pipe or file. + FILE *fp = p_cpp_stream; + freopen_s(&fp, p_file_name, p_mode, p_cpp_stream); // Redirect stream. + setvbuf(p_cpp_stream, nullptr, _IONBF, 0); // Disable stream buffering. + } + } +} - // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog +void RedirectIOToConsole() { + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + RedirectStream("CONIN$", "r", stdin, STD_INPUT_HANDLE); + RedirectStream("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE); + RedirectStream("CONOUT$", "w", stderr, STD_ERROR_HANDLE); - // point to console as well + printf("\n"); // Make sure our output is starting from the new line. + } } BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { - if (!EngineDebugger::is_active()) + if (!EngineDebugger::is_active()) { return FALSE; + } switch (dwCtrlType) { case CTRL_C_EVENT: @@ -172,7 +132,9 @@ void OS_Windows::initialize_debugging() { void OS_Windows::initialize() { crash_handler.initialize(); - //RedirectIOToConsole(); +#ifndef WINDOWS_SUBSYSTEM_CONSOLE + RedirectIOToConsole(); +#endif FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); @@ -184,12 +146,8 @@ void OS_Windows::initialize() { NetSocketPosix::make_default(); // We need to know how often the clock is updated - if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) - ticks_per_second = 1000; - // If timeAtGameStart is 0 then we get the time since - // the start of the computer when we call GetGameTime() - ticks_start = 0; - ticks_start = get_ticks_usec(); + QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second); + QueryPerformanceCounter((LARGE_INTEGER *)&ticks_start); // set minimum resolution for periodic timers, otherwise Sleep(n) may wait at least as // long as the windows scheduler resolution (~16-30ms) even for calls like Sleep(1) @@ -209,8 +167,9 @@ void OS_Windows::initialize() { } void OS_Windows::delete_main_loop() { - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; } @@ -223,8 +182,9 @@ void OS_Windows::finalize() { driver_midi.close(); #endif - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; } @@ -236,7 +196,13 @@ void OS_Windows::finalize_core() { NetSocketPosix::cleanup(); } -Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) { + NTSTATUS status = BCryptGenRandom(nullptr, r_buffer, p_bytes, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + ERR_FAIL_COND_V(status, FAILED); + return OK; +} + +Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { String path = p_path.replace("/", "\\"); if (!FileAccess::exists(path)) { @@ -264,6 +230,10 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han remove_dll_directory(cookie); } + if (r_resolved_path != nullptr) { + *r_resolved_path = path; + } + return OK; } @@ -298,12 +268,19 @@ OS::Date OS_Windows::get_date(bool p_utc) const { GetLocalTime(&systemtime); } + //Get DST information from Windows, but only if p_utc is false. + TIME_ZONE_INFORMATION info; + bool daylight = false; + if (!p_utc && GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) { + daylight = true; + } + Date date; date.day = systemtime.wDay; date.month = Month(systemtime.wMonth); date.weekday = Weekday(systemtime.wDayOfWeek); date.year = systemtime.wYear; - date.dst = false; + date.dst = daylight; return date; } @@ -325,19 +302,23 @@ OS::Time OS_Windows::get_time(bool p_utc) const { OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { TIME_ZONE_INFORMATION info; bool daylight = false; - if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) + if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) { daylight = true; + } + // Daylight Bias needs to be added to the bias if DST is in effect, or else it will not properly update. TimeZoneInfo ret; if (daylight) { ret.name = info.DaylightName; + ret.bias = info.Bias + info.DaylightBias; } else { ret.name = info.StandardName; + ret.bias = info.Bias + info.StandardBias; } // Bias value returned by GetTimeZoneInformation is inverted of what we expect // For example, on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 - ret.bias = -info.Bias; + ret.bias = -ret.bias; return ret; } @@ -359,18 +340,21 @@ double OS_Windows::get_unix_time() const { } void OS_Windows::delay_usec(uint32_t p_usec) const { - if (p_usec < 1000) + if (p_usec < 1000) { Sleep(1); - else + } else { Sleep(p_usec / 1000); + } } uint64_t OS_Windows::get_ticks_usec() const { uint64_t ticks; // This is the number of clock ticks since start - if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks)) - ticks = (UINT64)timeGetTime(); + QueryPerformanceCounter((LARGE_INTEGER *)&ticks); + // Subtract the ticks at game start to get + // the ticks since the game started + ticks -= ticks_start; // Divide by frequency to get the time in seconds // original calculation shown below is subject to overflow @@ -390,9 +374,6 @@ uint64_t OS_Windows::get_ticks_usec() const { // seconds time += seconds * 1000000L; - // Subtract the time at game start to get - // the time since the game started - time -= ticks_start; return time; } @@ -406,78 +387,135 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const { return p_text; } -Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { +static void _append_to_pipe(char *p_bytes, int p_size, String *r_pipe, Mutex *p_pipe_mutex) { + // Try to convert from default ANSI code page to Unicode. + LocalVector<wchar_t> wchars; + int total_wchars = MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, nullptr, 0); + if (total_wchars > 0) { + wchars.resize(total_wchars); + if (MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, wchars.ptr(), total_wchars) == 0) { + wchars.clear(); + } + } + + if (p_pipe_mutex) { + p_pipe_mutex->lock(); + } + if (wchars.is_empty()) { + // Let's hope it's compatible with UTF-8. + (*r_pipe) += String::utf8(p_bytes, p_size); + } else { + (*r_pipe) += String(wchars.ptr(), total_wchars); + } + if (p_pipe_mutex) { + p_pipe_mutex->unlock(); + } +} + +Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); for (const String &E : p_arguments) { command += " " + _quote_command_line_argument(E); } + ProcessInfo pi; + ZeroMemory(&pi.si, sizeof(pi.si)); + pi.si.cb = sizeof(pi.si); + ZeroMemory(&pi.pi, sizeof(pi.pi)); + LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; + + bool inherit_handles = false; + HANDLE pipe[2] = { nullptr, nullptr }; if (r_pipe) { + // Create pipe for StdOut and StdErr. + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = nullptr; + + ERR_FAIL_COND_V(!CreatePipe(&pipe[0], &pipe[1], &sa, 0), ERR_CANT_FORK); + ERR_FAIL_COND_V(!SetHandleInformation(pipe[0], HANDLE_FLAG_INHERIT, 0), ERR_CANT_FORK); // Read handle is for host process only and should not be inherited. + + pi.si.dwFlags |= STARTF_USESTDHANDLES; + pi.si.hStdOutput = pipe[1]; if (read_stderr) { - command += " 2>&1"; // Include stderr + pi.si.hStdError = pipe[1]; } - // Add extra quotes around the full command, to prevent it from stripping quotes in the command, - // because _wpopen calls command as "cmd.exe /c command", instead of executing it directly - command = _quote_command_line_argument(command); - - FILE *f = _wpopen((LPCWSTR)(command.utf16().get_data()), L"r"); - ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot create pipe from command: " + command); - char buf[65535]; - while (fgets(buf, 65535, f)) { - if (p_pipe_mutex) { - p_pipe_mutex->lock(); + inherit_handles = true; + } + DWORD creation_flags = NORMAL_PRIORITY_CLASS; + if (p_open_console) { + creation_flags |= CREATE_NEW_CONSOLE; + } else { + creation_flags |= CREATE_NO_WINDOW; + } + + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi); + if (!ret && r_pipe) { + CloseHandle(pipe[0]); // Cleanup pipe handles. + CloseHandle(pipe[1]); + } + ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); + + if (r_pipe) { + CloseHandle(pipe[1]); // Close pipe write handle (only child process is writing). + + LocalVector<char> bytes; + int bytes_in_buffer = 0; + + const int CHUNK_SIZE = 4096; + DWORD read = 0; + for (;;) { // Read StdOut and StdErr from pipe. + bytes.resize(bytes_in_buffer + CHUNK_SIZE); + const bool success = ReadFile(pipe[0], bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL); + if (!success || read == 0) { + break; } - (*r_pipe) += String::utf8(buf); - if (p_pipe_mutex) { - p_pipe_mutex->unlock(); + + // Assume that all possible encodings are ASCII-compatible. + // Break at newline to allow receiving long output in portions. + int newline_index = -1; + for (int i = read - 1; i >= 0; i--) { + if (bytes[bytes_in_buffer + i] == '\n') { + newline_index = i; + break; + } + } + if (newline_index == -1) { + bytes_in_buffer += read; + continue; } - } - int rv = _pclose(f); - if (r_exitcode) { - *r_exitcode = rv; - } - return OK; - } + const int bytes_to_convert = bytes_in_buffer + (newline_index + 1); + _append_to_pipe(bytes.ptr(), bytes_to_convert, r_pipe, p_pipe_mutex); - ProcessInfo pi; - ZeroMemory(&pi.si, sizeof(pi.si)); - pi.si.cb = sizeof(pi.si); - ZeroMemory(&pi.pi, sizeof(pi.pi)); - LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; + bytes_in_buffer = read - (newline_index + 1); + memmove(bytes.ptr(), bytes.ptr() + bytes_to_convert, bytes_in_buffer); + } - DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS; -#ifndef DEBUG_ENABLED - dwCreationFlags |= CREATE_NO_WINDOW; -#endif + if (bytes_in_buffer > 0) { + _append_to_pipe(bytes.ptr(), bytes_in_buffer, r_pipe, p_pipe_mutex); + } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, dwCreationFlags, nullptr, nullptr, si_w, &pi.pi); - ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); + CloseHandle(pipe[0]); // Close pipe read handle. + } else { + WaitForSingleObject(pi.pi.hProcess, INFINITE); + } - WaitForSingleObject(pi.pi.hProcess, INFINITE); if (r_exitcode) { DWORD ret2; GetExitCodeProcess(pi.pi.hProcess, &ret2); *r_exitcode = ret2; } + CloseHandle(pi.pi.hProcess); CloseHandle(pi.pi.hThread); return OK; -}; - -bool OS_Windows::_is_win11_terminal() const { - HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD dwMode = 0; - if (GetConsoleMode(hStdOut, &dwMode)) { - return ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING); - } else { - return false; - } } -Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) { +Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); for (const String &E : p_arguments) { @@ -490,16 +528,14 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS; -#ifndef DEBUG_ENABLED - dwCreationFlags |= CREATE_NO_WINDOW; -#endif - if (p_path == get_executable_path() && GetConsoleWindow() != nullptr && _is_win11_terminal()) { - // Open a new terminal as a workaround for Windows Terminal bug. - dwCreationFlags |= CREATE_NEW_CONSOLE; + DWORD creation_flags = NORMAL_PRIORITY_CLASS; + if (p_open_console) { + creation_flags |= CREATE_NEW_CONSOLE; + } else { + creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, dwCreationFlags, nullptr, nullptr, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi); ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); ProcessID pid = pi.pi.dwProcessId; @@ -509,7 +545,7 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg process_map->insert(pid, pi); return OK; -}; +} Error OS_Windows::kill(const ProcessID &p_pid) { ERR_FAIL_COND_V(!process_map->has(p_pid), FAILED); @@ -523,15 +559,35 @@ Error OS_Windows::kill(const ProcessID &p_pid) { CloseHandle(pi.hThread); return ret != 0 ? OK : FAILED; -}; +} int OS_Windows::get_process_id() const { return _getpid(); } +bool OS_Windows::is_process_running(const ProcessID &p_pid) const { + if (!process_map->has(p_pid)) { + return false; + } + + const PROCESS_INFORMATION &pi = (*process_map)[p_pid].pi; + + DWORD dw_exit_code = 0; + if (!GetExitCodeProcess(pi.hProcess, &dw_exit_code)) { + return false; + } + + if (dw_exit_code != STILL_ACTIVE) { + return false; + } + + return true; +} + Error OS_Windows::set_cwd(const String &p_cwd) { - if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) + if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) { return ERR_CANT_OPEN; + } return OK; } @@ -554,7 +610,7 @@ bool OS_Windows::has_environment(const String &p_var) const { free(env); return has_env; #endif -}; +} String OS_Windows::get_environment(const String &p_var) const { WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum @@ -573,7 +629,7 @@ String OS_Windows::get_stdin_string(bool p_block) { if (p_block) { char buff[1024]; return fgets(buff, 1024, stdin); - }; + } return String(); } @@ -611,17 +667,20 @@ String OS_Windows::get_locale() const { int sublang = SUBLANGID(langid); while (wl->locale) { - if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL) + if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL) { neutral = wl->locale; + } - if (lang == wl->main_lang && sublang == wl->sublang) + if (lang == wl->main_lang && sublang == wl->sublang) { return String(wl->locale).replace("-", "_"); + } wl++; } - if (!neutral.is_empty()) + if (!neutral.is_empty()) { return String(neutral).replace("-", "_"); + } return "en"; } @@ -648,25 +707,48 @@ BOOL is_wow64() { int OS_Windows::get_processor_count() const { SYSTEM_INFO sysinfo; - if (is_wow64()) + if (is_wow64()) { GetNativeSystemInfo(&sysinfo); - else + } else { GetSystemInfo(&sysinfo); + } return sysinfo.dwNumberOfProcessors; } +String OS_Windows::get_processor_name() const { + const String id = "Hardware\\Description\\System\\CentralProcessor\\0"; + + HKEY hkey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); + } + + WCHAR buffer[256]; + DWORD buffer_len = 256; + DWORD vtype = REG_SZ; + if (RegQueryValueExW(hkey, L"ProcessorNameString", NULL, &vtype, (LPBYTE)buffer, &buffer_len) == ERROR_SUCCESS) { + RegCloseKey(hkey); + return String::utf16((const char16_t *)buffer, buffer_len).strip_edges(); + } else { + RegCloseKey(hkey); + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); + } +} + void OS_Windows::run() { - if (!main_loop) + if (!main_loop) { return; + } main_loop->initialize(); while (!force_quit) { DisplayServer::get_singleton()->process_events(); // get rid of pending events - if (Main::iteration()) + if (Main::iteration()) { break; - }; + } + } main_loop->finalize(); } @@ -675,6 +757,58 @@ MainLoop *OS_Windows::get_main_loop() const { return main_loop; } +uint64_t OS_Windows::get_embedded_pck_offset() const { + Ref<FileAccess> f = FileAccess::open(get_executable_path(), FileAccess::READ); + if (f.is_null()) { + return 0; + } + + // Process header. + { + f->seek(0x3c); + uint32_t pe_pos = f->get_32(); + + f->seek(pe_pos); + uint32_t magic = f->get_32(); + if (magic != 0x00004550) { + return 0; + } + } + + int num_sections; + { + int64_t header_pos = f->get_position(); + + f->seek(header_pos + 2); + num_sections = f->get_16(); + f->seek(header_pos + 16); + uint16_t opt_header_size = f->get_16(); + + // Skip rest of header + optional header to go to the section headers. + f->seek(f->get_position() + 2 + opt_header_size); + } + int64_t section_table_pos = f->get_position(); + + // Search for the "pck" section. + int64_t off = 0; + for (int i = 0; i < num_sections; ++i) { + int64_t section_header_pos = section_table_pos + i * 40; + f->seek(section_header_pos); + + uint8_t section_name[9]; + f->get_buffer(section_name, 8); + section_name[8] = '\0'; + + if (strcmp((char *)section_name, "pck") == 0) { + f->seek(section_header_pos + 20); + off = f->get_32(); + break; + } + } + + return off; +} + String OS_Windows::get_config_path() const { // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_CONFIG_HOME")) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 086f63eb3e..378438a075 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,8 +40,6 @@ #include "drivers/winmidi/midi_driver_winmidi.h" #include "key_mapping_windows.h" #include "servers/audio_server.h" -#include "servers/rendering/renderer_compositor.h" -#include "servers/rendering_server.h" #ifdef XAUDIO2_ENABLED #include "drivers/xaudio2/audio_driver_xaudio2.h" #endif @@ -62,14 +60,14 @@ class JoypadWindows; class OS_Windows : public OS { #ifdef STDOUT_FILE - FILE *stdo; + FILE *stdo = nullptr; #endif uint64_t ticks_start; uint64_t ticks_per_second; HINSTANCE hInstance; - MainLoop *main_loop; + MainLoop *main_loop = nullptr; #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; @@ -108,7 +106,9 @@ protected: public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; + virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override; + + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; @@ -128,10 +128,11 @@ public: virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override; - virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; - virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override; + virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; + virtual bool is_process_running(const ProcessID &p_pid) const override; virtual bool has_environment(const String &p_var) const override; virtual String get_environment(const String &p_var) const override; @@ -142,6 +143,9 @@ public: virtual String get_locale() const override; virtual int get_processor_count() const override; + virtual String get_processor_name() const override; + + virtual uint64_t get_embedded_pck_offset() const override; virtual String get_config_path() const override; virtual String get_data_path() const override; @@ -157,7 +161,6 @@ public: void run(); - bool _is_win11_terminal() const; virtual bool _check_internal_feature_support(const String &p_feature) override; virtual void disable_crash_handler() override; diff --git a/platform/windows/platform_config.h b/platform/windows/platform_config.h index dace0f86af..8e80f8cacb 100644 --- a/platform/windows/platform_config.h +++ b/platform/windows/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/tts_windows.cpp b/platform/windows/tts_windows.cpp new file mode 100644 index 0000000000..05249934ba --- /dev/null +++ b/platform/windows/tts_windows.cpp @@ -0,0 +1,269 @@ +/*************************************************************************/ +/* tts_windows.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "tts_windows.h" + +TTS_Windows *TTS_Windows::singleton = nullptr; + +void __stdcall TTS_Windows::speech_event_callback(WPARAM wParam, LPARAM lParam) { + TTS_Windows *tts = TTS_Windows::get_singleton(); + SPEVENT event; + while (tts->synth->GetEvents(1, &event, NULL) == S_OK) { + if (tts->ids.has(event.ulStreamNum)) { + if (event.eEventId == SPEI_START_INPUT_STREAM) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, tts->ids[event.ulStreamNum].id); + } else if (event.eEventId == SPEI_END_INPUT_STREAM) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, tts->ids[event.ulStreamNum].id); + tts->ids.erase(event.ulStreamNum); + tts->_update_tts(); + } else if (event.eEventId == SPEI_WORD_BOUNDARY) { + const Char16String &string = tts->ids[event.ulStreamNum].string; + int pos = 0; + for (int i = 0; i < MIN(event.lParam, string.length()); i++) { + char16_t c = string[i]; + if ((c & 0xfffffc00) == 0xd800) { + i++; + } + pos++; + } + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, tts->ids[event.ulStreamNum].id, pos - tts->ids[event.ulStreamNum].offset); + } + } + } +} + +void TTS_Windows::_update_tts() { + if (!is_speaking() && !paused && queue.size() > 0) { + DisplayServer::TTSUtterance &message = queue.front()->get(); + + String text; + DWORD flags = SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_XML; + String pitch_tag = String("<pitch absmiddle=\"") + String::num_int64(message.pitch * 10 - 10, 10) + String("\">"); + text = pitch_tag + message.text + String("</pitch>"); + + IEnumSpObjectTokens *cpEnum; + ISpObjectToken *cpVoiceToken; + ULONG ulCount = 0; + ULONG stream_number = 0; + ISpObjectTokenCategory *cpCategory; + HRESULT hr = CoCreateInstance(CLSID_SpObjectTokenCategory, nullptr, CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (void **)&cpCategory); + if (SUCCEEDED(hr)) { + hr = cpCategory->SetId(SPCAT_VOICES, false); + if (SUCCEEDED(hr)) { + hr = cpCategory->EnumTokens(nullptr, nullptr, &cpEnum); + if (SUCCEEDED(hr)) { + hr = cpEnum->GetCount(&ulCount); + while (SUCCEEDED(hr) && ulCount--) { + wchar_t *w_id = 0L; + hr = cpEnum->Next(1, &cpVoiceToken, nullptr); + cpVoiceToken->GetId(&w_id); + if (String::utf16((const char16_t *)w_id) == message.voice) { + synth->SetVoice(cpVoiceToken); + cpVoiceToken->Release(); + break; + } + cpVoiceToken->Release(); + } + cpEnum->Release(); + } + } + cpCategory->Release(); + } + + UTData ut; + ut.string = text.utf16(); + ut.offset = pitch_tag.length(); // Substract injected <pitch> tag offset. + ut.id = message.id; + + synth->SetVolume(message.volume); + synth->SetRate(10.f * log10(message.rate) / log10(3.f)); + synth->Speak((LPCWSTR)ut.string.get_data(), flags, &stream_number); + + ids[stream_number] = ut; + + queue.pop_front(); + } +} + +bool TTS_Windows::is_speaking() const { + ERR_FAIL_COND_V(!synth, false); + + SPVOICESTATUS status; + synth->GetStatus(&status, nullptr); + return (status.dwRunningState == SPRS_IS_SPEAKING); +} + +bool TTS_Windows::is_paused() const { + ERR_FAIL_COND_V(!synth, false); + return paused; +} + +Array TTS_Windows::get_voices() const { + Array list; + IEnumSpObjectTokens *cpEnum; + ISpObjectToken *cpVoiceToken; + ISpDataKey *cpDataKeyAttribs; + ULONG ulCount = 0; + ISpObjectTokenCategory *cpCategory; + HRESULT hr = CoCreateInstance(CLSID_SpObjectTokenCategory, nullptr, CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (void **)&cpCategory); + if (SUCCEEDED(hr)) { + hr = cpCategory->SetId(SPCAT_VOICES, false); + if (SUCCEEDED(hr)) { + hr = cpCategory->EnumTokens(nullptr, nullptr, &cpEnum); + if (SUCCEEDED(hr)) { + hr = cpEnum->GetCount(&ulCount); + while (SUCCEEDED(hr) && ulCount--) { + hr = cpEnum->Next(1, &cpVoiceToken, nullptr); + HRESULT hr_attr = cpVoiceToken->OpenKey(SPTOKENKEY_ATTRIBUTES, &cpDataKeyAttribs); + if (SUCCEEDED(hr_attr)) { + wchar_t *w_id = nullptr; + wchar_t *w_lang = nullptr; + wchar_t *w_name = nullptr; + cpVoiceToken->GetId(&w_id); + cpDataKeyAttribs->GetStringValue(L"Language", &w_lang); + cpDataKeyAttribs->GetStringValue(nullptr, &w_name); + LCID locale = wcstol(w_lang, nullptr, 16); + + int locale_chars = GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, nullptr, 0); + int region_chars = GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, nullptr, 0); + wchar_t *w_lang_code = new wchar_t[locale_chars]; + wchar_t *w_reg_code = new wchar_t[region_chars]; + GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, w_lang_code, locale_chars); + GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, w_reg_code, region_chars); + + Dictionary voice_d; + voice_d["id"] = String::utf16((const char16_t *)w_id); + if (w_name) { + voice_d["name"] = String::utf16((const char16_t *)w_name); + } else { + voice_d["name"] = voice_d["id"].operator String().replace("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\", ""); + } + voice_d["language"] = String::utf16((const char16_t *)w_lang_code) + "_" + String::utf16((const char16_t *)w_reg_code); + list.push_back(voice_d); + + delete[] w_lang_code; + delete[] w_reg_code; + + cpDataKeyAttribs->Release(); + } + cpVoiceToken->Release(); + } + cpEnum->Release(); + } + } + cpCategory->Release(); + } + return list; +} + +void TTS_Windows::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + ERR_FAIL_COND(!synth); + if (p_interrupt) { + stop(); + } + + if (p_text.is_empty()) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id); + return; + } + + DisplayServer::TTSUtterance message; + message.text = p_text; + message.voice = p_voice; + message.volume = CLAMP(p_volume, 0, 100); + message.pitch = CLAMP(p_pitch, 0.f, 2.f); + message.rate = CLAMP(p_rate, 0.1f, 10.f); + message.id = p_utterance_id; + queue.push_back(message); + + if (is_paused()) { + resume(); + } else { + _update_tts(); + } +} + +void TTS_Windows::pause() { + ERR_FAIL_COND(!synth); + if (!paused) { + if (synth->Pause() == S_OK) { + paused = true; + } + } +} + +void TTS_Windows::resume() { + ERR_FAIL_COND(!synth); + synth->Resume(); + paused = false; +} + +void TTS_Windows::stop() { + ERR_FAIL_COND(!synth); + + SPVOICESTATUS status; + synth->GetStatus(&status, nullptr); + if (ids.has(status.ulCurrentStream)) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[status.ulCurrentStream].id); + ids.erase(status.ulCurrentStream); + } + for (DisplayServer::TTSUtterance &message : queue) { + DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id); + } + queue.clear(); + synth->Speak(nullptr, SPF_PURGEBEFORESPEAK, nullptr); + synth->Resume(); + paused = false; +} + +TTS_Windows *TTS_Windows::get_singleton() { + return singleton; +} + +TTS_Windows::TTS_Windows() { + singleton = this; + CoInitialize(nullptr); + + if (SUCCEEDED(CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL, IID_ISpVoice, (void **)&synth))) { + ULONGLONG event_mask = SPFEI(SPEI_END_INPUT_STREAM) | SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_WORD_BOUNDARY); + synth->SetInterest(event_mask, event_mask); + synth->SetNotifyCallbackFunction(&speech_event_callback, (WPARAM)(this), 0); + print_verbose("Text-to-Speech: SAPI initialized."); + } else { + print_verbose("Text-to-Speech: Cannot initialize ISpVoice!"); + } +} + +TTS_Windows::~TTS_Windows() { + if (synth) { + synth->Release(); + } + singleton = nullptr; +} diff --git a/platform/windows/tts_windows.h b/platform/windows/tts_windows.h new file mode 100644 index 0000000000..5da404baf9 --- /dev/null +++ b/platform/windows/tts_windows.h @@ -0,0 +1,80 @@ +/*************************************************************************/ +/* tts_windows.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 TTS_WINDOWS_H +#define TTS_WINDOWS_H + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/variant/array.h" +#include "servers/display_server.h" + +#include <objbase.h> +#include <sapi.h> +#include <wchar.h> +#include <winnls.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +class TTS_Windows { + List<DisplayServer::TTSUtterance> queue; + ISpVoice *synth = nullptr; + bool paused = false; + struct UTData { + Char16String string; + int offset; + int id; + }; + Map<ULONG, UTData> ids; + + static void __stdcall speech_event_callback(WPARAM wParam, LPARAM lParam); + void _update_tts(); + + static TTS_Windows *singleton; + +public: + static TTS_Windows *get_singleton(); + + bool is_speaking() const; + bool is_paused() const; + Array get_voices() const; + + void speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false); + void pause(); + void resume(); + void stop(); + + TTS_Windows(); + ~TTS_Windows(); +}; + +#endif // TTS_WINDOWS_H diff --git a/platform/windows/vulkan_context_win.cpp b/platform/windows/vulkan_context_win.cpp index db5e6466be..07c41395fb 100644 --- a/platform/windows/vulkan_context_win.cpp +++ b/platform/windows/vulkan_context_win.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h index 61e66b8ae0..e68f0125ca 100644 --- a/platform/windows/vulkan_context_win.h +++ b/platform/windows/vulkan_context_win.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index d4148630f0..df21977698 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -44,25 +44,29 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er const unsigned int BUFFER_SIZE = 16384; char buf[BUFFER_SIZE + 1]; // +1 for the terminating character int len = vsnprintf(buf, BUFFER_SIZE, p_format, p_list); - if (len <= 0) + if (len <= 0) { return; - if ((unsigned int)len >= BUFFER_SIZE) + } + if ((unsigned int)len >= BUFFER_SIZE) { len = BUFFER_SIZE; // Output is too big, will be truncated + } buf[len] = 0; int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, len, nullptr, 0); - if (wlen < 0) + if (wlen < 0) { return; + } wchar_t *wbuf = (wchar_t *)memalloc((len + 1) * sizeof(wchar_t)); ERR_FAIL_NULL_MSG(wbuf, "Out of memory."); MultiByteToWideChar(CP_UTF8, 0, buf, len, wbuf, wlen); wbuf[wlen] = 0; - if (p_err) + if (p_err) { fwprintf(stderr, L"%ls", wbuf); - else + } else { wprintf(L"%ls", wbuf); + } memfree(wbuf); diff --git a/platform/windows/windows_terminal_logger.h b/platform/windows/windows_terminal_logger.h index 86b65ae30a..1045f12201 100644 --- a/platform/windows/windows_terminal_logger.h +++ b/platform/windows/windows_terminal_logger.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ |