Skip to content

Instantly share code, notes, and snippets.

@GGPrompts
Last active April 15, 2026 19:08
Show Gist options
  • Select an option

  • Save GGPrompts/06a227f12c2336d54dd0ea7c655fe059 to your computer and use it in GitHub Desktop.

Select an option

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)
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);
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