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.cpp44
-rw-r--r--platform/windows/detect.py21
-rw-r--r--platform/windows/display_server_windows.cpp436
-rw-r--r--platform/windows/display_server_windows.h59
-rw-r--r--platform/windows/export/export.cpp87
-rw-r--r--platform/windows/export/export.h2
-rw-r--r--platform/windows/export/export_plugin.cpp222
-rw-r--r--platform/windows/export/export_plugin.h13
-rw-r--r--platform/windows/gl_manager_windows.cpp122
-rw-r--r--platform/windows/gl_manager_windows.h15
-rw-r--r--platform/windows/godot.icobin110755 -> 359559 bytes
-rw-r--r--platform/windows/godot.natvis2
-rw-r--r--platform/windows/godot_windows.cpp22
-rw-r--r--platform/windows/joypad_windows.cpp10
-rw-r--r--platform/windows/joypad_windows.h4
-rw-r--r--platform/windows/key_mapping_windows.cpp17
-rw-r--r--platform/windows/os_windows.cpp320
-rw-r--r--platform/windows/os_windows.h45
-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.h6
-rw-r--r--platform/windows/windows_terminal_logger.h2
24 files changed, 1464 insertions, 339 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 3b2c6fe9f6..6ce10e6f0f 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
@@ -129,13 +130,28 @@ 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");
+ }
+ // 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)) {
return EXCEPTION_CONTINUE_SEARCH;
@@ -174,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).is_empty()) {
- fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
- } else {
- fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
- }
- fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
-
int n = 0;
do {
if (skip_first) {
@@ -197,12 +199,12 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
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);
+ print_error(vformat("[%d] %s (%s:%d)", n, fnName.c_str(), (char *)line.FileName, (int)line.LineNumber));
} else {
- fprintf(stderr, "[%d] %s\n", n, fnName.c_str());
+ print_error(vformat("[%d] %s", n, fnName.c_str()));
}
} else {
- fprintf(stderr, "[%d] ???\n", n);
+ print_error(vformat("[%d] ???", n));
}
n++;
@@ -213,8 +215,8 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
}
} 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);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 249a0d2e79..dd2df1f004 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -198,7 +198,6 @@ def configure_msvc(env, manual_msvc_config):
elif env["target"] == "debug":
env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od", "/EHsc"])
# Allow big objects. Only needed for debug, see MinGW branch for rationale.
- env.AppendUnique(CCFLAGS=["/bigobj"])
env.Append(LINKFLAGS=["/DEBUG"])
if env["debug_symbols"]:
@@ -221,6 +220,10 @@ def configure_msvc(env, manual_msvc_config):
env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
+ # Once it was thought that only debug builds would be too large,
+ # but this has recently stopped being true. See the mingw function
+ # for notes on why this shouldn't be enabled for gcc
+ env.AppendUnique(CCFLAGS=["/bigobj"])
if manual_msvc_config: # should be automatic if SCons found it
if os.getenv("WindowsSdkDir") is not None:
@@ -252,6 +255,7 @@ def configure_msvc(env, manual_msvc_config):
"kernel32",
"ole32",
"oleaut32",
+ "sapi",
"user32",
"gdi32",
"IPHLPAPI",
@@ -266,14 +270,17 @@ def configure_msvc(env, manual_msvc_config):
"bcrypt",
"Avrt",
"dwmapi",
+ "dwrite",
]
- env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"])
- if not env["use_volk"]:
- LIBS += ["vulkan"]
+ if env["vulkan"]:
+ env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"])
+ if not env["use_volk"]:
+ LIBS += ["vulkan"]
- env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
- LIBS += ["opengl32"]
+ if env["opengl3"]:
+ env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
+ LIBS += ["opengl32"]
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
@@ -426,6 +433,7 @@ def configure_mingw(env):
"ws2_32",
"kernel32",
"oleaut32",
+ "sapi",
"dinput8",
"dxguid",
"ksuser",
@@ -434,6 +442,7 @@ def configure_mingw(env):
"avrt",
"uuid",
"dwmapi",
+ "dwrite",
]
)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 6b4b342389..f9988b23bc 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -84,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;
@@ -133,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_
@@ -150,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)) {
@@ -158,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);
@@ -430,9 +466,8 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau
}
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;
@@ -546,6 +581,9 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
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);
@@ -563,13 +601,17 @@ 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) {
+ if (wd.no_focus || wd.is_popup) {
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
+ ShowWindow(wd.hWnd, SW_SHOWNA);
+ } else {
+ ShowWindow(wd.hWnd, SW_SHOW);
SetForegroundWindow(wd.hWnd); // Slightly higher priority.
SetFocus(wd.hWnd); // Set keyboard focus.
}
@@ -581,10 +623,12 @@ 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()) {
- window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);
+ window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
}
if (wd.transient_parent != INVALID_WINDOW_ID) {
@@ -612,7 +656,9 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
void DisplayServerWindows::gl_window_make_current(DisplayServer::WindowID p_window_id) {
#if defined(GLES3_ENABLED)
- gl_manager->window_make_current(p_window_id);
+ if (gl_manager) {
+ gl_manager->window_make_current(p_window_id);
+ }
#endif
}
@@ -838,8 +884,8 @@ void DisplayServerWindows::window_set_exclusive(WindowID p_window, bool p_exclus
if (wd.exclusive != p_exclusive) {
wd.exclusive = p_exclusive;
if (wd.transient_parent != INVALID_WINDOW_ID) {
- WindowData &wd_parent = windows[wd.transient_parent];
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);
@@ -1019,6 +1065,7 @@ 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) {
@@ -1037,11 +1084,15 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
}
}
- 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;
}
@@ -1055,12 +1106,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.multiwindow_fs, 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;
@@ -1211,6 +1266,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");
@@ -1224,6 +1280,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;
}
@@ -1250,6 +1311,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;
}
@@ -1261,7 +1325,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);
@@ -1278,7 +1342,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 {
@@ -1433,11 +1499,11 @@ 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()) {
- Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
+ RBMap<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
if (cursor_c) {
if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
@@ -1540,13 +1606,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);
@@ -1742,15 +1803,17 @@ void DisplayServerWindows::make_rendering_thread() {
void DisplayServerWindows::swap_buffers() {
#if defined(GLES3_ENABLED)
- gl_manager->swap_buffers();
+ if (gl_manager) {
+ gl_manager->swap_buffers();
+ }
#endif
}
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;
@@ -1836,7 +1899,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);
}
@@ -1844,9 +1906,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();
@@ -1895,16 +1959,18 @@ 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);
+ if (context_vulkan) {
+ 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);
+ if (context_vulkan) {
+ return context_vulkan->get_vsync_mode(p_window);
+ }
#endif
return DisplayServer::VSYNC_ENABLED;
}
@@ -1942,7 +2008,7 @@ void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float
}
void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) {
- Map<int, Vector2>::Element *curr = touch_state.find(idx);
+ RBMap<int, Vector2>::Element *curr = touch_state.find(idx);
if (!curr) {
return;
}
@@ -1974,7 +2040,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) {
@@ -1989,35 +2055,193 @@ 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;
}
-// Our default window procedure to handle processing of window-related system messages/events.
-// Also known as DefProc or DefWindowProc.
+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);
+
+ if (win_id != p_window) {
+ // Only request close on related windows, not this window. We are already processing it.
+ _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_RBUTTONDOWN:
+ 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);
+ return 1;
+ }
+ } break;
+ }
+ }
+ return ::CallNextHookEx(mouse_monitor, code, wParam, lParam);
+}
+
+// Handle a single window message received while CreateWindowEx is still on the stack and our data
+// structures are not fully initialized.
+LRESULT DisplayServerWindows::_handle_early_window_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ switch (uMsg) {
+ case WM_GETMINMAXINFO: {
+ // We receive this during CreateWindowEx and we haven't initialized the window
+ // struct, so let Windows figure out the maximized size.
+ // Silently forward to user/default.
+ } break;
+ case WM_NCCREATE: {
+ // We tunnel an unowned pointer to our window context (WindowData) through the
+ // first possible message (WM_NCCREATE) to fix up our window context collection.
+ CREATESTRUCTW *pCreate = (CREATESTRUCTW *)lParam;
+ WindowData *pWindowData = reinterpret_cast<WindowData *>(pCreate->lpCreateParams);
+
+ // Fix this up so we can recognize the remaining messages.
+ pWindowData->hWnd = hWnd;
+ } break;
+ default: {
+ // Additional messages during window creation should happen after we fixed
+ // up the data structures on WM_NCCREATE, but this might change in the future,
+ // so report an error here and then we can implement them.
+ ERR_PRINT_ONCE(vformat("Unexpected window message 0x%x received for window we cannot recognize in our collection; sequence error.", uMsg));
+ } break;
+ }
+
+ if (user_proc) {
+ return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
+ }
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+}
+
+// The window procedure for our window class "Engine", used to handle processing of window-related system messages/events.
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures
LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (drop_events) {
@@ -2031,7 +2255,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
WindowID window_id = INVALID_WINDOW_ID;
bool window_created = false;
- // Check whether window exists.
+ // Check whether window exists
+ // FIXME this is O(n), where n is the set of currently open windows and subwindows
+ // we should have a secondary map from HWND to WindowID or even WindowData* alias, if we want to eliminate all the map lookups below
for (const KeyValue<WindowID, WindowData> &E : windows) {
if (E.value.hWnd == hWnd) {
window_id = E.key;
@@ -2040,14 +2266,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
- // Window doesn't exist or creation in progress, don't handle messages yet.
+ // WARNING: we get called with events before the window is registered in our collection
+ // specifically, even the call to CreateWindowEx already calls here while still on the stack,
+ // so there is no way to store the window handle in our collection before we get here
if (!window_created) {
- window_id = window_id_counter;
- ERR_FAIL_COND_V(!windows.has(window_id), 0);
+ // don't let code below operate on incompletely initialized window objects or missing window_id
+ return _handle_early_window_message(hWnd, uMsg, wParam, lParam);
}
// 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;
@@ -2147,7 +2382,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
case WM_MOUSELEAVE: {
old_invalid = true;
- outside = true;
+ windows[window_id].mouse_outside = true;
_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);
@@ -2257,6 +2492,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
windows[window_id].last_tilt = Vector2();
}
+ windows[window_id].last_pen_inverted = packet.pkStatus & TPS_INVERT;
+
POINT coords;
GetCursorPos(&coords);
ScreenToClient(windows[window_id].hWnd, &coords);
@@ -2275,6 +2512,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_pressure(windows[window_id].last_pressure);
mm->set_tilt(windows[window_id].last_tilt);
+ mm->set_pen_inverted(windows[window_id].last_pen_inverted);
mm->set_button_mask(last_button_state);
@@ -2309,7 +2547,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);
}
}
@@ -2374,7 +2612,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
- if (outside) {
+ if (windows[window_id].mouse_outside) {
// Mouse enter.
if (mouse_mode != MOUSE_MODE_CAPTURED) {
@@ -2384,7 +2622,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
- outside = false;
+ windows[window_id].mouse_outside = false;
// Once-off notification, must call again.
track_mouse_leave_event(hWnd);
@@ -2407,6 +2645,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if ((pen_info.penMask & PEN_MASK_TILT_X) && (pen_info.penMask & PEN_MASK_TILT_Y)) {
mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90));
}
+ mm->set_pen_inverted(pen_info.penFlags & (PEN_FLAG_INVERTED | PEN_FLAG_ERASER));
mm->set_ctrl_pressed(GetKeyState(VK_CONTROL) < 0);
mm->set_shift_pressed(GetKeyState(VK_SHIFT) < 0);
@@ -2451,7 +2690,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);
}
@@ -2474,7 +2713,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
- if (outside) {
+ if (windows[window_id].mouse_outside) {
// Mouse enter.
if (mouse_mode != MOUSE_MODE_CAPTURED) {
@@ -2484,7 +2723,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
- outside = false;
+ windows[window_id].mouse_outside = false;
// Once-off notification, must call again.
track_mouse_leave_event(hWnd);
@@ -2509,14 +2748,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} else {
windows[window_id].last_tilt = Vector2();
windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+ windows[window_id].last_pen_inverted = false;
}
} else {
windows[window_id].last_tilt = Vector2();
windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+ windows[window_id].last_pen_inverted = false;
}
mm->set_pressure(windows[window_id].last_pressure);
mm->set_tilt(windows[window_id].last_tilt);
+ mm->set_pen_inverted(windows[window_id].last_pen_inverted);
mm->set_button_mask(last_button_state);
@@ -2551,7 +2793,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);
}
@@ -2711,7 +2953,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;
@@ -3127,8 +3369,8 @@ void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const
if ((p_new_driver == "wintab") && wintab_available) {
wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
wd.wtlc.lcOptions |= CXO_MESSAGES;
- wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
- wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+ wd.wtlc.lcPktData = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+ wd.wtlc.lcMoveMask = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
wd.wtlc.lcPktMode = 0;
wd.wtlc.lcOutOrgX = 0;
wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
@@ -3204,11 +3446,23 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowRect.top,
WindowRect.right - WindowRect.left,
WindowRect.bottom - WindowRect.top,
- nullptr, nullptr, hInstance, nullptr);
+ nullptr,
+ nullptr,
+ hInstance,
+ // tunnel the WindowData we need to handle creation message
+ // lifetime is ensured because we are still on the stack when this is
+ // processed in the window proc
+ reinterpret_cast<void *>(&wd));
if (!wd.hWnd) {
MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
windows.erase(id);
- return INVALID_WINDOW_ID;
+ ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Windows OS window.");
+ }
+ if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
+ wd.fullscreen = true;
+ if (p_mode == WINDOW_MODE_FULLSCREEN) {
+ wd.multiwindow_fs = true;
+ }
}
if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.pre_fs_valid = true;
@@ -3228,7 +3482,14 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
#ifdef GLES3_ENABLED
if (gl_manager) {
Error err = gl_manager->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top);
- ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGL window.");
+
+ // shut down OpenGL, to mirror behavior of Vulkan code
+ if (err != OK) {
+ memdelete(gl_manager);
+ gl_manager = nullptr;
+ windows.erase(id);
+ ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create an OpenGL window.");
+ }
}
#endif
@@ -3238,8 +3499,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
if ((tablet_get_current_driver() == "wintab") && wintab_available) {
wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
wd.wtlc.lcOptions |= CXO_MESSAGES;
- wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
- wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+ wd.wtlc.lcPktData = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+ wd.wtlc.lcMoveMask = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
wd.wtlc.lcPktMode = 0;
wd.wtlc.lcOutOrgX = 0;
wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
@@ -3273,6 +3534,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
ImmReleaseContext(wd.hWnd, wd.im_himc);
wd.im_position = Vector2();
+
+ // FIXME this is wrong in cases where the window coordinates were changed due to full screen mode; use WindowRect
wd.last_pos = p_rect.position;
wd.width = p_rect.size.width;
wd.height = p_rect.size.height;
@@ -3345,16 +3608,17 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
shift_mem = false;
control_mem = false;
meta_mem = false;
- hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance();
+ hInstance = static_cast<OS_Windows *>(OS::get_singleton())->get_hinstance();
pressrc = 0;
old_invalid = true;
mouse_mode = MOUSE_MODE_VISIBLE;
- outside = true;
-
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) {
@@ -3457,11 +3721,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);
@@ -3487,7 +3752,11 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
#endif
- 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);
@@ -3508,7 +3777,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);
}
@@ -3545,12 +3814,17 @@ 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?
+ // FIXME wglDeleteContext is never called
#endif
if (windows.has(MAIN_WINDOW_ID)) {
@@ -3588,4 +3862,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 7561f9bb77..ddbf674c64 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -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"
@@ -81,10 +82,13 @@
#define DVC_ROTATION 18
#define CXO_MESSAGES 0x0004
+#define PK_STATUS 0x0002
#define PK_NORMAL_PRESSURE 0x0400
#define PK_TANGENT_PRESSURE 0x0800
#define PK_ORIENTATION 0x1000
+#define TPS_INVERT 0x0010 /* 1.1 */
+
typedef struct tagLOGCONTEXTW {
WCHAR lcName[40];
UINT lcOptions;
@@ -136,6 +140,7 @@ typedef struct tagORIENTATION {
} ORIENTATION;
typedef struct tagPACKET {
+ int pkStatus;
int pkNormalPressure;
int pkTangentPressure;
ORIENTATION pkOrientation;
@@ -157,6 +162,14 @@ typedef UINT32 POINTER_FLAGS;
typedef UINT32 PEN_FLAGS;
typedef UINT32 PEN_MASK;
+#ifndef PEN_FLAG_INVERTED
+#define PEN_FLAG_INVERTED 0x00000002
+#endif
+
+#ifndef PEN_FLAG_ERASER
+#define PEN_FLAG_ERASER 0x00000004
+#endif
+
#ifndef PEN_MASK_PRESSURE
#define PEN_MASK_PRESSURE 0x00000001
#endif
@@ -300,7 +313,6 @@ class DisplayServerWindows : public DisplayServer {
int key_event_pos;
bool old_invalid;
- bool outside;
int old_x, old_y;
Point2i center;
@@ -313,13 +325,15 @@ class DisplayServerWindows : public DisplayServer {
RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
#endif
- Map<int, Vector2> touch_state;
+ RBMap<int, Vector2> touch_state;
int pressrc;
HINSTANCE hInstance; // Holds The Instance Of The Application
String rendering_driver;
bool app_focused = false;
+ TTS_Windows *tts = nullptr;
+
struct WindowData {
HWND hWnd;
//layered window
@@ -354,11 +368,13 @@ class DisplayServerWindows : public DisplayServer {
int min_pressure;
int max_pressure;
bool tilt_supported;
+ bool pen_inverted = false;
bool block_mm = false;
int last_pressure_update;
float last_pressure;
Vector2 last_tilt;
+ bool last_pen_inverted = false;
HBITMAP hBitmap; //DIB section for layered window
uint8_t *dib_data = nullptr;
@@ -370,6 +386,7 @@ class DisplayServerWindows : public DisplayServer {
Size2 window_rect;
Point2 last_pos;
+ bool mouse_outside = true;
ObjectID instance_id;
@@ -386,14 +403,21 @@ class DisplayServerWindows : public DisplayServer {
Callable drop_files_callback;
WindowID transient_parent = INVALID_WINDOW_ID;
- Set<WindowID> transient_children;
+ HashSet<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;
- Map<WindowID, WindowData> windows;
+ RBMap<WindowID, WindowData> windows;
WindowID last_focused_window = INVALID_WINDOW_ID;
@@ -420,7 +444,7 @@ class DisplayServerWindows : public DisplayServer {
HCURSOR cursors[CURSOR_MAX] = { nullptr };
CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
- Map<CursorShape, Vector<Variant>> cursors_cache;
+ RBMap<CursorShape, Vector<Variant>> cursors_cache;
void _drag_event(WindowID p_window, float p_x, float p_y, int idx);
void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx);
@@ -438,16 +462,31 @@ class DisplayServerWindows : public DisplayServer {
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void _dispatch_input_event(const Ref<InputEvent> &p_event);
+ LRESULT _handle_early_window_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
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;
@@ -474,6 +513,10 @@ 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;
@@ -535,7 +578,7 @@ public:
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 17a24c08bf..20320470b8 100644
--- a/platform/windows/export/export.cpp
+++ b/platform/windows/export/export.cpp
@@ -30,10 +30,9 @@
#include "export.h"
+#include "editor/export/editor_export.h"
#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"));
@@ -50,91 +49,9 @@ void register_windows_exporter() {
Ref<EditorExportPlatformWindows> platform;
platform.instantiate();
-
- Ref<Image> img = memnew(Image(_windows_logo));
- Ref<ImageTexture> logo;
- logo.instantiate();
- logo->create_from_image(img);
- platform->set_logo(logo);
+ platform->set_logo(ImageTexture::create_from_image(memnew(Image(_windows_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 09399f2bee..1054e04b1e 100644
--- a/platform/windows/export/export.h
+++ b/platform/windows/export/export.h
@@ -33,4 +33,4 @@
void register_windows_exporter();
-#endif
+#endif // WINDOWS_EXPORT_H
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 5ebc930735..45bfc761fe 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -41,23 +41,79 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
}
+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);
+ if (f.is_null()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), vformat(TTR("Could not open file \"%s\"."), p_path));
+ return ERR_CANT_CREATE;
+ }
+
+ f->store_line("@echo off");
+ f->store_line("title \"" + p_app_name + "\"");
+ f->store_line("\"%~dp0" + p_pkg_name + "\" \"%*\"");
+ f->store_line("pause > nul");
+
+ 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")) {
+ _rcedit_add_data(p_preset, p_path);
+ }
+ return OK;
+}
+
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);
+ 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) {
+ _code_sign(p_preset, pck_path);
+ }
- if (err != OK) {
- return err;
+ 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);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path));
+ }
}
- _rcedit_add_data(p_preset, 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);
- if (p_preset->get("codesign/enable") && err == OK) {
- err = _code_sign(p_preset, p_path);
+ // 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";
+ if (_export_debug_script(p_preset, app_name, p_path.get_file(), scr_path) != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), TTR("Could not create console script."));
+ }
+ }
}
return err;
}
-bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+String EditorExportPlatformWindows::get_template_file_name(const String &p_target, const String &p_arch) const {
+ return "windows_" + p_target + "_" + p_arch + ".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 HashMap<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;
@@ -78,6 +134,7 @@ 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.0"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
@@ -88,17 +145,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()) {
- WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable.");
- return;
+ if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find rcedit executable at \"%s\"."), rcedit_path));
+ 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
@@ -106,8 +162,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;
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find wine executable at \"%s\"."), wine_path));
+ return ERR_FILE_NOT_FOUND;
}
if (wine_path.is_empty()) {
@@ -165,13 +221,26 @@ 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);
+ if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit)."));
+ return err;
+ }
+ print_line("rcedit (" + p_path + "): " + str);
+
+ if (str.find("Fatal error") != -1) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("rcedit failed to modify executable:\n%s"), str));
+ return FAILED;
+ }
+
+ return OK;
}
Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
@@ -180,7 +249,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#ifdef WINDOWS_ENABLED
String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool");
if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {
- ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting.");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find signtool executable at \"%s\"."), signtool_path));
return ERR_FILE_NOT_FOUND;
}
if (signtool_path.is_empty()) {
@@ -189,7 +258,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#else
String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode");
if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {
- ERR_PRINT("Could not find osslsigncode executable at " + signtool_path + ", aborting.");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find osslsigncode executable at \"%s\"."), signtool_path));
return ERR_FILE_NOT_FOUND;
}
if (signtool_path.is_empty()) {
@@ -209,7 +278,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
args.push_back("/f");
args.push_back(p_preset->get("codesign/identity"));
} else {
- EditorNode::add_io_error("codesign: no identity found");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
return FAILED;
}
} else if (id_type == 2) { //Windows certificate store
@@ -217,11 +286,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
args.push_back("/sha1");
args.push_back(p_preset->get("codesign/identity"));
} else {
- EditorNode::add_io_error("codesign: no identity found");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
return FAILED;
}
} else {
- EditorNode::add_io_error("codesign: invalid identity type");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid identity type."));
return FAILED;
}
#else
@@ -229,7 +298,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
args.push_back("-pkcs12");
args.push_back(p_preset->get("codesign/identity"));
} else {
- EditorNode::add_io_error("codesign: no identity found");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
return FAILED;
}
#endif
@@ -261,7 +330,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
args.push_back(p_preset->get("codesign/timestamp_server_url"));
#endif
} else {
- EditorNode::add_io_error("codesign: invalid timestamp server");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid timestamp server."));
return FAILED;
}
}
@@ -308,7 +377,10 @@ 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);
+ if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool)."));
+ return err;
+ }
print_line("codesign (" + p_path + "): " + str);
#ifndef WINDOWS_ENABLED
@@ -316,17 +388,24 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
#else
if (str.find("Failed") != -1) {
#endif
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Signtool failed to sign executable:\n%s"), str));
return FAILED;
}
#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);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to remove temporary file \"%s\"."), p_path));
+ return err;
+ }
err = tmp_dir->rename(p_path + "_signed", p_path);
- ERR_FAIL_COND_V(err != OK, err);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to rename temporary file \"%s\"."), p_path + "_signed"));
+ return err;
+ }
#endif
return OK;
@@ -337,7 +416,7 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr
bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
- if (rcedit_path.is_empty()) {
+ 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";
}
@@ -374,3 +453,82 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr
return valid;
}
+
+Error EditorExportPlatformWindows::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
+
+ if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size
+ add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Windows executables cannot be >= 4 GiB."));
+ return ERR_INVALID_DATA;
+ }
+
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
+ if (f.is_null()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path));
+ 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) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted."));
+ 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;
+ }
+ }
+
+ if (!found) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found."));
+ return ERR_FILE_CORRUPT;
+ }
+ return OK;
+}
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 86e9d49b05..b9e59829a0 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -33,20 +33,25 @@
#include "core/io/file_access.h"
#include "core/os/os.h"
-#include "editor/editor_export.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export_platform_pc.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) 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 get_export_option_visibility(const String &p_option, const HashMap<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) override;
};
-#endif
+#endif // WINDOWS_EXPORT_PLUGIN_H
diff --git a/platform/windows/gl_manager_windows.cpp b/platform/windows/gl_manager_windows.cpp
index a97fa99d7f..d509ff8c51 100644
--- a/platform/windows/gl_manager_windows.cpp
+++ b/platform/windows/gl_manager_windows.cpp
@@ -54,6 +54,18 @@
typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *);
+static String format_error_message(DWORD id) {
+ LPWSTR messageBuffer = nullptr;
+ size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr);
+
+ String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size);
+
+ LocalFree(messageBuffer);
+
+ return msg;
+}
+
int GLManager_Windows::_find_or_create_display(GLWindow &win) {
// find display NYI, only 1 supported so far
if (_displays.size()) {
@@ -79,7 +91,7 @@ int GLManager_Windows::_find_or_create_display(GLWindow &win) {
return new_display_id;
}
-Error GLManager_Windows::_create_context(GLWindow &win, GLDisplay &gl_display) {
+static Error _configure_pixel_format(HDC hDC) {
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1,
@@ -101,9 +113,6 @@ Error GLManager_Windows::_create_context(GLWindow &win, GLDisplay &gl_display) {
0, 0, 0 // Layer Masks Ignored
};
- // alias
- HDC hDC = win.hDC;
-
int pixel_format = ChoosePixelFormat(hDC, &pfd);
if (!pixel_format) // Did Windows Find A Matching Pixel Format?
{
@@ -116,13 +125,24 @@ Error GLManager_Windows::_create_context(GLWindow &win, GLDisplay &gl_display) {
return ERR_CANT_CREATE; // Return FALSE
}
- gl_display.hRC = wglCreateContext(hDC);
+ return OK;
+}
+
+Error GLManager_Windows::_create_context(GLWindow &win, GLDisplay &gl_display) {
+ Error err = _configure_pixel_format(win.hDC);
+ if (err != OK) {
+ return err;
+ }
+
+ gl_display.hRC = wglCreateContext(win.hDC);
if (!gl_display.hRC) // Are We Able To Get A Rendering Context?
{
return ERR_CANT_CREATE; // Return FALSE
}
- wglMakeCurrent(hDC, gl_display.hRC);
+ if (!wglMakeCurrent(win.hDC, gl_display.hRC)) {
+ ERR_PRINT("Could not attach OpenGL context to newly created window: " + format_error_message(GetLastError()));
+ }
int attribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3, //we want a 3.3 context
@@ -143,57 +163,61 @@ Error GLManager_Windows::_create_context(GLWindow &win, GLDisplay &gl_display) {
return ERR_CANT_CREATE;
}
- HGLRC new_hRC = wglCreateContextAttribsARB(hDC, 0, attribs);
+ HGLRC new_hRC = wglCreateContextAttribsARB(win.hDC, 0, attribs);
if (!new_hRC) {
wglDeleteContext(gl_display.hRC);
gl_display.hRC = 0;
- return ERR_CANT_CREATE; // Return false
+ return ERR_CANT_CREATE;
}
- wglMakeCurrent(hDC, nullptr);
+
+ if (!wglMakeCurrent(win.hDC, nullptr)) {
+ ERR_PRINT("Could not detach OpenGL context from newly created window: " + format_error_message(GetLastError()));
+ }
+
wglDeleteContext(gl_display.hRC);
gl_display.hRC = new_hRC;
- if (!wglMakeCurrent(hDC, gl_display.hRC)) // Try To Activate The Rendering Context
+ if (!wglMakeCurrent(win.hDC, gl_display.hRC)) // Try To Activate The Rendering Context
{
+ ERR_PRINT("Could not attach OpenGL context to newly created window with replaced OpenGL context: " + format_error_message(GetLastError()));
wglDeleteContext(gl_display.hRC);
gl_display.hRC = 0;
- return ERR_CANT_CREATE; // Return FALSE
+ return ERR_CANT_CREATE;
}
return OK;
}
Error GLManager_Windows::window_create(DisplayServer::WindowID p_window_id, HWND p_hwnd, HINSTANCE p_hinstance, int p_width, int p_height) {
- HDC hdc = GetDC(p_hwnd);
- if (!hdc) {
- return ERR_CANT_CREATE; // Return FALSE
+ HDC hDC = GetDC(p_hwnd);
+ if (!hDC) {
+ return ERR_CANT_CREATE;
}
- // make sure vector is big enough...
- // we can mirror the external vector, it is simpler
- // to keep the IDs identical for fast lookup
- if (p_window_id >= (int)_windows.size()) {
- _windows.resize(p_window_id + 1);
+ // configure the HDC to use a compatible pixel format
+ Error result = _configure_pixel_format(hDC);
+ if (result != OK) {
+ return result;
}
- GLWindow &win = _windows[p_window_id];
- win.in_use = true;
- win.window_id = p_window_id;
+ GLWindow win;
win.width = p_width;
win.height = p_height;
win.hwnd = p_hwnd;
- win.hDC = hdc;
+ win.hDC = hDC;
win.gldisplay_id = _find_or_create_display(win);
if (win.gldisplay_id == -1) {
- // release DC?
- _windows.remove_at(_windows.size() - 1);
return FAILED;
}
+ // WARNING: p_window_id is an eternally growing integer since popup windows keep coming and going
+ // and each of them has a higher id than the previous, so it must be used in a map not a vector
+ _windows[p_window_id] = win;
+
// make current
- window_make_current(_windows.size() - 1);
+ window_make_current(p_window_id);
return OK;
}
@@ -217,11 +241,10 @@ int GLManager_Windows::window_get_height(DisplayServer::WindowID p_window_id) {
void GLManager_Windows::window_destroy(DisplayServer::WindowID p_window_id) {
GLWindow &win = get_window(p_window_id);
- win.in_use = false;
-
if (_current_window == &win) {
_current_window = nullptr;
}
+ _windows.erase(p_window_id);
}
void GLManager_Windows::release_current() {
@@ -229,7 +252,9 @@ void GLManager_Windows::release_current() {
return;
}
- wglMakeCurrent(_current_window->hDC, nullptr);
+ if (!wglMakeCurrent(_current_window->hDC, nullptr)) {
+ ERR_PRINT("Could not detach OpenGL context from window marked current: " + format_error_message(GetLastError()));
+ }
}
void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id) {
@@ -237,10 +262,8 @@ void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id)
return;
}
+ // crash if our data structures are out of sync, i.e. not found
GLWindow &win = _windows[p_window_id];
- if (!win.in_use) {
- return;
- }
// noop
if (&win == _current_window) {
@@ -248,7 +271,9 @@ void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id)
}
const GLDisplay &disp = get_display(win.gldisplay_id);
- wglMakeCurrent(win.hDC, disp.hRC);
+ if (!wglMakeCurrent(win.hDC, disp.hRC)) {
+ ERR_PRINT("Could not switch OpenGL context to other window: " + format_error_message(GetLastError()));
+ }
_internal_set_current_window(&win);
}
@@ -257,34 +282,19 @@ void GLManager_Windows::make_current() {
if (!_current_window) {
return;
}
- if (!_current_window->in_use) {
- WARN_PRINT("current window not in use!");
- return;
- }
const GLDisplay &disp = get_current_display();
- wglMakeCurrent(_current_window->hDC, disp.hRC);
+ if (!wglMakeCurrent(_current_window->hDC, disp.hRC)) {
+ ERR_PRINT("Could not switch OpenGL context to window marked current: " + format_error_message(GetLastError()));
+ }
}
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) {
- return;
+ // on other platforms, OpenGL swaps buffers for all windows (on all displays, really?)
+ // Windows swaps buffers on a per-window basis
+ // REVISIT: this could be structurally bad, should we have "dirty" flags then?
+ for (KeyValue<DisplayServer::WindowID, GLWindow> &entry : _windows) {
+ SwapBuffers(entry.value.hDC);
}
- if (!_current_window->in_use) {
- WARN_PRINT("current window not in use!");
- return;
- }
-
- // print_line("\tswap_buffers");
-
- // only for debugging without drawing anything
- // glClearColor(Math::randf(), 0, 1, 1);
- //glClear(GL_COLOR_BUFFER_BIT);
-
- // const GLDisplay &disp = get_current_display();
- SwapBuffers(_current_window->hDC);
}
Error GLManager_Windows::initialize() {
diff --git a/platform/windows/gl_manager_windows.h b/platform/windows/gl_manager_windows.h
index 6423c54855..5e43a3de2a 100644
--- a/platform/windows/gl_manager_windows.h
+++ b/platform/windows/gl_manager_windows.h
@@ -52,19 +52,14 @@ public:
private:
// any data specific to the window
struct GLWindow {
- GLWindow() { in_use = false; }
- bool in_use;
-
- // the external ID .. should match the GL window number .. unused I think
- DisplayServer::WindowID window_id;
- int width;
- int height;
+ int width = 0;
+ int height = 0;
// windows specific
HDC hDC;
HWND hwnd;
- int gldisplay_id;
+ int gldisplay_id = 0;
};
struct GLDisplay {
@@ -72,10 +67,10 @@ private:
HGLRC hRC;
};
- LocalVector<GLWindow> _windows;
+ RBMap<DisplayServer::WindowID, 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.natvis b/platform/windows/godot.natvis
index bb855e4ac8..899956d65a 100644
--- a/platform/windows/godot.natvis
+++ b/platform/windows/godot.natvis
@@ -41,10 +41,12 @@
<DisplayString Condition="type == Variant::AABB">{_data._aabb}</DisplayString>
<DisplayString Condition="type == Variant::BASIS">{_data._basis}</DisplayString>
<DisplayString Condition="type == Variant::TRANSFORM3D">{_data._transform}</DisplayString>
+ <DisplayString Condition="type == Variant::PROJECTION">{_data._projection}</DisplayString>
<DisplayString Condition="type == Variant::STRING">{*(String *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::VECTOR2">{*(Vector2 *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::RECT2">{*(Rect2 *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::VECTOR3">{*(Vector3 *)_data._mem}</DisplayString>
+ <DisplayString Condition="type == Variant::VECTOR4">{*(Vector4 *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::PLANE">{*(Plane *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::QUATERNION">{*(Quaternion *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString>
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index ad4e3ae77c..72920d2816 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -39,7 +39,18 @@
#ifndef TOOLS_ENABLED
#if defined _MSC_VER
#pragma section("pck", read)
-__declspec(allocate("pck")) static const char dummy[8] = { 0 };
+__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
@@ -140,11 +151,6 @@ int widechar_main(int argc, wchar_t **argv) {
setlocale(LC_CTYPE, "");
-#ifndef TOOLS_ENABLED
- // Workaround to prevent LTCG (MSVC LTO) from removing "pck" section
- const char *dummy_guard = dummy;
-#endif
-
char **argv_utf8 = new char *[argc];
for (int i = 0; i < argc; ++i) {
@@ -160,6 +166,10 @@ int widechar_main(int argc, wchar_t **argv) {
delete[] argv_utf8[i];
}
delete[] argv_utf8;
+
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return 0;
+ }
return 255;
}
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 494e0b9105..d039fd13a7 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -250,7 +250,7 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
}
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;
}
@@ -258,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;
}
@@ -404,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++) {
diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h
index 4f15bcf080..d239471a5c 100644
--- a/platform/windows/joypad_windows.h
+++ b/platform/windows/joypad_windows.h
@@ -105,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;
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index e32dc0d1a6..2d8d68a575 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -179,7 +179,14 @@ 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)
+ { Key::F17, VK_F17 }, // (0x80)
+ { Key::F18, VK_F18 }, // (0x81)
+ { Key::F19, VK_F19 }, // (0x82)
+ { Key::F20, VK_F20 }, // (0x83)
+ { Key::F21, VK_F21 }, // (0x84)
+ { Key::F22, VK_F22 }, // (0x85)
+ { Key::F23, VK_F23 }, // (0x86)
+ { Key::F24, VK_F24 }, // (0x87)
// 0x88-8F are reserved for UI navigation.
{ Key::NUMLOCK, VK_NUMLOCK }, // (0x90)
{ Key::SCROLLLOCK, VK_SCROLL }, // (0x91)
@@ -409,6 +416,14 @@ static _WinTranslatePair _scancode_to_keycode[] = {
{ Key::F14, 0x65 },
{ Key::F15, 0x66 },
{ Key::F16, 0x67 },
+ { Key::F17, 0x68 },
+ { Key::F18, 0x69 },
+ { Key::F19, 0x6A },
+ { Key::F20, 0x6B },
+ { Key::F21, 0x6C },
+ { Key::F22, 0x6D },
+ { Key::F23, 0x6E },
+ { Key::F24, 0x76 },
{ Key::UNKNOWN, 0 }
};
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 13e3aa7883..ad4be950cc 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -48,6 +48,7 @@
#include <avrt.h>
#include <bcrypt.h>
#include <direct.h>
+#include <dwrite.h>
#include <knownfolders.h>
#include <process.h>
#include <regstr.h>
@@ -129,9 +130,34 @@ void OS_Windows::initialize_debugging() {
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
}
+#ifdef WINDOWS_DEBUG_OUTPUT_ENABLED
+static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
+ String err_str;
+ if (p_errorexp && p_errorexp[0]) {
+ err_str = String::utf8(p_errorexp);
+ } else {
+ err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error);
+ }
+
+ if (p_editor_notify) {
+ err_str += " (User)\n";
+ } else {
+ err_str += "\n";
+ }
+
+ OutputDebugStringW((LPCWSTR)err_str.utf16().ptr());
+}
+#endif
+
void OS_Windows::initialize() {
crash_handler.initialize();
+#ifdef WINDOWS_DEBUG_OUTPUT_ENABLED
+ error_handlers.errfunc = _error_handler;
+ error_handlers.userdata = this;
+ add_error_handler(&error_handlers);
+#endif
+
#ifndef WINDOWS_SUBSYSTEM_CONSOLE
RedirectIOToConsole();
#endif
@@ -153,7 +179,7 @@ void OS_Windows::initialize() {
// long as the windows scheduler resolution (~16-30ms) even for calls like Sleep(1)
timeBeginPeriod(1);
- process_map = memnew((Map<ProcessID, ProcessInfo>));
+ process_map = memnew((HashMap<ProcessID, ProcessInfo>));
// Add current Godot PID to the list of known PIDs
ProcessInfo current_pi = {};
@@ -194,6 +220,10 @@ void OS_Windows::finalize_core() {
memdelete(process_map);
NetSocketPosix::cleanup();
+
+#ifdef WINDOWS_DEBUG_OUTPUT_ENABLED
+ remove_error_handler(&error_handlers);
+#endif
}
Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
@@ -202,7 +232,7 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
return OK;
}
-Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+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)) {
@@ -230,6 +260,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;
}
@@ -264,12 +298,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;
}
@@ -295,16 +336,19 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const {
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;
}
@@ -373,6 +417,31 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const {
return p_text;
}
+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);
@@ -421,21 +490,44 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
if (r_pipe) {
CloseHandle(pipe[1]); // Close pipe write handle (only child process is writing).
- char buf[4096];
+
+ LocalVector<char> bytes;
+ int bytes_in_buffer = 0;
+
+ const int CHUNK_SIZE = 4096;
DWORD read = 0;
for (;;) { // Read StdOut and StdErr from pipe.
- bool success = ReadFile(pipe[0], buf, 4096, &read, NULL);
+ 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;
}
- if (p_pipe_mutex) {
- p_pipe_mutex->lock();
+
+ // 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;
+ }
}
- (*r_pipe) += String::utf8(buf, read);
- if (p_pipe_mutex) {
- p_pipe_mutex->unlock();
+ if (newline_index == -1) {
+ bytes_in_buffer += read;
+ continue;
}
+
+ const int bytes_to_convert = bytes_in_buffer + (newline_index + 1);
+ _append_to_pipe(bytes.ptr(), bytes_to_convert, r_pipe, p_pipe_mutex);
+
+ bytes_in_buffer = read - (newline_index + 1);
+ memmove(bytes.ptr(), bytes.ptr() + bytes_to_convert, bytes_in_buffer);
+ }
+
+ if (bytes_in_buffer > 0) {
+ _append_to_pipe(bytes.ptr(), bytes_in_buffer, r_pipe, p_pipe_mutex);
}
+
CloseHandle(pipe[0]); // Close pipe read handle.
} else {
WaitForSingleObject(pi.pi.hProcess, INFINITE);
@@ -503,6 +595,25 @@ 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) {
return ERR_CANT_OPEN;
@@ -511,6 +622,135 @@ Error OS_Windows::set_cwd(const String &p_cwd) {
return OK;
}
+Vector<String> OS_Windows::get_system_fonts() const {
+ Vector<String> ret;
+ HashSet<String> font_names;
+
+ ComAutoreleaseRef<IDWriteFactory> dwrite_factory;
+ HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory.reference));
+ ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), ret);
+
+ ComAutoreleaseRef<IDWriteFontCollection> font_collection;
+ hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false);
+ ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), ret);
+
+ UINT32 family_count = font_collection->GetFontFamilyCount();
+ for (UINT32 i = 0; i < family_count; i++) {
+ ComAutoreleaseRef<IDWriteFontFamily> family;
+ hr = font_collection->GetFontFamily(i, &family.reference);
+ ERR_CONTINUE(FAILED(hr) || family.is_null());
+
+ ComAutoreleaseRef<IDWriteLocalizedStrings> family_names;
+ hr = family->GetFamilyNames(&family_names.reference);
+ ERR_CONTINUE(FAILED(hr) || family_names.is_null());
+
+ UINT32 index = 0;
+ BOOL exists = false;
+ UINT32 length = 0;
+ Char16String name;
+
+ hr = family_names->FindLocaleName(L"en-us", &index, &exists);
+ ERR_CONTINUE(FAILED(hr));
+
+ hr = family_names->GetStringLength(index, &length);
+ ERR_CONTINUE(FAILED(hr));
+
+ name.resize(length + 1);
+ hr = family_names->GetString(index, (WCHAR *)name.ptrw(), length + 1);
+ ERR_CONTINUE(FAILED(hr));
+
+ font_names.insert(String::utf16(name.ptr(), length));
+ }
+
+ for (const String &E : font_names) {
+ ret.push_back(E);
+ }
+ return ret;
+}
+
+String OS_Windows::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const {
+ String font_name = p_font_name;
+ if (font_name.to_lower() == "sans-serif") {
+ font_name = "Arial";
+ } else if (font_name.to_lower() == "serif") {
+ font_name = "Times New Roman";
+ } else if (font_name.to_lower() == "monospace") {
+ font_name = "Courier New";
+ } else if (font_name.to_lower() == "cursive") {
+ font_name = "Comic Sans MS";
+ } else if (font_name.to_lower() == "fantasy") {
+ font_name = "Gabriola";
+ }
+
+ ComAutoreleaseRef<IDWriteFactory> dwrite_factory;
+ HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory.reference));
+ ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), String());
+
+ ComAutoreleaseRef<IDWriteFontCollection> font_collection;
+ hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false);
+ ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), String());
+
+ UINT32 index = 0;
+ BOOL exists = false;
+ font_collection->FindFamilyName((const WCHAR *)font_name.utf16().get_data(), &index, &exists);
+ if (FAILED(hr)) {
+ return String();
+ }
+
+ ComAutoreleaseRef<IDWriteFontFamily> family;
+ hr = font_collection->GetFontFamily(index, &family.reference);
+ if (FAILED(hr) || family.is_null()) {
+ return String();
+ }
+
+ ComAutoreleaseRef<IDWriteFont> dwrite_font;
+ hr = family->GetFirstMatchingFont(p_bold ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, p_italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, &dwrite_font.reference);
+ if (FAILED(hr) || dwrite_font.is_null()) {
+ return String();
+ }
+
+ ComAutoreleaseRef<IDWriteFontFace> dwrite_face;
+ hr = dwrite_font->CreateFontFace(&dwrite_face.reference);
+ if (FAILED(hr) || dwrite_face.is_null()) {
+ return String();
+ }
+
+ UINT32 number_of_files = 0;
+ hr = dwrite_face->GetFiles(&number_of_files, nullptr);
+ if (FAILED(hr)) {
+ return String();
+ }
+ Vector<ComAutoreleaseRef<IDWriteFontFile>> files;
+ files.resize(number_of_files);
+ hr = dwrite_face->GetFiles(&number_of_files, (IDWriteFontFile **)files.ptrw());
+ if (FAILED(hr)) {
+ return String();
+ }
+
+ for (UINT32 i = 0; i < number_of_files; i++) {
+ void const *reference_key = nullptr;
+ UINT32 reference_key_size = 0;
+ ComAutoreleaseRef<IDWriteLocalFontFileLoader> loader;
+
+ hr = files.write[i]->GetLoader((IDWriteFontFileLoader **)&loader.reference);
+ if (FAILED(hr) || loader.is_null()) {
+ continue;
+ }
+ hr = files.write[i]->GetReferenceKey(&reference_key, &reference_key_size);
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ WCHAR file_path[MAX_PATH];
+ hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH);
+ if (FAILED(hr)) {
+ continue;
+ }
+ return String::utf16((const char16_t *)&file_path[0]);
+ }
+ return String();
+}
+
String OS_Windows::get_executable_path() const {
WCHAR bufname[4096];
GetModuleFileNameW(nullptr, bufname, 4096);
@@ -676,6 +916,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")) {
@@ -789,9 +1081,9 @@ String OS_Windows::get_user_data_dir() const {
}
String OS_Windows::get_unique_id() const {
- HW_PROFILE_INFO HwProfInfo;
- ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), "");
- return String::utf16((const char16_t *)(HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN);
+ HW_PROFILE_INFOA HwProfInfo;
+ ERR_FAIL_COND_V(!GetCurrentHwProfileA(&HwProfInfo), "");
+ return String((HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN);
}
bool OS_Windows::_check_internal_feature_support(const String &p_feature) {
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 5bfd24327e..80fc860738 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -57,17 +57,42 @@
#include <windows.h>
#include <windowsx.h>
+#ifdef DEBUG_ENABLED
+// forward error messages to OutputDebugString
+#define WINDOWS_DEBUG_OUTPUT_ENABLED
+#endif
+
+template <class T>
+class ComAutoreleaseRef {
+public:
+ T *reference = nullptr;
+
+ _FORCE_INLINE_ T *operator->() { return reference; }
+ _FORCE_INLINE_ const T *operator->() const { return reference; }
+ _FORCE_INLINE_ T *operator*() { return reference; }
+ _FORCE_INLINE_ const T *operator*() const { return reference; }
+ _FORCE_INLINE_ bool is_valid() const { return reference != nullptr; }
+ _FORCE_INLINE_ bool is_null() const { return reference == nullptr; }
+ ComAutoreleaseRef() {}
+ ~ComAutoreleaseRef() {
+ if (reference != nullptr) {
+ reference->Release();
+ reference = nullptr;
+ }
+ }
+};
+
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;
@@ -81,6 +106,10 @@ class OS_Windows : public OS {
CrashHandler crash_handler;
+#ifdef WINDOWS_DEBUG_OUTPUT_ENABLED
+ ErrorHandlerList error_handlers;
+#endif
+
bool force_quit;
HWND main_window;
@@ -101,14 +130,14 @@ protected:
STARTUPINFO si;
PROCESS_INFORMATION pi;
};
- Map<ProcessID, ProcessInfo> *process_map;
+ HashMap<ProcessID, ProcessInfo> *process_map;
public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") 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) 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;
@@ -132,11 +161,15 @@ public:
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;
virtual bool set_environment(const String &p_var, const String &p_value) const override;
+ virtual Vector<String> get_system_fonts() const override;
+ virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override;
+
virtual String get_executable_path() const override;
virtual String get_locale() const override;
@@ -144,6 +177,8 @@ public:
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;
virtual String get_cache_path() const override;
@@ -173,4 +208,4 @@ public:
~OS_Windows();
};
-#endif
+#endif // OS_WINDOWS_H
diff --git a/platform/windows/tts_windows.cpp b/platform/windows/tts_windows.cpp
new file mode 100644
index 0000000000..e5daf602e6
--- /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(); // Subtract 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..d84a3d273a
--- /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/rb_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;
+ };
+ RBMap<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 07c41395fb..e62c6c1dc8 100644
--- a/platform/windows/vulkan_context_win.cpp
+++ b/platform/windows/vulkan_context_win.cpp
@@ -28,6 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#if defined(WINDOWS_ENABLED) && defined(VULKAN_ENABLED)
+
#include "vulkan_context_win.h"
#ifdef USE_VOLK
#include <volk.h>
@@ -57,3 +59,5 @@ VulkanContextWindows::VulkanContextWindows() {
VulkanContextWindows::~VulkanContextWindows() {
}
+
+#endif
diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h
index e68f0125ca..d5950a129a 100644
--- a/platform/windows/vulkan_context_win.h
+++ b/platform/windows/vulkan_context_win.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef VULKAN_DEVICE_WIN_H
-#define VULKAN_DEVICE_WIN_H
+#ifndef VULKAN_CONTEXT_WIN_H
+#define VULKAN_CONTEXT_WIN_H
#include "drivers/vulkan/vulkan_context.h"
@@ -46,4 +46,4 @@ public:
~VulkanContextWindows();
};
-#endif // VULKAN_DEVICE_WIN_H
+#endif // VULKAN_CONTEXT_WIN_H
diff --git a/platform/windows/windows_terminal_logger.h b/platform/windows/windows_terminal_logger.h
index 1045f12201..348a49c845 100644
--- a/platform/windows/windows_terminal_logger.h
+++ b/platform/windows/windows_terminal_logger.h
@@ -44,4 +44,4 @@ public:
#endif
-#endif
+#endif // WINDOWS_TERMINAL_LOGGER_H