FreeRDP
Loading...
Searching...
No Matches
sdl_win32.cpp
1
19#include "sdl_win32.hpp"
20#include "sdl_utils.hpp"
21
22#include <algorithm>
23#include <mutex>
24#include <tuple>
25
26#include <winpr/library.h>
27#include <winpr/platform.h>
28#include <freerdp/log.h>
29
30#if defined(_WIN32)
31
32#define TAG CLIENT_TAG("sdl.win32")
33
34#include <windows.h>
35#include <windowsx.h>
36
37/* Window currently in a native size/move loop. */
38static HWND g_sizeMoveHwnd = nullptr;
39/* WMSZ_* edge of the running sizing loop, 0 = none or not reported yet. */
40static int g_sizingEdge = 0;
41/* Set on the RDP thread (channel init), read by the window procedure on the main thread. */
42static std::mutex g_sizeMoveLock;
43static SdlWin32SizeMoveHandler g_sizeMove;
44static const wchar_t kFrameProp[] = L"FreeRDP.RailNativeFrame";
45static const wchar_t kIdProp[] = L"FreeRDP.RailWindowId";
46
47[[nodiscard]] static SdlWin32SizeMoveHandler sdl_win32_size_move_handler()
48{
49 std::scoped_lock lock(g_sizeMoveLock);
50 return g_sizeMove;
51}
52
53[[nodiscard]] static SDL_WindowID sdl_win32_window_id(HWND hwnd)
54{
55 return static_cast<SDL_WindowID>(reinterpret_cast<uintptr_t>(GetPropW(hwnd, kIdProp)));
56}
57
58[[nodiscard]] static LRESULT sdl_win32_frame_next(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
59{
60 auto next = reinterpret_cast<WNDPROC>(GetPropW(hwnd, kFrameProp));
61 return next ? CallWindowProcW(next, hwnd, msg, wParam, lParam)
62 : DefWindowProcW(hwnd, msg, wParam, lParam);
63}
64
65[[nodiscard]] static HWND sdl_win32_hwnd(SDL_Window* window)
66{
67 if (!window || !sdl::utils::isWindowsDriver())
68 return nullptr;
69 return static_cast<HWND>(SDL_GetPointerProperty(SDL_GetWindowProperties(window),
70 SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr));
71}
72
73bool sdl_win32_restack_windows(const std::vector<SDL_Window*>& topToBottom)
74{
75 struct Entry
76 {
77 HWND hwnd;
78 bool topmost;
79 };
80 std::vector<Entry> stack;
81 stack.reserve(topToBottom.size());
82 for (auto* window : topToBottom)
83 {
84 HWND hwnd = sdl_win32_hwnd(window);
85 if (hwnd)
86 stack.push_back({ hwnd, (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0 });
87 }
88 if (stack.size() < 2)
89 return false;
90
91 HDWP dwp = BeginDeferWindowPos(static_cast<int>(stack.size() - 1));
92 if (!dwp)
93 return false;
94 /* Restack within each band: below a topmost window, a window becomes topmost too. */
95 HWND prevTopmost = nullptr;
96 HWND prevNormal = nullptr;
97 for (const auto& e : stack)
98 {
99 HWND& prev = e.topmost ? prevTopmost : prevNormal;
100 if (prev)
101 {
102 dwp = DeferWindowPos(dwp, e.hwnd, prev, 0, 0, 0, 0,
103 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
104 if (!dwp)
105 return false;
106 }
107 prev = e.hwnd;
108 }
109 return EndDeferWindowPos(dwp) != FALSE;
110}
111
112bool sdl_win32_can_move_size(SDL_Window* window, bool move, int wmszEdge)
113{
114 return sdl_win32_hwnd(window) &&
115 (move || ((wmszEdge >= WMSZ_LEFT) && (wmszEdge <= WMSZ_BOTTOMRIGHT)));
116}
117
118void sdl_win32_run_move_size(SDL_Window* window, bool move, int wmszEdge)
119{
120 HWND hwnd = sdl_win32_hwnd(window);
121 if (!hwnd)
122 return;
123 /* Released already (a quick click): the loop would wait in keyboard mode instead. */
124 const int button = GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON;
125 if (GetAsyncKeyState(button) >= 0)
126 return;
127 /* The press captured the mouse for SDL; the loop needs it. */
128 if (!ReleaseCapture())
129 WLog_DBG(TAG, "ReleaseCapture failed: %" PRIu32, static_cast<uint32_t>(GetLastError()));
130 const WPARAM cmd = move ? (SC_MOVE | HTCAPTION) : (SC_SIZE | static_cast<WPARAM>(wmszEdge));
131 SendMessage(hwnd, WM_SYSCOMMAND, cmd, 0);
132}
133
134[[nodiscard]] static bool sdl_win32_frame_insets(HWND hwnd, RECT& wr, RECT& insets)
135{
136 RECT cr = {};
137 POINT origin = {};
138 if (!GetWindowRect(hwnd, &wr) || !GetClientRect(hwnd, &cr) || !ClientToScreen(hwnd, &origin))
139 return false;
140 insets.left = origin.x - wr.left;
141 insets.top = origin.y - wr.top;
142 insets.right = (wr.right - wr.left) - insets.left - cr.right;
143 insets.bottom = (wr.bottom - wr.top) - insets.top - cr.bottom;
144 return true;
145}
146
147static LRESULT CALLBACK sdl_win32_frame_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
148{
149 switch (msg)
150 {
151 /* DWM draws the frame; theme painting would show as glass where the top frame was. */
152 case WM_NCPAINT:
153 return 0;
154 case WM_NCACTIVATE:
155 return sdl_win32_frame_next(hwnd, msg, wParam, -1);
156 case WM_NCCALCSIZE:
157 /* DWM draws the top sizing frame of a captionless window: keep only the others. */
158 if (wParam && !IsZoomed(hwnd))
159 {
160 auto* params = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
161 const LONG top = params->rgrc[0].top;
162 const LRESULT rc = sdl_win32_frame_next(hwnd, msg, wParam, lParam);
163 params->rgrc[0].top = top;
164 return rc;
165 }
166 break;
167 case WM_NCHITTEST:
168 {
169 /* Native top-edge hit test without caption. */
170 const LRESULT hit = sdl_win32_frame_next(hwnd, msg, wParam, lParam);
171 if (IsZoomed(hwnd) || ((hit != HTCLIENT) && (hit != HTLEFT) && (hit != HTRIGHT)))
172 return hit;
173 RECT wr = {};
174 RECT in = {};
175 const int frame = sdl_win32_frame_insets(hwnd, wr, in) ? in.left : 0;
176 if (frame <= 0)
177 return hit;
178 const int x = GET_X_LPARAM(lParam);
179 if (GET_Y_LPARAM(lParam) >= wr.top + frame)
180 return hit;
181 if ((hit == HTLEFT) || (x < wr.left + 2 * frame))
182 return HTTOPLEFT;
183 return ((hit == HTRIGHT) || (x >= wr.right - 2 * frame)) ? HTTOPRIGHT : HTTOP;
184 }
185 case WM_GETMINMAXINFO:
186 {
187 /* SDL adds a top sizing frame to the track sizes, but WM_NCCALCSIZE drops it. */
188 const LRESULT rc = sdl_win32_frame_next(hwnd, msg, wParam, lParam);
189 RECT wr = {};
190 RECT in = {};
191 if (!IsZoomed(hwnd) && sdl_win32_frame_insets(hwnd, wr, in) && (in.top == 0))
192 {
193 auto* info = reinterpret_cast<MINMAXINFO*>(lParam);
194 info->ptMinTrackSize.y = std::max<LONG>(info->ptMinTrackSize.y - in.bottom, 0);
195 /* Without a maximum size the maximized window must fit the system default. */
196 int maxW = 0;
197 int maxH = 0;
198 SDL_Window* window = SDL_GetWindowFromID(sdl_win32_window_id(hwnd));
199 if (window && SDL_GetWindowMaximumSize(window, &maxW, &maxH) && (maxH > 0))
200 info->ptMaxTrackSize.y -= in.bottom;
201 }
202 return rc;
203 }
204 case WM_STYLECHANGING:
205 /* Keep the sizing frame, drop the caption: the server draws the title bar. */
206 if (wParam == static_cast<WPARAM>(GWL_STYLE))
207 reinterpret_cast<STYLESTRUCT*>(lParam)->styleNew &= ~WS_CAPTION;
208 break;
209 case WM_ENTERSIZEMOVE:
210 g_sizeMoveHwnd = hwnd;
211 g_sizingEdge = 0;
212 break;
213 case WM_SIZING:
214 {
215 const auto handler = sdl_win32_size_move_handler();
216 if ((g_sizingEdge == 0) && handler.sizing)
217 {
218 g_sizingEdge = static_cast<int>(wParam);
219 handler.sizing(handler.userdata, sdl_win32_window_id(hwnd), g_sizingEdge, false);
220 }
221 break;
222 }
223 case WM_TIMER:
224 /* Redraw from the loop timer: SDL sends expose under its event watch lock. */
225 if (hwnd == g_sizeMoveHwnd)
226 {
227 const auto handler = sdl_win32_size_move_handler();
228 const LRESULT rc = sdl_win32_frame_next(hwnd, msg, wParam, lParam);
229 if (handler.redraw)
230 handler.redraw(handler.userdata);
231 return rc;
232 }
233 break;
234 case WM_EXITSIZEMOVE:
235 {
236 g_sizeMoveHwnd = nullptr;
237 const LRESULT rc = sdl_win32_frame_next(hwnd, msg, wParam, lParam);
238 const int edge = g_sizingEdge;
239 g_sizingEdge = 0;
240 const auto handler = sdl_win32_size_move_handler();
241 if ((edge != 0) && handler.sizing)
242 handler.sizing(handler.userdata, sdl_win32_window_id(hwnd), edge, true);
243 return rc;
244 }
245 case WM_NCDESTROY:
246 {
247 /* Destroyed inside its loop: no WM_EXITSIZEMOVE follows. */
248 if (hwnd == g_sizeMoveHwnd)
249 {
250 g_sizeMoveHwnd = nullptr;
251 g_sizingEdge = 0;
252 }
253 RemovePropW(hwnd, kIdProp);
254 auto next = reinterpret_cast<WNDPROC>(RemovePropW(hwnd, kFrameProp));
255 if (next && !SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(next)))
256 WLog_WARN(TAG, "restoring the window procedure failed: %" PRIu32,
257 static_cast<uint32_t>(GetLastError()));
258 return next ? CallWindowProcW(next, hwnd, msg, wParam, lParam)
259 : DefWindowProcW(hwnd, msg, wParam, lParam);
260 }
261 default:
262 break;
263 }
264 return sdl_win32_frame_next(hwnd, msg, wParam, lParam);
265}
266
267/* Disable Win11 rounded corners and borders (drawn by server). */
268static void sdl_win32_plain_frame(HWND hwnd)
269{
270 using SetAttrFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD);
271 static const auto setAttr = []
272 {
273 HMODULE dwm = LoadLibraryExW(L"dwmapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
274 return dwm ? GetProcAddressAs(dwm, "DwmSetWindowAttribute", SetAttrFn) : nullptr;
275 }();
276 if (!setAttr)
277 return;
278 const DWORD none = 0xFFFFFFFE; /* DWMWA_COLOR_NONE */
279 const DWORD borderColor = 34; /* DWMWA_BORDER_COLOR */
280 if (FAILED(setAttr(hwnd, borderColor, &none, sizeof(none))))
281 WLog_DBG(TAG, "DWMWA_BORDER_COLOR not supported");
282 const DWORD square = 1; /* DWMWCP_DONOTROUND */
283 const DWORD cornerPref = 33; /* DWMWA_WINDOW_CORNER_PREFERENCE */
284 if (FAILED(setAttr(hwnd, cornerPref, &square, sizeof(square))))
285 WLog_DBG(TAG, "DWMWA_WINDOW_CORNER_PREFERENCE not supported");
286}
287
288bool sdl_win32_use_native_frame(SDL_Window* window)
289{
290 HWND hwnd = sdl_win32_hwnd(window);
291 if (!hwnd)
292 return false;
293 if (!GetPropW(hwnd, kFrameProp))
294 {
295 /* Window procedure chaining without comctl32 v6 dependency. */
296 const auto next =
297 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(sdl_win32_frame_proc));
298 if (!next)
299 return false;
300 if (!SetPropW(hwnd, kFrameProp, reinterpret_cast<HANDLE>(next)))
301 {
302 if (!SetWindowLongPtrW(hwnd, GWLP_WNDPROC, next))
303 WLog_WARN(TAG, "restoring the window procedure failed: %" PRIu32,
304 static_cast<uint32_t>(GetLastError()));
305 return false;
306 }
307 const auto id = static_cast<uintptr_t>(SDL_GetWindowID(window));
308 if (!SetPropW(hwnd, kIdProp, reinterpret_cast<HANDLE>(id)))
309 WLog_WARN(TAG, "SetPropW failed: %" PRIu32, static_cast<uint32_t>(GetLastError()));
310 }
311 sdl_win32_plain_frame(hwnd);
312 SetLastError(ERROR_SUCCESS);
313 if (!SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION) &&
314 (GetLastError() != ERROR_SUCCESS))
315 return false;
316 return SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
317 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
318 SWP_NOACTIVATE) != FALSE;
319}
320
321void sdl_win32_set_size_move_handler(const SdlWin32SizeMoveHandler& handler)
322{
323 std::scoped_lock lock(g_sizeMoveLock);
324 g_sizeMove = handler;
325}
326
327bool sdl_win32_set_client_rect(SDL_Window* window, const SDL_Rect& rect)
328{
329 HWND hwnd = sdl_win32_hwnd(window);
330 if (!hwnd || !GetPropW(hwnd, kFrameProp))
331 return false;
332 /* SDL assumes a standard frame; take the real one from the window itself. */
333 RECT wr = {};
334 RECT in = {};
335 if (!sdl_win32_frame_insets(hwnd, wr, in))
336 return false;
337 return SetWindowPos(hwnd, nullptr, rect.x - in.left, rect.y - in.top,
338 rect.w + in.left + in.right, rect.h + in.top + in.bottom,
339 SWP_NOZORDER | SWP_NOACTIVATE) != FALSE;
340}
341
342bool sdl_win32_send_left_button_release(SDL_Window* window)
343{
344 HWND hwnd = sdl_win32_hwnd(window);
345 POINT pt = {};
346 if (!hwnd || !GetCursorPos(&pt) || !ScreenToClient(hwnd, &pt))
347 return false;
348 const LPARAM pos = MAKELPARAM(static_cast<WORD>(pt.x), static_cast<WORD>(pt.y));
349 return PostMessage(hwnd, WM_LBUTTONUP, 0, pos) != FALSE;
350}
351
352#else /* !_WIN32 */
353
354bool sdl_win32_restack_windows(WINPR_ATTR_UNUSED const std::vector<SDL_Window*>& topToBottom)
355{
356 return false;
357}
358
359bool sdl_win32_can_move_size(WINPR_ATTR_UNUSED SDL_Window* window, WINPR_ATTR_UNUSED bool move,
360 WINPR_ATTR_UNUSED int wmszEdge)
361{
362 return false;
363}
364
365void sdl_win32_run_move_size(WINPR_ATTR_UNUSED SDL_Window* window, WINPR_ATTR_UNUSED bool move,
366 WINPR_ATTR_UNUSED int wmszEdge)
367{
368}
369
370bool sdl_win32_use_native_frame(WINPR_ATTR_UNUSED SDL_Window* window)
371{
372 return false;
373}
374
375void sdl_win32_set_size_move_handler(WINPR_ATTR_UNUSED const SdlWin32SizeMoveHandler& handler)
376{
377}
378
379bool sdl_win32_set_client_rect(WINPR_ATTR_UNUSED SDL_Window* window,
380 WINPR_ATTR_UNUSED const SDL_Rect& rect)
381{
382 return false;
383}
384
385bool sdl_win32_send_left_button_release(WINPR_ATTR_UNUSED SDL_Window* window)
386{
387 return false;
388}
389
390#endif