Files
LookingGlass/client/displayservers/Windows/windows.c
2026-11-06 14:34:25 +11:00

1848 lines
47 KiB
C

/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <windows.h>
#include <windowsx.h>
#undef SW_MAX
#include "interface/displayserver.h"
#include "input_event.h"
#include "platform.h"
#include "window_size.h"
#include "app.h"
#include "common/debug.h"
#include "common/event.h"
#include "common/thread.h"
#include "common/time.h"
#include "common/win32.h"
#include "common/windebug.h"
#include "egl_dynprocs.h"
#include "eglutil.h"
#include "input/keycodes.h"
#include "kb.h"
#undef SW_MAX
#ifdef ENABLE_EGL
#include <EGL/eglext.h>
#include <EGL/eglext_angle.h>
#endif
#include <limits.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define LG_WINDOW_CLASS L"LookingGlassClientWindow"
enum WindowsMessage
{
WM_LG_SHUTDOWN = WM_APP + 1,
WM_LG_SET_POINTER,
WM_LG_GRAB_POINTER,
WM_LG_GRAB_KEYBOARD,
WM_LG_WARP_POINTER,
WM_LG_GUEST_POINTER,
WM_LG_SET_SIZE,
WM_LG_SET_FULLSCREEN,
WM_LG_MINIMIZE,
WM_LG_ACTIVATE,
WM_LG_INHIBIT_IDLE,
WM_LG_SET_HANDLER,
};
struct WindowsPointMessage
{
int x;
int y;
bool exiting;
};
struct WindowsGuestPointerMessage
{
double localX;
double localY;
};
struct WindowsHandlerMessage
{
WindowsMessageHandler handler;
void * opaque;
};
struct WindowsDSState
{
HINSTANCE instance;
HWND window;
DWORD threadID;
HHOOK keyboardHook;
HCURSOR cursors[LG_POINTER_COUNT];
LGThread * eventThread;
LGEvent * initEvent;
WindowsInput input;
WindowsMessageHandler messageHandler;
void * messageOpaque;
WindowsWindowSize windowSize;
wchar_t * title;
int x;
int y;
bool center;
bool resizable;
bool borderless;
bool maximize;
bool initialFullscreen;
bool initOK;
bool dispatchReady;
bool idleInhibited;
DWORD windowedStyle;
DWORD windowedExStyle;
WINDOWPLACEMENT windowedPlacement;
UINT dpi;
double scale;
int rawAbsoluteX;
int rawAbsoluteY;
bool rawAbsoluteValid;
bool mouseLeaveTracked;
wchar_t highSurrogate;
_Atomic(int) pointer;
_Atomic(bool) pointerGrabbed;
_Atomic(bool) pointerCaptured;
_Atomic(bool) keyboardGrabbed;
_Atomic(bool) fullscreen;
_Atomic(bool) stopping;
};
static struct WindowsDSState windows;
static LRESULT CALLBACK windowProc(HWND window, UINT message,
WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK keyboardHookProc(int code, WPARAM wParam,
LPARAM lParam);
static void armPointerLeave(void);
static void requestWindowSize(int width, int height);
static void applyPendingWindowSize(void);
static UINT queryWindowDPI(HWND window)
{
typedef UINT (WINAPI * GetDpiForWindowFn)(HWND window);
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
const FARPROC procedure = user32 ?
GetProcAddress(user32, "GetDpiForWindow") : NULL;
GetDpiForWindowFn getDpiForWindow = NULL;
memcpy(&getDpiForWindow, &procedure, sizeof(getDpiForWindow));
if (getDpiForWindow)
{
const UINT dpi = getDpiForWindow(window);
if (dpi)
return dpi;
}
HDC dc = GetDC(window);
if (!dc)
return 96;
const int dpi = GetDeviceCaps(dc, LOGPIXELSX);
ReleaseDC(window, dc);
return dpi > 0 ? (UINT)dpi : 96;
}
static double logicalScale(void)
{
return windows.scale > 0.0 ? windows.scale : 1.0;
}
static int toLogical(int value)
{
return (int)(value / logicalScale() + 0.5);
}
static int toPhysical(int value)
{
return (int)(value * logicalScale() + 0.5);
}
static void updateWindowGeometry(void)
{
RECT client;
if (!GetClientRect(windows.window, &client))
return;
POINT origin = { 0, 0 };
ClientToScreen(windows.window, &origin);
windows.dpi = queryWindowDPI(windows.window);
windows.scale = (double)windows.dpi / 96.0;
const int width = client.right - client.left;
const int height = client.bottom - client.top;
app_updateWindowPos(toLogical(origin.x), toLogical(origin.y));
app_handleResizeEvent(toLogical(width), toLogical(height), windows.scale,
(struct Border) { 0, 0, 0, 0 });
app_invalidateWindow(true);
}
static bool cursorInClient(POINT point)
{
RECT client;
if (!GetClientRect(windows.window, &client))
return false;
return point.x >= client.left && point.x < client.right &&
point.y >= client.top && point.y < client.bottom;
}
static void updatePointerClip(void)
{
if (!windowsInputIsPointerGrabbed(&windows.input) ||
GetForegroundWindow() != windows.window)
{
ClipCursor(NULL);
return;
}
RECT clip;
if (!GetClientRect(windows.window, &clip))
return;
MapWindowPoints(windows.window, NULL, (POINT *)&clip, 2);
ClipCursor(&clip);
}
static void emitInputState(void)
{
const bool ctrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
const bool shift = (GetAsyncKeyState(VK_SHIFT ) & 0x8000) != 0;
const bool alt = (GetAsyncKeyState(VK_MENU ) & 0x8000) != 0;
const bool super = (GetAsyncKeyState(VK_LWIN ) & 0x8000) != 0 ||
(GetAsyncKeyState(VK_RWIN) & 0x8000) != 0;
const bool num = (GetKeyState(VK_NUMLOCK) & 1) != 0;
const bool caps = (GetKeyState(VK_CAPITAL) & 1) != 0;
const bool scroll = (GetKeyState(VK_SCROLL ) & 1) != 0;
windowsInputState(&windows.input, ctrl, shift, alt, super,
num, caps, scroll);
}
static int keyCode(WPARAM virtualKey, uint8_t scanCode, bool extended,
bool altDown, bool pressed)
{
if (virtualKey == VK_PAUSE)
return LG_KEY_PAUSE;
if (virtualKey == VK_SNAPSHOT)
return windowsInputMapPrintScreenEvent(
&windows.input, altDown, pressed);
return windowsInputMapScanCode(scanCode, extended);
}
static bool dispatchKey(WPARAM virtualKey, uint8_t scanCode,
bool extended, bool altDown, bool pressed, bool grabbedEvent)
{
const int key = keyCode(
virtualKey, scanCode, extended, altDown, pressed);
const bool handled = windowsInputKeyCode(&windows.input, key,
pressed, grabbedEvent);
if (handled)
{
if (grabbedEvent)
windowsInputTrackedState(&windows.input);
else
emitInputState();
}
return handled;
}
static bool virtualKeyHeld(UINT virtualKey)
{
return (GetAsyncKeyState((int)virtualKey) & 0x8000) != 0;
}
static bool mouseVirtualKey(UINT virtualKey)
{
return virtualKey == VK_LBUTTON || virtualKey == VK_RBUTTON ||
virtualKey == VK_MBUTTON || virtualKey == VK_XBUTTON1 ||
virtualKey == VK_XBUTTON2;
}
static bool heldKeyAlreadyAdded(const int * keys, size_t count, int key)
{
for (size_t i = 0; i < count; ++i)
if (keys[i] == key)
return true;
return false;
}
static size_t queryHeldKeys(int * keys, size_t capacity)
{
const bool altDown = virtualKeyHeld(VK_MENU);
size_t count = 0;
for (UINT virtualKey = 1; virtualKey < 0xFF && count < capacity;
++virtualKey)
{
if (!virtualKeyHeld(virtualKey) ||
mouseVirtualKey(virtualKey) ||
virtualKey == VK_SHIFT ||
virtualKey == VK_CONTROL ||
virtualKey == VK_MENU)
continue;
const UINT scanCodeEx = MapVirtualKeyW(
virtualKey, MAPVK_VK_TO_VSC_EX);
const bool extended = (scanCodeEx & 0xFF00U) == 0xE000U;
const int key = keyCode(virtualKey,
(uint8_t)(scanCodeEx & 0xFFU), extended, altDown, true);
if (key <= LG_KEY_RESERVED || key >= LG_KEY_MAX ||
heldKeyAlreadyAdded(keys, count, key))
continue;
keys[count++] = key;
}
return count;
}
static void syncHeldKeys(void)
{
int keys[256];
const size_t count = queryHeldKeys(keys, sizeof(keys) / sizeof(*keys));
windowsInputSyncHeldKeys(&windows.input, keys, count,
windowsInputIsKeyboardGrabbed(&windows.input));
}
static void emitCodepoint(uint32_t codepoint)
{
char text[5] = { 0 };
if (codepoint <= 0x7F)
text[0] = (char)codepoint;
else if (codepoint <= 0x7FF)
{
text[0] = (char)(0xC0 | codepoint >> 6);
text[1] = (char)(0x80 | (codepoint & 0x3F));
}
else if (codepoint <= 0xFFFF)
{
text[0] = (char)(0xE0 | codepoint >> 12);
text[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
text[2] = (char)(0x80 | (codepoint & 0x3F));
}
else if (codepoint <= 0x10FFFF)
{
text[0] = (char)(0xF0 | codepoint >> 18);
text[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
text[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
text[3] = (char)(0x80 | (codepoint & 0x3F));
}
if (text[0])
windowsInputText(&windows.input, text);
}
static void handleUTF16(wchar_t value)
{
if (value >= 0xD800 && value <= 0xDBFF)
{
windows.highSurrogate = value;
return;
}
uint32_t codepoint = value;
if (value >= 0xDC00 && value <= 0xDFFF &&
windows.highSurrogate >= 0xD800 &&
windows.highSurrogate <= 0xDBFF)
{
codepoint = UINT32_C(0x10000) +
((uint32_t)(windows.highSurrogate - 0xD800) << 10) +
(uint32_t)(value - 0xDC00);
}
windows.highSurrogate = 0;
if (codepoint < 0xD800 || codepoint > 0xDFFF)
emitCodepoint(codepoint);
}
static unsigned int mouseButton(UINT message, WPARAM wParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
return 1;
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
return 2;
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
return 3;
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? 6 : 7;
}
return 0;
}
static bool mouseButtonPressed(UINT message)
{
return message == WM_LBUTTONDOWN || message == WM_MBUTTONDOWN ||
message == WM_RBUTTONDOWN || message == WM_XBUTTONDOWN;
}
static void checkPointerLeave(void)
{
POINT point;
if (!GetCursorPos(&point))
return;
ScreenToClient(windows.window, &point);
if (cursorInClient(point))
{
armPointerLeave();
windowsInputPointerEnter(&windows.input,
point.x / logicalScale(), point.y / logicalScale());
}
else
windowsInputPointerLeave(&windows.input);
}
static void armPointerLeave(void)
{
if (windows.mouseLeaveTracked)
return;
TRACKMOUSEEVENT track =
{
.cbSize = sizeof(track),
.dwFlags = TME_LEAVE,
.hwndTrack = windows.window,
.dwHoverTime = HOVER_DEFAULT,
};
windows.mouseLeaveTracked = TrackMouseEvent(&track) != FALSE;
}
static void handleMouseButton(UINT message, WPARAM wParam)
{
const unsigned int button = mouseButton(message, wParam);
const bool pressed = mouseButtonPressed(message);
if (!windows.input.entered)
{
POINT point;
if (GetCursorPos(&point))
{
ScreenToClient(windows.window, &point);
if (cursorInClient(point))
{
armPointerLeave();
windowsInputPointerEnter(&windows.input,
point.x / logicalScale(), point.y / logicalScale());
}
}
}
if (!windowsInputPointerButton(&windows.input, button, pressed))
return;
if (pressed)
SetCapture(windows.window);
else if (!windows.input.buttons &&
!windowsInputIsPointerGrabbed(&windows.input))
{
ReleaseCapture();
checkPointerLeave();
}
}
static void handleMouseMove(LPARAM lParam)
{
const int x = GET_X_LPARAM(lParam);
const int y = GET_Y_LPARAM(lParam);
const POINT point = { .x = x, .y = y };
if (!cursorInClient(point))
{
windowsInputPointerLeave(&windows.input);
return;
}
armPointerLeave();
if (!windowsInputPointerEnter(&windows.input,
x / logicalScale(), y / logicalScale()))
windowsInputPointerMotion(&windows.input,
x / logicalScale(), y / logicalScale());
}
static void handleRawMouse(const RAWMOUSE * mouse)
{
double x;
double y;
if (mouse->usFlags & MOUSE_MOVE_ABSOLUTE)
{
const bool virtualDesktop =
(mouse->usFlags & MOUSE_VIRTUAL_DESKTOP) != 0;
const int originX = virtualDesktop ?
GetSystemMetrics(SM_XVIRTUALSCREEN) : 0;
const int originY = virtualDesktop ?
GetSystemMetrics(SM_YVIRTUALSCREEN) : 0;
const int width = GetSystemMetrics(
virtualDesktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
const int height = GetSystemMetrics(
virtualDesktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
const int currentX = originX +
(int)((uint64_t)(uint32_t)mouse->lLastX * (uint32_t)width / 65535U);
const int currentY = originY +
(int)((uint64_t)(uint32_t)mouse->lLastY * (uint32_t)height / 65535U);
if (!windows.rawAbsoluteValid)
{
windows.rawAbsoluteX = currentX;
windows.rawAbsoluteY = currentY;
windows.rawAbsoluteValid = true;
return;
}
x = currentX - windows.rawAbsoluteX;
y = currentY - windows.rawAbsoluteY;
windows.rawAbsoluteX = currentX;
windows.rawAbsoluteY = currentY;
}
else
{
windows.rawAbsoluteValid = false;
x = mouse->lLastX;
y = mouse->lLastY;
}
if (x != 0.0 || y != 0.0)
windowsInputRelativeMotion(&windows.input, x, y);
}
static void handleRawInput(HRAWINPUT handle)
{
if (!windows.input.focused ||
!windowsInputIsPointerGrabbed(&windows.input) ||
GetForegroundWindow() != windows.window)
{
windows.rawAbsoluteValid = false;
return;
}
RAWINPUT input;
UINT size = sizeof(input);
if (GetRawInputData(handle, RID_INPUT, &input, &size,
sizeof(RAWINPUTHEADER)) != size)
return;
if (input.header.dwType == RIM_TYPEMOUSE)
handleRawMouse(&input.data.mouse);
}
static bool installKeyboardHook(void)
{
if (windows.keyboardHook)
return true;
windows.keyboardHook = SetWindowsHookExW(
WH_KEYBOARD_LL, keyboardHookProc, windows.instance, 0);
if (!windows.keyboardHook)
{
DEBUG_WINERROR("SetWindowsHookExW failed", GetLastError());
return false;
}
return true;
}
static void removeKeyboardHook(void)
{
if (!windows.keyboardHook)
return;
UnhookWindowsHookEx(windows.keyboardHook);
windows.keyboardHook = NULL;
}
static void setKeyboardGrab(bool grab)
{
if (grab == windowsInputIsKeyboardGrabbed(&windows.input))
return;
if (grab && !installKeyboardHook())
return;
if (!grab)
removeKeyboardHook();
windowsInputSetKeyboardGrabbed(&windows.input, grab);
atomic_store_explicit(&windows.keyboardGrabbed, grab,
memory_order_release);
}
static void setPointerGrab(bool grab, bool captured)
{
const bool old = windowsInputIsPointerGrabbed(&windows.input);
if (grab)
{
windowsInputSetPointerGrabbed(&windows.input, true);
windows.rawAbsoluteValid = false;
SetCapture(windows.window);
updatePointerClip();
}
else
{
ClipCursor(NULL);
windowsInputSetPointerGrabbed(&windows.input, false);
if (!windows.input.buttons)
ReleaseCapture();
}
atomic_store_explicit(&windows.pointerGrabbed, grab,
memory_order_release);
atomic_store_explicit(&windows.pointerCaptured, grab && captured,
memory_order_release);
if (old != grab)
app_handleGrabEvent(grab);
if (!grab)
checkPointerLeave();
}
static void releasePointerState(bool releaseCapture)
{
windowsInputReleaseButtons(&windows.input);
const bool captured = atomic_load_explicit(
&windows.pointerCaptured, memory_order_acquire);
if (windowsInputIsPointerGrabbed(&windows.input))
{
ClipCursor(NULL);
windowsInputSetPointerGrabbed(&windows.input, false);
atomic_store_explicit(&windows.pointerGrabbed, false,
memory_order_release);
atomic_store_explicit(&windows.pointerCaptured, false,
memory_order_release);
if (releaseCapture)
ReleaseCapture();
if (captured)
app_handleCaptureLost();
else
app_handleGrabEvent(false);
}
else if (releaseCapture)
ReleaseCapture();
checkPointerLeave();
}
static void setFullscreen(bool fullscreen)
{
const bool old = atomic_load_explicit(
&windows.fullscreen, memory_order_acquire);
if (old == fullscreen)
return;
if (fullscreen)
{
atomic_store_explicit(&windows.fullscreen, true,
memory_order_release);
windows.windowedStyle = (DWORD)GetWindowLongPtrW(
windows.window, GWL_STYLE);
windows.windowedExStyle = (DWORD)GetWindowLongPtrW(
windows.window, GWL_EXSTYLE);
windows.windowedPlacement = (WINDOWPLACEMENT)
{
.length = sizeof(windows.windowedPlacement),
};
GetWindowPlacement(windows.window, &windows.windowedPlacement);
const HMONITOR monitor = MonitorFromWindow(
windows.window, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = { .cbSize = sizeof(info) };
GetMonitorInfoW(monitor, &info);
SetWindowLongPtrW(windows.window, GWL_STYLE,
WS_POPUP | WS_VISIBLE);
SetWindowLongPtrW(windows.window, GWL_EXSTYLE,
windows.windowedExStyle & ~(DWORD)WS_EX_WINDOWEDGE);
SetWindowPos(windows.window, HWND_TOP,
info.rcMonitor.left, info.rcMonitor.top,
info.rcMonitor.right - info.rcMonitor.left,
info.rcMonitor.bottom - info.rcMonitor.top,
SWP_FRAMECHANGED | SWP_NOOWNERZORDER);
}
else
{
SetWindowLongPtrW(windows.window, GWL_STYLE,
windows.windowedStyle);
SetWindowLongPtrW(windows.window, GWL_EXSTYLE,
windows.windowedExStyle);
SetWindowPlacement(windows.window, &windows.windowedPlacement);
SetWindowPos(windows.window, NULL, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
SWP_NOOWNERZORDER | SWP_NOZORDER);
atomic_store_explicit(&windows.fullscreen, false,
memory_order_release);
applyPendingWindowSize();
}
}
static bool deferWindowResize(void)
{
return atomic_load_explicit(
&windows.fullscreen, memory_order_acquire) ||
IsZoomed(windows.window);
}
static void applyWindowSize(int width, int height)
{
RECT rect =
{
.left = 0,
.top = 0,
.right = toPhysical(width),
.bottom = toPhysical(height),
};
const DWORD style = (DWORD)GetWindowLongPtrW(
windows.window, GWL_STYLE);
const DWORD exStyle = (DWORD)GetWindowLongPtrW(
windows.window, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, windows.dpi);
SetWindowPos(windows.window, NULL, 0, 0,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
static void requestWindowSize(int width, int height)
{
if (windowsWindowSizeRequest(
&windows.windowSize, width, height, deferWindowResize()))
applyWindowSize(width, height);
}
static void applyPendingWindowSize(void)
{
int width;
int height;
if (windowsWindowSizeTakePending(&windows.windowSize,
deferWindowResize(), &width, &height))
applyWindowSize(width, height);
}
static void centerWindow(void)
{
const HMONITOR monitor = MonitorFromWindow(
windows.window, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = { .cbSize = sizeof(info) };
RECT window;
if (!GetMonitorInfoW(monitor, &info) ||
!GetWindowRect(windows.window, &window))
return;
const int width = window.right - window.left;
const int height = window.bottom - window.top;
const int x = info.rcWork.left +
(info.rcWork.right - info.rcWork.left - width) / 2;
const int y = info.rcWork.top +
(info.rcWork.bottom - info.rcWork.top - height) / 2;
SetWindowPos(windows.window, NULL, x, y, 0, 0,
SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
static void setPointerPosition(const struct WindowsPointMessage * point)
{
POINT position =
{
.x = toPhysical(point->x),
.y = toPhysical(point->y),
};
ClientToScreen(windows.window, &position);
SetCursorPos(position.x, position.y);
}
static void setGuestPointerPosition(
const struct WindowsGuestPointerMessage * point)
{
if (atomic_load_explicit(
&windows.pointerCaptured, memory_order_acquire) ||
!windows.input.entered)
return;
struct WindowsPointMessage position =
{
.x = (int)(point->localX + 0.5),
.y = (int)(point->localY + 0.5),
};
setPointerPosition(&position);
}
static void setCursorShape(LG_DSPointer pointer)
{
const int index = (int)pointer;
if (index < LG_POINTER_NONE || index >= LG_POINTER_COUNT)
pointer = LG_POINTER_ARROW;
atomic_store_explicit(&windows.pointer, pointer, memory_order_release);
POINT point;
if (windowsInputIsPointerGrabbed(&windows.input) ||
(GetCursorPos(&point) &&
ScreenToClient(windows.window, &point) && cursorInClient(point)))
SetCursor(windows.cursors[pointer]);
}
static LRESULT handleCustomMessage(UINT message, WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_LG_SHUTDOWN:
setKeyboardGrab(false);
setPointerGrab(false, false);
if (windows.idleInhibited)
SetThreadExecutionState(ES_CONTINUOUS);
DestroyWindow(windows.window);
return 0;
case WM_LG_SET_POINTER:
setCursorShape((LG_DSPointer)wParam);
return 0;
case WM_LG_GRAB_POINTER:
setPointerGrab(wParam != 0, lParam != 0);
return 0;
case WM_LG_GRAB_KEYBOARD:
setKeyboardGrab(wParam != 0);
return 0;
case WM_LG_WARP_POINTER:
setPointerPosition((const struct WindowsPointMessage *)lParam);
return 0;
case WM_LG_GUEST_POINTER:
setGuestPointerPosition(
(const struct WindowsGuestPointerMessage *)lParam);
return 0;
case WM_LG_SET_SIZE:
requestWindowSize((int)wParam, (int)lParam);
return 0;
case WM_LG_SET_FULLSCREEN:
setFullscreen(wParam != 0);
return 0;
case WM_LG_MINIMIZE:
ShowWindow(windows.window, SW_MINIMIZE);
return 0;
case WM_LG_ACTIVATE:
{
FLASHWINFO flash =
{
.cbSize = sizeof(flash),
.hwnd = windows.window,
.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG,
.uCount = 3,
};
SetForegroundWindow(windows.window);
FlashWindowEx(&flash);
return 0;
}
case WM_LG_INHIBIT_IDLE:
windows.idleInhibited = wParam != 0;
SetThreadExecutionState(ES_CONTINUOUS |
(windows.idleInhibited ? ES_DISPLAY_REQUIRED : 0));
return 0;
case WM_LG_SET_HANDLER:
{
const struct WindowsHandlerMessage * handler =
(const struct WindowsHandlerMessage *)lParam;
windows.messageHandler = handler->handler;
windows.messageOpaque = handler->opaque;
return 0;
}
}
return 0;
}
static LRESULT CALLBACK windowProc(HWND window, UINT message,
WPARAM wParam, LPARAM lParam)
{
if (message >= WM_LG_SHUTDOWN && message <= WM_LG_SET_HANDLER)
return handleCustomMessage(message, wParam, lParam);
if (windows.dispatchReady && windows.messageHandler)
{
LRESULT result = 0;
if (windows.messageHandler(windows.messageOpaque, window,
message, wParam, lParam, &result))
return result;
}
switch (message)
{
case WM_CLOSE:
app_handleCloseEvent();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_ERASEBKGND:
return 1;
case WM_NCHITTEST:
if (windows.borderless && windows.resizable &&
!atomic_load_explicit(&windows.fullscreen, memory_order_acquire))
{
RECT bounds;
if (!GetWindowRect(window, &bounds))
break;
const int x = GET_X_LPARAM(lParam);
const int y = GET_Y_LPARAM(lParam);
const int margin = toPhysical(8);
const bool left = x < bounds.left + margin;
const bool right = x >= bounds.right - margin;
const bool top = y < bounds.top + margin;
const bool bottom = y >= bounds.bottom - margin;
if (top && left)
return HTTOPLEFT;
if (top && right)
return HTTOPRIGHT;
if (bottom && left)
return HTBOTTOMLEFT;
if (bottom && right)
return HTBOTTOMRIGHT;
if (left)
return HTLEFT;
if (right)
return HTRIGHT;
if (top)
return HTTOP;
if (bottom)
return HTBOTTOM;
}
break;
case WM_PAINT:
{
PAINTSTRUCT paint;
BeginPaint(window, &paint);
EndPaint(window, &paint);
app_invalidateWindow(true);
return 0;
}
case WM_MOVE:
if (windows.dispatchReady && !IsIconic(window))
{
updateWindowGeometry();
updatePointerClip();
}
return 0;
case WM_SIZE:
if (windows.dispatchReady && wParam != SIZE_MINIMIZED)
{
updateWindowGeometry();
updatePointerClip();
if (wParam == SIZE_RESTORED)
applyPendingWindowSize();
}
return 0;
case WM_DPICHANGED:
if (windows.dispatchReady)
{
const RECT * suggested = (const RECT *)lParam;
SetWindowPos(window, NULL,
suggested->left, suggested->top,
suggested->right - suggested->left,
suggested->bottom - suggested->top,
SWP_NOOWNERZORDER | SWP_NOZORDER);
updateWindowGeometry();
}
return 0;
case WM_SETFOCUS:
if (windows.dispatchReady)
{
const bool changed = windowsInputFocus(&windows.input, true);
if (changed)
syncHeldKeys();
emitInputState();
updatePointerClip();
}
return 0;
case WM_KILLFOCUS:
if (windows.dispatchReady)
{
releasePointerState(true);
setKeyboardGrab(false);
windowsInputFocus(&windows.input, false);
windows.rawAbsoluteValid = false;
}
return 0;
case WM_MOUSEMOVE:
if (windows.dispatchReady)
handleMouseMove(lParam);
return 0;
case WM_MOUSELEAVE:
if (windows.dispatchReady)
{
windows.mouseLeaveTracked = false;
windowsInputPointerLeave(&windows.input);
}
return 0;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
if (windows.dispatchReady)
handleMouseButton(message, wParam);
return message == WM_XBUTTONDOWN || message == WM_XBUTTONUP ?
TRUE : 0;
case WM_MOUSEWHEEL:
if (windows.dispatchReady)
windowsInputPointerWheel(&windows.input,
(double)GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA);
return 0;
case WM_INPUT:
if (windows.dispatchReady)
handleRawInput((HRAWINPUT)lParam);
return DefWindowProcW(window, message, wParam, lParam);
case WM_CAPTURECHANGED:
if (windows.dispatchReady && (HWND)lParam != windows.window)
releasePointerState(false);
return 0;
case WM_CANCELMODE:
if (windows.dispatchReady)
releasePointerState(true);
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
if (windows.dispatchReady)
{
const bool pressed = message == WM_KEYDOWN ||
message == WM_SYSKEYDOWN;
dispatchKey(wParam, (uint8_t)((lParam >> 16) & 0xFF),
(lParam & (UINT32_C(1) << 24)) != 0,
(lParam & (UINT32_C(1) << 29)) != 0, pressed, false);
}
return 0;
case WM_CHAR:
case WM_SYSCHAR:
if (windows.dispatchReady)
handleUTF16((wchar_t)wParam);
return 0;
case WM_UNICHAR:
if (wParam == UNICODE_NOCHAR)
return TRUE;
if (windows.dispatchReady)
emitCodepoint((uint32_t)wParam);
return 0;
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
const int pointer = atomic_load_explicit(
&windows.pointer, memory_order_acquire);
SetCursor(windows.cursors[pointer]);
return TRUE;
}
break;
}
return DefWindowProcW(window, message, wParam, lParam);
}
static LRESULT CALLBACK keyboardHookProc(int code, WPARAM wParam,
LPARAM lParam)
{
if (code == HC_ACTION &&
windowsInputIsKeyboardGrabbed(&windows.input) &&
windows.input.focused &&
GetForegroundWindow() == windows.window)
{
const KBDLLHOOKSTRUCT * event = (const KBDLLHOOKSTRUCT *)lParam;
const bool pressed = wParam == WM_KEYDOWN ||
wParam == WM_SYSKEYDOWN;
const bool released = wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP;
if (pressed || released)
{
dispatchKey(event->vkCode, (uint8_t)event->scanCode,
(event->flags & LLKHF_EXTENDED) != 0,
(event->flags & LLKHF_ALTDOWN) != 0, pressed, true);
return 1;
}
}
return CallNextHookEx(windows.keyboardHook, code, wParam, lParam);
}
static HCURSOR createSquareCursor(bool large)
{
const int width = GetSystemMetrics(SM_CXCURSOR);
const int height = GetSystemMetrics(SM_CYCURSOR);
if (width <= 0 || height <= 0)
return NULL;
const size_t stride = ((size_t)width + 15) / 16 * 2;
if ((size_t)height > SIZE_MAX / stride)
return NULL;
const size_t size = stride * (size_t)height;
uint8_t * andMask = malloc(size);
uint8_t * xorMask = calloc(size, 1);
if (!andMask || !xorMask)
{
free(andMask);
free(xorMask);
return NULL;
}
memset(andMask, 0xFF, size);
int dotSize = large ? 9 : 5;
if (dotSize > width)
dotSize = width;
if (dotSize > height)
dotSize = height;
const int left = (width - dotSize) / 2;
const int top = (height - dotSize) / 2;
for (int y = top; y < top + dotSize; ++y)
for (int x = left; x < left + dotSize; ++x)
andMask[(size_t)y * stride + (size_t)x / 8] &=
(uint8_t)~(UINT8_C(0x80) >> (x & 7));
HCURSOR cursor = CreateCursor(windows.instance,
width / 2, height / 2, width, height, andMask, xorMask);
free(andMask);
free(xorMask);
return cursor;
}
static bool loadCursors(bool largeCursorDot)
{
windows.cursors[LG_POINTER_NONE ] = NULL;
windows.cursors[LG_POINTER_SQUARE ] = createSquareCursor(largeCursorDot);
windows.cursors[LG_POINTER_ARROW ] = LoadCursorW(NULL, IDC_ARROW);
windows.cursors[LG_POINTER_INPUT ] = LoadCursorW(NULL, IDC_IBEAM);
windows.cursors[LG_POINTER_MOVE ] = LoadCursorW(NULL, IDC_SIZEALL);
windows.cursors[LG_POINTER_RESIZE_NS ] = LoadCursorW(NULL, IDC_SIZENS);
windows.cursors[LG_POINTER_RESIZE_EW ] = LoadCursorW(NULL, IDC_SIZEWE);
windows.cursors[LG_POINTER_RESIZE_NESW] = LoadCursorW(NULL, IDC_SIZENESW);
windows.cursors[LG_POINTER_RESIZE_NWSE] = LoadCursorW(NULL, IDC_SIZENWSE);
windows.cursors[LG_POINTER_HAND ] = LoadCursorW(NULL, IDC_HAND);
windows.cursors[LG_POINTER_NOT_ALLOWED] = LoadCursorW(NULL, IDC_NO);
if (!windows.cursors[LG_POINTER_SQUARE])
{
DEBUG_WINERROR("CreateCursor failed", GetLastError());
return false;
}
return true;
}
static void freeCursors(void)
{
if (!windows.cursors[LG_POINTER_SQUARE])
return;
DestroyCursor(windows.cursors[LG_POINTER_SQUARE]);
windows.cursors[LG_POINTER_SQUARE] = NULL;
}
static DWORD initialStyle(void)
{
if (windows.borderless)
return WS_POPUP;
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_MINIMIZEBOX;
if (windows.resizable)
style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
return style;
}
static int eventThread(void * opaque)
{
(void)opaque;
windows.threadID = GetCurrentThreadId();
HICON icon = LoadIconW(windows.instance, IDI_APPLICATION);
if (!icon)
icon = LoadIconW(NULL, IDI_APPLICATION);
WNDCLASSEXW windowClass =
{
.cbSize = sizeof(windowClass),
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = windowProc,
.hInstance = windows.instance,
.hIcon = icon,
.hCursor = windows.cursors[LG_POINTER_ARROW],
.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH),
.lpszClassName = LG_WINDOW_CLASS,
.hIconSm = icon,
};
if (!RegisterClassExW(&windowClass))
{
DEBUG_WINERROR("RegisterClassExW failed", GetLastError());
lgSignalEvent(windows.initEvent);
return 0;
}
windows.dpi = 96;
windows.scale = 1.0;
RECT rect =
{
.left = 0,
.top = 0,
.right = windows.windowSize.width,
.bottom = windows.windowSize.height,
};
const DWORD style = initialStyle();
const DWORD exStyle = windows.borderless ? 0 : WS_EX_APPWINDOW;
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
int x = windows.x;
int y = windows.y;
if (windows.center)
{
const HMONITOR monitor = MonitorFromPoint(
(POINT) { windows.x, windows.y }, MONITOR_DEFAULTTONEAREST);
MONITORINFO info = { .cbSize = sizeof(info) };
GetMonitorInfoW(monitor, &info);
x = info.rcWork.left +
(info.rcWork.right - info.rcWork.left - (rect.right - rect.left)) / 2;
y = info.rcWork.top +
(info.rcWork.bottom - info.rcWork.top - (rect.bottom - rect.top)) / 2;
}
windows.window = CreateWindowExW(exStyle, LG_WINDOW_CLASS, windows.title,
style, x, y, rect.right - rect.left, rect.bottom - rect.top,
NULL, NULL, windows.instance, NULL);
if (!windows.window)
{
DEBUG_WINERROR("CreateWindowExW failed", GetLastError());
UnregisterClassW(LG_WINDOW_CLASS, windows.instance);
lgSignalEvent(windows.initEvent);
return 0;
}
windows.dpi = queryWindowDPI(windows.window);
windows.scale = (double)windows.dpi / 96.0;
requestWindowSize(windows.windowSize.width, windows.windowSize.height);
if (windows.center)
centerWindow();
RAWINPUTDEVICE mouse =
{
.usUsagePage = 0x01,
.usUsage = 0x02,
.dwFlags = 0,
.hwndTarget = windows.window,
};
if (!RegisterRawInputDevices(&mouse, 1, sizeof(mouse)))
{
DEBUG_WINERROR("RegisterRawInputDevices failed", GetLastError());
DestroyWindow(windows.window);
windows.window = NULL;
UnregisterClassW(LG_WINDOW_CLASS, windows.instance);
lgSignalEvent(windows.initEvent);
return 0;
}
windows.dispatchReady = true;
windows.initOK = true;
updateWindowGeometry();
ShowWindow(windows.window, windows.maximize ? SW_MAXIMIZE : SW_SHOW);
UpdateWindow(windows.window);
if (windows.initialFullscreen)
setFullscreen(true);
lgSignalEvent(windows.initEvent);
MSG message;
while (GetMessageW(&message, NULL, 0, 0) > 0)
{
TranslateMessage(&message);
DispatchMessageW(&message);
}
windows.dispatchReady = false;
windows.window = NULL;
removeKeyboardHook();
ClipCursor(NULL);
UnregisterClassW(LG_WINDOW_CLASS, windows.instance);
return 0;
}
static void inputPosition(void * opaque, double x, double y)
{
(void)opaque;
app_updateCursorPos(x, y);
}
static void inputRelative(void * opaque, double x, double y,
double rawX, double rawY)
{
(void)opaque;
app_handleMouseRelative(x, y, rawX, rawY);
}
static void inputButton(void * opaque, unsigned int button, bool pressed)
{
(void)opaque;
if (pressed)
app_handleButtonPress(button);
else
app_handleButtonRelease(button);
}
static void inputWheel(void * opaque, double motion)
{
(void)opaque;
app_handleWheelMotion(motion);
}
static void inputKey(void * opaque, int scancode, bool pressed)
{
(void)opaque;
if (pressed)
app_handleKeyPress(scancode);
else
app_handleKeyRelease(scancode);
}
static void inputText(void * opaque, const char * text)
{
(void)opaque;
app_handleKeyboardTyped(text);
}
static void inputModifiers(void * opaque, bool ctrl, bool shift,
bool alt, bool super)
{
(void)opaque;
app_handleKeyboardModifiers(ctrl, shift, alt, super);
}
static void inputLEDs(void * opaque, bool numLock, bool capsLock,
bool scrollLock)
{
(void)opaque;
app_handleKeyboardLEDs(numLock, capsLock, scrollLock);
}
static void inputEnter(void * opaque, bool entered)
{
(void)opaque;
app_handleEnterEvent(entered);
}
static void inputFocus(void * opaque, bool focused)
{
(void)opaque;
app_handleFocusEvent(focused);
}
static const LG_DSInputSink inputSink =
{
.position = inputPosition,
.relative = inputRelative,
.button = inputButton,
.wheel = inputWheel,
.key = inputKey,
.text = inputText,
.modifiers = inputModifiers,
.leds = inputLEDs,
.enter = inputEnter,
.focus = inputFocus,
};
static void windowsSetup(void)
{
}
static bool windowsProbe(void)
{
return true;
}
static bool windowsEarlyInit(void)
{
typedef BOOL (WINAPI * SetProcessDpiAwarenessContextFn)(HANDLE value);
const HMODULE user32 =
GetModuleHandleW(L"user32.dll");
const FARPROC procedure = user32 ? GetProcAddress(
user32, "SetProcessDpiAwarenessContext") : NULL;
SetProcessDpiAwarenessContextFn setDPIAwareness = NULL;
memcpy(&setDPIAwareness, &procedure, sizeof(setDPIAwareness));
if (setDPIAwareness)
setDPIAwareness((HANDLE)(intptr_t)-4);
return true;
}
static bool windowsInit(const LG_DSInitParams params)
{
memset(&windows, 0, sizeof(windows));
windowsInputInit(&windows.input, &inputSink, NULL);
windows.instance = GetModuleHandleW(NULL);
windows.title = lgUtf8ToWide(params.title);
windows.x = params.x;
windows.y = params.y;
windows.center = params.center;
windows.resizable = params.resizable;
windows.borderless = params.borderless;
windows.maximize = params.maximize;
windows.initialFullscreen = params.fullscreen;
windowsWindowSizeInit(&windows.windowSize, params.w, params.h);
windows.windowedPlacement.length = sizeof(windows.windowedPlacement);
atomic_init(&windows.pointer, LG_POINTER_SQUARE);
atomic_init(&windows.pointerGrabbed, false);
atomic_init(&windows.pointerCaptured, false);
atomic_init(&windows.keyboardGrabbed, false);
atomic_init(&windows.fullscreen, false);
atomic_init(&windows.stopping, false);
if (!windows.title)
{
DEBUG_ERROR("Failed to convert the window title to UTF-16");
return false;
}
if (!loadCursors(params.largeCursorDot))
{
free(windows.title);
windows.title = NULL;
return false;
}
windows.initEvent = lgCreateEvent(true, 0);
if (!windows.initEvent)
{
DEBUG_ERROR("Failed to create the Win32 initialization event");
freeCursors();
free(windows.title);
windows.title = NULL;
return false;
}
if (!lgCreateThread("Win32EventThread", eventThread, NULL,
&windows.eventThread))
{
DEBUG_ERROR("Failed to create the Win32 event thread");
lgFreeEvent(windows.initEvent);
freeCursors();
free(windows.title);
windows.initEvent = NULL;
windows.title = NULL;
return false;
}
lgWaitEvent(windows.initEvent, TIMEOUT_INFINITE);
if (!windows.initOK)
{
lgJoinThread(windows.eventThread, NULL);
windows.eventThread = NULL;
lgFreeEvent(windows.initEvent);
freeCursors();
free(windows.title);
windows.initEvent = NULL;
windows.title = NULL;
return false;
}
return true;
}
static void windowsStartup(void)
{
}
static void windowsShutdown(void)
{
if (!windows.eventThread || atomic_exchange_explicit(
&windows.stopping, true, memory_order_acq_rel))
return;
if (windows.window)
SendMessageW(windows.window, WM_LG_SHUTDOWN, 0, 0);
else if (windows.threadID)
PostThreadMessageW(windows.threadID, WM_QUIT, 0, 0);
lgJoinThread(windows.eventThread, NULL);
windows.eventThread = NULL;
}
static void windowsFree(void)
{
windowsShutdown();
if (windows.initEvent)
{
lgFreeEvent(windows.initEvent);
windows.initEvent = NULL;
}
free(windows.title);
windows.title = NULL;
freeCursors();
}
static bool windowsGetProp(LG_DSProperty prop, void * ret)
{
switch (prop)
{
case LG_DS_WARP_SUPPORT:
*(enum LG_DSWarpSupport *)ret = LG_DS_WARP_SCREEN;
return true;
case LG_DS_NATIVE_HDR:
*(bool *)ret = false;
return true;
case LG_DS_MAX_MULTISAMPLE:
case LG_DS_HDR_WHITE_LEVELS:
return false;
}
return false;
}
#ifdef ENABLE_EGL
static EGLDisplay windowsGetEGLDisplay(void)
{
const EGLAttrib coreAttributes[] =
{
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_NONE,
};
const EGLint extAttributes [] =
{
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_NONE,
};
if (g_egl_dynProcs.eglGetPlatformDisplay)
return g_egl_dynProcs.eglGetPlatformDisplay(
EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, coreAttributes);
if (g_egl_dynProcs.eglGetPlatformDisplayEXT)
return g_egl_dynProcs.eglGetPlatformDisplayEXT(
EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, extAttributes);
return eglGetDisplay(EGL_DEFAULT_DISPLAY);
}
static EGLNativeWindowType windowsGetEGLNativeWindow(void)
{
return (EGLNativeWindowType)windows.window;
}
static bool windowsEGLSwapBuffers(EGLDisplay display, EGLSurface surface,
const struct Rect * damage, int count, uint64_t frameToken,
uint64_t * swapTime, bool * presentTracked)
{
static struct SwapWithDamageData swap = { 0 };
if (!swap.init)
swapWithDamageInit(&swap, display);
(void)frameToken;
*presentTracked = false;
const uint64_t start = nanotime();
const bool result = swapWithDamage(
&swap, display, surface, damage, count);
*swapTime = nanotime() - start;
return result;
}
#endif
static bool windowsGetFramePeriod(uint64_t * period)
{
const HMONITOR monitor = MonitorFromWindow(
windows.window, MONITOR_DEFAULTTONEAREST);
MONITORINFOEXW info = { .cbSize = sizeof(info) };
if (!GetMonitorInfoW(monitor, (MONITORINFO *)&info))
return false;
DEVMODEW mode = { .dmSize = sizeof(mode) };
if (!EnumDisplaySettingsW(info.szDevice, ENUM_CURRENT_SETTINGS, &mode) ||
mode.dmDisplayFrequency <= 1)
return false;
*period = UINT64_C(1000000000) / mode.dmDisplayFrequency;
return true;
}
static void windowsGuestPointerUpdated(double x, double y,
double localX, double localY)
{
(void)x;
(void)y;
const struct WindowsGuestPointerMessage point =
{
.localX = localX,
.localY = localY,
};
SendMessageW(windows.window, WM_LG_GUEST_POINTER, 0, (LPARAM)&point);
}
static void windowsSetPointer(LG_DSPointer pointer)
{
SendMessageW(windows.window, WM_LG_SET_POINTER, pointer, 0);
}
static void windowsGrabKeyboard(void)
{
SendMessageW(windows.window, WM_LG_GRAB_KEYBOARD, true, 0);
}
static void windowsUngrabKeyboard(void)
{
SendMessageW(windows.window, WM_LG_GRAB_KEYBOARD, false, 0);
}
static void windowsGrabPointer(void)
{
SendMessageW(windows.window, WM_LG_GRAB_POINTER, true, false);
}
static void windowsUngrabPointer(void)
{
SendMessageW(windows.window, WM_LG_GRAB_POINTER, false, false);
}
static bool windowsIsPointerGrabbed(void)
{
return atomic_load_explicit(
&windows.pointerGrabbed, memory_order_acquire);
}
static void windowsCapturePointer(void)
{
SendMessageW(windows.window, WM_LG_GRAB_POINTER, true, true);
}
static void windowsUncapturePointer(void)
{
SendMessageW(windows.window, WM_LG_GRAB_POINTER, false, false);
}
static bool windowsIsPointerCaptured(void)
{
return atomic_load_explicit(
&windows.pointerCaptured, memory_order_acquire);
}
static bool windowsGetKeyLabel(int scancode, char * label, size_t size)
{
if (scancode < 0 || scancode >= LG_KEY_MAX || !label ||
size < 2 || size > INT_MAX)
return false;
const uint32_t ps2 = lg_keycode_to_ps2[scancode];
if (!ps2)
return false;
LONG keyNameParam = (LONG)((ps2 & 0xFF) << 16);
if ((ps2 & 0xFF00) == 0xE000)
keyNameParam |= 1L << 24;
wchar_t wide[64];
const int wideLength = GetKeyNameTextW(
keyNameParam, wide, (int)(sizeof(wide) / sizeof(*wide)));
if (wideLength <= 0)
return false;
const int length = WideCharToMultiByte(CP_UTF8, 0,
wide, wideLength, label, (int)size - 1, NULL, NULL);
if (length <= 0)
return false;
label[length] = '\0';
return true;
}
static void windowsWarpPointer(int x, int y, bool exiting)
{
const struct WindowsPointMessage point =
{
.x = x,
.y = y,
.exiting = exiting,
};
SendMessageW(windows.window, WM_LG_WARP_POINTER, 0, (LPARAM)&point);
}
static void windowsRealignPointer(void)
{
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
}
static bool windowsIsValidPointerPos(int x, int y)
{
const POINT point =
{
.x = toPhysical(x),
.y = toPhysical(y),
};
return MonitorFromPoint(point, MONITOR_DEFAULTTONULL) != NULL;
}
static void windowsInhibitIdle(void)
{
SendMessageW(windows.window, WM_LG_INHIBIT_IDLE, true, 0);
}
static void windowsUninhibitIdle(void)
{
SendMessageW(windows.window, WM_LG_INHIBIT_IDLE, false, 0);
}
static void windowsRequestActivation(void)
{
PostMessageW(windows.window, WM_LG_ACTIVATE, 0, 0);
}
static void windowsWait(unsigned int time)
{
Sleep(time);
}
static void windowsSetWindowSize(int width, int height)
{
SendMessageW(windows.window, WM_LG_SET_SIZE, width, height);
}
static bool windowsGetFullscreen(void)
{
return atomic_load_explicit(&windows.fullscreen, memory_order_acquire);
}
static void windowsSetFullscreen(bool fullscreen)
{
SendMessageW(windows.window, WM_LG_SET_FULLSCREEN, fullscreen, 0);
}
static void windowsMinimize(void)
{
PostMessageW(windows.window, WM_LG_MINIMIZE, 0, 0);
}
HWND windowsGetWindow(void)
{
return windows.window;
}
void windowsSetMessageHandler(WindowsMessageHandler handler, void * opaque)
{
const struct WindowsHandlerMessage message =
{
.handler = handler,
.opaque = opaque,
};
SendMessageW(windows.window, WM_LG_SET_HANDLER, 0, (LPARAM)&message);
}
struct LG_DisplayServerOps LGDS_Windows =
{
.name = "Windows",
.setup = windowsSetup,
.probe = windowsProbe,
.earlyInit = windowsEarlyInit,
.init = windowsInit,
.startup = windowsStartup,
.shutdown = windowsShutdown,
.free = windowsFree,
.getProp = windowsGetProp,
#ifdef ENABLE_EGL
.getEGLDisplay = windowsGetEGLDisplay,
.getEGLNativeWindow = windowsGetEGLNativeWindow,
.eglSwapBuffers = windowsEGLSwapBuffers,
#endif
.getFramePeriod = windowsGetFramePeriod,
.guestPointerUpdated = windowsGuestPointerUpdated,
.setPointer = windowsSetPointer,
.grabKeyboard = windowsGrabKeyboard,
.ungrabKeyboard = windowsUngrabKeyboard,
.grabPointer = windowsGrabPointer,
.ungrabPointer = windowsUngrabPointer,
.isPointerGrabbed = windowsIsPointerGrabbed,
.capturePointer = windowsCapturePointer,
.uncapturePointer = windowsUncapturePointer,
.isPointerCaptured = windowsIsPointerCaptured,
.getKeyLabel = windowsGetKeyLabel,
.warpPointer = windowsWarpPointer,
.realignPointer = windowsRealignPointer,
.isValidPointerPos = windowsIsValidPointerPos,
.inhibitIdle = windowsInhibitIdle,
.uninhibitIdle = windowsUninhibitIdle,
.requestActivation = windowsRequestActivation,
.wait = windowsWait,
.setWindowSize = windowsSetWindowSize,
.getFullscreen = windowsGetFullscreen,
.setFullscreen = windowsSetFullscreen,
.minimize = windowsMinimize,
};