OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / video_win32.c
4.5 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// All rights reserved. License: 2-clause BSD
2
3
#include <SDL.h>
4
#include <SDL_syswm.h>
5
6
#include <windows.h>
7
#include <dwmapi.h>
8
9
#include "glue.h"
10
11
void video_win32_set_rounded_corners(SDL_Window *window)
12
{
13
	SDL_SysWMinfo wmInfo;
14
	SDL_VERSION(&wmInfo.version);
15
	SDL_GetWindowWMInfo(window, &wmInfo);
16
17
	HWND hwnd = wmInfo.info.win.window;
18
	DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUNDSMALL;
19
	DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference));
20
}
21
22
// --- Keep the emulator running while the window is dragged/resized ----------
23
//
24
// Moving or resizing a window on Windows spins the OS in a modal message loop
25
// (WM_ENTERSIZEMOVE..WM_EXITSIZEMOVE) that never returns to our main loop, so
26
// emulation and painting would otherwise freeze until the drag ends.
27
//
28
// SDL2's SDL_SetWindowsMessageHook is invoked from WIN_PumpEvents (SDL's own
29
// PeekMessage loop). That loop does NOT run while the OS owns the thread inside
30
// the modal move/resize loop, so the hook never sees WM_ENTERSIZEMOVE/WM_TIMER
31
// and cannot drive stepping. Instead we subclass the window procedure directly
32
// (SetWindowLongPtrW / GWLP_WNDPROC): the OS dispatches messages straight to our
33
// proc even inside the modal loop. We arm a WM_TIMER for the duration of that
34
// loop and advance the emulator one frame per tick, chaining every other message
35
// to SDL's original proc so SDL keeps working exactly as before.
36
37
#define X16_MOVE_TIMER_ID 0xB16
38
39
static WNDPROC video_win32_orig_wndproc = NULL;
40
static bool    win32_in_modal_move      = false;
41
42
static LRESULT CALLBACK
43
video_win32_subclass_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
44
{
45
	switch (message) {
46
		case WM_ENTERSIZEMOVE:
47
			win32_in_modal_move = true;
48
			// The timer covers the case the messages below cannot: a drag that
49
			// is held perfectly still still needs the machine to keep running.
50
			// 10 ms is the shortest interval Windows honours.
51
			SetTimer(hWnd, X16_MOVE_TIMER_ID, 10, NULL);
52
			break;
53
		case WM_EXITSIZEMOVE:
54
			if (win32_in_modal_move) {
55
				KillTimer(hWnd, X16_MOVE_TIMER_ID);
56
				win32_in_modal_move = false;
57
			}
58
			break;
59
		case WM_TIMER:
60
			if (win32_in_modal_move && wParam == X16_MOVE_TIMER_ID) {
61
				emulator_step_during_move();
62
				return 0; // handled; don't hand our private timer to SDL
63
			}
64
			break;
65
		// WM_TIMER is the lowest-priority message there is: Windows only
66
		// synthesises it when the queue has nothing else waiting. Drag the
67
		// window quickly and the queue never empties, so the timer stops firing
68
		// and the machine appears to pause -- the very case this is meant to
69
		// fix. Drive emulation from the messages a drag actually produces as
70
		// well; between the two, the machine keeps running whether the pointer
71
		// is flying or sitting still. emulator_step_during_move() paces itself,
72
		// so being called far more often than 100 Hz costs almost nothing.
73
		case WM_MOVING:
74
		case WM_SIZING:
75
		case WM_WINDOWPOSCHANGED:
76
		case WM_NCMOUSEMOVE:
77
		case WM_MOUSEMOVE:
78
			if (win32_in_modal_move) {
79
				emulator_step_during_move();
80
			}
81
			break; // still needs its normal handling below
82
	}
83
	if (video_win32_orig_wndproc) {
84
		return CallWindowProcW(video_win32_orig_wndproc, hWnd, message, wParam, lParam);
85
	}
86
	return DefWindowProcW(hWnd, message, wParam, lParam);
87
}
88
89
static HWND
90
video_win32_hwnd(SDL_Window *window)
91
{
92
	SDL_SysWMinfo wmInfo;
93
	SDL_VERSION(&wmInfo.version);
94
	if (!SDL_GetWindowWMInfo(window, &wmInfo)) {
95
		return NULL;
96
	}
97
	return wmInfo.info.win.window;
98
}
99
100
void video_win32_install_move_hook(SDL_Window *window)
101
{
102
	HWND hwnd = video_win32_hwnd(window);
103
	if (hwnd == NULL) {
104
		return;
105
	}
106
	// Don't double-subclass the same window (our proc would chain to itself).
107
	// All SDL windows share the same WIN_WindowProc, so one saved original is
108
	// enough for chaining regardless of how many windows we subclass.
109
	WNDPROC prev = (WNDPROC)(LONG_PTR)GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
110
	if (prev == video_win32_subclass_proc) {
111
		return; // already installed on this window
112
	}
113
	SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)video_win32_subclass_proc);
114
	if (video_win32_orig_wndproc == NULL) {
115
		video_win32_orig_wndproc = prev;
116
	}
117
}
118
119
void video_win32_remove_move_hook(SDL_Window *window)
120
{
121
	if (video_win32_orig_wndproc == NULL) {
122
		return;
123
	}
124
	HWND hwnd = video_win32_hwnd(window);
125
	if (hwnd == NULL) {
126
		return;
127
	}
128
	WNDPROC cur = (WNDPROC)(LONG_PTR)GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
129
	if (cur == video_win32_subclass_proc) {
130
		SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)video_win32_orig_wndproc);
131
	}
132
}
133