godot/platform/windows/display_server_windows.cpp
Pedro J. Estébanez dc187324be Add input buffering framework
Input buffering is implicitly used by event accumulation, but this commit makes it more generic so it can be enabled for other uses.

For desktop OSs it's currently not feasible given main and UI threads are the same).
2021-08-13 11:19:19 +02:00

3402 lines
99 KiB
C++

/*************************************************************************/
/* display_server_windows.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 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 "display_server_windows.h"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "main/main.h"
#include "os_windows.h"
#include "scene/resources/texture.h"
#include <avrt.h>
#ifdef DEBUG_ENABLED
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;
}
#endif // DEBUG_ENABLED
bool DisplayServerWindows::has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_SUBWINDOWS:
case FEATURE_TOUCHSCREEN:
case FEATURE_MOUSE:
case FEATURE_MOUSE_WARP:
case FEATURE_CLIPBOARD:
case FEATURE_CURSOR_SHAPE:
case FEATURE_CUSTOM_CURSOR_SHAPE:
case FEATURE_CONSOLE_WINDOW:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
case FEATURE_ICON:
case FEATURE_NATIVE_ICON:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
return true;
default:
return false;
}
}
String DisplayServerWindows::get_name() const {
return "Windows";
}
void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) {
// Mouse is grabbed (captured or confined).
WindowData &wd = windows[MAIN_WINDOW_ID];
RECT clipRect;
GetClientRect(wd.hWnd, &clipRect);
ClientToScreen(wd.hWnd, (POINT *)&clipRect.left);
ClientToScreen(wd.hWnd, (POINT *)&clipRect.right);
ClipCursor(&clipRect);
if (p_mode == MOUSE_MODE_CAPTURED) {
center = window_get_size() / 2;
POINT pos = { (int)center.x, (int)center.y };
ClientToScreen(wd.hWnd, &pos);
SetCursorPos(pos.x, pos.y);
SetCapture(wd.hWnd);
}
} else {
// Mouse is free to move around (not captured or confined).
ReleaseCapture();
ClipCursor(nullptr);
}
if (p_mode == MOUSE_MODE_HIDDEN || p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) {
if (hCursor == nullptr) {
hCursor = SetCursor(nullptr);
} else {
SetCursor(nullptr);
}
} else {
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
}
}
void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
_THREAD_SAFE_METHOD_
if (mouse_mode == p_mode)
return;
mouse_mode = p_mode;
_set_mouse_mode_impl(p_mode);
}
DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const {
return mouse_mode;
}
void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) {
_THREAD_SAFE_METHOD_
if (!windows.has(last_focused_window)) {
return; //no window focused?
}
if (mouse_mode == MOUSE_MODE_CAPTURED) {
old_x = p_to.x;
old_y = p_to.y;
} else {
POINT p;
p.x = p_to.x;
p.y = p_to.y;
ClientToScreen(windows[last_focused_window].hWnd, &p);
SetCursorPos(p.x, p.y);
}
}
Point2i DisplayServerWindows::mouse_get_position() const {
POINT p;
GetCursorPos(&p);
return Point2i(p.x, p.y);
//return Point2(old_x, old_y);
}
MouseButton DisplayServerWindows::mouse_get_button_state() const {
return last_button_state;
}
void DisplayServerWindows::clipboard_set(const String &p_text) {
_THREAD_SAFE_METHOD_
if (!windows.has(last_focused_window)) {
return; //no window focused?
}
// Convert LF line endings to CRLF in clipboard content
// Otherwise, line endings won't be visible when pasted in other software
String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n
if (!OpenClipboard(windows[last_focused_window].hWnd)) {
ERR_FAIL_MSG("Unable to open clipboard.");
}
EmptyClipboard();
Char16String utf16 = text.utf16();
HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (utf16.length() + 1) * sizeof(WCHAR));
ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents.");
LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem);
memcpy(lptstrCopy, utf16.get_data(), (utf16.length() + 1) * sizeof(WCHAR));
GlobalUnlock(mem);
SetClipboardData(CF_UNICODETEXT, mem);
// set the CF_TEXT version (not needed?)
CharString utf8 = text.utf8();
mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1);
ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents.");
LPTSTR ptr = (LPTSTR)GlobalLock(mem);
memcpy(ptr, utf8.get_data(), utf8.length());
ptr[utf8.length()] = 0;
GlobalUnlock(mem);
SetClipboardData(CF_TEXT, mem);
CloseClipboard();
}
String DisplayServerWindows::clipboard_get() const {
_THREAD_SAFE_METHOD_
if (!windows.has(last_focused_window)) {
return String(); //no window focused?
}
String ret;
if (!OpenClipboard(windows[last_focused_window].hWnd)) {
ERR_FAIL_V_MSG("", "Unable to open clipboard.");
};
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
if (mem != nullptr) {
LPWSTR ptr = (LPWSTR)GlobalLock(mem);
if (ptr != nullptr) {
ret = String::utf16((const char16_t *)ptr);
GlobalUnlock(mem);
};
};
} else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
if (mem != nullptr) {
LPTSTR ptr = (LPTSTR)GlobalLock(mem);
if (ptr != nullptr) {
ret.parse_utf8((const char *)ptr);
GlobalUnlock(mem);
};
};
};
CloseClipboard();
return ret;
}
typedef struct {
int count;
int screen;
HMONITOR monitor;
} EnumScreenData;
static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumScreenData *data = (EnumScreenData *)dwData;
if (data->monitor == hMonitor) {
data->screen = data->count;
}
data->count++;
return TRUE;
}
static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
int *data = (int *)dwData;
(*data)++;
return TRUE;
}
int DisplayServerWindows::get_screen_count() const {
_THREAD_SAFE_METHOD_
int data = 0;
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcCount, (LPARAM)&data);
return data;
}
typedef struct {
int count;
int screen;
Point2 pos;
} EnumPosData;
static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumPosData *data = (EnumPosData *)dwData;
if (data->count == data->screen) {
data->pos.x = lprcMonitor->left;
data->pos.y = lprcMonitor->top;
}
data->count++;
return TRUE;
}
Point2i DisplayServerWindows::screen_get_position(int p_screen) const {
_THREAD_SAFE_METHOD_
EnumPosData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Point2() };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcPos, (LPARAM)&data);
return data.pos;
}
typedef struct {
int count;
int screen;
Size2 size;
} EnumSizeData;
typedef struct {
int count;
int screen;
Rect2i rect;
} EnumRectData;
static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumSizeData *data = (EnumSizeData *)dwData;
if (data->count == data->screen) {
data->size.x = lprcMonitor->right - lprcMonitor->left;
data->size.y = lprcMonitor->bottom - lprcMonitor->top;
}
data->count++;
return TRUE;
}
Size2i DisplayServerWindows::screen_get_size(int p_screen) const {
_THREAD_SAFE_METHOD_
EnumSizeData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Size2() };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcSize, (LPARAM)&data);
return data.size;
}
static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumRectData *data = (EnumRectData *)dwData;
if (data->count == data->screen) {
MONITORINFO minfo;
memset(&minfo, 0, sizeof(MONITORINFO));
minfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoA(hMonitor, &minfo);
data->rect.position.x = minfo.rcWork.left;
data->rect.position.y = minfo.rcWork.top;
data->rect.size.x = minfo.rcWork.right - minfo.rcWork.left;
data->rect.size.y = minfo.rcWork.bottom - minfo.rcWork.top;
}
data->count++;
return TRUE;
}
Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const {
_THREAD_SAFE_METHOD_
EnumRectData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Rect2i() };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcUsableSize, (LPARAM)&data);
return data.rect;
}
typedef struct {
int count;
int screen;
int dpi;
} EnumDpiData;
enum _MonitorDpiType {
MDT_Effective_DPI = 0,
MDT_Angular_DPI = 1,
MDT_Raw_DPI = 2,
MDT_Default = MDT_Effective_DPI
};
static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) {
int dpiX = 96, dpiY = 96;
static HMODULE Shcore = nullptr;
typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY);
static GetDPIForMonitor_t getDPIForMonitor = nullptr;
if (Shcore == nullptr) {
Shcore = LoadLibraryW(L"Shcore.dll");
getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : nullptr;
if ((Shcore == nullptr) || (getDPIForMonitor == nullptr)) {
if (Shcore)
FreeLibrary(Shcore);
Shcore = (HMODULE)INVALID_HANDLE_VALUE;
}
}
UINT x = 0, y = 0;
HRESULT hr = E_FAIL;
if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) {
hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y);
if (SUCCEEDED(hr) && (x > 0) && (y > 0)) {
dpiX = (int)x;
dpiY = (int)y;
}
} else {
static int overallX = 0, overallY = 0;
if (overallX <= 0 || overallY <= 0) {
HDC hdc = GetDC(nullptr);
if (hdc) {
overallX = GetDeviceCaps(hdc, LOGPIXELSX);
overallY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(nullptr, hdc);
}
}
if (overallX > 0 && overallY > 0) {
dpiX = overallX;
dpiY = overallY;
}
}
return (dpiX + dpiY) / 2;
}
static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumDpiData *data = (EnumDpiData *)dwData;
if (data->count == data->screen) {
data->dpi = QueryDpiForMonitor(hMonitor);
}
data->count++;
return TRUE;
}
int DisplayServerWindows::screen_get_dpi(int p_screen) const {
_THREAD_SAFE_METHOD_
EnumDpiData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, 72 };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}
bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const {
#ifndef _MSC_VER
#warning touchscreen not working
#endif
return false;
}
void DisplayServerWindows::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) {
}
DisplayServer::ScreenOrientation DisplayServerWindows::screen_get_orientation(int p_screen) const {
return SCREEN_LANDSCAPE;
}
void DisplayServerWindows::screen_set_keep_on(bool p_enable) {
}
bool DisplayServerWindows::screen_is_kept_on() const {
return false;
}
Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const {
_THREAD_SAFE_METHOD_
Vector<DisplayServer::WindowID> ret;
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
ret.push_back(E->key());
}
return ret;
}
DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(const Point2i &p_position) const {
POINT p;
p.x = p_position.x;
p.y = p_position.y;
HWND hwnd = WindowFromPoint(p);
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
if (E->get().hWnd == hwnd) {
return E->key();
}
}
return INVALID_WINDOW_ID;
}
DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
_THREAD_SAFE_METHOD_
WindowID window_id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect);
ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window.");
WindowData &wd = windows[window_id];
if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) {
wd.resizable = false;
}
if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
wd.borderless = true;
}
if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) {
wd.always_on_top = true;
}
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
wd.no_focus = true;
}
return window_id;
}
void DisplayServerWindows::show_window(WindowID p_id) {
WindowData &wd = windows[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) {
SetForegroundWindow(wd.hWnd); // Slightly Higher Priority
SetFocus(wd.hWnd); // Sets Keyboard Focus To
}
}
void DisplayServerWindows::delete_sub_window(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted.");
WindowData &wd = windows[p_window];
while (wd.transient_children.size()) {
window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);
}
if (wd.transient_parent != INVALID_WINDOW_ID) {
window_set_transient(p_window, INVALID_WINDOW_ID);
}
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
context_vulkan->window_destroy(p_window);
}
#endif
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
wintab_WTClose(windows[p_window].wtctx);
windows[p_window].wtctx = 0;
}
DestroyWindow(windows[p_window].hWnd);
windows.erase(p_window);
}
void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].instance_id = p_instance;
}
ObjectID DisplayServerWindows::window_get_attached_instance_id(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
return windows[p_window].instance_id;
}
void DisplayServerWindows::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].rect_changed_callback = p_callable;
}
void DisplayServerWindows::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].event_callback = p_callable;
}
void DisplayServerWindows::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].input_event_callback = p_callable;
}
void DisplayServerWindows::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].input_text_callback = p_callable;
}
void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].drop_files_callback = p_callable;
}
void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data()));
}
void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].mpath = p_region;
_update_window_mouse_passthrough(p_window);
}
void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) {
if (windows[p_window].mpath.size() == 0) {
SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE);
} else {
POINT *points = (POINT *)memalloc(sizeof(POINT) * windows[p_window].mpath.size());
for (int i = 0; i < windows[p_window].mpath.size(); i++) {
if (windows[p_window].borderless) {
points[i].x = windows[p_window].mpath[i].x;
points[i].y = windows[p_window].mpath[i].y;
} else {
points[i].x = windows[p_window].mpath[i].x + GetSystemMetrics(SM_CXSIZEFRAME);
points[i].y = windows[p_window].mpath[i].y + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION);
}
}
HRGN region = CreatePolygonRgn(points, windows[p_window].mpath.size(), ALTERNATE);
SetWindowRgn(windows[p_window].hWnd, region, TRUE);
DeleteObject(region);
memfree(points);
}
}
int DisplayServerWindows::window_get_current_screen(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), -1);
EnumScreenData data = { 0, 0, MonitorFromWindow(windows[p_window].hWnd, MONITOR_DEFAULTTONEAREST) };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcScreen, (LPARAM)&data);
return data.screen;
}
void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_INDEX(p_screen, get_screen_count());
Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
window_set_position(ofs + screen_get_position(p_screen), p_window);
}
Point2i DisplayServerWindows::window_get_position(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
const WindowData &wd = windows[p_window];
if (wd.minimized) {
return wd.last_pos;
}
POINT point;
point.x = 0;
point.y = 0;
ClientToScreen(wd.hWnd, &point);
return Point2i(point.x, point.y);
#if 0
//do not use this method, as it includes windows decorations
RECT r;
GetWindowRect(wd.hWnd, &r);
return Point2(r.left, r.top);
#endif
}
void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) {
POINT mouse_pos;
if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) {
if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) {
old_x = mouse_pos.x;
old_y = mouse_pos.y;
old_invalid = false;
Input::get_singleton()->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y));
}
}
}
void DisplayServerWindows::window_set_position(const Point2i &p_position, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
if (wd.fullscreen)
return;
#if 0
//wrong needs to account properly for decorations
RECT r;
GetWindowRect(wd.hWnd, &r);
MoveWindow(wd.hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE);
#else
RECT rc;
rc.left = p_position.x;
rc.right = p_position.x + wd.width;
rc.bottom = p_position.y + wd.height;
rc.top = p_position.y;
const DWORD style = GetWindowLongPtr(wd.hWnd, GWL_STYLE);
const DWORD exStyle = GetWindowLongPtr(wd.hWnd, GWL_EXSTYLE);
AdjustWindowRectEx(&rc, style, false, exStyle);
MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
#endif
// Don't let the mouse leave the window when moved
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT rect;
GetClientRect(wd.hWnd, &rect);
ClientToScreen(wd.hWnd, (POINT *)&rect.left);
ClientToScreen(wd.hWnd, (POINT *)&rect.right);
ClipCursor(&rect);
}
wd.last_pos = p_position;
_update_real_mouse_position(p_window);
}
void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_parent) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(p_window == p_parent);
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd_window = windows[p_window];
ERR_FAIL_COND(wd_window.transient_parent == p_parent);
ERR_FAIL_COND_MSG(wd_window.always_on_top, "Windows with the 'on top' can't become transient.");
if (p_parent == INVALID_WINDOW_ID) {
//remove transient
ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
WindowData &wd_parent = windows[wd_window.transient_parent];
wd_window.transient_parent = INVALID_WINDOW_ID;
wd_parent.transient_children.erase(p_window);
SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
} else {
ERR_FAIL_COND(!windows.has(p_parent));
ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
WindowData &wd_parent = windows[p_parent];
wd_window.transient_parent = p_parent;
wd_parent.transient_children.insert(p_window);
SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
}
}
void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
if ((p_size != Size2()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
return;
}
wd.max_size = p_size;
}
Size2i DisplayServerWindows::window_get_max_size(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
const WindowData &wd = windows[p_window];
return wd.max_size;
}
void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
if ((p_size != Size2()) && (wd.max_size != Size2()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
ERR_PRINT("Minimum window size can't be larger than maximum window size!");
return;
}
wd.min_size = p_size;
}
Size2i DisplayServerWindows::window_get_min_size(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
const WindowData &wd = windows[p_window];
return wd.min_size;
}
void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
int w = p_size.width;
int h = p_size.height;
wd.width = w;
wd.height = h;
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
context_vulkan->window_resize(p_window, w, h);
}
#endif
if (wd.fullscreen) {
return;
}
RECT rect;
GetWindowRect(wd.hWnd, &rect);
if (!wd.borderless) {
RECT crect;
GetClientRect(wd.hWnd, &crect);
w += (rect.right - rect.left) - (crect.right - crect.left);
h += (rect.bottom - rect.top) - (crect.bottom - crect.top);
}
MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE);
// Don't let the mouse leave the window when resizing to a smaller resolution
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT crect;
GetClientRect(wd.hWnd, &crect);
ClientToScreen(wd.hWnd, (POINT *)&crect.left);
ClientToScreen(wd.hWnd, (POINT *)&crect.right);
ClipCursor(&crect);
}
}
Size2i DisplayServerWindows::window_get_size(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
const WindowData &wd = windows[p_window];
if (wd.minimized) {
return Size2(wd.width, wd.height);
}
RECT r;
if (GetClientRect(wd.hWnd, &r)) { // Only area inside of window border
return Size2(r.right - r.left, r.bottom - r.top);
}
return Size2();
}
Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), Size2i());
const WindowData &wd = windows[p_window];
RECT r;
if (GetWindowRect(wd.hWnd, &r)) { // Includes area of the window border
return Size2(r.right - r.left, r.bottom - r.top);
}
return Size2();
}
void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
r_style = 0;
r_style_ex = WS_EX_WINDOWEDGE;
if (p_main_window) {
r_style_ex |= WS_EX_APPWINDOW;
}
if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP;
//if (p_borderless) {
// r_style_ex |= WS_EX_TOOLWINDOW;
//}
} else {
if (p_resizable) {
if (p_maximized) {
r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE;
} else {
r_style = WS_OVERLAPPEDWINDOW;
}
} else {
r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
}
}
if (!p_borderless) {
r_style |= WS_VISIBLE;
}
if (p_no_activate_focus) {
r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
}
r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
}
void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
DWORD style = 0;
DWORD style_ex = 0;
_get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
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 (p_repaint) {
RECT rect;
GetWindowRect(wd.hWnd, &rect);
MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
}
}
void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) {
RECT rect;
wd.fullscreen = false;
wd.maximized = wd.was_maximized;
if (wd.pre_fs_valid) {
rect = wd.pre_fs_rect;
} else {
rect.left = 0;
rect.right = wd.width;
rect.top = 0;
rect.bottom = wd.height;
wd.pre_fs_valid = true;
}
_update_window_style(p_window, false);
MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
} else if (p_mode == WINDOW_MODE_WINDOWED) {
ShowWindow(wd.hWnd, SW_RESTORE);
wd.maximized = false;
wd.minimized = false;
}
if (p_mode == WINDOW_MODE_MAXIMIZED) {
ShowWindow(wd.hWnd, SW_MAXIMIZE);
wd.maximized = true;
wd.minimized = false;
}
if (p_mode == WINDOW_MODE_MINIMIZED) {
ShowWindow(wd.hWnd, SW_MINIMIZE);
wd.maximized = false;
wd.minimized = true;
}
if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) {
if (wd.minimized) {
ShowWindow(wd.hWnd, SW_RESTORE);
}
wd.was_maximized = wd.maximized;
if (wd.pre_fs_valid) {
GetWindowRect(wd.hWnd, &wd.pre_fs_rect);
}
int cs = window_get_current_screen(p_window);
Point2 pos = screen_get_position(cs);
Size2 size = screen_get_size(cs);
wd.fullscreen = true;
wd.maximized = false;
wd.minimized = false;
_update_window_style(false);
MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
}
}
DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
const WindowData &wd = windows[p_window];
if (wd.fullscreen) {
return WINDOW_MODE_FULLSCREEN;
} else if (wd.minimized) {
return WINDOW_MODE_MINIMIZED;
} else if (wd.maximized) {
return WINDOW_MODE_MAXIMIZED;
} else {
return WINDOW_MODE_WINDOWED;
}
}
bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), false);
// FIXME: Implement this, or confirm that it should always be true.
return true; //no idea
}
void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: {
wd.resizable = !p_enabled;
_update_window_style(p_window);
} break;
case WINDOW_FLAG_BORDERLESS: {
wd.borderless = p_enabled;
_update_window_style(p_window);
_update_window_mouse_passthrough(p_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");
wd.always_on_top = p_enabled;
_update_window_style(p_window);
} break;
case WINDOW_FLAG_TRANSPARENT: {
// FIXME: Implement.
} break;
case WINDOW_FLAG_NO_FOCUS: {
wd.no_focus = p_enabled;
_update_window_style(p_window);
} break;
case WINDOW_FLAG_MAX:
break;
}
}
bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), false);
const WindowData &wd = windows[p_window];
switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: {
return !wd.resizable;
} break;
case WINDOW_FLAG_BORDERLESS: {
return wd.borderless;
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
return wd.always_on_top;
} break;
case WINDOW_FLAG_TRANSPARENT: {
// FIXME: Implement.
} break;
case WINDOW_FLAG_NO_FOCUS: {
return wd.no_focus;
} break;
case WINDOW_FLAG_MAX:
break;
}
return false;
}
void DisplayServerWindows::window_request_attention(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
FLASHWINFO info;
info.cbSize = sizeof(FLASHWINFO);
info.hwnd = wd.hWnd;
info.dwFlags = FLASHW_TRAY;
info.dwTimeout = 0;
info.uCount = 2;
FlashWindowEx(&info);
}
void DisplayServerWindows::window_move_to_foreground(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
SetForegroundWindow(wd.hWnd);
}
bool DisplayServerWindows::window_can_draw(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), false);
const WindowData &wd = windows[p_window];
return !wd.minimized;
}
bool DisplayServerWindows::can_any_window_draw() const {
_THREAD_SAFE_METHOD_
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
if (!E->get().minimized) {
return true;
}
}
return false;
}
void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
if (p_active) {
ImmAssociateContext(wd.hWnd, wd.im_himc);
window_set_ime_position(wd.im_position, p_window);
} else {
ImmAssociateContext(wd.hWnd, (HIMC)0);
}
}
void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
wd.im_position = p_pos;
HIMC himc = ImmGetContext(wd.hWnd);
if (himc == (HIMC)0)
return;
COMPOSITIONFORM cps;
cps.dwStyle = CFS_FORCE_POSITION;
cps.ptCurrentPos.x = wd.im_position.x;
cps.ptCurrentPos.y = wd.im_position.y;
ImmSetCompositionWindow(himc, &cps);
ImmReleaseContext(wd.hWnd, himc);
}
void DisplayServerWindows::console_set_visible(bool p_enabled) {
_THREAD_SAFE_METHOD_
if (console_visible == p_enabled)
return;
ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE);
console_visible = p_enabled;
}
bool DisplayServerWindows::is_console_visible() const {
return console_visible;
}
void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) {
_THREAD_SAFE_METHOD_
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (cursor_shape == p_shape)
return;
if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
cursor_shape = p_shape;
return;
}
static const LPCTSTR win_cursors[CURSOR_MAX] = {
IDC_ARROW,
IDC_IBEAM,
IDC_HAND, //finger
IDC_CROSS,
IDC_WAIT,
IDC_APPSTARTING,
IDC_ARROW,
IDC_ARROW,
IDC_NO,
IDC_SIZENS,
IDC_SIZEWE,
IDC_SIZENESW,
IDC_SIZENWSE,
IDC_SIZEALL,
IDC_SIZENS,
IDC_SIZEWE,
IDC_HELP
};
if (cursors[p_shape] != nullptr) {
SetCursor(cursors[p_shape]);
} else {
SetCursor(LoadCursor(hInstance, win_cursors[p_shape]));
}
cursor_shape = p_shape;
}
DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const {
return cursor_shape;
}
void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) {
// Get the system display DC
HDC hDC = GetDC(nullptr);
// Create helper DC
HDC hMainDC = CreateCompatibleDC(hDC);
HDC hAndMaskDC = CreateCompatibleDC(hDC);
HDC hXorMaskDC = CreateCompatibleDC(hDC);
// Get the dimensions of the source bitmap
BITMAP bm;
GetObject(hSourceBitmap, sizeof(BITMAP), &bm);
// Create the mask bitmaps
hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color
// Release the system display DC
ReleaseDC(nullptr, hDC);
// Select the bitmaps to helper DC
HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap);
HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap);
HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap);
// Assign the monochrome AND mask bitmap pixels so that the pixels of the source bitmap
// with 'clrTransparent' will be white pixels of the monochrome bitmap
SetBkColor(hMainDC, clrTransparent);
BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY);
// Assign the color XOR mask bitmap pixels so that the pixels of the source bitmap
// with 'clrTransparent' will be black and rest the pixels same as corresponding
// pixels of the source bitmap
SetBkColor(hXorMaskDC, RGB(0, 0, 0));
SetTextColor(hXorMaskDC, RGB(255, 255, 255));
BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY);
BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND);
// Deselect bitmaps from the helper DC
SelectObject(hMainDC, hOldMainBitmap);
SelectObject(hAndMaskDC, hOldAndMaskBitmap);
SelectObject(hXorMaskDC, hOldXorMaskBitmap);
// Delete the helper DC
DeleteDC(hXorMaskDC);
DeleteDC(hAndMaskDC);
DeleteDC(hMainDC);
}
void DisplayServerWindows::cursor_set_custom_image(const RES &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);
if (cursor_c) {
if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
cursor_set_shape(p_shape);
return;
}
cursors_cache.erase(p_shape);
}
Ref<Texture2D> texture = p_cursor;
Ref<AtlasTexture> atlas_texture = p_cursor;
Ref<Image> image;
Size2 texture_size;
Rect2 atlas_rect;
if (texture.is_valid()) {
image = texture->get_image();
}
if (!image.is_valid() && 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 if (image.is_valid()) {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(!texture.is_valid());
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);
image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
UINT image_size = texture_size.width * texture_size.height;
// Create the BITMAP with alpha channel
COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size);
for (UINT index = 0; index < image_size; index++) {
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()) {
column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1);
}
*(buffer + index) = image->get_pixel(column_index, row_index).to_argb32();
}
// Using 4 channels, so 4 * 8 bits
HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer);
COLORREF clrTransparent = -1;
// Create the AND and XOR masks for the bitmap
HBITMAP hAndMask = nullptr;
HBITMAP hXorMask = nullptr;
GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask);
if (nullptr == hAndMask || nullptr == hXorMask) {
memfree(buffer);
DeleteObject(bitmap);
return;
}
// Finally, create the icon
ICONINFO iconinfo;
iconinfo.fIcon = FALSE;
iconinfo.xHotspot = p_hotspot.x;
iconinfo.yHotspot = p_hotspot.y;
iconinfo.hbmMask = hAndMask;
iconinfo.hbmColor = hXorMask;
if (cursors[p_shape])
DestroyIcon(cursors[p_shape]);
cursors[p_shape] = CreateIconIndirect(&iconinfo);
Vector<Variant> params;
params.push_back(p_cursor);
params.push_back(p_hotspot);
cursors_cache.insert(p_shape, params);
if (p_shape == cursor_shape) {
if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
SetCursor(cursors[p_shape]);
}
}
if (hAndMask != nullptr) {
DeleteObject(hAndMask);
}
if (hXorMask != nullptr) {
DeleteObject(hXorMask);
}
memfree(buffer);
DeleteObject(bitmap);
} else {
// Reset to default system cursor
if (cursors[p_shape]) {
DestroyIcon(cursors[p_shape]);
cursors[p_shape] = nullptr;
}
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
cursors_cache.erase(p_shape);
}
}
bool DisplayServerWindows::get_swap_cancel_ok() {
return true;
}
void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
_THREAD_SAFE_METHOD_
AllowSetForegroundWindow(pid);
}
int DisplayServerWindows::keyboard_get_layout_count() const {
return GetKeyboardLayoutList(0, nullptr);
}
int DisplayServerWindows::keyboard_get_current_layout() const {
HKL cur_layout = GetKeyboardLayout(0);
int layout_count = GetKeyboardLayoutList(0, nullptr);
HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
GetKeyboardLayoutList(layout_count, layouts);
for (int i = 0; i < layout_count; i++) {
if (cur_layout == layouts[i]) {
memfree(layouts);
return i;
}
}
memfree(layouts);
return -1;
}
void DisplayServerWindows::keyboard_set_current_layout(int p_index) {
int layout_count = GetKeyboardLayoutList(0, nullptr);
ERR_FAIL_INDEX(p_index, layout_count);
HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
GetKeyboardLayoutList(layout_count, layouts);
ActivateKeyboardLayout(layouts[p_index], KLF_SETFORPROCESS);
memfree(layouts);
}
String DisplayServerWindows::keyboard_get_layout_language(int p_index) const {
int layout_count = GetKeyboardLayoutList(0, nullptr);
ERR_FAIL_INDEX_V(p_index, layout_count, "");
HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
GetKeyboardLayoutList(layout_count, layouts);
WCHAR buf[LOCALE_NAME_MAX_LENGTH];
memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));
LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
memfree(layouts);
return String::utf16((const char16_t *)buf).substr(0, 2);
}
String _get_full_layout_name_from_registry(HKL p_layout) {
String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0");
String ret;
HKEY hkey;
WCHAR layout_text[1024];
memset(layout_text, 0, 1024 * sizeof(WCHAR));
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
return ret;
}
DWORD buffer = 1024;
DWORD vtype = REG_SZ;
if (RegQueryValueExW(hkey, L"Layout Text", nullptr, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) {
ret = String::utf16((const char16_t *)layout_text);
}
RegCloseKey(hkey);
return ret;
}
String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
int layout_count = GetKeyboardLayoutList(0, nullptr);
ERR_FAIL_INDEX_V(p_index, layout_count, "");
HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
GetKeyboardLayoutList(layout_count, layouts);
String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).
if (ret == String()) {
WCHAR buf[LOCALE_NAME_MAX_LENGTH];
memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));
LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
WCHAR name[1024];
memset(name, 0, 1024 * sizeof(WCHAR));
GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024);
ret = String::utf16((const char16_t *)name);
}
memfree(layouts);
return ret;
}
void DisplayServerWindows::process_events() {
_THREAD_SAFE_METHOD_
MSG msg;
if (!drop_events) {
joypad->process_joypads();
}
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (!drop_events) {
_process_key_events();
Input::get_singleton()->flush_buffered_events();
}
}
void DisplayServerWindows::force_process_and_drop_events() {
_THREAD_SAFE_METHOD_
drop_events = true;
process_events();
drop_events = false;
}
void DisplayServerWindows::release_rendering_thread() {
}
void DisplayServerWindows::make_rendering_thread() {
}
void DisplayServerWindows::swap_buffers() {
}
void DisplayServerWindows::set_native_icon(const String &p_filename) {
_THREAD_SAFE_METHOD_
FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'.");
ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
int pos = 0;
icon_dir->idReserved = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
icon_dir->idType = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
ERR_FAIL_COND_MSG(icon_dir->idType != 1, "Invalid icon file format!");
icon_dir->idCount = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY));
f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY));
int small_icon_index = -1; // Select 16x16 with largest color count
int small_icon_cc = 0;
int big_icon_index = -1; // Select largest
int big_icon_width = 16;
int big_icon_cc = 0;
for (int i = 0; i < icon_dir->idCount; i++) {
int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount;
int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth;
if (width == 16) {
if (colors >= small_icon_cc) {
small_icon_index = i;
small_icon_cc = colors;
}
}
if (width >= big_icon_width) {
if (colors >= big_icon_cc) {
big_icon_index = i;
big_icon_width = width;
big_icon_cc = colors;
}
}
}
ERR_FAIL_COND_MSG(big_icon_index == -1, "No valid icons found!");
if (small_icon_index == -1) {
WARN_PRINT("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!");
small_icon_index = big_icon_index;
small_icon_cc = big_icon_cc;
}
// Read the big icon
DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes;
Vector<uint8_t> data_big;
data_big.resize(bytecount_big);
pos = icon_dir->idEntries[big_icon_index].dwImageOffset;
f->seek(pos);
f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big);
HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000);
ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
// Read the small icon
DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes;
Vector<uint8_t> data_small;
data_small.resize(bytecount_small);
pos = icon_dir->idEntries[small_icon_index].dwImageOffset;
f->seek(pos);
f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small);
HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000);
ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + ".");
// Online tradition says to be sure last error is cleared and set the small icon first
int err = 0;
SetLastError(err);
SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);
err = GetLastError();
ERR_FAIL_COND_MSG(err, "Error setting ICON_SMALL: " + format_error_message(err) + ".");
SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big);
err = GetLastError();
ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + ".");
memdelete(f);
memdelete(icon_dir);
}
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);
int w = icon->get_width();
int h = icon->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 = icon->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 hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
/* Set the icon for the window */
SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
/* Set the icon in the task manager (should we do this?) */
SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
}
void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(VULKAN_ENABLED)
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)
return context_vulkan->get_vsync_mode(p_window);
#else
return DisplayServer::VSYNC_ENABLED;
#endif
}
void DisplayServerWindows::set_context(Context p_context) {
}
#define MI_WP_SIGNATURE 0xFF515700
#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)
// This one tells whether the event comes from touchscreen (and not from pen)
#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) {
// Defensive
if (touch_state.has(idx) == p_pressed)
return;
if (p_pressed) {
touch_state.insert(idx, Vector2(p_x, p_y));
} else {
touch_state.erase(idx);
}
Ref<InputEventScreenTouch> event;
event.instantiate();
event->set_index(idx);
event->set_window_id(p_window);
event->set_pressed(p_pressed);
event->set_position(Vector2(p_x, p_y));
Input::get_singleton()->parse_input_event(event);
}
void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) {
Map<int, Vector2>::Element *curr = touch_state.find(idx);
// Defensive
if (!curr)
return;
if (curr->get() == Vector2(p_x, p_y))
return;
Ref<InputEventScreenDrag> event;
event.instantiate();
event->set_window_id(p_window);
event->set_index(idx);
event->set_position(Vector2(p_x, p_y));
event->set_relative(Vector2(p_x, p_y) - curr->get());
Input::get_singleton()->parse_input_event(event);
curr->get() = Vector2(p_x, p_y);
}
void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent p_event) {
if (!wd.event_callback.is_null()) {
Variant event = int(p_event);
Variant *eventp = &event;
Variant ret;
Callable::CallError ce;
wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
}
}
void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event) {
((DisplayServerWindows *)(get_singleton()))->_dispatch_input_event(p_event);
}
void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) {
_THREAD_SAFE_METHOD_
if (in_dispatch_input_event) {
return;
}
in_dispatch_input_event = true;
Variant ev = p_event;
Variant *evp = &ev;
Variant ret;
Callable::CallError ce;
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 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;
}
callable.call((const Variant **)&evp, 1, ret, ce);
} else {
//send to all windows
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
Callable callable = E->get().input_event_callback;
if (callable.is_null()) {
continue;
}
callable.call((const Variant **)&evp, 1, ret, ce);
}
}
in_dispatch_input_event = false;
}
LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (drop_events) {
if (user_proc) {
return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
} else {
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
};
WindowID window_id = INVALID_WINDOW_ID;
bool window_created = false;
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
if (E->get().hWnd == hWnd) {
window_id = E->key();
window_created = true;
break;
}
}
if (!window_created) {
// Window creation in progress.
window_id = window_id_counter;
ERR_FAIL_COND_V(!windows.has(window_id), 0);
}
switch (uMsg) // Check For Windows Messages
{
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 (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) {
_touch_event(window_id, false, E->get().x, E->get().y, E->key());
}
touch_state.clear();
bool self_steal = false;
HWND new_hwnd = (HWND)wParam;
if (IsWindow(new_hwnd)) {
self_steal = true;
}
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;
}
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);
}
return 0; // Return To The Message Loop
}
case WM_GETMINMAXINFO: {
if (windows[window_id].resizable && !windows[window_id].fullscreen) {
Size2 decor = window_get_size(window_id) - window_get_real_size(window_id); // Size of window decorations
MINMAXINFO *min_max_info = (MINMAXINFO *)lParam;
if (windows[window_id].min_size != Size2()) {
min_max_info->ptMinTrackSize.x = windows[window_id].min_size.x + decor.x;
min_max_info->ptMinTrackSize.y = windows[window_id].min_size.y + decor.y;
}
if (windows[window_id].max_size != Size2()) {
min_max_info->ptMaxTrackSize.x = windows[window_id].max_size.x + decor.x;
min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y;
}
return 0;
} else {
break;
}
}
case WM_PAINT:
Main::force_redraw();
break;
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
case SC_KEYMENU:
if ((lParam >> 16) <= 0)
return 0;
}
break; // Exit
}
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
}
case WM_MOUSELEAVE: {
old_invalid = true;
outside = true;
_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);
} break;
case WM_INPUT: {
if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
break;
}
UINT dwSize;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[dwSize];
if (lpb == nullptr) {
return 0;
}
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
OutputDebugString(TEXT("GetRawInputData does not return correct size !\n"));
RAWINPUT *raw = (RAWINPUT *)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
mm->set_ctrl_pressed(control_mem);
mm->set_shift_pressed(shift_mem);
mm->set_alt_pressed(alt_mem);
mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f);
mm->set_button_mask(last_button_state);
Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
// centering just so it works as before
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(windows[window_id].hWnd, &pos);
SetCursorPos(pos.x, pos.y);
mm->set_position(c);
mm->set_global_position(c);
Input::get_singleton()->set_mouse_position(c);
mm->set_speed(Vector2(0, 0));
if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY));
} else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
Vector2 abs_pos(
(double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft,
(double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop);
POINT coords; //client coords
coords.x = abs_pos.x;
coords.y = abs_pos.y;
ScreenToClient(hWnd, &coords);
mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y));
old_x = coords.x;
old_y = coords.y;
/*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth));
Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight));
*/
}
if (windows[window_id].window_has_focus && mm->get_relative() != Vector2())
Input::get_singleton()->parse_input_event(mm);
}
delete[] lpb;
} break;
case WT_CSRCHANGE:
case WT_PROXIMITY: {
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
AXIS pressure;
if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
windows[window_id].min_pressure = int(pressure.axMin);
windows[window_id].max_pressure = int(pressure.axMax);
}
AXIS orientation[3];
if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
windows[window_id].tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
}
return 0;
}
} break;
case WT_PACKET: {
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
PACKET packet;
if (wintab_WTPacket(windows[window_id].wtctx, wParam, &packet)) {
float pressure = float(packet.pkNormalPressure - windows[window_id].min_pressure) / float(windows[window_id].max_pressure - windows[window_id].min_pressure);
windows[window_id].last_pressure = pressure;
windows[window_id].last_pressure_update = 0;
double azim = (packet.pkOrientation.orAzimuth / 10.0f) * (Math_PI / 180);
double alt = Math::tan((Math::abs(packet.pkOrientation.orAltitude / 10.0f)) * (Math_PI / 180));
if (windows[window_id].tilt_supported) {
windows[window_id].last_tilt = Vector2(Math::atan(Math::sin(azim) / alt), Math::atan(Math::cos(azim) / alt));
} else {
windows[window_id].last_tilt = Vector2();
}
POINT coords;
GetCursorPos(&coords);
ScreenToClient(windows[window_id].hWnd, &coords);
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
break;
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
mm->set_ctrl_pressed(GetKeyState(VK_CONTROL) < 0);
mm->set_shift_pressed(GetKeyState(VK_SHIFT) < 0);
mm->set_alt_pressed(alt_mem);
mm->set_pressure(windows[window_id].last_pressure);
mm->set_tilt(windows[window_id].last_tilt);
mm->set_button_mask(last_button_state);
mm->set_position(Vector2(coords.x, coords.y));
mm->set_global_position(Vector2(coords.x, coords.y));
if (mouse_mode == MOUSE_MODE_CAPTURED) {
Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
old_x = c.x;
old_y = c.y;
if (mm->get_position() == c) {
center = c;
return 0;
}
Point2i ncenter = mm->get_position();
center = ncenter;
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(windows[window_id].hWnd, &pos);
SetCursorPos(pos.x, pos.y);
}
Input::get_singleton()->set_mouse_position(mm->get_position());
mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
if (old_invalid) {
old_x = mm->get_position().x;
old_y = mm->get_position().y;
old_invalid = false;
}
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)
Input::get_singleton()->parse_input_event(mm);
}
return 0;
}
} break;
case WM_POINTERENTER: {
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
if ((tablet_get_current_driver() != "winink") || !winink_available) {
break;
}
uint32_t pointer_id = LOWORD(wParam);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
break;
}
if (pointer_type != PT_PEN) {
break;
}
windows[window_id].block_mm = true;
return 0;
} break;
case WM_POINTERLEAVE: {
windows[window_id].block_mm = false;
return 0;
} break;
case WM_POINTERUPDATE: {
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
if ((tablet_get_current_driver() != "winink") || !winink_available) {
break;
}
uint32_t pointer_id = LOWORD(wParam);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
break;
}
if (pointer_type != PT_PEN) {
break;
}
POINTER_PEN_INFO pen_info;
if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) {
break;
}
if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
// Universal translation enabled; ignore OS translation
LPARAM extra = GetMessageExtraInfo();
if (IsTouchEvent(extra)) {
break;
}
}
if (outside) {
//mouse enter
if (mouse_mode != MOUSE_MODE_CAPTURED) {
_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
}
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
outside = false;
//Once-Off notification, must call again....
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
}
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
}
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
if (pen_info.penMask & PEN_MASK_PRESSURE) {
mm->set_pressure((float)pen_info.pressure / 1024);
} else {
mm->set_pressure((HIWORD(wParam) & POINTER_MESSAGE_FLAG_FIRSTBUTTON) ? 1.0f : 0.0f);
}
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_ctrl_pressed(GetKeyState(VK_CONTROL) < 0);
mm->set_shift_pressed(GetKeyState(VK_SHIFT) < 0);
mm->set_alt_pressed(alt_mem);
mm->set_button_mask(last_button_state);
POINT coords; //client coords
coords.x = GET_X_LPARAM(lParam);
coords.y = GET_Y_LPARAM(lParam);
ScreenToClient(windows[window_id].hWnd, &coords);
mm->set_position(Vector2(coords.x, coords.y));
mm->set_global_position(Vector2(coords.x, coords.y));
if (mouse_mode == MOUSE_MODE_CAPTURED) {
Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
old_x = c.x;
old_y = c.y;
if (mm->get_position() == c) {
center = c;
return 0;
}
Point2i ncenter = mm->get_position();
center = ncenter;
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(hWnd, &pos);
SetCursorPos(pos.x, pos.y);
}
Input::get_singleton()->set_mouse_position(mm->get_position());
mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
if (old_invalid) {
old_x = mm->get_position().x;
old_y = mm->get_position().y;
old_invalid = false;
}
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) {
Input::get_singleton()->parse_input_event(mm);
}
return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event
} break;
case WM_MOUSEMOVE: {
if (windows[window_id].block_mm) {
break;
}
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
// Universal translation enabled; ignore OS translation
LPARAM extra = GetMessageExtraInfo();
if (IsTouchEvent(extra)) {
break;
}
}
if (outside) {
//mouse enter
if (mouse_mode != MOUSE_MODE_CAPTURED) {
_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);
}
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
outside = false;
//Once-Off notification, must call again....
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
}
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
}
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
mm->set_ctrl_pressed((wParam & MK_CONTROL) != 0);
mm->set_shift_pressed((wParam & MK_SHIFT) != 0);
mm->set_alt_pressed(alt_mem);
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[window_id].wtctx) {
// Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not updated recently.
if (windows[window_id].last_pressure_update < 10) {
windows[window_id].last_pressure_update++;
} else {
windows[window_id].last_tilt = Vector2();
windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
}
} else {
windows[window_id].last_tilt = Vector2();
windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
}
mm->set_pressure(windows[window_id].last_pressure);
mm->set_tilt(windows[window_id].last_tilt);
mm->set_button_mask(last_button_state);
mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
if (mouse_mode == MOUSE_MODE_CAPTURED) {
Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
old_x = c.x;
old_y = c.y;
if (mm->get_position() == c) {
center = c;
return 0;
}
Point2i ncenter = mm->get_position();
center = ncenter;
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(windows[window_id].hWnd, &pos);
SetCursorPos(pos.x, pos.y);
}
Input::get_singleton()->set_mouse_position(mm->get_position());
mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
if (old_invalid) {
old_x = mm->get_position().x;
old_y = mm->get_position().y;
old_invalid = false;
}
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)
Input::get_singleton()->parse_input_event(mm);
} break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
// Universal translation enabled; ignore OS translations for left button
LPARAM extra = GetMessageExtraInfo();
if (IsTouchEvent(extra)) {
break;
}
}
[[fallthrough]];
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_XBUTTONDBLCLK:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP: {
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_window_id(window_id);
switch (uMsg) {
case WM_LBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_LEFT);
} break;
case WM_LBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(MOUSE_BUTTON_LEFT);
} break;
case WM_MBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_MIDDLE);
} break;
case WM_MBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(MOUSE_BUTTON_MIDDLE);
} break;
case WM_RBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_RIGHT);
} break;
case WM_RBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(MOUSE_BUTTON_RIGHT);
} break;
case WM_LBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_LEFT);
mb->set_double_click(true);
} break;
case WM_RBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_RIGHT);
mb->set_double_click(true);
} break;
case WM_MBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(MOUSE_BUTTON_MIDDLE);
mb->set_double_click(true);
} break;
case WM_MOUSEWHEEL: {
mb->set_pressed(true);
int motion = (short)HIWORD(wParam);
if (!motion) {
return 0;
}
if (motion > 0) {
mb->set_button_index(MOUSE_BUTTON_WHEEL_UP);
} else {
mb->set_button_index(MOUSE_BUTTON_WHEEL_DOWN);
}
mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
} break;
case WM_MOUSEHWHEEL: {
mb->set_pressed(true);
int motion = (short)HIWORD(wParam);
if (!motion) {
return 0;
}
if (motion < 0) {
mb->set_button_index(MOUSE_BUTTON_WHEEL_LEFT);
} else {
mb->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT);
}
mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
} break;
case WM_XBUTTONDOWN: {
mb->set_pressed(true);
if (HIWORD(wParam) == XBUTTON1) {
mb->set_button_index(MOUSE_BUTTON_XBUTTON1);
} else {
mb->set_button_index(MOUSE_BUTTON_XBUTTON2);
}
} break;
case WM_XBUTTONUP: {
mb->set_pressed(false);
if (HIWORD(wParam) == XBUTTON1) {
mb->set_button_index(MOUSE_BUTTON_XBUTTON1);
} else {
mb->set_button_index(MOUSE_BUTTON_XBUTTON2);
}
} break;
case WM_XBUTTONDBLCLK: {
mb->set_pressed(true);
if (HIWORD(wParam) == XBUTTON1) {
mb->set_button_index(MOUSE_BUTTON_XBUTTON1);
} else {
mb->set_button_index(MOUSE_BUTTON_XBUTTON2);
}
mb->set_double_click(true);
} break;
default: {
return 0;
}
}
mb->set_ctrl_pressed((wParam & MK_CONTROL) != 0);
mb->set_shift_pressed((wParam & MK_SHIFT) != 0);
mb->set_alt_pressed(alt_mem);
//mb->is_alt_pressed()=(wParam&MK_MENU)!=0;
if (mb->is_pressed()) {
last_button_state |= MouseButton(1 << (mb->get_button_index() - 1));
} else {
last_button_state &= (MouseButton) ~(1 << (mb->get_button_index() - 1));
}
mb->set_button_mask(last_button_state);
mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) {
mb->set_position(Vector2(old_x, old_y));
}
if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) {
if (mb->is_pressed()) {
if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED)
SetCapture(hWnd);
} else {
if (--pressrc <= 0) {
if (mouse_mode != MOUSE_MODE_CAPTURED) {
ReleaseCapture();
}
pressrc = 0;
}
}
} else {
// for reasons unknown to mankind, wheel comes in screen coordinates
POINT coords;
coords.x = mb->get_position().x;
coords.y = mb->get_position().y;
ScreenToClient(hWnd, &coords);
mb->set_position(Vector2(coords.x, coords.y));
}
mb->set_global_position(mb->get_position());
Input::get_singleton()->parse_input_event(mb);
if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) {
//send release for mouse wheel
Ref<InputEventMouseButton> mbd = mb->duplicate();
mbd->set_window_id(window_id);
last_button_state &= (MouseButton) ~(1 << (mbd->get_button_index() - 1));
mbd->set_button_mask(last_button_state);
mbd->set_pressed(false);
Input::get_singleton()->parse_input_event(mbd);
}
} break;
case WM_MOVE: {
if (!IsIconic(windows[window_id].hWnd)) {
int x = int16_t(LOWORD(lParam));
int y = int16_t(HIWORD(lParam));
windows[window_id].last_pos = Point2(x, y);
if (!windows[window_id].rect_changed_callback.is_null()) {
Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
Variant *sizep = &size;
Variant ret;
Callable::CallError ce;
windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
}
}
} break;
case WM_SIZE: {
// Ignore size when a SIZE_MINIMIZED event is triggered
if (wParam != SIZE_MINIMIZED) {
int window_w = LOWORD(lParam);
int window_h = HIWORD(lParam);
if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) {
windows[window_id].width = window_w;
windows[window_id].height = window_h;
#if defined(VULKAN_ENABLED)
if ((rendering_driver == "vulkan") && window_created) {
context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height);
}
#endif
} else {
windows[window_id].preserve_window_size = false;
window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id);
}
} else {
windows[window_id].preserve_window_size = true;
}
if (!windows[window_id].rect_changed_callback.is_null()) {
Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
Variant *sizep = &size;
Variant ret;
Callable::CallError ce;
windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
}
if (wParam == SIZE_MAXIMIZED) {
windows[window_id].maximized = true;
windows[window_id].minimized = false;
} else if (wParam == SIZE_MINIMIZED) {
windows[window_id].maximized = false;
windows[window_id].minimized = true;
} else if (wParam == SIZE_RESTORED) {
windows[window_id].maximized = false;
windows[window_id].minimized = false;
}
#if 0
if (is_layered_allowed() && layered_window) {
DeleteObject(hBitmap);
RECT r;
GetWindowRect(hWnd, &r);
dib_size = Size2i(r.right - r.left, r.bottom - r.top);
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = dib_size.x;
bmi.bmiHeader.biHeight = dib_size.y;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0);
SelectObject(hDC_dib, hBitmap);
ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
}
#endif
//return 0; // Jump Back
} break;
case WM_ENTERSIZEMOVE: {
Input::get_singleton()->release_pressed_events();
windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
} break;
case WM_EXITSIZEMOVE: {
KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id);
} break;
case WM_TIMER: {
if (wParam == windows[window_id].move_timer_id) {
_process_key_events();
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_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYUP:
case WM_KEYDOWN: {
if (wParam == VK_SHIFT)
shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
if (wParam == VK_CONTROL)
control_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
if (wParam == VK_MENU) {
alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
if (lParam & (1 << 24))
gr_mem = alt_mem;
}
if (mouse_mode == MOUSE_MODE_CAPTURED) {
// When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves
if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) {
_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
}
}
/*
if (wParam==VK_WIN) TODO wtf is this?
meta_mem=uMsg==WM_KEYDOWN;
*/
[[fallthrough]];
}
case WM_CHAR: {
ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
// Make sure we don't include modifiers for the modifier key itself.
KeyEvent ke;
ke.shift = (wParam != VK_SHIFT) ? shift_mem : false;
ke.alt = (!(wParam == VK_MENU && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN))) ? alt_mem : false;
ke.control = (wParam != VK_CONTROL) ? control_mem : false;
ke.meta = meta_mem;
ke.uMsg = uMsg;
ke.window_id = window_id;
if (ke.uMsg == WM_SYSKEYDOWN)
ke.uMsg = WM_KEYDOWN;
if (ke.uMsg == WM_SYSKEYUP)
ke.uMsg = WM_KEYUP;
ke.wParam = wParam;
ke.lParam = lParam;
key_event_buffer[key_event_pos++] = ke;
} break;
case WM_INPUTLANGCHANGEREQUEST: {
// FIXME: Do something?
} break;
case WM_TOUCH: {
BOOL bHandled = FALSE;
UINT cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs);
if (pInputs) {
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) {
for (UINT i = 0; i < cInputs; i++) {
TOUCHINPUT ti = pInputs[i];
POINT touch_pos = {
TOUCH_COORD_TO_PIXEL(ti.x),
TOUCH_COORD_TO_PIXEL(ti.y),
};
ScreenToClient(hWnd, &touch_pos);
//do something with each touch input entry
if (ti.dwFlags & TOUCHEVENTF_MOVE) {
_drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID);
} else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
_touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID);
};
}
bHandled = TRUE;
} else {
/* handle the error here */
}
memdelete_arr(pInputs);
} else {
/* handle the error here, probably out of memory */
}
if (bHandled) {
CloseTouchInputHandle((HTOUCHINPUT)lParam);
return 0;
};
} break;
case WM_DEVICECHANGE: {
joypad->probe_joypads();
} break;
case WM_SETCURSOR: {
if (LOWORD(lParam) == HTCLIENT) {
if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
//Hide the cursor
if (hCursor == nullptr) {
hCursor = SetCursor(nullptr);
} else {
SetCursor(nullptr);
}
} else {
if (hCursor != nullptr) {
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
hCursor = nullptr;
}
}
}
} break;
case WM_DROPFILES: {
HDROP hDropInfo = (HDROP)wParam;
const int buffsize = 4096;
WCHAR buf[buffsize];
int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
Vector<String> files;
for (int i = 0; i < fcount; i++) {
DragQueryFileW(hDropInfo, i, buf, buffsize);
String file = String::utf16((const char16_t *)buf);
files.push_back(file);
}
if (files.size() && !windows[window_id].drop_files_callback.is_null()) {
Variant v = files;
Variant *vp = &v;
Variant ret;
Callable::CallError ce;
windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
}
} break;
default: {
if (user_proc) {
return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
};
};
}
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
if (ds_win)
return ds_win->WndProc(hWnd, uMsg, wParam, lParam);
else
return DefWindowProcW(hWnd, uMsg, wParam, 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;
alt_mem = false;
control_mem = false;
shift_mem = false;
} 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;
alt_mem = false;
}
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) {
wintab_WTEnable(windows[p_window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam));
}
}
void DisplayServerWindows::_process_key_events() {
for (int i = 0; i < key_event_pos; i++) {
KeyEvent &ke = key_event_buffer[i];
switch (ke.uMsg) {
case WM_CHAR: {
// extended keys should only be processed as WM_KEYDOWN message.
if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) {
static char32_t prev_wc = 0;
char32_t unicode = ke.wParam;
if ((unicode & 0xfffffc00) == 0xd800) {
if (prev_wc != 0) {
ERR_PRINT("invalid utf16 surrogate input");
}
prev_wc = unicode;
break; // Skip surrogate.
} else if ((unicode & 0xfffffc00) == 0xdc00) {
if (prev_wc == 0) {
ERR_PRINT("invalid utf16 surrogate input");
break; // Skip invalid surrogate.
}
unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
prev_wc = 0;
} else {
prev_wc = 0;
}
Ref<InputEventKey> k;
k.instantiate();
k->set_window_id(ke.window_id);
k->set_shift_pressed(ke.shift);
k->set_alt_pressed(ke.alt);
k->set_ctrl_pressed(ke.control);
k->set_meta_pressed(ke.meta);
k->set_pressed(true);
k->set_keycode((Key)KeyMappingWindows::get_keysym(ke.wParam));
k->set_physical_keycode((Key)(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))));
k->set_unicode(unicode);
if (k->get_unicode() && gr_mem) {
k->set_alt_pressed(false);
k->set_ctrl_pressed(false);
}
if (k->get_unicode() < 32)
k->set_unicode(0);
Input::get_singleton()->parse_input_event(k);
}
//do nothing
} break;
case WM_KEYUP:
case WM_KEYDOWN: {
Ref<InputEventKey> k;
k.instantiate();
k->set_window_id(ke.window_id);
k->set_shift_pressed(ke.shift);
k->set_alt_pressed(ke.alt);
k->set_ctrl_pressed(ke.control);
k->set_meta_pressed(ke.meta);
k->set_pressed(ke.uMsg == WM_KEYDOWN);
if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) {
// Special case for Numpad Enter key
k->set_keycode(KEY_KP_ENTER);
} else {
k->set_keycode((Key)KeyMappingWindows::get_keysym(ke.wParam));
}
k->set_physical_keycode((Key)(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))));
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
char32_t unicode = key_event_buffer[i + 1].wParam;
static char32_t prev_wck = 0;
if ((unicode & 0xfffffc00) == 0xd800) {
if (prev_wck != 0) {
ERR_PRINT("invalid utf16 surrogate input");
}
prev_wck = unicode;
break; // Skip surrogate.
} else if ((unicode & 0xfffffc00) == 0xdc00) {
if (prev_wck == 0) {
ERR_PRINT("invalid utf16 surrogate input");
break; // Skip invalid surrogate.
}
unicode = (prev_wck << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
prev_wck = 0;
} else {
prev_wck = 0;
}
k->set_unicode(unicode);
}
if (k->get_unicode() && gr_mem) {
k->set_alt_pressed(false);
k->set_ctrl_pressed(false);
}
if (k->get_unicode() < 32)
k->set_unicode(0);
k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30))));
Input::get_singleton()->parse_input_event(k);
} break;
}
}
key_event_pos = 0;
}
void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const String &p_new_driver) {
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
WindowData &wd = E->get();
wd.block_mm = false;
if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) {
wintab_WTEnable(wd.wtctx, false);
wintab_WTClose(wd.wtctx);
wd.wtctx = 0;
}
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.lcPktMode = 0;
wd.wtlc.lcOutOrgX = 0;
wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
wd.wtlc.lcOutOrgY = 0;
wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
if (wd.wtctx) {
wintab_WTEnable(wd.wtctx, true);
AXIS pressure;
if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
wd.min_pressure = int(pressure.axMin);
wd.max_pressure = int(pressure.axMax);
}
AXIS orientation[3];
if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
}
wintab_WTEnable(wd.wtctx, true);
} else {
print_verbose("WinTab context creation failed.");
}
}
}
}
DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
DWORD dwExStyle;
DWORD dwStyle;
_get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
RECT WindowRect;
WindowRect.left = p_rect.position.x;
WindowRect.right = p_rect.position.x + p_rect.size.x;
WindowRect.top = p_rect.position.y;
WindowRect.bottom = p_rect.position.y + p_rect.size.y;
if (p_mode == WINDOW_MODE_FULLSCREEN) {
int nearest_area = 0;
Rect2i screen_rect;
for (int i = 0; i < get_screen_count(); i++) {
Rect2i r;
r.position = screen_get_position(i);
r.size = screen_get_size(i);
Rect2 inters = r.intersection(p_rect);
int area = inters.size.width * inters.size.height;
if (area >= nearest_area) {
screen_rect = r;
nearest_area = area;
}
}
WindowRect.left = screen_rect.position.x;
WindowRect.right = screen_rect.position.x + screen_rect.size.x;
WindowRect.top = screen_rect.position.y;
WindowRect.bottom = screen_rect.position.y + screen_rect.size.y;
}
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
WindowID id = window_id_counter;
{
WindowData &wd = windows[id];
wd.hWnd = CreateWindowExW(
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,
WindowRect.bottom - WindowRect.top,
nullptr, nullptr, hInstance, nullptr);
if (!wd.hWnd) {
MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
windows.erase(id);
return INVALID_WINDOW_ID;
}
if (p_mode != WINDOW_MODE_FULLSCREEN) {
wd.pre_fs_valid = true;
}
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) {
memdelete(context_vulkan);
context_vulkan = nullptr;
windows.erase(id);
ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window.");
}
}
#endif
RegisterTouchWindow(wd.hWnd, 0);
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = wd.hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
DragAcceptFiles(wd.hWnd, true);
if ((tablet_get_current_driver() == "wintab") && wintab_available) {
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.lcPktMode = 0;
wd.wtlc.lcOutOrgX = 0;
wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
wd.wtlc.lcOutOrgY = 0;
wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
if (wd.wtctx) {
wintab_WTEnable(wd.wtctx, true);
AXIS pressure;
if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
wd.min_pressure = int(pressure.axMin);
wd.max_pressure = int(pressure.axMax);
}
AXIS orientation[3];
if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
}
} else {
print_verbose("WinTab context creation failed.");
}
} else {
wd.wtctx = 0;
}
wd.last_pressure = 0;
wd.last_pressure_update = 0;
wd.last_tilt = Vector2();
// IME
wd.im_himc = ImmGetContext(wd.hWnd);
ImmReleaseContext(wd.hWnd, wd.im_himc);
wd.im_position = Vector2();
wd.last_pos = p_rect.position;
wd.width = p_rect.size.width;
wd.height = p_rect.size.height;
window_id_counter++;
}
return id;
}
// WinTab API
bool DisplayServerWindows::wintab_available = false;
WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr;
WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr;
WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr;
WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr;
WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr;
// Windows Ink API
bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2
} SHC_PROCESS_DPI_AWARENESS;
int DisplayServerWindows::tablet_get_driver_count() const {
return tablet_drivers.size();
}
String DisplayServerWindows::tablet_get_driver_name(int p_driver) const {
if (p_driver < 0 || p_driver >= tablet_drivers.size()) {
return "";
} else {
return tablet_drivers[p_driver];
}
}
String DisplayServerWindows::tablet_get_current_driver() const {
return tablet_driver;
}
void DisplayServerWindows::tablet_set_current_driver(const String &p_driver) {
if (tablet_get_driver_count() == 0) {
return;
}
bool found = false;
for (int i = 0; i < tablet_get_driver_count(); i++) {
if (p_driver == tablet_get_driver_name(i)) {
found = true;
}
}
if (found) {
_update_tablet_ctx(tablet_driver, p_driver);
tablet_driver = p_driver;
} else {
ERR_PRINT("Unknown tablet driver " + p_driver + ".");
}
}
DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
drop_events = false;
key_event_pos = 0;
alt_mem = false;
gr_mem = false;
shift_mem = false;
control_mem = false;
meta_mem = false;
console_visible = IsWindowVisible(GetConsoleWindow());
hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance();
pressrc = 0;
old_invalid = true;
mouse_mode = MOUSE_MODE_VISIBLE;
outside = true;
//Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
if (wintab_lib) {
wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW");
wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose");
wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW");
wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket");
wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable");
wintab_available = wintab_WTOpen && wintab_WTClose && wintab_WTInfo && wintab_WTPacket && wintab_WTEnable;
}
if (wintab_available) {
tablet_drivers.push_back("wintab");
}
//Note: Windows Ink API for pen input, available on Windows 8+ only.
HMODULE user32_lib = LoadLibraryW(L"user32.dll");
if (user32_lib) {
win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo;
}
if (winink_available) {
tablet_drivers.push_back("winink");
}
if (OS::get_singleton()->is_hidpi_allowed()) {
HMODULE Shcore = LoadLibraryW(L"Shcore.dll");
if (Shcore != nullptr) {
typedef HRESULT(WINAPI * SetProcessDpiAwareness_t)(SHC_PROCESS_DPI_AWARENESS);
SetProcessDpiAwareness_t SetProcessDpiAwareness = (SetProcessDpiAwareness_t)GetProcAddress(Shcore, "SetProcessDpiAwareness");
if (SetProcessDpiAwareness) {
SetProcessDpiAwareness(SHC_PROCESS_SYSTEM_DPI_AWARE);
}
}
}
memset(&wc, 0, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
wc.lpfnWndProc = (WNDPROC)::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
//wc.hInstance = hInstance;
wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr);
wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = L"Engine";
if (!RegisterClassExW(&wc)) {
MessageBox(nullptr, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
r_error = ERR_UNAVAILABLE;
return;
}
use_raw_input = true;
RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x02;
Rid[0].dwFlags = 0;
Rid[0].hwndTarget = 0;
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) {
//registration failed.
use_raw_input = false;
}
rendering_driver = "vulkan";
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
context_vulkan = memnew(VulkanContextWindows);
if (context_vulkan->initialize() != OK) {
memdelete(context_vulkan);
context_vulkan = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
}
#endif
#if defined(OPENGL_ENABLED)
if (rendering_driver_index == VIDEO_DRIVER_GLES2) {
context_gles2 = memnew(ContextGL_Windows(hWnd, false));
if (context_gles2->initialize() != OK) {
memdelete(context_gles2);
context_gles2 = nullptr;
ERR_FAIL_V(ERR_UNAVAILABLE);
}
context_gles2->set_use_vsync(video_mode.use_vsync);
if (RasterizerGLES2::is_viable() == OK) {
RasterizerGLES2::register_config();
RasterizerGLES2::make_current();
} else {
memdelete(context_gles2);
context_gles2 = nullptr;
ERR_FAIL_V(ERR_UNAVAILABLE);
}
}
#endif
Point2i window_position(
(screen_get_size(0).width - p_resolution.width) / 2,
(screen_get_size(0).height - p_resolution.height) / 2);
WindowID main_window = _create_window(p_mode, p_vsync_mode, 0, Rect2i(window_position, p_resolution));
ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window.");
for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
if (p_flags & (1 << i)) {
window_set_flag(WindowFlags(i), true, main_window);
}
}
show_window(MAIN_WINDOW_ID);
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
rendering_device_vulkan = memnew(RenderingDeviceVulkan);
rendering_device_vulkan->initialize(context_vulkan);
RendererCompositorRD::make_current();
}
#endif
//set_ime_active(false);
if (!OS::get_singleton()->is_in_low_processor_usage_mode()) {
//SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
DWORD index = 0;
HANDLE handle = AvSetMmThreadCharacteristics("Games", &index);
if (handle)
AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL);
// This is needed to make sure that background work does not starve the main thread.
// This is only setting priority of this thread, not the whole process.
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
}
cursor_shape = CURSOR_ARROW;
_update_real_mouse_position(MAIN_WINDOW_ID);
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
r_error = OK;
((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
}
Vector<String> DisplayServerWindows::get_rendering_drivers_func() {
Vector<String> drivers;
#ifdef VULKAN_ENABLED
drivers.push_back("vulkan");
#endif
#ifdef OPENGL_ENABLED
drivers.push_back("opengl");
#endif
return drivers;
}
DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
if (r_error != OK) {
OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan versions.\n"
"Please update your drivers or if you have a very old or integrated GPU upgrade it.",
"Unable to initialize Video driver");
}
return ds;
}
void DisplayServerWindows::register_windows_driver() {
register_create_function("windows", create_func, get_rendering_drivers_func);
}
DisplayServerWindows::~DisplayServerWindows() {
delete joypad;
touch_state.clear();
cursors_cache.clear();
if (user_proc) {
SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
};
if (windows.has(MAIN_WINDOW_ID)) {
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
context_vulkan->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;
}
DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
}
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
if (rendering_device_vulkan) {
rendering_device_vulkan->finalize();
memdelete(rendering_device_vulkan);
}
if (context_vulkan)
memdelete(context_vulkan);
}
#endif
}