Last active
April 15, 2026 19:08
-
-
Save GGPrompts/06a227f12c2336d54dd0ea7c655fe059 to your computer and use it in GitHub Desktop.
WSLg Weston patches: per-monitor minmax info + enable local move for snap layouts (microsoft/wslg#22)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/client/Windows/wf_rail.c b/client/Windows/wf_rail.c | |
| index a775a588a..ef2dc2705 100644 | |
| --- a/client/Windows/wf_rail.c | |
| +++ b/client/Windows/wf_rail.c | |
| @@ -16,11 +16,12 @@ | |
| * limitations under the License. | |
| */ | |
| -#include <freerdp/config.h> | |
| +#ifdef HAVE_CONFIG_H | |
| +#include "config.h" | |
| +#endif | |
| #include <winpr/crt.h> | |
| #include <freerdp/log.h> | |
| -#include <freerdp/client/rail.h> | |
| #include <winpr/tchar.h> | |
| #include <winpr/print.h> | |
| @@ -49,12 +50,13 @@ struct wf_rail_window | |
| /* RemoteApp Core Protocol Extension */ | |
| -typedef struct | |
| +struct _WINDOW_STYLE | |
| { | |
| UINT32 style; | |
| const char* name; | |
| BOOL multi; | |
| -} WINDOW_STYLE; | |
| +}; | |
| +typedef struct _WINDOW_STYLE WINDOW_STYLE; | |
| static const WINDOW_STYLE WINDOW_STYLES[] = { { WS_BORDER, "WS_BORDER", FALSE }, | |
| { WS_CAPTION, "WS_CAPTION", FALSE }, | |
| @@ -111,9 +113,10 @@ static const WINDOW_STYLE EXTENDED_WINDOW_STYLES[] = { | |
| static void PrintWindowStyles(UINT32 style) | |
| { | |
| + int i; | |
| WLog_INFO(TAG, "\tWindow Styles:\t{"); | |
| - for (size_t i = 0; i < ARRAYSIZE(WINDOW_STYLES); i++) | |
| + for (i = 0; i < ARRAYSIZE(WINDOW_STYLES); i++) | |
| { | |
| if (style & WINDOW_STYLES[i].style) | |
| { | |
| @@ -130,9 +133,10 @@ static void PrintWindowStyles(UINT32 style) | |
| static void PrintExtendedWindowStyles(UINT32 style) | |
| { | |
| + int i; | |
| WLog_INFO(TAG, "\tExtended Window Styles:\t{"); | |
| - for (size_t i = 0; i < ARRAYSIZE(EXTENDED_WINDOW_STYLES); i++) | |
| + for (i = 0; i < ARRAYSIZE(EXTENDED_WINDOW_STYLES); i++) | |
| { | |
| if (style & EXTENDED_WINDOW_STYLES[i].style) | |
| { | |
| @@ -177,9 +181,9 @@ static void PrintRailWindowState(const WINDOW_ORDER_INFO* orderInfo, | |
| if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE) | |
| { | |
| - const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; | |
| - char* title = | |
| - ConvertWCharNToUtf8Alloc(str, windowState->titleInfo.length / sizeof(WCHAR), nullptr); | |
| + char* title = NULL; | |
| + ConvertFromUnicode(CP_UTF8, 0, (WCHAR*)windowState->titleInfo.string, | |
| + windowState->titleInfo.length / 2, &title, 0, NULL, NULL); | |
| WLog_INFO(TAG, "\tTitleInfo: %s (length = %hu)", title, windowState->titleInfo.length); | |
| free(title); | |
| } | |
| @@ -226,10 +230,11 @@ static void PrintRailWindowState(const WINDOW_ORDER_INFO* orderInfo, | |
| if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) | |
| { | |
| + UINT32 index; | |
| RECTANGLE_16* rect; | |
| WLog_INFO(TAG, "\tnumWindowRects: %u", windowState->numWindowRects); | |
| - for (UINT32 index = 0; index < windowState->numWindowRects; index++) | |
| + for (index = 0; index < windowState->numWindowRects; index++) | |
| { | |
| rect = &windowState->windowRects[index]; | |
| WLog_INFO(TAG, "\twindowRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", index, | |
| @@ -245,10 +250,11 @@ static void PrintRailWindowState(const WINDOW_ORDER_INFO* orderInfo, | |
| if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) | |
| { | |
| + UINT32 index; | |
| RECTANGLE_16* rect; | |
| WLog_INFO(TAG, "\tnumVisibilityRects: %u", windowState->numVisibilityRects); | |
| - for (UINT32 index = 0; index < windowState->numVisibilityRects; index++) | |
| + for (index = 0; index < windowState->numVisibilityRects; index++) | |
| { | |
| rect = &windowState->visibilityRects[index]; | |
| WLog_INFO(TAG, "\tvisibilityRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", index, | |
| @@ -279,6 +285,36 @@ static void PrintRailIconInfo(const WINDOW_ORDER_INFO* orderInfo, const ICON_INF | |
| WLog_INFO(TAG, "}"); | |
| } | |
| +/* Send the current window rectangle to the RAIL server so the remote | |
| + * side knows where the window ended up after a local move/resize. */ | |
| +static void wf_rail_send_window_move(wfRailWindow* railWindow) | |
| +{ | |
| + RailClientContext* rail; | |
| + RAIL_WINDOW_MOVE_ORDER windowMove; | |
| + RECT rc; | |
| + | |
| + if (!railWindow || !railWindow->wfc) | |
| + return; | |
| + | |
| + rail = railWindow->wfc->rail; | |
| + if (!rail || !rail->ClientWindowMove) | |
| + return; | |
| + | |
| + GetWindowRect(railWindow->hWnd, &rc); | |
| + windowMove.windowId = railWindow->windowId; | |
| + windowMove.left = (INT16)rc.left; | |
| + windowMove.top = (INT16)rc.top; | |
| + windowMove.right = (INT16)rc.right; | |
| + windowMove.bottom = (INT16)rc.bottom; | |
| + rail->ClientWindowMove(rail, &windowMove); | |
| + | |
| + /* Keep the local tracking in sync. */ | |
| + railWindow->x = rc.left; | |
| + railWindow->y = rc.top; | |
| + railWindow->width = rc.right - rc.left; | |
| + railWindow->height = rc.bottom - rc.top; | |
| +} | |
| + | |
| LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) | |
| { | |
| HDC hDC; | |
| @@ -289,9 +325,9 @@ LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara | |
| UINT32 yPos; | |
| PAINTSTRUCT ps; | |
| UINT32 inputFlags; | |
| - wfContext* wfc = nullptr; | |
| - rdpInput* input = nullptr; | |
| - rdpContext* context = nullptr; | |
| + wfContext* wfc = NULL; | |
| + rdpInput* input = NULL; | |
| + rdpContext* context = NULL; | |
| wfRailWindow* railWindow; | |
| railWindow = (wfRailWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); | |
| @@ -306,6 +342,37 @@ LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara | |
| switch (msg) | |
| { | |
| + /* ── Snap / DWM integration ────────────────────────────── */ | |
| + | |
| + case WM_NCCALCSIZE: | |
| + /* We keep WS_THICKFRAME so DWM offers snap layouts, but | |
| + * suppress the visible non-client frame by returning 0. | |
| + * The server renders its own decorations. */ | |
| + if (wParam == TRUE) | |
| + return 0; | |
| + return DefWindowProc(hWnd, msg, wParam, lParam); | |
| + | |
| + case WM_ENTERSIZEMOVE: | |
| + if (railWindow) | |
| + railWindow->isLocalMoveSizing = TRUE; | |
| + return 0; | |
| + | |
| + case WM_EXITSIZEMOVE: | |
| + if (railWindow) | |
| + { | |
| + railWindow->isLocalMoveSizing = FALSE; | |
| + wf_rail_send_window_move(railWindow); | |
| + } | |
| + return 0; | |
| + | |
| + case WM_WINDOWPOSCHANGED: | |
| + /* During a local move/resize DWM may snap the window. | |
| + * Forward intermediate position updates so the server | |
| + * re-renders at the correct size in real time. */ | |
| + if (railWindow && railWindow->isLocalMoveSizing) | |
| + wf_rail_send_window_move(railWindow); | |
| + return DefWindowProc(hWnd, msg, wParam, lParam); | |
| + | |
| case WM_PAINT: | |
| { | |
| if (!wfc) | |
| @@ -410,8 +477,11 @@ LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara | |
| return 0; | |
| } | |
| -#define RAIL_DISABLED_WINDOW_STYLES \ | |
| - (WS_BORDER | WS_THICKFRAME | WS_DLGFRAME | WS_CAPTION | WS_OVERLAPPED | WS_VSCROLL | \ | |
| +/* WS_THICKFRAME is intentionally kept: DWM requires it for snap layouts | |
| + * (drag-to-edge, Win11 Snap Layouts). We suppress the visible resize frame | |
| + * by handling WM_NCCALCSIZE to return a zero-size non-client area. */ | |
| +#define RAIL_DISABLED_WINDOW_STYLES \ | |
| + (WS_BORDER | WS_DLGFRAME | WS_CAPTION | WS_OVERLAPPED | WS_VSCROLL | \ | |
| WS_HSCROLL | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) | |
| #define RAIL_DISABLED_EXTENDED_WINDOW_STYLES \ | |
| (WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE) | |
| @@ -419,7 +489,7 @@ LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara | |
| static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, | |
| const WINDOW_STATE_ORDER* windowState) | |
| { | |
| - wfRailWindow* railWindow = nullptr; | |
| + wfRailWindow* railWindow = NULL; | |
| wfContext* wfc = (wfContext*)context; | |
| RailClientContext* rail = wfc->rail; | |
| UINT32 fieldFlags = orderInfo->fieldFlags; | |
| @@ -427,18 +497,24 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| if (fieldFlags & WINDOW_ORDER_STATE_NEW) | |
| { | |
| - BOOL rc; | |
| HANDLE hInstance; | |
| - WCHAR* titleW = nullptr; | |
| - WNDCLASSEX wndClassEx = WINPR_C_ARRAY_INIT; | |
| + WCHAR* titleW = NULL; | |
| + WNDCLASSEX wndClassEx; | |
| railWindow = (wfRailWindow*)calloc(1, sizeof(wfRailWindow)); | |
| if (!railWindow) | |
| return FALSE; | |
| railWindow->wfc = wfc; | |
| + railWindow->windowId = orderInfo->windowId; | |
| + railWindow->isLocalMoveSizing = FALSE; | |
| railWindow->dwStyle = windowState->style; | |
| railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; | |
| + /* Re-add WS_THICKFRAME for DWM snap support, but only on | |
| + * windows the server originally marked as resizable. This | |
| + * avoids enabling resize/snap on dialogs and tool windows. */ | |
| + if (windowState->style & WS_THICKFRAME) | |
| + railWindow->dwStyle |= WS_THICKFRAME; | |
| railWindow->dwExStyle = windowState->extendedStyle; | |
| railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; | |
| railWindow->x = windowState->windowOffsetX; | |
| @@ -448,8 +524,7 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) | |
| { | |
| - const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; | |
| - char* title = nullptr; | |
| + char* title = NULL; | |
| if (windowState->titleInfo.length == 0) | |
| { | |
| @@ -459,8 +534,9 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| /* error handled below */ | |
| } | |
| } | |
| - else if (!(title = ConvertWCharNToUtf8Alloc( | |
| - str, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) | |
| + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*)windowState->titleInfo.string, | |
| + windowState->titleInfo.length / 2, &title, 0, NULL, | |
| + NULL) < 1) | |
| { | |
| WLog_ERR(TAG, "failed to convert window title"); | |
| /* error handled below */ | |
| @@ -480,21 +556,21 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| return FALSE; | |
| } | |
| - titleW = ConvertUtf8ToWCharAlloc(railWindow->title, nullptr); | |
| - hInstance = GetModuleHandle(nullptr); | |
| - | |
| + ConvertToUnicode(CP_UTF8, 0, railWindow->title, -1, &titleW, 0); | |
| + hInstance = GetModuleHandle(NULL); | |
| + ZeroMemory(&wndClassEx, sizeof(WNDCLASSEX)); | |
| wndClassEx.cbSize = sizeof(WNDCLASSEX); | |
| wndClassEx.style = 0; | |
| wndClassEx.lpfnWndProc = wf_RailWndProc; | |
| wndClassEx.cbClsExtra = 0; | |
| wndClassEx.cbWndExtra = 0; | |
| - wndClassEx.hIcon = nullptr; | |
| - wndClassEx.hCursor = nullptr; | |
| - wndClassEx.hbrBackground = nullptr; | |
| - wndClassEx.lpszMenuName = nullptr; | |
| + wndClassEx.hIcon = NULL; | |
| + wndClassEx.hCursor = NULL; | |
| + wndClassEx.hbrBackground = NULL; | |
| + wndClassEx.lpszMenuName = NULL; | |
| wndClassEx.lpszClassName = _T("RdpRailWindow"); | |
| wndClassEx.hInstance = hInstance; | |
| - wndClassEx.hIconSm = nullptr; | |
| + wndClassEx.hIconSm = NULL; | |
| RegisterClassEx(&wndClassEx); | |
| railWindow->hWnd = CreateWindowExW(railWindow->dwExStyle, /* dwExStyle */ | |
| _T("RdpRailWindow"), /* lpClassName */ | |
| @@ -504,10 +580,10 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| railWindow->y, /* y */ | |
| railWindow->width, /* nWidth */ | |
| railWindow->height, /* nHeight */ | |
| - nullptr, /* hWndParent */ | |
| - nullptr, /* hMenu */ | |
| + NULL, /* hWndParent */ | |
| + NULL, /* hMenu */ | |
| hInstance, /* hInstance */ | |
| - nullptr /* lpParam */ | |
| + NULL /* lpParam */ | |
| ); | |
| if (!railWindow->hWnd) | |
| @@ -520,11 +596,10 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| } | |
| SetWindowLongPtr(railWindow->hWnd, GWLP_USERDATA, (LONG_PTR)railWindow); | |
| - rc = HashTable_Insert(wfc->railWindows, (void*)(UINT_PTR)orderInfo->windowId, | |
| - (void*)railWindow); | |
| + HashTable_Add(wfc->railWindows, (void*)(UINT_PTR)orderInfo->windowId, (void*)railWindow); | |
| free(titleW); | |
| UpdateWindow(railWindow->hWnd); | |
| - return rc; | |
| + return TRUE; | |
| } | |
| else | |
| { | |
| @@ -537,20 +612,26 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) || (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)) | |
| { | |
| - if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) | |
| + /* While a local move/resize is active (DWM owns the drag loop), | |
| + * suppress server-driven geometry updates to avoid fighting the | |
| + * local DWM positioning and prevent feedback loops. */ | |
| + if (!railWindow->isLocalMoveSizing) | |
| { | |
| - railWindow->x = windowState->windowOffsetX; | |
| - railWindow->y = windowState->windowOffsetY; | |
| - } | |
| + if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) | |
| + { | |
| + railWindow->x = windowState->windowOffsetX; | |
| + railWindow->y = windowState->windowOffsetY; | |
| + } | |
| - if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) | |
| - { | |
| - railWindow->width = windowState->windowWidth; | |
| - railWindow->height = windowState->windowHeight; | |
| - } | |
| + if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) | |
| + { | |
| + railWindow->width = windowState->windowWidth; | |
| + railWindow->height = windowState->windowHeight; | |
| + } | |
| - SetWindowPos(railWindow->hWnd, nullptr, railWindow->x, railWindow->y, railWindow->width, | |
| - railWindow->height, 0); | |
| + SetWindowPos(railWindow->hWnd, NULL, railWindow->x, railWindow->y, railWindow->width, | |
| + railWindow->height, 0); | |
| + } | |
| } | |
| if (fieldFlags & WINDOW_ORDER_FIELD_OWNER) | |
| @@ -561,6 +642,8 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| { | |
| railWindow->dwStyle = windowState->style; | |
| railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; | |
| + if (windowState->style & WS_THICKFRAME) | |
| + railWindow->dwStyle |= WS_THICKFRAME; /* DWM snap support */ | |
| railWindow->dwExStyle = windowState->extendedStyle; | |
| railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; | |
| SetWindowLongPtr(railWindow->hWnd, GWL_STYLE, (LONG)railWindow->dwStyle); | |
| @@ -574,8 +657,8 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) | |
| { | |
| - const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; | |
| - char* title = nullptr; | |
| + char* title = NULL; | |
| + WCHAR* titleW = NULL; | |
| if (windowState->titleInfo.length == 0) | |
| { | |
| @@ -585,8 +668,8 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| return FALSE; | |
| } | |
| } | |
| - else if (!(title = ConvertWCharNToUtf8Alloc( | |
| - str, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) | |
| + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*)windowState->titleInfo.string, | |
| + windowState->titleInfo.length / 2, &title, 0, NULL, NULL) < 1) | |
| { | |
| WLog_ERR(TAG, "failed to convert window title"); | |
| return FALSE; | |
| @@ -594,7 +677,9 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| free(railWindow->title); | |
| railWindow->title = title; | |
| - SetWindowTextW(railWindow->hWnd, str); | |
| + ConvertToUnicode(CP_UTF8, 0, railWindow->title, -1, &titleW, 0); | |
| + SetWindowTextW(railWindow->hWnd, titleW); | |
| + free(titleW); | |
| } | |
| if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) | |
| @@ -619,6 +704,7 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) | |
| { | |
| + UINT32 index; | |
| HRGN hWndRect; | |
| HRGN hWndRects; | |
| RECTANGLE_16* rect; | |
| @@ -628,7 +714,7 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| rect = &(windowState->windowRects[0]); | |
| hWndRects = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); | |
| - for (UINT32 index = 1; index < windowState->numWindowRects; index++) | |
| + for (index = 1; index < windowState->numWindowRects; index++) | |
| { | |
| rect = &(windowState->windowRects[index]); | |
| hWndRect = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); | |
| @@ -655,7 +741,7 @@ static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* | |
| static BOOL wf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) | |
| { | |
| - wfRailWindow* railWindow = nullptr; | |
| + wfRailWindow* railWindow = NULL; | |
| wfContext* wfc = (wfContext*)context; | |
| RailClientContext* rail = wfc->rail; | |
| WLog_DBG(TAG, "RailWindowDelete"); | |
| @@ -680,8 +766,8 @@ static BOOL wf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* or | |
| int height; | |
| HICON hIcon; | |
| BOOL bigIcon; | |
| - ICONINFO iconInfo = WINPR_C_ARRAY_INIT; | |
| - BITMAPINFO bitmapInfo = WINPR_C_ARRAY_INIT; | |
| + ICONINFO iconInfo; | |
| + BITMAPINFO bitmapInfo; | |
| wfRailWindow* railWindow; | |
| BITMAPINFOHEADER* bitmapInfoHeader; | |
| wfContext* wfc = (wfContext*)context; | |
| @@ -699,7 +785,7 @@ static BOOL wf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* or | |
| iconInfo.fIcon = TRUE; | |
| iconInfo.xHotspot = 0; | |
| iconInfo.yHotspot = 0; | |
| - | |
| + ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO)); | |
| bitmapInfoHeader = &(bitmapInfo.bmiHeader); | |
| bpp = windowIcon->iconInfo->bpp; | |
| width = windowIcon->iconInfo->width; | |
| @@ -731,7 +817,7 @@ static BOOL wf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* or | |
| SendMessage(railWindow->hWnd, WM_SETICON, wParam, lParam); | |
| } | |
| - ReleaseDC(nullptr, hDC); | |
| + ReleaseDC(NULL, hDC); | |
| if (windowIcon->iconInfo->cacheEntry != 0xFFFF) | |
| { | |
| @@ -863,6 +949,76 @@ static UINT wf_rail_server_system_param(RailClientContext* context, | |
| return CHANNEL_RC_OK; | |
| } | |
| +static UINT wf_rail_server_start_cmd(RailClientContext* context) | |
| +{ | |
| + UINT status; | |
| + RAIL_EXEC_ORDER exec = { 0 }; | |
| + RAIL_SYSPARAM_ORDER sysparam = { 0 }; | |
| + RAIL_CLIENT_STATUS_ORDER clientStatus = { 0 }; | |
| + wfContext* wfc = (wfContext*)context->custom; | |
| + rdpSettings* settings = wfc->context.settings; | |
| + clientStatus.flags = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; | |
| + | |
| + if (settings->AutoReconnectionEnabled) | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT; | |
| + | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC; | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED; | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED; | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED; | |
| + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED; | |
| + status = context->ClientInformation(context, &clientStatus); | |
| + | |
| + if (status != CHANNEL_RC_OK) | |
| + return status; | |
| + | |
| + if (settings->RemoteAppLanguageBarSupported) | |
| + { | |
| + RAIL_LANGBAR_INFO_ORDER langBarInfo; | |
| + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ | |
| + status = context->ClientLanguageBarInfo(context, &langBarInfo); | |
| + | |
| + /* We want the language bar, but the server might not support it. */ | |
| + switch (status) | |
| + { | |
| + case CHANNEL_RC_OK: | |
| + case ERROR_BAD_CONFIGURATION: | |
| + break; | |
| + default: | |
| + return status; | |
| + } | |
| + } | |
| + | |
| + sysparam.params = 0; | |
| + sysparam.params |= SPI_MASK_SET_HIGH_CONTRAST; | |
| + sysparam.highContrast.colorScheme.string = NULL; | |
| + sysparam.highContrast.colorScheme.length = 0; | |
| + sysparam.highContrast.flags = 0x7E; | |
| + sysparam.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; | |
| + sysparam.mouseButtonSwap = FALSE; | |
| + sysparam.params |= SPI_MASK_SET_KEYBOARD_PREF; | |
| + sysparam.keyboardPref = FALSE; | |
| + sysparam.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; | |
| + sysparam.dragFullWindows = FALSE; | |
| + sysparam.params |= SPI_MASK_SET_KEYBOARD_CUES; | |
| + sysparam.keyboardCues = FALSE; | |
| + sysparam.params |= SPI_MASK_SET_WORK_AREA; | |
| + sysparam.workArea.left = 0; | |
| + sysparam.workArea.top = 0; | |
| + sysparam.workArea.right = settings->DesktopWidth; | |
| + sysparam.workArea.bottom = settings->DesktopHeight; | |
| + sysparam.dragFullWindows = FALSE; | |
| + status = context->ClientSystemParam(context, &sysparam); | |
| + | |
| + if (status != CHANNEL_RC_OK) | |
| + return status; | |
| + | |
| + exec.RemoteApplicationProgram = settings->RemoteApplicationProgram; | |
| + exec.RemoteApplicationWorkingDir = settings->ShellWorkingDirectory; | |
| + exec.RemoteApplicationArguments = settings->RemoteApplicationCmdLine; | |
| + return context->ClientExecute(context, &exec); | |
| +} | |
| + | |
| /** | |
| * Function description | |
| * | |
| @@ -871,7 +1027,7 @@ static UINT wf_rail_server_system_param(RailClientContext* context, | |
| static UINT wf_rail_server_handshake(RailClientContext* context, | |
| const RAIL_HANDSHAKE_ORDER* handshake) | |
| { | |
| - return client_rail_server_start_cmd(context); | |
| + return wf_rail_server_start_cmd(context); | |
| } | |
| /** | |
| @@ -882,7 +1038,7 @@ static UINT wf_rail_server_handshake(RailClientContext* context, | |
| static UINT wf_rail_server_handshake_ex(RailClientContext* context, | |
| const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) | |
| { | |
| - return client_rail_server_start_cmd(context); | |
| + return wf_rail_server_start_cmd(context); | |
| } | |
| /** | |
| @@ -893,6 +1049,88 @@ static UINT wf_rail_server_handshake_ex(RailClientContext* context, | |
| static UINT wf_rail_server_local_move_size(RailClientContext* context, | |
| const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) | |
| { | |
| + wfContext* wfc = (wfContext*)context->custom; | |
| + wfRailWindow* railWindow; | |
| + | |
| + railWindow = (wfRailWindow*)HashTable_GetItemValue( | |
| + wfc->railWindows, (void*)(UINT_PTR)localMoveSize->windowId); | |
| + | |
| + if (!railWindow) | |
| + return ERROR_INTERNAL_ERROR; | |
| + | |
| + if (localMoveSize->isMoveSizeStart) | |
| + { | |
| + POINT pt; | |
| + WPARAM sc; | |
| + railWindow->isLocalMoveSizing = TRUE; | |
| + pt.x = localMoveSize->posX; | |
| + pt.y = localMoveSize->posY; | |
| + | |
| + switch (localMoveSize->moveSizeType) | |
| + { | |
| + case RAIL_WMSZ_LEFT: | |
| + sc = SC_SIZE | WMSZ_LEFT; | |
| + break; | |
| + case RAIL_WMSZ_RIGHT: | |
| + sc = SC_SIZE | WMSZ_RIGHT; | |
| + break; | |
| + case RAIL_WMSZ_TOP: | |
| + sc = SC_SIZE | WMSZ_TOP; | |
| + break; | |
| + case RAIL_WMSZ_TOPLEFT: | |
| + sc = SC_SIZE | WMSZ_TOPLEFT; | |
| + break; | |
| + case RAIL_WMSZ_TOPRIGHT: | |
| + sc = SC_SIZE | WMSZ_TOPRIGHT; | |
| + break; | |
| + case RAIL_WMSZ_BOTTOM: | |
| + sc = SC_SIZE | WMSZ_BOTTOM; | |
| + break; | |
| + case RAIL_WMSZ_BOTTOMLEFT: | |
| + sc = SC_SIZE | WMSZ_BOTTOMLEFT; | |
| + break; | |
| + case RAIL_WMSZ_BOTTOMRIGHT: | |
| + sc = SC_SIZE | WMSZ_BOTTOMRIGHT; | |
| + break; | |
| + case RAIL_WMSZ_MOVE: | |
| + case RAIL_WMSZ_KEYMOVE: | |
| + sc = SC_MOVE; | |
| + break; | |
| + case RAIL_WMSZ_KEYSIZE: | |
| + sc = SC_SIZE; | |
| + break; | |
| + default: | |
| + return CHANNEL_RC_OK; | |
| + } | |
| + | |
| + if ((localMoveSize->moveSizeType != RAIL_WMSZ_KEYMOVE) && | |
| + (localMoveSize->moveSizeType != RAIL_WMSZ_KEYSIZE)) | |
| + { | |
| + ClientToScreen(railWindow->hWnd, &pt); | |
| + } | |
| + | |
| + /* Initiate the system-managed move/resize so DWM takes over | |
| + * and can offer snap zones during the drag. */ | |
| + if (localMoveSize->moveSizeType == RAIL_WMSZ_MOVE) | |
| + { | |
| + /* Bare SC_MOVE is enough for keyboard snapping, but Windows 11's | |
| + * drag snap UI expects a real caption drag/non-client move loop. */ | |
| + ReleaseCapture(); | |
| + SendMessage(railWindow->hWnd, WM_NCLBUTTONDOWN, HTCAPTION, | |
| + MAKELPARAM((SHORT)pt.x, (SHORT)pt.y)); | |
| + } | |
| + else | |
| + { | |
| + SendMessage(railWindow->hWnd, WM_SYSCOMMAND, sc, | |
| + MAKELPARAM((SHORT)pt.x, (SHORT)pt.y)); | |
| + } | |
| + } | |
| + else | |
| + { | |
| + railWindow->isLocalMoveSizing = FALSE; | |
| + wf_rail_send_window_move(railWindow); | |
| + } | |
| + | |
| return CHANNEL_RC_OK; | |
| } | |
| @@ -904,6 +1142,15 @@ static UINT wf_rail_server_local_move_size(RailClientContext* context, | |
| static UINT wf_rail_server_min_max_info(RailClientContext* context, | |
| const RAIL_MINMAXINFO_ORDER* minMaxInfo) | |
| { | |
| + /* TODO: Store the server's min/max constraints on the railWindow | |
| + * and apply them in a WM_GETMINMAXINFO handler. For now, accept | |
| + * the order and let DWM use its defaults — snap will work but | |
| + * the server's size constraints won't be enforced client-side. */ | |
| + WLog_DBG(TAG, "ServerMinMaxInfo: wid=0x%08X max=%dx%d minTrack=%dx%d maxTrack=%dx%d", | |
| + minMaxInfo->windowId, | |
| + minMaxInfo->maxWidth, minMaxInfo->maxHeight, | |
| + minMaxInfo->minTrackWidth, minMaxInfo->minTrackHeight, | |
| + minMaxInfo->maxTrackWidth, minMaxInfo->maxTrackHeight); | |
| return CHANNEL_RC_OK; | |
| } | |
| @@ -931,16 +1178,18 @@ static UINT wf_rail_server_get_appid_response(RailClientContext* context, | |
| void wf_rail_invalidate_region(wfContext* wfc, REGION16* invalidRegion) | |
| { | |
| + int index; | |
| + int count; | |
| RECT updateRect; | |
| RECTANGLE_16 windowRect; | |
| - ULONG_PTR* pKeys = nullptr; | |
| + ULONG_PTR* pKeys = NULL; | |
| wfRailWindow* railWindow; | |
| const RECTANGLE_16* extents; | |
| REGION16 windowInvalidRegion; | |
| region16_init(&windowInvalidRegion); | |
| - size_t count = HashTable_GetKeys(wfc->railWindows, &pKeys); | |
| + count = HashTable_GetKeys(wfc->railWindows, &pKeys); | |
| - for (size_t index = 0; index < count; index++) | |
| + for (index = 0; index < count; index++) | |
| { | |
| railWindow = (wfRailWindow*)HashTable_GetItemValue(wfc->railWindows, (void*)pKeys[index]); | |
| @@ -983,12 +1232,12 @@ BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail) | |
| rail->ServerGetAppIdResponse = wf_rail_server_get_appid_response; | |
| wf_rail_register_update_callbacks(context->update); | |
| wfc->railWindows = HashTable_New(TRUE); | |
| - return (wfc->railWindows != nullptr); | |
| + return (wfc->railWindows != NULL); | |
| } | |
| void wf_rail_uninit(wfContext* wfc, RailClientContext* rail) | |
| { | |
| - wfc->rail = nullptr; | |
| - rail->custom = nullptr; | |
| + wfc->rail = NULL; | |
| + rail->custom = NULL; | |
| HashTable_Free(wfc->railWindows); | |
| } | |
| diff --git a/client/Windows/wf_rail.h b/client/Windows/wf_rail.h | |
| index 2b73821e1..6f3040e35 100644 | |
| --- a/client/Windows/wf_rail.h | |
| +++ b/client/Windows/wf_rail.h | |
| @@ -25,6 +25,25 @@ typedef struct wf_rail_window wfRailWindow; | |
| #include <freerdp/client/rail.h> | |
| +struct wf_rail_window | |
| +{ | |
| + wfContext* wfc; | |
| + | |
| + HWND hWnd; | |
| + UINT32 windowId; | |
| + | |
| + DWORD dwStyle; | |
| + DWORD dwExStyle; | |
| + | |
| + int x; | |
| + int y; | |
| + int width; | |
| + int height; | |
| + char* title; | |
| + | |
| + BOOL isLocalMoveSizing; /* TRUE while DWM is handling a local move/resize */ | |
| +}; | |
| + | |
| BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail); | |
| void wf_rail_uninit(wfContext* wfc, RailClientContext* rail); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c | |
| index de9c03c..4d2ab75 100644 | |
| --- a/libweston/backend-rdp/rdprail.c | |
| +++ b/libweston/backend-rdp/rdprail.c | |
| @@ -3753,7 +3753,7 @@ rdp_rail_send_window_minmax_info( | |
| struct weston_compositor *compositor = surface->compositor; | |
| struct weston_surface_rail_state *rail_state = surface->backend_state; | |
| struct rdp_backend *b = to_rdp_backend(compositor); | |
| - struct weston_output *output = rdp_output_get_primary(compositor); | |
| + struct weston_output *output = surface->output; | |
| RdpPeerContext *peer_ctx; | |
| RailServerContext *rail_ctx; | |
| RAIL_MINMAXINFO_ORDER minmax_order; | |
| @@ -3765,10 +3765,11 @@ rdp_rail_send_window_minmax_info( | |
| peer_ctx = (RdpPeerContext *)b->rdp_peer->context; | |
| - /* apply global to output transform, and translate to client coordinate */ | |
| - /* minmax info is based on primary monitor */ | |
| - /* https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo */ | |
| - if (output) { | |
| + /* apply global to output transform, and translate to client coordinate */ | |
| + /* https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo */ | |
| + if (!output) | |
| + output = rdp_output_get_primary(compositor); | |
| + if (output) { | |
| to_client_coordinate(peer_ctx, output, | |
| &maxPosSize->x, &maxPosSize->y, | |
| &maxPosSize->width, &maxPosSize->height); | |
| diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c | |
| index 75fc836..57820d0 100644 | |
| --- a/rdprail-shell/shell.c | |
| +++ b/rdprail-shell/shell.c | |
| @@ -536,9 +536,11 @@ shell_send_minmax_info(struct weston_surface *surface) | |
| shell = shsurf->shell; | |
| if (shell->rdprail_api->send_window_minmax_info) { | |
| - /* minmax info is based on primary monitor */ | |
| - /* https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo */ | |
| - output = get_default_output(shell->compositor); | |
| + /* Use the surface's current output when available so snapped | |
| + * sizing tracks the monitor the window is actually on. */ | |
| + output = surface->output; | |
| + if (!output) | |
| + output = get_default_output(shell->compositor); | |
| assert(output); | |
| maxPosSize.x = output->x; | |
| @@ -782,9 +784,9 @@ shell_configuration(struct desktop_shell *shell) | |
| shell_rdp_debug(shell, "RDPRAIL-shell: binding-modifier:%s\n", s); | |
| free(s); | |
| - /* default to disable local move (not fully supported yet */ | |
| + /* enable local move so DWM can show snap layouts on drag-to-edge */ | |
| weston_config_section_get_bool(section, | |
| - "local-move", &is_localmove_supported, false); | |
| + "local-move", &is_localmove_supported, true); | |
| is_localmove_supported = read_rdpshell_config_bool( | |
| "WESTON_RDPRAIL_SHELL_LOCAL_MOVE", is_localmove_supported); | |
| shell->is_localmove_supported = is_localmove_supported; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment