FreeRDP
Loading...
Searching...
No Matches
android_rail.c
1/*
2 Android RAIL/RemoteApp Channel
3
4 Copyright 2026 Ibrahim Sevinc <ibrahim.sevinc.mail@gmail.com>
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9*/
10
11#include <freerdp/config.h>
12
13#include <stdlib.h>
14
15#include <jni.h>
16
17#include <freerdp/client/rail.h>
18#include <freerdp/settings.h>
19#include <freerdp/window.h>
20
21#include "android_jni_callback.h"
22#include "android_rail.h"
23
24#define TAG CLIENT_TAG("android.rail")
25
26static BOOL android_rail_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
27 const MONITORED_DESKTOP_ORDER* monitoredDesktop)
28{
29 if (!context || !orderInfo)
30 return FALSE;
31
32 const UINT32 flags = orderInfo->fieldFlags;
33
34 if (flags & WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED)
35 {
36 androidContext* afc = (androidContext*)context;
37 if (afc->rail && !afc->railExecSent)
38 {
39 const char* app =
40 freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram);
41 if (app && *app)
42 {
43 afc->railExecSent = TRUE;
44 WLog_DBG(TAG, "RAIL ARC_COMPLETED -> sending exec");
45 UINT rc = client_rail_server_start_cmd(afc->rail);
46 if (rc != CHANNEL_RC_OK)
47 {
48 afc->railExecSent = FALSE;
49 WLog_ERR(TAG, "RAIL start cmd failed after ARC_COMPLETED: %u", rc);
50 }
51 }
52 }
53 }
54
55 /* Forward z-order and active window together (windowIds nullptr / activeWindowId 0 if absent).
56 */
57 const BOOL hasZOrder = (flags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER) && monitoredDesktop &&
58 monitoredDesktop->numWindowIds > 0 && monitoredDesktop->windowIds;
59 const BOOL hasActive = (flags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND) && monitoredDesktop;
60
61 if (hasZOrder || hasActive)
62 {
63 JNIEnv* env = nullptr;
64 jboolean attached = jni_attach_thread(&env);
65 if (env)
66 {
67 jlongArray arr = nullptr;
68 if (hasZOrder)
69 {
70 const jsize n = (jsize)monitoredDesktop->numWindowIds;
71 arr = (*env)->NewLongArray(env, n);
72 if (arr)
73 {
74 jlong* tmp = (*env)->GetLongArrayElements(env, arr, nullptr);
75 if (tmp)
76 {
77 for (jsize i = 0; i < n; i++)
78 tmp[i] = (jlong)monitoredDesktop->windowIds[i];
79 (*env)->ReleaseLongArrayElements(env, arr, tmp, 0);
80 }
81 }
82 }
83 const jlong activeWindowId = hasActive ? (jlong)monitoredDesktop->activeWindowId : 0;
84 freerdp_callback("OnRailMonitoredDesktop", "(J[JJ)V", (jlong)context->instance, arr,
85 activeWindowId);
86 if (arr)
87 (*env)->DeleteLocalRef(env, arr);
88 }
89 if (attached)
90 jni_detach_thread();
91 }
92
93 return TRUE;
94}
95
96static UINT android_rail_server_system_param(RailClientContext* rail,
97 const RAIL_SYSPARAM_ORDER* sysparam)
98{
99 if (!rail || !sysparam)
100 return ERROR_INVALID_PARAMETER;
101
102 return CHANNEL_RC_OK;
103}
104
105static UINT android_rail_server_execute_result(RailClientContext* rail,
106 const RAIL_EXEC_RESULT_ORDER* execResult)
107{
108 if (!rail || !execResult)
109 return ERROR_INVALID_PARAMETER;
110
111 if (execResult->execResult != 0)
112 WLog_ERR(TAG, "RAIL execute failed: execResult=0x%04X rawResult=0x%08X",
113 execResult->execResult, execResult->rawResult);
114 else
115 WLog_DBG(TAG, "RAIL execute success");
116
117 return CHANNEL_RC_OK;
118}
119
120static UINT android_rail_server_local_move_size(RailClientContext* rail,
121 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
122{
123 if (!rail || !localMoveSize)
124 return ERROR_INVALID_PARAMETER;
125
126 WLog_DBG(TAG, "RAIL window 0x%08X %s pos=(%d,%d)", localMoveSize->windowId,
127 localMoveSize->isMoveSizeStart ? "move/size start" : "move/size end",
128 localMoveSize->posX, localMoveSize->posY);
129
130 if (!localMoveSize->isMoveSizeStart)
131 {
132 androidContext* afc = (androidContext*)rail->custom;
133 rdpContext* context = (rdpContext*)afc;
134 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
135 (jlong)localMoveSize->windowId, (jint)localMoveSize->posX,
136 (jint)localMoveSize->posY, (jint)-1, (jint)-1);
137 }
138 return CHANNEL_RC_OK;
139}
140
141static UINT android_rail_server_min_max_info(RailClientContext* rail,
142 const RAIL_MINMAXINFO_ORDER* minMaxInfo)
143{
144 if (!rail || !minMaxInfo)
145 return ERROR_INVALID_PARAMETER;
146
147 return CHANNEL_RC_OK;
148}
149
150static UINT android_rail_server_z_order_sync(RailClientContext* rail,
151 const RAIL_ZORDER_SYNC* zOrderSync)
152{
153 if (!rail || !zOrderSync)
154 return ERROR_INVALID_PARAMETER;
155
156 return CHANNEL_RC_OK;
157}
158
159static UINT android_rail_server_cloak(RailClientContext* rail, const RAIL_CLOAK* cloak)
160{
161 if (!rail || !cloak)
162 return ERROR_INVALID_PARAMETER;
163
164 WLog_DBG(TAG, "RAIL window 0x%08X %s", cloak->windowId, cloak->cloak ? "cloaked" : "uncloaked");
165 return CHANNEL_RC_OK;
166}
167
168static UINT android_rail_server_language_bar_info(RailClientContext* rail,
169 const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
170{
171 if (!rail || !langBarInfo)
172 return ERROR_INVALID_PARAMETER;
173
174 return CHANNEL_RC_OK;
175}
176
177static UINT android_rail_server_get_appid_response(RailClientContext* rail,
178 const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
179{
180 if (!rail || !getAppIdResp)
181 return ERROR_INVALID_PARAMETER;
182
183 return CHANNEL_RC_OK;
184}
185
186static BOOL android_rail_non_monitored_desktop(rdpContext* context,
187 const WINDOW_ORDER_INFO* orderInfo)
188{
189 if (!context)
190 return FALSE;
191
192 freerdp_callback("OnRailSessionEnd", "(J)V", (jlong)context->instance);
193 return TRUE;
194}
195
196static BOOL android_rail_window_state(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
197 const WINDOW_STATE_ORDER* windowState)
198{
199 if (!context || !orderInfo || !windowState)
200 return FALSE;
201
202 const UINT32 flags = orderInfo->fieldFlags;
203 const BOOL isNew = (flags & WINDOW_ORDER_STATE_NEW) != 0;
204 const BOOL isHidden =
205 (flags & WINDOW_ORDER_FIELD_SHOW) && windowState->showState == WINDOW_HIDE;
206
207 if (isHidden)
208 {
209 freerdp_callback("OnRailWindowHide", "(JJ)V", (jlong)context->instance,
210 (jlong)orderInfo->windowId);
211 return TRUE;
212 }
213
214 const BOOL isShownAgain = !isNew && (flags & WINDOW_ORDER_FIELD_SHOW) && !isHidden &&
215 !(flags & WINDOW_ORDER_FIELD_WND_OFFSET);
216 if (isShownAgain)
217 {
218 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
219 (jlong)orderInfo->windowId, (jint)-1, (jint)-1, (jint)-1, (jint)-1);
220 return TRUE;
221 }
222
223 if (isNew)
224 {
225 const BOOL hasOffset = (flags & WINDOW_ORDER_FIELD_WND_OFFSET) != 0;
226 const BOOL hasOffsetAndSize = hasOffset && (flags & WINDOW_ORDER_FIELD_WND_SIZE) != 0;
227 const INT32 x = hasOffset ? windowState->windowOffsetX : 0;
228 const INT32 y = hasOffset ? windowState->windowOffsetY : 0;
229 const INT32 w = hasOffsetAndSize ? (INT32)windowState->windowWidth : -1;
230 const INT32 h = hasOffsetAndSize ? (INT32)windowState->windowHeight : -1;
231 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
232 (jlong)orderInfo->windowId, (jint)x, (jint)y, (jint)w, (jint)h);
233 }
234 else if (flags & WINDOW_ORDER_FIELD_WND_OFFSET)
235 {
236 const BOOL hasSize = (flags & WINDOW_ORDER_FIELD_WND_SIZE) != 0;
237 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
238 (jlong)orderInfo->windowId, (jint)windowState->windowOffsetX,
239 (jint)windowState->windowOffsetY,
240 hasSize ? (jint)windowState->windowWidth : (jint)-1,
241 hasSize ? (jint)windowState->windowHeight : (jint)-1);
242 }
243 return TRUE;
244}
245
246static BOOL android_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
247{
248 if (!context || !orderInfo)
249 return FALSE;
250
251 freerdp_callback("OnRailWindowDestroy", "(JJ)V", (jlong)context->instance,
252 (jlong)orderInfo->windowId);
253 return TRUE;
254}
255
256BOOL android_rail_init(androidContext* afc, RailClientContext* rail)
257{
258 if (!afc || !rail)
259 return FALSE;
260
261 afc->rail = rail;
262 afc->railExecSent = FALSE;
263 rail->custom = (void*)afc;
264 rail->ServerSystemParam = android_rail_server_system_param;
265 rail->ServerExecuteResult = android_rail_server_execute_result;
266 rail->ServerLocalMoveSize = android_rail_server_local_move_size;
267 rail->ServerMinMaxInfo = android_rail_server_min_max_info;
268 rail->ServerZOrderSync = android_rail_server_z_order_sync;
269 rail->ServerCloak = android_rail_server_cloak;
270 rail->ServerLanguageBarInfo = android_rail_server_language_bar_info;
271 rail->ServerGetAppIdResponse = android_rail_server_get_appid_response;
272
273 rdpContext* context = (rdpContext*)afc;
274 context->update->window->MonitoredDesktop = android_rail_monitored_desktop;
275 context->update->window->NonMonitoredDesktop = android_rail_non_monitored_desktop;
276 context->update->window->WindowCreate = android_rail_window_state;
277 context->update->window->WindowUpdate = android_rail_window_state;
278 context->update->window->WindowDelete = android_rail_window_delete;
279 return TRUE;
280}
281
282BOOL android_rail_uninit(androidContext* afc, RailClientContext* rail)
283{
284 if (!afc || !rail)
285 return FALSE;
286
287 rail->custom = nullptr;
288 afc->rail = nullptr;
289 afc->railExecSent = FALSE;
290 return TRUE;
291}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.