diff options
Diffstat (limited to 'platform/windows/display_server_windows.cpp')
-rw-r--r-- | platform/windows/display_server_windows.cpp | 1053 |
1 files changed, 869 insertions, 184 deletions
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index fa73740e04..2e007b5efc 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -35,18 +35,28 @@ #include "core/config/project_settings.h" #include "core/io/marshalls.h" +#include "core/version.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" -#include "scene/resources/atlas_texture.h" +#if defined(VULKAN_ENABLED) +#include "rendering_context_driver_vulkan_windows.h" +#endif +#if defined(D3D12_ENABLED) +#include "drivers/d3d12/rendering_context_driver_d3d12.h" +#endif #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" #endif #include <avrt.h> #include <dwmapi.h> +#include <propkey.h> +#include <propvarutil.h> +#include <shellapi.h> #include <shlwapi.h> #include <shobjidl.h> +#include <wbemcli.h> #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -56,6 +66,8 @@ #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19 #endif +#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1) + #if defined(__GNUC__) // Workaround GCC warning from -Wcast-function-type. #define GetProcAddress (void *)GetProcAddress @@ -84,6 +96,11 @@ static void track_mouse_leave_event(HWND hWnd) { bool DisplayServerWindows::has_feature(Feature p_feature) const { switch (p_feature) { +#ifndef DISABLE_DEPRECATED + case FEATURE_GLOBAL_MENU: { + return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)); + } break; +#endif case FEATURE_SUBWINDOWS: case FEATURE_TOUCHSCREEN: case FEATURE_MOUSE: @@ -97,10 +114,13 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_ICON: case FEATURE_NATIVE_ICON: case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_INPUT: + case FEATURE_NATIVE_DIALOG_FILE: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: case FEATURE_SCREEN_CAPTURE: + case FEATURE_STATUS_INDICATOR: return true; default: return false; @@ -184,8 +204,8 @@ void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) rid[1].hwndTarget = windows[p_target_window].hWnd; } else { // Follow the keyboard focus - rid[0].hwndTarget = 0; - rid[1].hwndTarget = 0; + rid[0].hwndTarget = nullptr; + rid[1].hwndTarget = nullptr; } if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) { @@ -254,7 +274,7 @@ public: QITABENT(FileDialogEventHandler, IFileDialogEvents), QITABENT(FileDialogEventHandler, IFileDialogControlEvents), #endif - { 0, 0 }, + { nullptr, 0 }, }; return QISearch(this, qit, riid, ppv); } @@ -744,30 +764,54 @@ Ref<Image> DisplayServerWindows::clipboard_get_image() const { } } else if (IsClipboardFormatAvailable(CF_DIB)) { HGLOBAL mem = GetClipboardData(CF_DIB); - if (mem != NULL) { + if (mem != nullptr) { BITMAPINFO *ptr = static_cast<BITMAPINFO *>(GlobalLock(mem)); - if (ptr != NULL) { + if (ptr != nullptr) { BITMAPINFOHEADER *info = &ptr->bmiHeader; - PackedByteArray pba; - - for (LONG y = info->biHeight - 1; y > -1; y--) { - for (LONG x = 0; x < info->biWidth; x++) { - tagRGBQUAD *rgbquad = ptr->bmiColors + (info->biWidth * y) + x; - pba.append(rgbquad->rgbRed); - pba.append(rgbquad->rgbGreen); - pba.append(rgbquad->rgbBlue); - pba.append(rgbquad->rgbReserved); + void *dib_bits = (void *)(ptr->bmiColors); + + // Draw DIB image to temporary DC surface and read it back as BGRA8. + HDC dc = GetDC(nullptr); + if (dc) { + HDC hdc = CreateCompatibleDC(dc); + if (hdc) { + HBITMAP hbm = CreateCompatibleBitmap(dc, info->biWidth, abs(info->biHeight)); + if (hbm) { + SelectObject(hdc, hbm); + SetDIBitsToDevice(hdc, 0, 0, info->biWidth, abs(info->biHeight), 0, 0, 0, abs(info->biHeight), dib_bits, ptr, DIB_RGB_COLORS); + + BITMAPINFO bmp_info = {}; + bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader); + bmp_info.bmiHeader.biWidth = info->biWidth; + bmp_info.bmiHeader.biHeight = -abs(info->biHeight); + bmp_info.bmiHeader.biPlanes = 1; + bmp_info.bmiHeader.biBitCount = 32; + bmp_info.bmiHeader.biCompression = BI_RGB; + + Vector<uint8_t> img_data; + img_data.resize(info->biWidth * abs(info->biHeight) * 4); + GetDIBits(hdc, hbm, 0, abs(info->biHeight), img_data.ptrw(), &bmp_info, DIB_RGB_COLORS); + + uint8_t *wr = (uint8_t *)img_data.ptrw(); + for (int i = 0; i < info->biWidth * abs(info->biHeight); i++) { + SWAP(wr[i * 4 + 0], wr[i * 4 + 2]); // Swap B and R. + if (info->biBitCount != 32) { + wr[i * 4 + 3] = 255; // Set A to solid if it's not in the source image. + } + } + image = Image::create_from_data(info->biWidth, abs(info->biHeight), false, Image::Format::FORMAT_RGBA8, img_data); + + DeleteObject(hbm); + } + DeleteDC(hdc); } + ReleaseDC(nullptr, dc); } - image.instantiate(); - image->create_from_data(info->biWidth, info->biHeight, false, Image::Format::FORMAT_RGBA8, pba); - GlobalUnlock(mem); } } } - CloseClipboard(); return image; @@ -826,7 +870,7 @@ int DisplayServerWindows::get_screen_count() const { } int DisplayServerWindows::get_primary_screen() const { - EnumScreenData data = { 0, 0, 0 }; + EnumScreenData data = { 0, 0, nullptr }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcPrim, (LPARAM)&data); return data.screen; } @@ -861,8 +905,7 @@ static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRE static BOOL CALLBACK _MonitorEnumProcOrigin(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumPosData *data = (EnumPosData *)dwData; - data->pos.x = MIN(data->pos.x, lprcMonitor->left); - data->pos.y = MIN(data->pos.y, lprcMonitor->top); + data->pos = data->pos.min(Point2(lprcMonitor->left, lprcMonitor->top)); return TRUE; } @@ -1074,16 +1117,16 @@ Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const { p.x = pos.x; p.y = pos.y; if (win81p_LogicalToPhysicalPointForPerMonitorDPI) { - win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p); + win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p); } - HDC dc = GetDC(0); + HDC dc = GetDC(nullptr); if (dc) { COLORREF col = GetPixel(dc, p.x, p.y); if (col != CLR_INVALID) { - ReleaseDC(NULL, dc); + ReleaseDC(nullptr, dc); return Color(float(col & 0x000000FF) / 255.0f, float((col & 0x0000FF00) >> 8) / 255.0f, float((col & 0x00FF0000) >> 16) / 255.0f, 1.0f); } - ReleaseDC(NULL, dc); + ReleaseDC(nullptr, dc); } return Color(); @@ -1114,12 +1157,12 @@ Ref<Image> DisplayServerWindows::screen_get_image(int p_screen) const { p2.x = pos.x + size.x; p2.y = pos.y + size.y; if (win81p_LogicalToPhysicalPointForPerMonitorDPI) { - win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p1); - win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p2); + win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p1); + win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p2); } Ref<Image> img; - HDC dc = GetDC(0); + HDC dc = GetDC(nullptr); if (dc) { HDC hdc = CreateCompatibleDC(dc); int width = p2.x - p1.x; @@ -1144,7 +1187,7 @@ Ref<Image> DisplayServerWindows::screen_get_image(int p_screen) const { uint8_t *wr = (uint8_t *)img_data.ptrw(); for (int i = 0; i < width * height; i++) { - SWAP(wr[i * 4 + 0], wr[i * 4 + 2]); + SWAP(wr[i * 4 + 0], wr[i * 4 + 2]); // Swap B and R. } img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); @@ -1152,7 +1195,7 @@ Ref<Image> DisplayServerWindows::screen_get_image(int p_screen) const { } DeleteDC(hdc); } - ReleaseDC(NULL, dc); + ReleaseDC(nullptr, dc); } return img; @@ -1291,6 +1334,11 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (mainwindow_icon) { SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon); } +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_create(window_id); + } +#endif return window_id; } @@ -1336,6 +1384,15 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { WindowData &wd = windows[p_window]; + IPropertyStore *prop_store; + HRESULT hr = SHGetPropertyStoreForWindow(wd.hWnd, IID_IPropertyStore, (void **)&prop_store); + if (hr == S_OK) { + PROPVARIANT val; + PropVariantInit(&val); + prop_store->SetValue(PKEY_AppUserModel_ID, val); + prop_store->Release(); + } + while (wd.transient_children.size()) { window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID); } @@ -1345,8 +1402,12 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { } #ifdef RD_ENABLED - if (context_rd) { - context_rd->window_destroy(p_window); + if (rendering_device) { + rendering_device->screen_free(p_window); + } + + if (rendering_context) { + rendering_context->window_destroy(p_window); } #endif #ifdef GLES3_ENABLED @@ -1360,7 +1421,7 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) { wintab_WTClose(windows[p_window].wtctx); - windows[p_window].wtctx = 0; + windows[p_window].wtctx = nullptr; } DestroyWindow(windows[p_window].hWnd); windows.erase(p_window); @@ -1481,7 +1542,7 @@ Size2i DisplayServerWindows::window_get_title_size(const String &p_title, Window return size; } - HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW); + HDC hdc = GetDCEx(wd.hWnd, nullptr, DCX_WINDOW); if (hdc) { Char16String s = p_title.utf16(); SIZE text_size; @@ -1499,8 +1560,8 @@ Size2i DisplayServerWindows::window_get_title_size(const String &p_title, Window ClientToScreen(wd.hWnd, (POINT *)&rect.right); if (win81p_PhysicalToLogicalPointForPerMonitorDPI) { - win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left); - win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right); + win81p_PhysicalToLogicalPointForPerMonitorDPI(nullptr, (POINT *)&rect.left); + win81p_PhysicalToLogicalPointForPerMonitorDPI(nullptr, (POINT *)&rect.right); } size.x += (rect.right - rect.left); @@ -1577,8 +1638,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi Size2i wsize = window_get_size(p_window); wpos += srect.position; - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3); window_set_position(wpos, p_window); } } @@ -1777,8 +1837,8 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo wd.height = h; #if defined(RD_ENABLED) - if (context_rd) { - context_rd->window_resize(p_window, w, h); + if (rendering_context) { + rendering_context->window_set_size(p_window, w, h); } #endif #if defined(GLES3_ENABLED) @@ -1931,7 +1991,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); if (restore_mouse_trails > 1) { - SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0); + SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, nullptr, 0); restore_mouse_trails = 0; } } @@ -1988,7 +2048,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) // Save number of trails so we can restore when exiting, then turn off mouse trails SystemParametersInfoA(SPI_GETMOUSETRAILS, 0, &restore_mouse_trails, 0); if (restore_mouse_trails > 1) { - SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0); + SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, nullptr, 0); } } } @@ -2243,10 +2303,10 @@ void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p if (p_active) { wd.ime_active = true; ImmAssociateContext(wd.hWnd, wd.im_himc); - CreateCaret(wd.hWnd, NULL, 1, 1); + CreateCaret(wd.hWnd, nullptr, 1, 1); window_set_ime_position(wd.im_position, p_window); } else { - ImmAssociateContext(wd.hWnd, (HIMC)0); + ImmAssociateContext(wd.hWnd, (HIMC) nullptr); DestroyCaret(); wd.ime_active = false; } @@ -2261,7 +2321,7 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI wd.im_position = p_pos; HIMC himc = ImmGetContext(wd.hWnd); - if (himc == (HIMC)0) { + if (himc == (HIMC) nullptr) { return; } @@ -2337,38 +2397,10 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor cursors_cache.erase(p_shape); } - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Ref<AtlasTexture> atlas_texture = p_cursor; - Size2 texture_size; Rect2 atlas_rect; - - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - Ref<Image> image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - if (image->is_compressed()) { - image = image->duplicate(true); - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); + Vector2i texture_size = image->get_size(); UINT image_size = texture_size.width * texture_size.height; @@ -2397,7 +2429,7 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor int row_index = floor(index / texture_size.width) + atlas_rect.position.y; int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; - if (atlas_texture.is_valid()) { + if (atlas_rect.has_area()) { column_index = MIN(column_index, atlas_rect.size.width - 1); row_index = MIN(row_index, atlas_rect.size.height - 1); } @@ -2463,6 +2495,298 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { AllowSetForegroundWindow(pid); } +static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) { + if (msg == TDN_CREATED) { + // To match the input text dialog. + SendMessageW(hwnd, WM_SETICON, ICON_BIG, 0); + SendMessageW(hwnd, WM_SETICON, ICON_SMALL, 0); + } + + return 0; +} + +Error DisplayServerWindows::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + TASKDIALOGCONFIG config; + ZeroMemory(&config, sizeof(TASKDIALOGCONFIG)); + config.cbSize = sizeof(TASKDIALOGCONFIG); + + Char16String title = p_title.utf16(); + Char16String message = p_description.utf16(); + List<Char16String> buttons; + for (String s : p_buttons) { + buttons.push_back(s.utf16()); + } + + config.pszWindowTitle = (LPCWSTR)(title.get_data()); + config.pszContent = (LPCWSTR)(message.get_data()); + + const int button_count = MIN(buttons.size(), 8); + config.cButtons = button_count; + + // No dynamic stack array size :( + TASKDIALOG_BUTTON *tbuttons = button_count != 0 ? (TASKDIALOG_BUTTON *)alloca(sizeof(TASKDIALOG_BUTTON) * button_count) : nullptr; + if (tbuttons) { + for (int i = 0; i < button_count; i++) { + tbuttons[i].nButtonID = i; + tbuttons[i].pszButtonText = (LPCWSTR)(buttons[i].get_data()); + } + } + config.pButtons = tbuttons; + config.pfCallback = win32_task_dialog_callback; + + Error result = FAILED; + HMODULE comctl = LoadLibraryW(L"comctl32.dll"); + if (comctl) { + typedef HRESULT(WINAPI * TaskDialogIndirectPtr)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked); + + TaskDialogIndirectPtr task_dialog_indirect = (TaskDialogIndirectPtr)GetProcAddress(comctl, "TaskDialogIndirect"); + int button_pressed; + + if (task_dialog_indirect && SUCCEEDED(task_dialog_indirect(&config, &button_pressed, nullptr, nullptr))) { + if (!p_callback.is_null()) { + Variant button = button_pressed; + const Variant *args[1] = { &button }; + Variant ret; + Callable::CallError ce; + p_callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute dialog callback: %s.", Variant::get_callable_error_text(p_callback, args, 1, ce))); + } + } + + result = OK; + } + FreeLibrary(comctl); + } else { + ERR_PRINT("Unable to create native dialog."); + } + + return result; +} + +struct Win32InputTextDialogInit { + const char16_t *title; + const char16_t *description; + const char16_t *partial; + const Callable &callback; +}; + +static int scale_with_dpi(int p_pos, int p_dpi) { + return IsProcessDPIAware() ? (p_pos * p_dpi / 96) : p_pos; +} + +static INT_PTR input_text_dialog_init(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) { + Win32InputTextDialogInit init = *(Win32InputTextDialogInit *)lParam; + SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG_PTR)&init.callback); // Set dialog callback. + + SetWindowTextW(hWnd, (LPCWSTR)init.title); + + const int dpi = DisplayServerWindows::get_singleton()->screen_get_dpi(); + + const int margin = scale_with_dpi(7, dpi); + const SIZE dlg_size = { scale_with_dpi(300, dpi), scale_with_dpi(50, dpi) }; + + int str_len = lstrlenW((LPCWSTR)init.description); + SIZE str_size = { dlg_size.cx, 0 }; + if (str_len > 0) { + HDC hdc = GetDC(nullptr); + RECT trect = { margin, margin, margin + dlg_size.cx, margin + dlg_size.cy }; + SelectObject(hdc, (HFONT)SendMessageW(hWnd, WM_GETFONT, 0, 0)); + + // `+ margin` adds some space between the static text and the edit field. + // Don't scale this with DPI because DPI is already handled by DrawText. + str_size.cy = DrawTextW(hdc, (LPCWSTR)init.description, str_len, &trect, DT_LEFT | DT_WORDBREAK | DT_CALCRECT) + margin; + + ReleaseDC(nullptr, hdc); + } + + RECT crect, wrect; + GetClientRect(hWnd, &crect); + GetWindowRect(hWnd, &wrect); + int sw = GetSystemMetrics(SM_CXSCREEN); + int sh = GetSystemMetrics(SM_CYSCREEN); + int new_width = dlg_size.cx + margin * 2 + wrect.right - wrect.left - crect.right; + int new_height = dlg_size.cy + margin * 2 + wrect.bottom - wrect.top - crect.bottom + str_size.cy; + + MoveWindow(hWnd, (sw - new_width) / 2, (sh - new_height) / 2, new_width, new_height, true); + + HWND ok_button = GetDlgItem(hWnd, 1); + MoveWindow(ok_button, + dlg_size.cx + margin - scale_with_dpi(65, dpi), + dlg_size.cy + str_size.cy + margin - scale_with_dpi(20, dpi), + scale_with_dpi(65, dpi), scale_with_dpi(20, dpi), true); + + HWND description = GetDlgItem(hWnd, 3); + MoveWindow(description, margin, margin, dlg_size.cx, str_size.cy, true); + SetWindowTextW(description, (LPCWSTR)init.description); + + HWND text_edit = GetDlgItem(hWnd, 2); + MoveWindow(text_edit, margin, str_size.cy + margin, dlg_size.cx, scale_with_dpi(20, dpi), true); + SetWindowTextW(text_edit, (LPCWSTR)init.partial); + + return TRUE; +} + +static INT_PTR input_text_dialog_cmd_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) { + if (LOWORD(wParam) == 1) { + HWND text_edit = GetDlgItem(hWnd, 2); + ERR_FAIL_NULL_V(text_edit, false); + + Char16String text; + text.resize(GetWindowTextLengthW(text_edit) + 1); + GetWindowTextW(text_edit, (LPWSTR)text.get_data(), text.size()); + + const Callable *callback = (const Callable *)GetWindowLongPtrW(hWnd, GWLP_USERDATA); + if (callback && callback->is_valid()) { + Variant v_result = String((const wchar_t *)text.get_data()); + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &v_result }; + + callback->callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute input dialog callback: %s.", Variant::get_callable_error_text(*callback, args, 1, ce))); + } + } + + return EndDialog(hWnd, 0); + } + + return false; +} + +static INT_PTR CALLBACK input_text_dialog_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) { + switch (code) { + case WM_INITDIALOG: + return input_text_dialog_init(hWnd, code, wParam, lParam); + + case WM_COMMAND: + return input_text_dialog_cmd_proc(hWnd, code, wParam, lParam); + + default: + return FALSE; + } +} + +Error DisplayServerWindows::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) { +#pragma pack(push, 1) + + // NOTE: Use default/placeholder coordinates here. Windows uses its own coordinate system + // specifically for dialogs which relies on font sizes instead of pixels. + const struct { + WORD dlgVer; // must be 1 + WORD signature; // must be 0xFFFF + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; + WCHAR menu[1]; // must be 0 + WCHAR windowClass[7]; // must be "#32770" -- the default window class for dialogs + WCHAR title[1]; // must be 0 + WORD pointsize; + WORD weight; + BYTE italic; + BYTE charset; + WCHAR font[13]; // must be "MS Shell Dlg" + } template_base = { + 1, 0xFFFF, 0, 0, + DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU, + 3, 0, 0, 20, 20, L"", L"#32770", L"", 8, FW_NORMAL, 0, DEFAULT_CHARSET, L"MS Shell Dlg" + }; + + const struct { + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; + WCHAR windowClass[7]; // must be "Button" + WCHAR title[3]; // must be "OK" + WORD extraCount; + } ok_button = { + 0, 0, WS_VISIBLE | BS_DEFPUSHBUTTON, 0, 0, 50, 14, 1, WC_BUTTONW, L"OK", 0 + }; + const struct { + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; + WCHAR windowClass[5]; // must be "Edit" + WCHAR title[1]; // must be 0 + WORD extraCount; + } text_field = { + 0, 0, WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 0, 0, 250, 14, 2, WC_EDITW, L"", 0 + }; + const struct { + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; + WCHAR windowClass[7]; // must be "Static" + WCHAR title[1]; // must be 0 + WORD extraCount; + } static_text = { + 0, 0, WS_VISIBLE, 0, 0, 250, 14, 3, WC_STATICW, L"", 0 + }; + +#pragma pack(pop) + + // Dialog template + const size_t data_size = sizeof(template_base) + (sizeof(template_base) % 4) + + sizeof(ok_button) + (sizeof(ok_button) % 4) + + sizeof(text_field) + (sizeof(text_field) % 4) + + sizeof(static_text) + (sizeof(static_text) % 4); + + void *data_template = memalloc(data_size); + ERR_FAIL_NULL_V_MSG(data_template, FAILED, "Unable to allocate memory for the dialog template."); + ZeroMemory(data_template, data_size); + + char *current_block = (char *)data_template; + CopyMemory(current_block, &template_base, sizeof(template_base)); + current_block += sizeof(template_base) + (sizeof(template_base) % 4); + CopyMemory(current_block, &ok_button, sizeof(ok_button)); + current_block += sizeof(ok_button) + (sizeof(ok_button) % 4); + CopyMemory(current_block, &text_field, sizeof(text_field)); + current_block += sizeof(text_field) + (sizeof(text_field) % 4); + CopyMemory(current_block, &static_text, sizeof(static_text)); + + Char16String title16 = p_title.utf16(); + Char16String description16 = p_description.utf16(); + Char16String partial16 = p_partial.utf16(); + + Win32InputTextDialogInit init = { + title16.get_data(), description16.get_data(), partial16.get_data(), p_callback + }; + + // No modal dialogs for specific windows? Assume main window here. + INT_PTR ret = DialogBoxIndirectParamW(hInstance, (LPDLGTEMPLATEW)data_template, nullptr, (DLGPROC)input_text_dialog_proc, (LPARAM)(&init)); + + Error result = ret != -1 ? OK : FAILED; + memfree(data_template); + + if (result == FAILED) { + ERR_PRINT("Unable to create native dialog."); + } + return result; +} + int DisplayServerWindows::keyboard_get_layout_count() const { return GetKeyboardLayoutList(0, nullptr); } @@ -2828,11 +3152,177 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { } } +DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { + HICON hicon = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon; + if (img != icon) { + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + } + + int w = img->get_width(); + int h = img->get_height(); + + // Create temporary bitmap buffer. + int icon_len = 40 + h * w * 4; + Vector<BYTE> v; + v.resize(icon_len); + BYTE *icon_bmp = v.ptrw(); + + encode_uint32(40, &icon_bmp[0]); + encode_uint32(w, &icon_bmp[4]); + encode_uint32(h * 2, &icon_bmp[8]); + encode_uint16(1, &icon_bmp[12]); + encode_uint16(32, &icon_bmp[14]); + encode_uint32(BI_RGB, &icon_bmp[16]); + encode_uint32(w * h * 4, &icon_bmp[20]); + encode_uint32(0, &icon_bmp[24]); + encode_uint32(0, &icon_bmp[28]); + encode_uint32(0, &icon_bmp[32]); + encode_uint32(0, &icon_bmp[36]); + + uint8_t *wr = &icon_bmp[40]; + const uint8_t *r = img->get_data().ptr(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; + uint8_t *wpx = &wr[(i * w + j) * 4]; + wpx[0] = rpx[2]; + wpx[1] = rpx[1]; + wpx[2] = rpx[0]; + wpx[3] = rpx[3]; + } + } + + hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); + } + + IndicatorData idat; + idat.callback = p_callback; + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = indicator_id_counter; + ndat.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; + ndat.uCallbackMessage = WM_INDICATOR_CALLBACK_MESSAGE; + ndat.hIcon = hicon; + memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR)); + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_ADD, &ndat); + Shell_NotifyIconW(NIM_SETVERSION, &ndat); + + IndicatorID iid = indicator_id_counter++; + indicators[iid] = idat; + + return iid; +} + +void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { + ERR_FAIL_COND(!indicators.has(p_id)); + + HICON hicon = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon; + if (img != icon) { + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + } + + int w = img->get_width(); + int h = img->get_height(); + + // Create temporary bitmap buffer. + int icon_len = 40 + h * w * 4; + Vector<BYTE> v; + v.resize(icon_len); + BYTE *icon_bmp = v.ptrw(); + + encode_uint32(40, &icon_bmp[0]); + encode_uint32(w, &icon_bmp[4]); + encode_uint32(h * 2, &icon_bmp[8]); + encode_uint16(1, &icon_bmp[12]); + encode_uint16(32, &icon_bmp[14]); + encode_uint32(BI_RGB, &icon_bmp[16]); + encode_uint32(w * h * 4, &icon_bmp[20]); + encode_uint32(0, &icon_bmp[24]); + encode_uint32(0, &icon_bmp[28]); + encode_uint32(0, &icon_bmp[32]); + encode_uint32(0, &icon_bmp[36]); + + uint8_t *wr = &icon_bmp[40]; + const uint8_t *r = img->get_data().ptr(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; + uint8_t *wpx = &wr[(i * w + j) * 4]; + wpx[0] = rpx[2]; + wpx[1] = rpx[1]; + wpx[2] = rpx[0]; + wpx[3] = rpx[3]; + } + } + + hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); + } + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uFlags = NIF_ICON; + ndat.hIcon = hicon; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_MODIFY, &ndat); +} + +void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uFlags = NIF_TIP; + memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR)); + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_MODIFY, &ndat); +} + +void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { + ERR_FAIL_COND(!indicators.has(p_id)); + + indicators[p_id].callback = p_callback; +} + +void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_DELETE, &ndat); + indicators.erase(p_id); +} + void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { _THREAD_SAFE_METHOD_ #if defined(RD_ENABLED) - if (context_rd) { - context_rd->set_vsync_mode(p_window, p_vsync_mode); + if (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif @@ -2849,8 +3339,8 @@ void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsyn DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_window) const { _THREAD_SAFE_METHOD_ #if defined(RD_ENABLED) - if (context_rd) { - return context_rd->get_vsync_mode(p_window); + if (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif @@ -2872,9 +3362,9 @@ void DisplayServerWindows::set_context(Context p_context) { #define SIGNATURE_MASK 0xFFFFFF00 // Keeping the name suggested by Microsoft, but this macro really answers: // Is this mouse event emulated from touch or pen input? -#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) +#define IsPenEvent(dw) (((dw) & SIGNATURE_MASK) == MI_WP_SIGNATURE) // This one tells whether the event comes from touchscreen (and not from pen). -#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) +#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw) & 0x80)) void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { if (touch_state.has(idx) == p_pressed) { @@ -2913,6 +3403,7 @@ void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, event->set_index(idx); event->set_position(Vector2(p_x, p_y)); event->set_relative(Vector2(p_x, p_y) - curr->get()); + event->set_relative_screen_position(event->get_relative()); Input::get_singleton()->parse_input_event(event); @@ -3173,6 +3664,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_MENUCOMMAND: { + native_menu->_menu_activate(HMENU(lParam), (int)wParam); + } break; case WM_CREATE: { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); @@ -3215,59 +3709,18 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA 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; - - // Restore mouse mode. - _set_mouse_mode_impl(mouse_mode); - - if (!app_focused) { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } - app_focused = true; - } - } break; - case WM_KILLFOCUS: { - windows[window_id].window_has_focus = false; - last_focused_window = window_id; - - // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. - ReleaseCapture(); - - // Release every touch to avoid sticky points. - for (const KeyValue<int, Vector2> &E : touch_state) { - _touch_event(window_id, false, E.value.x, E.value.y, E.key); - } - touch_state.clear(); - - bool self_steal = false; - HWND new_hwnd = (HWND)wParam; - if (IsWindow(new_hwnd)) { - self_steal = true; + case WM_ACTIVATEAPP: { + bool new_app_focused = (bool)wParam; + if (new_app_focused == app_focused) { + break; } - - if (!self_steal) { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } - app_focused = false; + app_focused = new_app_focused; + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(app_focused ? MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN : MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); } } break; - case WM_ACTIVATE: { // Watch for window activate message. - if (!windows[window_id].window_focused) { - _process_activate_event(window_id, wParam, lParam); - } else { - windows[window_id].saved_wparam = wParam; - windows[window_id].saved_lparam = lParam; - - // Run a timer to prevent event catching warning if the focused window is closing. - windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); - } - if (wParam != WA_INACTIVE) { - track_mouse_leave_event(hWnd); - } + case WM_ACTIVATE: { + _process_activate_event(window_id, wParam, lParam); return 0; // Return to the message loop. } break; case WM_GETMINMAXINFO: { @@ -3309,13 +3762,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_PAINT: { Main::force_redraw(); } break; - case WM_SETTINGCHANGE: { + case WM_SETTINGCHANGE: + case WM_SYSCOLORCHANGE: { if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } + if (system_theme_changed.is_valid()) { + system_theme_changed.call(); + } } break; case WM_THEMECHANGED: { if (is_dark_mode_supported() && dark_title_available) { @@ -3336,11 +3793,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } break; + case WM_INDICATOR_CALLBACK_MESSAGE: { + if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_MBUTTONDOWN || lParam == WM_XBUTTONDOWN) { + IndicatorID iid = (IndicatorID)wParam; + MouseButton mb = MouseButton::LEFT; + if (lParam == WM_RBUTTONDOWN) { + mb = MouseButton::RIGHT; + } else if (lParam == WM_MBUTTONDOWN) { + mb = MouseButton::MIDDLE; + } else if (lParam == WM_XBUTTONDOWN) { + mb = MouseButton::MB_XBUTTON1; + } + if (indicators.has(iid)) { + if (indicators[iid].callback.is_valid()) { + Variant v_button = mb; + Variant v_pos = mouse_get_position(); + Variant *v_args[2] = { &v_button, &v_pos }; + Variant ret; + Callable::CallError ce; + indicators[iid].callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + return 0; + } + } break; case WM_CLOSE: // Did we receive a close message? { - if (windows[window_id].focus_timer_id != 0U) { - KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id); - } _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); return 0; // Jump back. @@ -3425,6 +3903,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(c); mm->set_global_position(c); mm->set_velocity(Vector2(0, 0)); + mm->set_screen_velocity(Vector2(0, 0)); if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); @@ -3449,6 +3928,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = coords.x; old_y = coords.y; } + mm->set_relative_screen_position(mm->get_relative()); if ((windows[window_id].window_has_focus || windows[window_id].is_popup) && mm->get_relative() != Vector2()) { Input::get_singleton()->parse_input_event(mm); @@ -3536,6 +4016,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3544,6 +4025,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { @@ -3683,6 +4165,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3691,6 +4174,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { @@ -3802,6 +4286,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3810,6 +4295,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; @@ -4046,9 +4532,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA rect_changed = true; } #if defined(RD_ENABLED) - if (context_rd && window.context_created) { + if (rendering_context && window.context_created) { // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed. - context_rd->window_resize(window_id, window.width, window.height); + rendering_context->window_set_size(window_id, window.width, window.height); } #endif } @@ -4091,10 +4577,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (!Main::is_iterating()) { Main::iteration(); } - } else if (wParam == windows[window_id].focus_timer_id) { - _process_activate_event(window_id, windows[window_id].saved_wparam, windows[window_id].saved_lparam); - KillTimer(windows[window_id].hWnd, wParam); - windows[window_id].focus_timer_id = 0U; } } break; case WM_SYSKEYUP: @@ -4300,20 +4782,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) { if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { - _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN); - windows[p_window_id].window_focused = true; + last_focused_window = p_window_id; alt_mem = false; control_mem = false; shift_mem = false; gr_mem = false; - - // Restore mouse mode. _set_mouse_mode_impl(mouse_mode); + if (!IsIconic(windows[p_window_id].hWnd)) { + SetFocus(windows[p_window_id].hWnd); + } + windows[p_window_id].window_focused = true; + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN); } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); - _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); - windows[p_window_id].window_focused = false; + track_mouse_leave_event(windows[p_window_id].hWnd); + // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. + ReleaseCapture(); alt_mem = false; + windows[p_window_id].window_focused = false; + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); } if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) { @@ -4481,7 +4968,7 @@ void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) { wintab_WTEnable(wd.wtctx, false); wintab_WTClose(wd.wtctx); - wd.wtctx = 0; + wd.wtctx = nullptr; } if ((p_new_driver == "wintab") && wintab_available) { wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc); @@ -4542,8 +5029,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, Rect2i srect = screen_get_usable_rect(rq_screen); Point2i wpos = p_rect.position; if (srect != Rect2i()) { - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); } WindowRect.left = wpos.x; @@ -4570,8 +5056,6 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, dwExStyle, L"Engine", L"", dwStyle, - // (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2, - // (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2, WindowRect.left, WindowRect.top, WindowRect.right - WindowRect.left, @@ -4604,13 +5088,13 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, } #ifdef RD_ENABLED - if (context_rd) { + if (rendering_context) { union { #ifdef VULKAN_ENABLED - VulkanContextWindows::WindowPlatformData vulkan; + RenderingContextDriverVulkanWindows::WindowPlatformData vulkan; #endif #ifdef D3D12_ENABLED - D3D12Context::WindowPlatformData d3d12; + RenderingContextDriverD3D12::WindowPlatformData d3d12; #endif } wpd; #ifdef VULKAN_ENABLED @@ -4624,12 +5108,16 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wpd.d3d12.window = wd.hWnd; } #endif - if (context_rd->window_create(id, p_vsync_mode, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, &wpd) != OK) { - memdelete(context_rd); - context_rd = nullptr; + if (rendering_context->window_create(id, &wpd) != OK) { + ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; windows.erase(id); - ERR_FAIL_V_MSG(INVALID_WINDOW_ID, vformat("Failed to create %s Window.", context_rd->get_api_name())); + return INVALID_WINDOW_ID; } + + rendering_context->window_set_size(id, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top); + rendering_context->window_set_vsync_mode(id, p_vsync_mode); wd.context_created = true; } #endif @@ -4685,7 +5173,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, print_verbose("WinTab context creation failed."); } } else { - wd.wtctx = 0; + wd.wtctx = nullptr; } if (p_mode == WINDOW_MODE_MAXIMIZED) { @@ -4702,9 +5190,36 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.last_pressure_update = 0; wd.last_tilt = Vector2(); + IPropertyStore *prop_store; + HRESULT hr = SHGetPropertyStoreForWindow(wd.hWnd, IID_IPropertyStore, (void **)&prop_store); + if (hr == S_OK) { + PROPVARIANT val; + String appname; + if (Engine::get_singleton()->is_editor_hint()) { + appname = "Godot.GodotEditor." + String(VERSION_BRANCH); + } else { + String name = GLOBAL_GET("application/config/name"); + String version = GLOBAL_GET("application/config/version"); + if (version.is_empty()) { + version = "0"; + } + String clean_app_name = name.to_pascal_case(); + for (int i = 0; i < clean_app_name.length(); i++) { + if (!is_ascii_alphanumeric_char(clean_app_name[i]) && clean_app_name[i] != '_' && clean_app_name[i] != '.') { + clean_app_name[i] = '_'; + } + } + clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix("."); + appname = "Godot." + clean_app_name + "." + version; + } + InitPropVariantFromString((PCWSTR)appname.utf16().get_data(), &val); + prop_store->SetValue(PKEY_AppUserModel_ID, val); + prop_store->Release(); + } + // IME. wd.im_himc = ImmGetContext(wd.hWnd); - ImmAssociateContext(wd.hWnd, (HIMC)0); + ImmAssociateContext(wd.hWnd, (HIMC) nullptr); wd.im_position = Vector2(); @@ -4752,6 +5267,68 @@ GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr; PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr; +Vector2i _get_device_ids(const String &p_device_name) { + if (p_device_name.is_empty()) { + return Vector2i(); + } + + REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID + REFIID uuid = IID_IWbemLocator; // Interface UUID + IWbemLocator *wbemLocator = nullptr; // to get the services + IWbemServices *wbemServices = nullptr; // to get the class + IEnumWbemClassObject *iter = nullptr; + IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc. + + HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator); + if (hr != S_OK) { + return Vector2i(); + } + BSTR resource_name = SysAllocString(L"root\\CIMV2"); + hr = wbemLocator->ConnectServer(resource_name, nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemServices); + SysFreeString(resource_name); + + SAFE_RELEASE(wbemLocator) // from now on, use `wbemServices` + if (hr != S_OK) { + SAFE_RELEASE(wbemServices) + return Vector2i(); + } + + Vector2i ids; + + const String gpu_device_class_query = vformat("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceName = \"%s\"", p_device_name); + BSTR query = SysAllocString((const WCHAR *)gpu_device_class_query.utf16().get_data()); + BSTR query_lang = SysAllocString(L"WQL"); + hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, nullptr, &iter); + SysFreeString(query_lang); + SysFreeString(query); + if (hr == S_OK) { + ULONG resultCount; + hr = iter->Next(5000, 1, pnpSDriverObject, &resultCount); // Get exactly 1. Wait max 5 seconds. + + if (hr == S_OK && resultCount > 0) { + VARIANT did; + VariantInit(&did); + BSTR object_name = SysAllocString(L"DeviceID"); + hr = pnpSDriverObject[0]->Get(object_name, 0, &did, nullptr, nullptr); + SysFreeString(object_name); + if (hr == S_OK) { + String device_id = String(V_BSTR(&did)); + ids.x = device_id.get_slice("&", 0).lstrip("PCI\\VEN_").hex_to_int(); + ids.y = device_id.get_slice("&", 1).lstrip("DEV_").hex_to_int(); + } + + for (ULONG i = 0; i < resultCount; i++) { + SAFE_RELEASE(pnpSDriverObject[i]) + } + } + } + + SAFE_RELEASE(wbemServices) + SAFE_RELEASE(iter) + + return ids; +} + typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_DPI_UNAWARE = 0, SHC_PROCESS_SYSTEM_DPI_AWARE = 1, @@ -4775,6 +5352,19 @@ Color DisplayServerWindows::get_accent_color() const { return Color((argb & 0xFF) / 255.f, ((argb & 0xFF00) >> 8) / 255.f, ((argb & 0xFF0000) >> 16) / 255.f, ((argb & 0xFF000000) >> 24) / 255.f); } +Color DisplayServerWindows::get_base_color() const { + if (!ux_theme_available) { + return Color(0, 0, 0, 0); + } + + int argb = GetImmersiveColorFromColorSetEx((UINT)GetImmersiveUserColorSetPreference(false, false), GetImmersiveColorTypeFromName(ShouldAppsUseDarkMode() ? L"ImmersiveDarkChromeMediumLow" : L"ImmersiveLightChromeMediumLow"), false, 0); + return Color((argb & 0xFF) / 255.f, ((argb & 0xFF00) >> 8) / 255.f, ((argb & 0xFF0000) >> 16) / 255.f, ((argb & 0xFF000000) >> 24) / 255.f); +} + +void DisplayServerWindows::set_system_theme_change_callback(const Callable &p_callable) { + system_theme_changed = p_callable; +} + int DisplayServerWindows::tablet_get_driver_count() const { return tablet_drivers.size(); } @@ -4833,6 +5423,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (tts_enabled) { tts = memnew(TTS_Windows); } + native_menu = memnew(NativeMenuWindows); // Enforce default keep screen on value. screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); @@ -4858,6 +5449,32 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win GetImmersiveColorFromColorSetEx = (GetImmersiveColorFromColorSetExPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(95)); GetImmersiveColorTypeFromName = (GetImmersiveColorTypeFromNamePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(96)); GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98)); + if (os_ver.dwBuildNumber >= 17763) { + AllowDarkModeForAppPtr AllowDarkModeForApp = nullptr; + SetPreferredAppModePtr SetPreferredAppMode = nullptr; + FlushMenuThemesPtr FlushMenuThemes = nullptr; + if (os_ver.dwBuildNumber < 18362) { + AllowDarkModeForApp = (AllowDarkModeForAppPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135)); + } else { + SetPreferredAppMode = (SetPreferredAppModePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135)); + FlushMenuThemes = (FlushMenuThemesPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(136)); + } + RefreshImmersiveColorPolicyStatePtr RefreshImmersiveColorPolicyState = (RefreshImmersiveColorPolicyStatePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(104)); + if (ShouldAppsUseDarkMode) { + bool dark_mode = ShouldAppsUseDarkMode(); + if (SetPreferredAppMode) { + SetPreferredAppMode(dark_mode ? APPMODE_ALLOWDARK : APPMODE_DEFAULT); + } else if (AllowDarkModeForApp) { + AllowDarkModeForApp(dark_mode); + } + if (RefreshImmersiveColorPolicyState) { + RefreshImmersiveColorPolicyState(); + } + if (FlushMenuThemes) { + FlushMenuThemes(); + } + } + } ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference; if (os_ver.dwBuildNumber >= 18363) { @@ -4916,6 +5533,23 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } + HMODULE comctl32 = LoadLibraryW(L"comctl32.dll"); + if (comctl32) { + typedef BOOL(WINAPI * InitCommonControlsExPtr)(_In_ const INITCOMMONCONTROLSEX *picce); + InitCommonControlsExPtr init_common_controls_ex = (InitCommonControlsExPtr)GetProcAddress(comctl32, "InitCommonControlsEx"); + + // Fails if the incorrect version was loaded. Probably not a big enough deal to print an error about. + if (init_common_controls_ex) { + INITCOMMONCONTROLSEX icc = {}; + icc.dwICC = ICC_STANDARD_CLASSES; + icc.dwSize = sizeof(INITCOMMONCONTROLSEX); + if (!init_common_controls_ex(&icc)) { + WARN_PRINT("Unable to initialize Windows common controls. Native dialogs may not work properly."); + } + } + FreeLibrary(comctl32); + } + memset(&wc, 0, sizeof(WNDCLASSEXW)); wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_OWNDC | CS_DBLCLKS; @@ -4940,19 +5574,19 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win #if defined(RD_ENABLED) #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_rd = memnew(VulkanContextWindows); + rendering_context = memnew(RenderingContextDriverVulkanWindows); } #endif #if defined(D3D12_ENABLED) if (rendering_driver == "d3d12") { - context_rd = memnew(D3D12Context); + rendering_context = memnew(RenderingContextDriverD3D12); } #endif - if (context_rd) { - if (context_rd->initialize() != OK) { - memdelete(context_rd); - context_rd = nullptr; + if (rendering_context) { + if (rendering_context->initialize() != OK) { + memdelete(rendering_context); + rendering_context = nullptr; r_error = ERR_UNAVAILABLE; return; } @@ -4966,19 +5600,29 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (rendering_driver == "opengl3") { rendering_driver = "opengl3_angle"; } -#else +#elif defined(EGL_STATIC) bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle"); if (fallback && (rendering_driver == "opengl3")) { Dictionary gl_info = detect_wgl(); bool force_angle = false; + Vector2i device_id = _get_device_ids(gl_info["name"]); Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices"); for (int i = 0; i < device_list.size(); i++) { const Dictionary &device = device_list[i]; - if (device.has("vendor") && device.has("name") && gl_info["vendor"].operator String().to_upper().contains(device["vendor"].operator String().to_upper()) && (device["name"] == "*" || gl_info["name"].operator String().to_upper().contains(device["name"].operator String().to_upper()))) { - force_angle = true; - break; + if (device.has("vendor") && device.has("name")) { + const String &vendor = device["vendor"]; + const String &name = device["name"]; + if (device_id != Vector2i() && vendor.begins_with("0x") && name.begins_with("0x") && device_id.x == vendor.lstrip("0x").hex_to_int() && device_id.y == name.lstrip("0x").hex_to_int()) { + // Check vendor/device IDs. + force_angle = true; + break; + } else if (gl_info["vendor"].operator String().to_upper().contains(vendor.to_upper()) && (name == "*" || gl_info["name"].operator String().to_upper().contains(name.to_upper()))) { + // Check vendor/device names. + force_angle = true; + break; + } } } @@ -5015,6 +5659,26 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } #endif + String appname; + if (Engine::get_singleton()->is_editor_hint()) { + appname = "Godot.GodotEditor." + String(VERSION_BRANCH); + } else { + String name = GLOBAL_GET("application/config/name"); + String version = GLOBAL_GET("application/config/version"); + if (version.is_empty()) { + version = "0"; + } + String clean_app_name = name.to_pascal_case(); + for (int i = 0; i < clean_app_name.length(); i++) { + if (!is_ascii_alphanumeric_char(clean_app_name[i]) && clean_app_name[i] != '_' && clean_app_name[i] != '.') { + clean_app_name[i] = '_'; + } + } + clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix("."); + appname = "Godot." + clean_app_name + "." + version; + } + SetCurrentProcessExplicitAppUserModelID((PCWSTR)appname.utf16().get_data()); + mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId()); Point2i window_position; @@ -5042,9 +5706,10 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win show_window(MAIN_WINDOW_ID); #if defined(RD_ENABLED) - if (context_rd) { + if (rendering_context) { rendering_device = memnew(RenderingDevice); - rendering_device->initialize(context_rd); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -5138,6 +5803,18 @@ DisplayServerWindows::~DisplayServerWindows() { cursors_cache.clear(); + // Destroy all status indicators. + for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E; ++E) { + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = E->key; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_DELETE, &ndat); + } + if (mouse_monitor) { UnhookWindowsHookEx(mouse_monitor); } @@ -5149,6 +5826,11 @@ DisplayServerWindows::~DisplayServerWindows() { // Close power request handle. screen_set_keep_on(false); + if (native_menu) { + memdelete(native_menu); + native_menu = nullptr; + } + #ifdef GLES3_ENABLED // destroy windows .. NYI? // FIXME wglDeleteContext is never called @@ -5156,32 +5838,35 @@ DisplayServerWindows::~DisplayServerWindows() { if (windows.has(MAIN_WINDOW_ID)) { #ifdef RD_ENABLED - if (context_rd) { - context_rd->window_destroy(MAIN_WINDOW_ID); + if (rendering_device) { + rendering_device->screen_free(MAIN_WINDOW_ID); + } + + if (rendering_context) { + rendering_context->window_destroy(MAIN_WINDOW_ID); } #endif if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) { wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx); - windows[MAIN_WINDOW_ID].wtctx = 0; + windows[MAIN_WINDOW_ID].wtctx = nullptr; } DestroyWindow(windows[MAIN_WINDOW_ID].hWnd); } #ifdef RD_ENABLED if (rendering_device) { - rendering_device->finalize(); memdelete(rendering_device); rendering_device = nullptr; } - if (context_rd) { - memdelete(context_rd); - context_rd = nullptr; + if (rendering_context) { + memdelete(rendering_context); + rendering_context = nullptr; } #endif if (restore_mouse_trails > 1) { - SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0); + SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, nullptr, 0); } #ifdef GLES3_ENABLED if (gl_manager_angle) { |