godot/platform/windows/os_windows.cpp
Rémi Verschelde b0f782a0e3 Disable driver fallback to GLES2 by default
GLES2 is not designed to be a drop-in replacement for the GLES3 backend,
so the fallback mode has to be used knowingly. It *can* make sense for
simple projects which make sure to handle the differences between both
rendering backends, but most users should stick to one supported backend.

By making it opt-in, we can now use this parameter to define whether to
export ETC textures to Android and iOS when using GLES3 + Fallback.

When using GLES3 without Fallback on Android, set the proper min GLES
version in the AndroidManifest.

Also made the option boolean and renamed it for clarity and to avoid
conflict with the previous String option (which would always evaluate as
"true" otherwise).

Fixes #26569.
2019-03-05 16:36:46 +01:00

3082 lines
76 KiB
C++

/*************************************************************************/
/* os_windows.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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. */
/*************************************************************************/
// Must include Winsock before windows.h (included by os_windows.h)
#include "drivers/unix/net_socket_posix.h"
#include "os_windows.h"
#include "core/io/marshalls.h"
#include "core/version_generated.gen.h"
#include "drivers/gles2/rasterizer_gles2.h"
#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/windows/dir_access_windows.h"
#include "drivers/windows/file_access_windows.h"
#include "drivers/windows/mutex_windows.h"
#include "drivers/windows/rw_lock_windows.h"
#include "drivers/windows/semaphore_windows.h"
#include "drivers/windows/thread_windows.h"
#include "joypad_windows.h"
#include "lang_table.h"
#include "main/main.h"
#include "servers/audio_server.h"
#include "servers/visual/visual_server_raster.h"
#include "servers/visual/visual_server_wrap_mt.h"
#include "windows_terminal_logger.h"
#include <avrt.h>
#include <direct.h>
#include <process.h>
#include <regstr.h>
#include <shlobj.h>
static const WORD MAX_CONSOLE_LINES = 1500;
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
// Workaround mingw-w64 < 4.0 bug
#ifndef WM_TOUCH
#define WM_TOUCH 576
#endif
typedef struct {
int count;
int screen;
Size2 size;
} EnumSizeData;
typedef struct {
int count;
int screen;
Point2 pos;
} EnumPosData;
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;
}
static String format_error_message(DWORD id) {
LPWSTR messageBuffer = NULL;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL);
String msg = "Error " + itos(id) + ": " + String(messageBuffer, size);
LocalFree(messageBuffer);
return msg;
}
extern HINSTANCE godot_hinstance;
void RedirectIOToConsole() {
int hConHandle;
intptr_t lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
// allocate a console for this app
AllocConsole();
// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
coninfo.dwSize);
// redirect unbuffered STDOUT to the console
lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "w");
*stdout = *fp;
setvbuf(stdout, NULL, _IONBF, 0);
// redirect unbuffered STDIN to the console
lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "r");
*stdin = *fp;
setvbuf(stdin, NULL, _IONBF, 0);
// redirect unbuffered STDERR to the console
lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen(hConHandle, "w");
*stderr = *fp;
setvbuf(stderr, NULL, _IONBF, 0);
// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
// point to console as well
}
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
if (ScriptDebugger::get_singleton() == NULL)
return FALSE;
switch (dwCtrlType) {
case CTRL_C_EVENT:
ScriptDebugger::get_singleton()->set_depth(-1);
ScriptDebugger::get_singleton()->set_lines_left(1);
return TRUE;
default:
return FALSE;
}
}
void OS_Windows::initialize_debugging() {
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
}
void OS_Windows::initialize_core() {
crash_handler.initialize();
last_button_state = 0;
//RedirectIOToConsole();
maximized = false;
minimized = false;
borderless = false;
ThreadWindows::make_default();
SemaphoreWindows::make_default();
MutexWindows::make_default();
RWLockWindows::make_default();
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA);
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM);
//FileAccessBufferedFA<FileAccessWindows>::make_default();
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM);
NetSocketPosix::make_default();
// We need to know how often the clock is updated
if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second))
ticks_per_second = 1000;
// If timeAtGameStart is 0 then we get the time since
// the start of the computer when we call GetGameTime()
ticks_start = 0;
ticks_start = get_ticks_usec();
// set minimum resolution for periodic timers, otherwise Sleep(n) may wait at least as
// long as the windows scheduler resolution (~16-30ms) even for calls like Sleep(1)
timeBeginPeriod(1);
process_map = memnew((Map<ProcessID, ProcessInfo>));
IP_Unix::make_default();
cursor_shape = CURSOR_ARROW;
}
bool OS_Windows::can_draw() const {
return !minimized;
};
#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 OS_Windows::_touch_event(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.instance();
event->set_index(idx);
event->set_pressed(p_pressed);
event->set_position(Vector2(p_x, p_y));
if (main_loop) {
input->accumulate_input_event(event);
}
};
void OS_Windows::_drag_event(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;
curr->get() = Vector2(p_x, p_y);
Ref<InputEventScreenDrag> event;
event.instance();
event->set_index(idx);
event->set_position(Vector2(p_x, p_y));
if (main_loop)
input->accumulate_input_event(event);
};
LRESULT OS_Windows::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);
}
};
switch (uMsg) // Check For Windows Messages
{
case WM_SETFOCUS: {
window_has_focus = true;
// Restore mouse mode
_set_mouse_mode_impl(mouse_mode);
break;
}
case WM_KILLFOCUS: {
window_has_focus = false;
// 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(false, E->get().x, E->get().y, E->key());
}
touch_state.clear();
break;
}
case WM_ACTIVATE: // Watch For Window Activate Message
{
minimized = HIWORD(wParam) != 0;
if (!main_loop) {
return 0;
};
if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
alt_mem = false;
control_mem = false;
shift_mem = false;
} else { // WM_INACTIVE
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
alt_mem = false;
};
return 0; // Return To The Message Loop
}
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 (main_loop)
main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
//force_quit=true;
return 0; // Jump Back
}
case WM_MOUSELEAVE: {
old_invalid = true;
outside = true;
if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
if (input)
input->set_mouse_in_window(false);
} break;
case WM_INPUT: {
if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
break;
}
UINT dwSize;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[dwSize];
if (lpb == NULL) {
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.instance();
mm->set_control(control_mem);
mm->set_shift(shift_mem);
mm->set_alt(alt_mem);
mm->set_button_mask(last_button_state);
Point2i c(video_mode.width / 2, video_mode.height / 2);
// centering just so it works as before
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(hWnd, &pos);
SetCursorPos(pos.x, pos.y);
mm->set_position(c);
mm->set_global_position(c);
input->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 (window_has_focus && main_loop && mm->get_relative() != Vector2())
input->accumulate_input_event(mm);
}
delete[] lpb;
} break;
case WM_MOUSEMOVE: {
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
}
if (input->is_emulating_mouse_from_touch()) {
// Universal translation enabled; ignore OS translation
LPARAM extra = GetMessageExtraInfo();
if (IsTouchEvent(extra)) {
break;
}
}
if (outside) {
//mouse enter
if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
if (input)
input->set_mouse_in_window(true);
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
set_cursor_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 (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
break;
Ref<InputEventMouseMotion> mm;
mm.instance();
mm->set_control((wParam & MK_CONTROL) != 0);
mm->set_shift((wParam & MK_SHIFT) != 0);
mm->set_alt(alt_mem);
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(video_mode.width / 2, video_mode.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->set_mouse_position(mm->get_position());
mm->set_speed(input->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 (window_has_focus && main_loop)
input->accumulate_input_event(mm);
} break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
if (input->is_emulating_mouse_from_touch()) {
// Universal translation enabled; ignore OS translations for left button
LPARAM extra = GetMessageExtraInfo();
if (IsTouchEvent(extra)) {
break;
}
}
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.instance();
switch (uMsg) {
case WM_LBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(1);
} break;
case WM_LBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(1);
} break;
case WM_MBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(3);
} break;
case WM_MBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(3);
} break;
case WM_RBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(2);
} break;
case WM_RBUTTONUP: {
mb->set_pressed(false);
mb->set_button_index(2);
} break;
case WM_LBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(1);
mb->set_doubleclick(true);
} break;
case WM_RBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(2);
mb->set_doubleclick(true);
} break;
case WM_MBUTTONDBLCLK: {
mb->set_pressed(true);
mb->set_button_index(3);
mb->set_doubleclick(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(BUTTON_WHEEL_UP);
else
mb->set_button_index(BUTTON_WHEEL_DOWN);
} break;
case WM_MOUSEHWHEEL: {
mb->set_pressed(true);
int motion = (short)HIWORD(wParam);
if (!motion)
return 0;
if (motion < 0) {
mb->set_button_index(BUTTON_WHEEL_LEFT);
mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA));
} else {
mb->set_button_index(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(BUTTON_XBUTTON1);
else
mb->set_button_index(BUTTON_XBUTTON2);
} break;
case WM_XBUTTONUP: {
mb->set_pressed(false);
if (HIWORD(wParam) == XBUTTON1)
mb->set_button_index(BUTTON_XBUTTON1);
else
mb->set_button_index(BUTTON_XBUTTON2);
} break;
case WM_XBUTTONDBLCLK: {
mb->set_pressed(true);
if (HIWORD(wParam) == XBUTTON1)
mb->set_button_index(BUTTON_XBUTTON1);
else
mb->set_button_index(BUTTON_XBUTTON2);
mb->set_doubleclick(true);
} break;
default: { return 0; }
}
mb->set_control((wParam & MK_CONTROL) != 0);
mb->set_shift((wParam & MK_SHIFT) != 0);
mb->set_alt(alt_mem);
//mb->get_alt()=(wParam&MK_MENU)!=0;
if (mb->is_pressed())
last_button_state |= (1 << (mb->get_button_index() - 1));
else
last_button_state &= ~(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 if (mouse_mode != MOUSE_MODE_CAPTURED) {
// for reasons unknown to mankind, wheel comes in screen cordinates
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());
if (main_loop) {
input->accumulate_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();
last_button_state &= ~(1 << (mbd->get_button_index() - 1));
mbd->set_button_mask(last_button_state);
mbd->set_pressed(false);
input->accumulate_input_event(mbd);
}
}
} break;
case WM_MOVE: {
if (!IsIconic(hWnd)) {
int x = LOWORD(lParam);
int y = HIWORD(lParam);
last_pos = Point2(x, y);
}
} 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 && !preserve_window_size) {
video_mode.width = window_w;
video_mode.height = window_h;
} else {
preserve_window_size = false;
set_window_size(Size2(video_mode.width, video_mode.height));
}
}
if (wParam == SIZE_MAXIMIZED) {
maximized = true;
minimized = false;
} else if (wParam == SIZE_MINIMIZED) {
maximized = false;
minimized = true;
} else if (wParam == SIZE_RESTORED) {
maximized = false;
minimized = false;
}
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, NULL, 0x0);
SelectObject(hDC_dib, hBitmap);
ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
}
//return 0; // Jump Back
} break;
case WM_ENTERSIZEMOVE: {
move_timer_id = SetTimer(hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC)NULL);
} break;
case WM_EXITSIZEMOVE: {
KillTimer(hWnd, move_timer_id);
} break;
case WM_TIMER: {
if (wParam == move_timer_id) {
process_key_events();
if (!Main::is_iterating()) {
Main::iteration();
}
}
} break;
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYUP:
case WM_KEYDOWN: {
if (wParam == VK_SHIFT)
shift_mem = uMsg == WM_KEYDOWN;
if (wParam == VK_CONTROL)
control_mem = uMsg == WM_KEYDOWN;
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)) {
if (main_loop)
main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_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;
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(touch_pos.x, touch_pos.y, ti.dwID);
} else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
_touch_event(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 (window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) {
//Hide the cursor
if (hCursor == NULL)
hCursor = SetCursor(NULL);
else
SetCursor(NULL);
} else {
if (hCursor != NULL) {
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
set_cursor_shape(c);
hCursor = NULL;
}
}
}
} break;
case WM_DROPFILES: {
HDROP hDropInfo = (HDROP)wParam;
const int buffsize = 4096;
wchar_t buf[buffsize];
int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0);
Vector<String> files;
for (int i = 0; i < fcount; i++) {
DragQueryFileW(hDropInfo, i, buf, buffsize);
String file = buf;
files.push_back(file);
}
if (files.size() && main_loop) {
main_loop->drop_files(files, 0);
}
} 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) {
OS_Windows *os_win = static_cast<OS_Windows *>(OS::get_singleton());
if (os_win)
return os_win->WndProc(hWnd, uMsg, wParam, lParam);
else
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
void OS_Windows::process_key_events() {
for (int i = 0; i < key_event_pos; i++) {
KeyEvent &ke = key_event_buffer[i];
switch (ke.uMsg) {
case WM_CHAR: {
if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) {
Ref<InputEventKey> k;
k.instance();
k->set_shift(ke.shift);
k->set_alt(ke.alt);
k->set_control(ke.control);
k->set_metakey(ke.meta);
k->set_pressed(true);
k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
k->set_unicode(ke.wParam);
if (k->get_unicode() && gr_mem) {
k->set_alt(false);
k->set_control(false);
}
if (k->get_unicode() < 32)
k->set_unicode(0);
input->accumulate_input_event(k);
}
//do nothing
} break;
case WM_KEYUP:
case WM_KEYDOWN: {
Ref<InputEventKey> k;
k.instance();
k->set_shift(ke.shift);
k->set_alt(ke.alt);
k->set_control(ke.control);
k->set_metakey(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_scancode(KEY_KP_ENTER);
} else {
k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
}
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
k->set_unicode(key_event_buffer[i + 1].wParam);
}
if (k->get_unicode() && gr_mem) {
k->set_alt(false);
k->set_control(false);
}
if (k->get_unicode() < 32)
k->set_unicode(0);
k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30))));
input->accumulate_input_event(k);
} break;
}
}
key_event_pos = 0;
}
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 = NULL;
typedef HRESULT(WINAPI * GetDPIForMonitor_t)(HMONITOR hmonitor, _MonitorDpiType dpiType, UINT * dpiX, UINT * dpiY);
static GetDPIForMonitor_t getDPIForMonitor = NULL;
if (Shcore == NULL) {
Shcore = LoadLibraryW(L"Shcore.dll");
getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : NULL;
if ((Shcore == NULL) || (getDPIForMonitor == NULL)) {
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(NULL);
if (hdc) {
overallX = GetDeviceCaps(hdc, LOGPIXELSX);
overallY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
}
}
if (overallX > 0 && overallY > 0) {
dpiX = overallX;
dpiY = overallY;
}
}
return (dpiX + dpiY) / 2;
}
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 OS_Windows::get_current_video_driver() const {
return video_driver_index;
}
Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
main_loop = NULL;
outside = true;
window_has_focus = true;
WNDCLASSEXW wc;
if (is_hidpi_allowed()) {
HMODULE Shcore = LoadLibraryW(L"Shcore.dll");
if (Shcore != NULL) {
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);
}
}
}
video_mode = p_desired;
//printf("**************** desired %s, mode %s\n", p_desired.fullscreen?"true":"false", video_mode.fullscreen?"true":"false");
RECT WindowRect;
WindowRect.left = 0;
WindowRect.right = video_mode.width;
WindowRect.top = 0;
WindowRect.bottom = video_mode.height;
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 = godot_hinstance ? godot_hinstance : GetModuleHandle(NULL);
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Engine";
if (!RegisterClassExW(&wc)) {
MessageBox(NULL, "Failed To Register The Window Class.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return ERR_UNAVAILABLE;
}
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;
}
pre_fs_valid = true;
if (video_mode.fullscreen) {
/* this returns DPI unaware size, commenting
DEVMODE current;
memset(&current, 0, sizeof(current));
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &current);
WindowRect.right = current.dmPelsWidth;
WindowRect.bottom = current.dmPelsHeight;
*/
EnumSizeData data = { 0, 0, Size2() };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data);
WindowRect.right = data.size.width;
WindowRect.bottom = data.size.height;
/* DEVMODE dmScreenSettings;
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
dmScreenSettings.dmSize=sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = video_mode.width;
dmScreenSettings.dmPelsHeight = video_mode.height;
dmScreenSettings.dmBitsPerPel = current.dmBitsPerPel;
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
LONG err = ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN);
if (err!=DISP_CHANGE_SUCCESSFUL) {
video_mode.fullscreen=false;
}*/
pre_fs_valid = false;
}
DWORD dwExStyle;
DWORD dwStyle;
if (video_mode.fullscreen || video_mode.borderless_window) {
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
} else {
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
if (!video_mode.resizable) {
dwStyle &= ~WS_THICKFRAME;
dwStyle &= ~WS_MAXIMIZEBOX;
}
}
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
char *windowid;
#ifdef MINGW_ENABLED
windowid = getenv("GODOT_WINDOWID");
#else
size_t len;
_dupenv_s(&windowid, &len, "GODOT_WINDOWID");
#endif
if (windowid) {
// strtoull on mingw
#ifdef MINGW_ENABLED
hWnd = (HWND)strtoull(windowid, NULL, 0);
#else
hWnd = (HWND)_strtoui64(windowid, NULL, 0);
#endif
free(windowid);
SetLastError(0);
user_proc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)::WndProc);
DWORD le = GetLastError();
if (user_proc == 0 && le != 0) {
printf("Error setting WNDPROC: %li\n", le);
};
GetWindowLongPtr(hWnd, GWLP_WNDPROC);
RECT rect;
if (!GetClientRect(hWnd, &rect)) {
MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
return ERR_UNAVAILABLE;
};
video_mode.width = rect.right;
video_mode.height = rect.bottom;
video_mode.fullscreen = false;
} else {
hWnd = CreateWindowExW(
dwExStyle,
L"Engine", L"",
dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
(GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2,
(GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2,
WindowRect.right - WindowRect.left,
WindowRect.bottom - WindowRect.top,
NULL, NULL, hInstance, NULL);
if (!hWnd) {
MessageBoxW(NULL, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
return ERR_UNAVAILABLE;
}
};
if (video_mode.always_on_top) {
SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
#if defined(OPENGL_ENABLED)
bool gles3_context = true;
if (p_video_driver == VIDEO_DRIVER_GLES2) {
gles3_context = false;
}
bool editor = Engine::get_singleton()->is_editor_hint();
bool gl_initialization_error = false;
gl_context = NULL;
while (!gl_context) {
gl_context = memnew(ContextGL_Windows(hWnd, gles3_context));
if (gl_context->initialize() != OK) {
memdelete(gl_context);
gl_context = NULL;
if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
if (p_video_driver == VIDEO_DRIVER_GLES2) {
gl_initialization_error = true;
break;
}
p_video_driver = VIDEO_DRIVER_GLES2;
gles3_context = false;
} else {
gl_initialization_error = true;
break;
}
}
}
while (true) {
if (gles3_context) {
if (RasterizerGLES3::is_viable() == OK) {
RasterizerGLES3::register_config();
RasterizerGLES3::make_current();
break;
} else {
if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) {
p_video_driver = VIDEO_DRIVER_GLES2;
gles3_context = false;
continue;
} else {
gl_initialization_error = true;
break;
}
}
} else {
if (RasterizerGLES2::is_viable() == OK) {
RasterizerGLES2::register_config();
RasterizerGLES2::make_current();
break;
} else {
gl_initialization_error = true;
break;
}
}
}
if (gl_initialization_error) {
OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n"
"Please update your drivers or if you have a very old or integrated GPU upgrade it.",
"Unable to initialize Video driver");
return ERR_UNAVAILABLE;
}
video_driver_index = p_video_driver;
gl_context->set_use_vsync(video_mode.use_vsync);
#endif
visual_server = memnew(VisualServerRaster);
if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
}
visual_server->init();
input = memnew(InputDefault);
joypad = memnew(JoypadWindows(input, &hWnd));
power_manager = memnew(PowerWindows);
AudioDriverManager::initialize(p_audio_driver);
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
RegisterTouchWindow(hWnd, 0);
_ensure_user_data_dir();
DragAcceptFiles(hWnd, true);
move_timer_id = 1;
if (!is_no_window_mode_enabled()) {
ShowWindow(hWnd, SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To
}
if (p_desired.layered_splash) {
set_window_per_pixel_transparency_enabled(true);
}
// IME
im_himc = ImmGetContext(hWnd);
ImmReleaseContext(hWnd, im_himc);
im_position = Vector2();
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);
}
update_real_mouse_position();
return OK;
}
void OS_Windows::set_clipboard(const String &p_text) {
if (!OpenClipboard(hWnd)) {
ERR_EXPLAIN("Unable to open clipboard.");
ERR_FAIL();
};
EmptyClipboard();
HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (p_text.length() + 1) * sizeof(CharType));
if (mem == NULL) {
ERR_EXPLAIN("Unable to allocate memory for clipboard contents.");
ERR_FAIL();
};
LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem);
memcpy(lptstrCopy, p_text.c_str(), (p_text.length() + 1) * sizeof(CharType));
//memset((lptstrCopy + p_text.length()), 0, sizeof(CharType));
GlobalUnlock(mem);
SetClipboardData(CF_UNICODETEXT, mem);
// set the CF_TEXT version (not needed?)
CharString utf8 = p_text.utf8();
mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1);
if (mem == NULL) {
ERR_EXPLAIN("Unable to allocate memory for clipboard contents.");
ERR_FAIL();
};
LPTSTR ptr = (LPTSTR)GlobalLock(mem);
memcpy(ptr, utf8.get_data(), utf8.length());
ptr[utf8.length()] = 0;
GlobalUnlock(mem);
SetClipboardData(CF_TEXT, mem);
CloseClipboard();
};
String OS_Windows::get_clipboard() const {
String ret;
if (!OpenClipboard(hWnd)) {
ERR_EXPLAIN("Unable to open clipboard.");
ERR_FAIL_V("");
};
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
if (mem != NULL) {
LPWSTR ptr = (LPWSTR)GlobalLock(mem);
if (ptr != NULL) {
ret = String((CharType *)ptr);
GlobalUnlock(mem);
};
};
} else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
if (mem != NULL) {
LPTSTR ptr = (LPTSTR)GlobalLock(mem);
if (ptr != NULL) {
ret.parse_utf8((const char *)ptr);
GlobalUnlock(mem);
};
};
};
CloseClipboard();
return ret;
};
void OS_Windows::delete_main_loop() {
if (main_loop)
memdelete(main_loop);
main_loop = NULL;
}
void OS_Windows::set_main_loop(MainLoop *p_main_loop) {
input->set_main_loop(p_main_loop);
main_loop = p_main_loop;
}
void OS_Windows::finalize() {
#ifdef WINMIDI_ENABLED
driver_midi.close();
#endif
if (main_loop)
memdelete(main_loop);
main_loop = NULL;
memdelete(joypad);
memdelete(input);
touch_state.clear();
visual_server->finish();
memdelete(visual_server);
#ifdef OPENGL_ENABLED
if (gl_context)
memdelete(gl_context);
#endif
if (user_proc) {
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
};
}
void OS_Windows::finalize_core() {
timeEndPeriod(1);
memdelete(process_map);
NetSocketPosix::cleanup();
}
void OS_Windows::alert(const String &p_alert, const String &p_title) {
if (!is_no_window_mode_enabled())
MessageBoxW(NULL, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
else
print_line("ALERT: " + p_alert);
}
void OS_Windows::set_mouse_mode(MouseMode p_mode) {
if (mouse_mode == p_mode)
return;
_set_mouse_mode_impl(p_mode);
mouse_mode = p_mode;
}
void OS_Windows::_set_mouse_mode_impl(MouseMode p_mode) {
if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) {
RECT clipRect;
GetClientRect(hWnd, &clipRect);
ClientToScreen(hWnd, (POINT *)&clipRect.left);
ClientToScreen(hWnd, (POINT *)&clipRect.right);
ClipCursor(&clipRect);
if (p_mode == MOUSE_MODE_CAPTURED) {
center = Point2i(video_mode.width / 2, video_mode.height / 2);
POINT pos = { (int)center.x, (int)center.y };
ClientToScreen(hWnd, &pos);
SetCursorPos(pos.x, pos.y);
SetCapture(hWnd);
}
} else {
ReleaseCapture();
ClipCursor(NULL);
}
if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) {
hCursor = SetCursor(NULL);
} else {
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
set_cursor_shape(c);
}
}
OS_Windows::MouseMode OS_Windows::get_mouse_mode() const {
return mouse_mode;
}
void OS_Windows::warp_mouse_position(const Point2 &p_to) {
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(hWnd, &p);
SetCursorPos(p.x, p.y);
}
}
Point2 OS_Windows::get_mouse_position() const {
return Point2(old_x, old_y);
}
void OS_Windows::update_real_mouse_position() {
POINT mouse_pos;
if (GetCursorPos(&mouse_pos) && ScreenToClient(hWnd, &mouse_pos)) {
if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= video_mode.width && mouse_pos.y <= video_mode.height) {
old_x = mouse_pos.x;
old_y = mouse_pos.y;
old_invalid = false;
input->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y));
}
}
}
int OS_Windows::get_mouse_button_state() const {
return last_button_state;
}
void OS_Windows::set_window_title(const String &p_title) {
SetWindowTextW(hWnd, p_title.c_str());
}
void OS_Windows::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
}
OS::VideoMode OS_Windows::get_video_mode(int p_screen) const {
return video_mode;
}
void OS_Windows::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
}
static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
int *data = (int *)dwData;
(*data)++;
return TRUE;
}
int OS_Windows::get_screen_count() const {
int data = 0;
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcCount, (LPARAM)&data);
return data;
}
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;
}
int OS_Windows::get_current_screen() const {
EnumScreenData data = { 0, 0, MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcScreen, (LPARAM)&data);
return data.screen;
}
void OS_Windows::set_current_screen(int p_screen) {
Vector2 ofs = get_window_position() - get_screen_position(get_current_screen());
set_window_position(ofs + get_screen_position(p_screen));
}
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;
}
Point2 OS_Windows::get_screen_position(int p_screen) const {
EnumPosData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Point2() };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcPos, (LPARAM)&data);
return data.pos;
}
Size2 OS_Windows::get_screen_size(int p_screen) const {
EnumSizeData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, Size2() };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data);
return data.size;
}
typedef struct {
int count;
int screen;
int dpi;
} EnumDpiData;
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 OS_Windows::get_screen_dpi(int p_screen) const {
EnumDpiData data = { 0, p_screen == -1 ? get_current_screen() : p_screen, 72 };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}
Point2 OS_Windows::get_window_position() const {
if (minimized) {
return last_pos;
}
RECT r;
GetWindowRect(hWnd, &r);
return Point2(r.left, r.top);
}
void OS_Windows::set_window_position(const Point2 &p_position) {
if (video_mode.fullscreen) return;
RECT r;
GetWindowRect(hWnd, &r);
MoveWindow(hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE);
// Don't let the mouse leave the window when moved
if (mouse_mode == MOUSE_MODE_CONFINED) {
RECT rect;
GetClientRect(hWnd, &rect);
ClientToScreen(hWnd, (POINT *)&rect.left);
ClientToScreen(hWnd, (POINT *)&rect.right);
ClipCursor(&rect);
}
last_pos = p_position;
update_real_mouse_position();
}
Size2 OS_Windows::get_window_size() const {
if (minimized) {
return Size2(video_mode.width, video_mode.height);
}
RECT r;
if (GetClientRect(hWnd, &r)) { // Only area inside of window border
return Size2(r.right - r.left, r.bottom - r.top);
}
return Size2();
}
Size2 OS_Windows::get_real_window_size() const {
RECT r;
if (GetWindowRect(hWnd, &r)) { // Includes area of the window border
return Size2(r.right - r.left, r.bottom - r.top);
}
return Size2();
}
void OS_Windows::set_window_size(const Size2 p_size) {
int w = p_size.width;
int h = p_size.height;
video_mode.width = w;
video_mode.height = h;
if (video_mode.fullscreen) {
return;
}
RECT rect;
GetWindowRect(hWnd, &rect);
if (!video_mode.borderless_window) {
RECT crect;
GetClientRect(hWnd, &crect);
w += (rect.right - rect.left) - (crect.right - crect.left);
h += (rect.bottom - rect.top) - (crect.bottom - crect.top);
}
MoveWindow(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) {
RECT rect;
GetClientRect(hWnd, &rect);
ClientToScreen(hWnd, (POINT *)&rect.left);
ClientToScreen(hWnd, (POINT *)&rect.right);
ClipCursor(&rect);
}
}
void OS_Windows::set_window_fullscreen(bool p_enabled) {
if (video_mode.fullscreen == p_enabled)
return;
if (layered_window)
set_window_per_pixel_transparency_enabled(false);
if (p_enabled) {
if (pre_fs_valid) {
GetWindowRect(hWnd, &pre_fs_rect);
}
int cs = get_current_screen();
Point2 pos = get_screen_position(cs);
Size2 size = get_screen_size(cs);
video_mode.fullscreen = true;
_update_window_style(false);
MoveWindow(hWnd, pos.x, pos.y, size.width, size.height, TRUE);
} else {
RECT rect;
video_mode.fullscreen = false;
if (pre_fs_valid) {
rect = pre_fs_rect;
} else {
rect.left = 0;
rect.right = video_mode.width;
rect.top = 0;
rect.bottom = video_mode.height;
}
_update_window_style(false);
MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
pre_fs_valid = true;
}
}
bool OS_Windows::is_window_fullscreen() const {
return video_mode.fullscreen;
}
void OS_Windows::set_window_resizable(bool p_enabled) {
if (video_mode.resizable == p_enabled)
return;
video_mode.resizable = p_enabled;
_update_window_style();
}
bool OS_Windows::is_window_resizable() const {
return video_mode.resizable;
}
void OS_Windows::set_window_minimized(bool p_enabled) {
if (p_enabled) {
maximized = false;
minimized = true;
ShowWindow(hWnd, SW_MINIMIZE);
} else {
ShowWindow(hWnd, SW_RESTORE);
maximized = false;
minimized = false;
}
}
bool OS_Windows::is_window_minimized() const {
return minimized;
}
void OS_Windows::set_window_maximized(bool p_enabled) {
if (p_enabled) {
maximized = true;
minimized = false;
ShowWindow(hWnd, SW_MAXIMIZE);
} else {
ShowWindow(hWnd, SW_RESTORE);
maximized = false;
minimized = false;
}
}
bool OS_Windows::is_window_maximized() const {
return maximized;
}
void OS_Windows::set_window_always_on_top(bool p_enabled) {
if (video_mode.always_on_top == p_enabled)
return;
video_mode.always_on_top = p_enabled;
_update_window_style();
}
bool OS_Windows::is_window_always_on_top() const {
return video_mode.always_on_top;
}
bool OS_Windows::get_window_per_pixel_transparency_enabled() const {
if (!is_layered_allowed()) return false;
return layered_window;
}
void OS_Windows::set_window_per_pixel_transparency_enabled(bool p_enabled) {
if (!is_layered_allowed()) return;
if (layered_window != p_enabled) {
if (p_enabled) {
set_borderless_window(true);
//enable per-pixel alpha
hDC_dib = CreateCompatibleDC(GetDC(hWnd));
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
RECT r;
GetWindowRect(hWnd, &r);
dib_size = Size2(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, NULL, 0x0);
SelectObject(hDC_dib, hBitmap);
ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
layered_window = true;
} else {
//disable per-pixel alpha
layered_window = false;
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
//cleanup
DeleteObject(hBitmap);
DeleteDC(hDC_dib);
}
}
}
uint8_t *OS_Windows::get_layered_buffer_data() {
return (is_layered_allowed() && layered_window) ? dib_data : NULL;
}
Size2 OS_Windows::get_layered_buffer_size() {
return (is_layered_allowed() && layered_window) ? dib_size : Size2();
}
void OS_Windows::swap_layered_buffer() {
if (is_layered_allowed() && layered_window) {
//premultiply alpha
for (int y = 0; y < dib_size.y; y++) {
for (int x = 0; x < dib_size.x; x++) {
float alpha = (float)dib_data[y * (int)dib_size.x * 4 + x * 4 + 3] / (float)0xFF;
dib_data[y * (int)dib_size.x * 4 + x * 4 + 0] *= alpha;
dib_data[y * (int)dib_size.x * 4 + x * 4 + 1] *= alpha;
dib_data[y * (int)dib_size.x * 4 + x * 4 + 2] *= alpha;
}
}
//swap layered window buffer
POINT ptSrc = { 0, 0 };
SIZE sizeWnd = { (long)dib_size.x, (long)dib_size.y };
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = AC_SRC_ALPHA;
bf.SourceConstantAlpha = 0xFF;
UpdateLayeredWindow(hWnd, NULL, NULL, &sizeWnd, hDC_dib, &ptSrc, 0, &bf, ULW_ALPHA);
}
}
void OS_Windows::set_borderless_window(bool p_borderless) {
if (video_mode.borderless_window == p_borderless)
return;
if (!p_borderless && layered_window)
set_window_per_pixel_transparency_enabled(false);
video_mode.borderless_window = p_borderless;
preserve_window_size = true;
_update_window_style();
}
bool OS_Windows::get_borderless_window() {
return video_mode.borderless_window;
}
void OS_Windows::_update_window_style(bool repaint) {
if (video_mode.fullscreen || video_mode.borderless_window) {
SetWindowLongPtr(hWnd, GWL_STYLE, WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE);
} else {
if (video_mode.resizable) {
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
} else {
SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_MINIMIZEBOX | WS_POPUPWINDOW | WS_VISIBLE);
}
}
SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
if (repaint) {
RECT rect;
GetWindowRect(hWnd, &rect);
MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
}
}
Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
String path = p_path;
if (!FileAccess::exists(path)) {
//this code exists so gdnative can load .dll files from within the executable path
path = get_executable_path().get_base_dir().plus_file(p_path.get_file());
}
typedef DLL_DIRECTORY_COOKIE(WINAPI * PAddDllDirectory)(PCWSTR);
typedef BOOL(WINAPI * PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE);
PAddDllDirectory add_dll_directory = (PAddDllDirectory)GetProcAddress(GetModuleHandle("kernel32.dll"), "AddDllDirectory");
PRemoveDllDirectory remove_dll_directory = (PRemoveDllDirectory)GetProcAddress(GetModuleHandle("kernel32.dll"), "RemoveDllDirectory");
bool has_dll_directory_api = ((add_dll_directory != NULL) && (remove_dll_directory != NULL));
DLL_DIRECTORY_COOKIE cookie = NULL;
if (p_also_set_library_path && has_dll_directory_api) {
cookie = add_dll_directory(path.get_base_dir().c_str());
}
p_library_handle = (void *)LoadLibraryExW(path.c_str(), NULL, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
if (cookie) {
remove_dll_directory(cookie);
}
if (!p_library_handle) {
ERR_EXPLAIN("Can't open dynamic library: " + p_path + ". Error: " + format_error_message(GetLastError()));
ERR_FAIL_V(ERR_CANT_OPEN);
}
return OK;
}
Error OS_Windows::close_dynamic_library(void *p_library_handle) {
if (!FreeLibrary((HMODULE)p_library_handle)) {
return FAILED;
}
return OK;
}
Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
p_symbol_handle = (void *)GetProcAddress((HMODULE)p_library_handle, p_name.utf8().get_data());
if (!p_symbol_handle) {
if (!p_optional) {
ERR_EXPLAIN("Can't resolve symbol " + p_name + ". Error: " + String::num(GetLastError()));
ERR_FAIL_V(ERR_CANT_RESOLVE);
} else {
return ERR_CANT_RESOLVE;
}
}
return OK;
}
void OS_Windows::request_attention() {
FLASHWINFO info;
info.cbSize = sizeof(FLASHWINFO);
info.hwnd = hWnd;
info.dwFlags = FLASHW_TRAY;
info.dwTimeout = 0;
info.uCount = 2;
FlashWindowEx(&info);
}
String OS_Windows::get_name() {
return "Windows";
}
OS::Date OS_Windows::get_date(bool utc) const {
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
else
GetLocalTime(&systemtime);
Date date;
date.day = systemtime.wDay;
date.month = Month(systemtime.wMonth);
date.weekday = Weekday(systemtime.wDayOfWeek);
date.year = systemtime.wYear;
date.dst = false;
return date;
}
OS::Time OS_Windows::get_time(bool utc) const {
SYSTEMTIME systemtime;
if (utc)
GetSystemTime(&systemtime);
else
GetLocalTime(&systemtime);
Time time;
time.hour = systemtime.wHour;
time.min = systemtime.wMinute;
time.sec = systemtime.wSecond;
return time;
}
OS::TimeZoneInfo OS_Windows::get_time_zone_info() const {
TIME_ZONE_INFORMATION info;
bool daylight = false;
if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT)
daylight = true;
TimeZoneInfo ret;
if (daylight) {
ret.name = info.DaylightName;
} else {
ret.name = info.StandardName;
}
// Bias value returned by GetTimeZoneInformation is inverted of what we expect
// For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180
ret.bias = -info.Bias;
return ret;
}
uint64_t OS_Windows::get_unix_time() const {
FILETIME ft;
SYSTEMTIME st;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
SYSTEMTIME ep;
ep.wYear = 1970;
ep.wMonth = 1;
ep.wDayOfWeek = 4;
ep.wDay = 1;
ep.wHour = 0;
ep.wMinute = 0;
ep.wSecond = 0;
ep.wMilliseconds = 0;
FILETIME fep;
SystemTimeToFileTime(&ep, &fep);
return (*(uint64_t *)&ft - *(uint64_t *)&fep) / 10000000;
};
uint64_t OS_Windows::get_system_time_secs() const {
return get_system_time_msecs() / 1000;
}
uint64_t OS_Windows::get_system_time_msecs() const {
const uint64_t WINDOWS_TICK = 10000;
const uint64_t MSEC_TO_UNIX_EPOCH = 11644473600000LL;
SYSTEMTIME st;
GetSystemTime(&st);
FILETIME ft;
SystemTimeToFileTime(&st, &ft);
uint64_t ret;
ret = ft.dwHighDateTime;
ret <<= 32;
ret |= ft.dwLowDateTime;
return (uint64_t)(ret / WINDOWS_TICK - MSEC_TO_UNIX_EPOCH);
}
void OS_Windows::delay_usec(uint32_t p_usec) const {
if (p_usec < 1000)
Sleep(1);
else
Sleep(p_usec / 1000);
}
uint64_t OS_Windows::get_ticks_usec() const {
uint64_t ticks;
uint64_t time;
// This is the number of clock ticks since start
if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks))
ticks = (UINT64)timeGetTime();
// Divide by frequency to get the time in seconds
time = ticks * 1000000L / ticks_per_second;
// Subtract the time at game start to get
// the time since the game started
time -= ticks_start;
return time;
}
void OS_Windows::process_events() {
MSG msg;
if (!drop_events) {
joypad->process_joypads();
}
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (!drop_events) {
process_key_events();
input->flush_accumulated_events();
}
}
void OS_Windows::set_cursor_shape(CursorShape p_shape) {
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] != NULL) {
SetCursor(cursors[p_shape]);
} else {
SetCursor(LoadCursor(hInstance, win_cursors[p_shape]));
}
cursor_shape = p_shape;
}
void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
Ref<Texture> 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_data();
}
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_data();
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);
image->lock();
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();
}
image->unlock();
// 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 = NULL;
HBITMAP hXorMask = NULL;
GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask);
if (NULL == hAndMask || NULL == hXorMask) {
memfree(buffer);
DeleteObject(bitmap);
return;
}
// Finally, create the icon
ICONINFO iconinfo = { 0 };
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);
if (p_shape == cursor_shape) {
if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
SetCursor(cursors[p_shape]);
}
}
if (hAndMask != NULL) {
DeleteObject(hAndMask);
}
if (hXorMask != NULL) {
DeleteObject(hXorMask);
}
memfree(buffer);
DeleteObject(bitmap);
} else {
// Reset to default system cursor
cursors[p_shape] = NULL;
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
set_cursor_shape(c);
}
}
void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) {
// Get the system display DC
HDC hDC = GetDC(NULL);
// 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(NULL, 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 a 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 a 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);
}
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) {
if (p_blocking && r_pipe) {
String argss;
argss = "\"\"" + p_path + "\"";
for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
argss += String(" \"") + E->get() + "\"";
}
FILE *f = _wpopen(argss.c_str(), L"r");
ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
char buf[65535];
while (fgets(buf, 65535, f)) {
(*r_pipe) += buf;
}
int rv = _pclose(f);
if (r_exitcode)
*r_exitcode = rv;
return OK;
}
String cmdline = "\"" + p_path + "\"";
const List<String>::Element *I = p_arguments.front();
while (I) {
cmdline += " \"" + I->get() + "\"";
I = I->next();
};
ProcessInfo pi;
ZeroMemory(&pi.si, sizeof(pi.si));
pi.si.cb = sizeof(pi.si);
ZeroMemory(&pi.pi, sizeof(pi.pi));
LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
Vector<CharType> modstr; //windows wants to change this no idea why
modstr.resize(cmdline.size());
for (int i = 0; i < cmdline.size(); i++)
modstr.write[i] = cmdline[i];
int ret = CreateProcessW(NULL, modstr.ptrw(), NULL, NULL, 0, NORMAL_PRIORITY_CLASS, NULL, NULL, si_w, &pi.pi);
ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK);
if (p_blocking) {
DWORD ret = WaitForSingleObject(pi.pi.hProcess, INFINITE);
if (r_exitcode)
*r_exitcode = ret;
CloseHandle(pi.pi.hProcess);
CloseHandle(pi.pi.hThread);
} else {
ProcessID pid = pi.pi.dwProcessId;
if (r_child_id) {
*r_child_id = pid;
};
process_map->insert(pid, pi);
};
return OK;
};
Error OS_Windows::kill(const ProcessID &p_pid) {
ERR_FAIL_COND_V(!process_map->has(p_pid), FAILED);
const PROCESS_INFORMATION pi = (*process_map)[p_pid].pi;
process_map->erase(p_pid);
const int ret = TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return ret != 0 ? OK : FAILED;
};
int OS_Windows::get_process_id() const {
return _getpid();
}
Error OS_Windows::set_cwd(const String &p_cwd) {
if (_wchdir(p_cwd.c_str()) != 0)
return ERR_CANT_OPEN;
return OK;
}
String OS_Windows::get_executable_path() const {
wchar_t bufname[4096];
GetModuleFileNameW(NULL, bufname, 4096);
String s = bufname;
return s;
}
void OS_Windows::set_icon(const Ref<Image> &p_icon) {
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];
PoolVector<uint8_t>::Read r = icon->get_data().read();
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(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
/* Set the icon in the task manager (should we do this?) */
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
}
bool OS_Windows::has_environment(const String &p_var) const {
#ifdef MINGW_ENABLED
return _wgetenv(p_var.c_str()) != NULL;
#else
wchar_t *env;
size_t len;
_wdupenv_s(&env, &len, p_var.c_str());
const bool has_env = env != NULL;
free(env);
return has_env;
#endif
};
String OS_Windows::get_environment(const String &p_var) const {
wchar_t wval[0x7Fff]; // MSDN says 32767 char is the maximum
int wlen = GetEnvironmentVariableW(p_var.c_str(), wval, 0x7Fff);
if (wlen > 0) {
return wval;
}
return "";
}
bool OS_Windows::set_environment(const String &p_var, const String &p_value) const {
return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str());
}
String OS_Windows::get_stdin_string(bool p_block) {
if (p_block) {
char buff[1024];
return fgets(buff, 1024, stdin);
};
return String();
}
void OS_Windows::enable_for_stealing_focus(ProcessID pid) {
AllowSetForegroundWindow(pid);
}
void OS_Windows::move_window_to_foreground() {
SetForegroundWindow(hWnd);
}
Error OS_Windows::shell_open(String p_uri) {
ShellExecuteW(NULL, L"open", p_uri.c_str(), NULL, NULL, SW_SHOWNORMAL);
return OK;
}
String OS_Windows::get_locale() const {
const _WinLocale *wl = &_win_locales[0];
LANGID langid = GetUserDefaultUILanguage();
String neutral;
int lang = langid & ((1 << 9) - 1);
int sublang = langid & ~((1 << 9) - 1);
while (wl->locale) {
if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL)
neutral = wl->locale;
if (lang == wl->main_lang && sublang == wl->sublang)
return wl->locale;
wl++;
}
if (neutral != "")
return neutral;
return "en";
}
// We need this because GetSystemInfo() is unreliable on WOW64
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724381(v=vs.85).aspx
// Taken from MSDN
typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL is_wow64() {
BOOL wow64 = FALSE;
fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (fnIsWow64Process) {
if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) {
wow64 = FALSE;
}
}
return wow64;
}
int OS_Windows::get_processor_count() const {
SYSTEM_INFO sysinfo;
if (is_wow64())
GetNativeSystemInfo(&sysinfo);
else
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
}
OS::LatinKeyboardVariant OS_Windows::get_latin_keyboard_variant() const {
unsigned long azerty[] = {
0x00020401, // Arabic (102) AZERTY
0x0001080c, // Belgian (Comma)
0x0000080c, // Belgian French
0x0000040c, // French
0 // <--- STOP MARK
};
unsigned long qwertz[] = {
0x0000041a, // Croation
0x00000405, // Czech
0x00000407, // German
0x00010407, // German (IBM)
0x0000040e, // Hungarian
0x0000046e, // Luxembourgish
0x00010415, // Polish (214)
0x00000418, // Romanian (Legacy)
0x0000081a, // Serbian (Latin)
0x0000041b, // Slovak
0x00000424, // Slovenian
0x0001042e, // Sorbian Extended
0x0002042e, // Sorbian Standard
0x0000042e, // Sorbian Standard (Legacy)
0x0000100c, // Swiss French
0x00000807, // Swiss German
0 // <--- STOP MARK
};
unsigned long dvorak[] = {
0x00010409, // US-Dvorak
0x00030409, // US-Dvorak for left hand
0x00040409, // US-Dvorak for right hand
0 // <--- STOP MARK
};
char name[KL_NAMELENGTH + 1];
name[0] = 0;
GetKeyboardLayoutNameA(name);
unsigned long hex = strtoul(name, NULL, 16);
int i = 0;
while (azerty[i] != 0) {
if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY;
i++;
}
i = 0;
while (qwertz[i] != 0) {
if (qwertz[i] == hex) return LATIN_KEYBOARD_QWERTZ;
i++;
}
i = 0;
while (dvorak[i] != 0) {
if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK;
i++;
}
return LATIN_KEYBOARD_QWERTY;
}
void OS_Windows::release_rendering_thread() {
gl_context->release_current();
}
void OS_Windows::make_rendering_thread() {
gl_context->make_current();
}
void OS_Windows::swap_buffers() {
gl_context->swap_buffers();
}
void OS_Windows::force_process_input() {
process_events(); // get rid of pending events
}
void OS_Windows::run() {
if (!main_loop)
return;
main_loop->init();
while (!force_quit) {
process_events(); // get rid of pending events
if (Main::iteration())
break;
};
main_loop->finish();
}
MainLoop *OS_Windows::get_main_loop() const {
return main_loop;
}
String OS_Windows::get_config_path() const {
if (has_environment("XDG_CONFIG_HOME")) { // unlikely, but after all why not?
return get_environment("XDG_CONFIG_HOME");
} else if (has_environment("APPDATA")) {
return get_environment("APPDATA");
} else {
return ".";
}
}
String OS_Windows::get_data_path() const {
if (has_environment("XDG_DATA_HOME")) {
return get_environment("XDG_DATA_HOME");
} else {
return get_config_path();
}
}
String OS_Windows::get_cache_path() const {
if (has_environment("XDG_CACHE_HOME")) {
return get_environment("XDG_CACHE_HOME");
} else if (has_environment("TEMP")) {
return get_environment("TEMP");
} else {
return get_config_path();
}
}
// Get properly capitalized engine name for system paths
String OS_Windows::get_godot_dir_name() const {
return String(VERSION_SHORT_NAME).capitalize();
}
String OS_Windows::get_system_dir(SystemDir p_dir) const {
int id;
switch (p_dir) {
case SYSTEM_DIR_DESKTOP: {
id = CSIDL_DESKTOPDIRECTORY;
} break;
case SYSTEM_DIR_DCIM: {
id = CSIDL_MYPICTURES;
} break;
case SYSTEM_DIR_DOCUMENTS: {
id = CSIDL_PERSONAL;
} break;
case SYSTEM_DIR_DOWNLOADS: {
id = 0x000C;
} break;
case SYSTEM_DIR_MOVIES: {
id = CSIDL_MYVIDEO;
} break;
case SYSTEM_DIR_MUSIC: {
id = CSIDL_MYMUSIC;
} break;
case SYSTEM_DIR_PICTURES: {
id = CSIDL_MYPICTURES;
} break;
case SYSTEM_DIR_RINGTONES: {
id = CSIDL_MYMUSIC;
} break;
}
WCHAR szPath[MAX_PATH];
HRESULT res = SHGetFolderPathW(NULL, id, NULL, 0, szPath);
ERR_FAIL_COND_V(res != S_OK, String());
return String(szPath);
}
String OS_Windows::get_user_data_dir() const {
String appname = get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name"));
if (appname != "") {
bool use_custom_dir = ProjectSettings::get_singleton()->get("application/config/use_custom_user_dir");
if (use_custom_dir) {
String custom_dir = get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/custom_user_dir_name"), true);
if (custom_dir == "") {
custom_dir = appname;
}
return get_data_path().plus_file(custom_dir).replace("\\", "/");
} else {
return get_data_path().plus_file(get_godot_dir_name()).plus_file("app_userdata").plus_file(appname).replace("\\", "/");
}
}
return ProjectSettings::get_singleton()->get_resource_path();
}
String OS_Windows::get_unique_id() const {
HW_PROFILE_INFO HwProfInfo;
ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), "");
return String(HwProfInfo.szHwProfileGuid);
}
void OS_Windows::set_ime_active(const bool p_active) {
if (p_active) {
ImmAssociateContext(hWnd, im_himc);
set_ime_position(im_position);
} else {
ImmAssociateContext(hWnd, (HIMC)0);
}
}
void OS_Windows::set_ime_position(const Point2 &p_pos) {
im_position = p_pos;
HIMC himc = ImmGetContext(hWnd);
if (himc == (HIMC)0)
return;
COMPOSITIONFORM cps;
cps.dwStyle = CFS_FORCE_POSITION;
cps.ptCurrentPos.x = im_position.x;
cps.ptCurrentPos.y = im_position.y;
ImmSetCompositionWindow(himc, &cps);
ImmReleaseContext(hWnd, himc);
}
bool OS_Windows::is_joy_known(int p_device) {
return input->is_joy_mapped(p_device);
}
String OS_Windows::get_joy_guid(int p_device) const {
return input->get_joy_guid_remapped(p_device);
}
void OS_Windows::_set_use_vsync(bool p_enable) {
if (gl_context)
gl_context->set_use_vsync(p_enable);
}
/*
bool OS_Windows::is_vsync_enabled() const {
if (gl_context)
return gl_context->is_using_vsync();
return true;
}*/
OS::PowerState OS_Windows::get_power_state() {
return power_manager->get_power_state();
}
int OS_Windows::get_power_seconds_left() {
return power_manager->get_power_seconds_left();
}
int OS_Windows::get_power_percent_left() {
return power_manager->get_power_percent_left();
}
bool OS_Windows::_check_internal_feature_support(const String &p_feature) {
return p_feature == "pc";
}
void OS_Windows::disable_crash_handler() {
crash_handler.disable();
}
bool OS_Windows::is_disable_crash_handler() const {
return crash_handler.is_disabled();
}
void OS_Windows::process_and_drop_events() {
drop_events = true;
process_events();
drop_events = false;
}
Error OS_Windows::move_to_trash(const String &p_path) {
SHFILEOPSTRUCTW sf;
WCHAR *from = new WCHAR[p_path.length() + 2];
wcscpy_s(from, p_path.length() + 1, p_path.c_str());
from[p_path.length() + 1] = 0;
sf.hwnd = hWnd;
sf.wFunc = FO_DELETE;
sf.pFrom = from;
sf.pTo = NULL;
sf.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
sf.fAnyOperationsAborted = FALSE;
sf.hNameMappings = NULL;
sf.lpszProgressTitle = NULL;
int ret = SHFileOperationW(&sf);
delete[] from;
if (ret) {
ERR_PRINTS("SHFileOperation error: " + itos(ret));
return FAILED;
}
return OK;
}
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
drop_events = false;
key_event_pos = 0;
layered_window = false;
hBitmap = NULL;
force_quit = false;
alt_mem = false;
gr_mem = false;
shift_mem = false;
control_mem = false;
meta_mem = false;
minimized = false;
hInstance = _hInstance;
pressrc = 0;
old_invalid = true;
mouse_mode = MOUSE_MODE_VISIBLE;
#ifdef STDOUT_FILE
stdo = fopen("stdout.txt", "wb");
#endif
user_proc = NULL;
#ifdef WASAPI_ENABLED
AudioDriverManager::add_driver(&driver_wasapi);
#endif
#ifdef XAUDIO2_ENABLED
AudioDriverManager::add_driver(&driver_xaudio2);
#endif
Vector<Logger *> loggers;
loggers.push_back(memnew(WindowsTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
}
OS_Windows::~OS_Windows() {
if (is_layered_allowed() && layered_window) {
DeleteObject(hBitmap);
DeleteDC(hDC_dib);
}
#ifdef STDOUT_FILE
fclose(stdo);
#endif
}