mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +00:00
1848 lines
47 KiB
C
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,
|
|
};
|