summaryrefslogtreecommitdiff
path: root/platform/windows
diff options
context:
space:
mode:
Diffstat (limited to 'platform/windows')
-rw-r--r--platform/windows/SCsub1
-rw-r--r--platform/windows/crash_handler_windows.cpp72
-rw-r--r--platform/windows/crash_handler_windows.h4
-rw-r--r--platform/windows/detect.py22
-rw-r--r--platform/windows/display_server_windows.cpp769
-rw-r--r--platform/windows/display_server_windows.h52
-rw-r--r--platform/windows/export/export.cpp83
-rw-r--r--platform/windows/export/export.h4
-rw-r--r--platform/windows/export/export_plugin.cpp237
-rw-r--r--platform/windows/export/export_plugin.h20
-rw-r--r--platform/windows/gl_manager_windows.cpp43
-rw-r--r--platform/windows/gl_manager_windows.h17
-rw-r--r--platform/windows/godot.icobin110755 -> 359559 bytes
-rw-r--r--platform/windows/godot_windows.cpp20
-rw-r--r--platform/windows/joypad_windows.cpp104
-rw-r--r--platform/windows/joypad_windows.h13
-rw-r--r--platform/windows/key_mapping_windows.cpp321
-rw-r--r--platform/windows/key_mapping_windows.h4
-rw-r--r--platform/windows/lang_table.h4
-rw-r--r--platform/windows/os_windows.cpp432
-rw-r--r--platform/windows/os_windows.h23
-rw-r--r--platform/windows/platform_config.h4
-rw-r--r--platform/windows/tts_windows.cpp269
-rw-r--r--platform/windows/tts_windows.h80
-rw-r--r--platform/windows/vulkan_context_win.cpp4
-rw-r--r--platform/windows/vulkan_context_win.h4
-rw-r--r--platform/windows/windows_terminal_logger.cpp18
-rw-r--r--platform/windows/windows_terminal_logger.h4
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
index dd611e07da..25830ffdc6 100644
--- a/platform/windows/godot.ico
+++ b/platform/windows/godot.ico
Binary files differ
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 */