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 NULL / activeWindowId 0 if absent). */
56 const BOOL hasZOrder = (flags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER) && monitoredDesktop &&
57 monitoredDesktop->numWindowIds > 0 && monitoredDesktop->windowIds;
58 const BOOL hasActive = (flags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND) && monitoredDesktop;
59
60 if (hasZOrder || hasActive)
61 {
62 JNIEnv* env = NULL;
63 jboolean attached = jni_attach_thread(&env);
64 if (env)
65 {
66 jlongArray arr = NULL;
67 if (hasZOrder)
68 {
69 const jsize n = (jsize)monitoredDesktop->numWindowIds;
70 arr = (*env)->NewLongArray(env, n);
71 if (arr)
72 {
73 jlong* tmp = (*env)->GetLongArrayElements(env, arr, NULL);
74 if (tmp)
75 {
76 for (jsize i = 0; i < n; i++)
77 tmp[i] = (jlong)monitoredDesktop->windowIds[i];
78 (*env)->ReleaseLongArrayElements(env, arr, tmp, 0);
79 }
80 }
81 }
82 const jlong activeWindowId = hasActive ? (jlong)monitoredDesktop->activeWindowId : 0;
83 freerdp_callback("OnRailMonitoredDesktop", "(J[JJ)V", (jlong)context->instance, arr,
84 activeWindowId);
85 if (arr)
86 (*env)->DeleteLocalRef(env, arr);
87 }
88 if (attached)
89 jni_detach_thread();
90 }
91
92 return TRUE;
93}
94
95static UINT android_rail_server_system_param(RailClientContext* rail,
96 const RAIL_SYSPARAM_ORDER* sysparam)
97{
98 if (!rail || !sysparam)
99 return ERROR_INVALID_PARAMETER;
100
101 return CHANNEL_RC_OK;
102}
103
104static UINT android_rail_server_execute_result(RailClientContext* rail,
105 const RAIL_EXEC_RESULT_ORDER* execResult)
106{
107 if (!rail || !execResult)
108 return ERROR_INVALID_PARAMETER;
109
110 if (execResult->execResult != 0)
111 WLog_ERR(TAG, "RAIL execute failed: execResult=0x%04X rawResult=0x%08X",
112 execResult->execResult, execResult->rawResult);
113 else
114 WLog_DBG(TAG, "RAIL execute success");
115
116 return CHANNEL_RC_OK;
117}
118
119static UINT android_rail_server_local_move_size(RailClientContext* rail,
120 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
121{
122 if (!rail || !localMoveSize)
123 return ERROR_INVALID_PARAMETER;
124
125 WLog_DBG(TAG, "RAIL window 0x%08X %s pos=(%d,%d)", localMoveSize->windowId,
126 localMoveSize->isMoveSizeStart ? "move/size start" : "move/size end",
127 localMoveSize->posX, localMoveSize->posY);
128
129 if (!localMoveSize->isMoveSizeStart)
130 {
131 androidContext* afc = (androidContext*)rail->custom;
132 rdpContext* context = (rdpContext*)afc;
133 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
134 (jlong)localMoveSize->windowId, (jint)localMoveSize->posX,
135 (jint)localMoveSize->posY, (jint)-1, (jint)-1);
136 }
137 return CHANNEL_RC_OK;
138}
139
140static UINT android_rail_server_min_max_info(RailClientContext* rail,
141 const RAIL_MINMAXINFO_ORDER* minMaxInfo)
142{
143 if (!rail || !minMaxInfo)
144 return ERROR_INVALID_PARAMETER;
145
146 return CHANNEL_RC_OK;
147}
148
149static UINT android_rail_server_z_order_sync(RailClientContext* rail,
150 const RAIL_ZORDER_SYNC* zOrderSync)
151{
152 if (!rail || !zOrderSync)
153 return ERROR_INVALID_PARAMETER;
154
155 return CHANNEL_RC_OK;
156}
157
158static UINT android_rail_server_cloak(RailClientContext* rail, const RAIL_CLOAK* cloak)
159{
160 if (!rail || !cloak)
161 return ERROR_INVALID_PARAMETER;
162
163 WLog_DBG(TAG, "RAIL window 0x%08X %s", cloak->windowId, cloak->cloak ? "cloaked" : "uncloaked");
164 return CHANNEL_RC_OK;
165}
166
167static UINT android_rail_server_language_bar_info(RailClientContext* rail,
168 const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
169{
170 if (!rail || !langBarInfo)
171 return ERROR_INVALID_PARAMETER;
172
173 return CHANNEL_RC_OK;
174}
175
176static UINT android_rail_server_get_appid_response(RailClientContext* rail,
177 const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
178{
179 if (!rail || !getAppIdResp)
180 return ERROR_INVALID_PARAMETER;
181
182 return CHANNEL_RC_OK;
183}
184
185static BOOL android_rail_non_monitored_desktop(rdpContext* context,
186 const WINDOW_ORDER_INFO* orderInfo)
187{
188 if (!context)
189 return FALSE;
190
191 freerdp_callback("OnRailSessionEnd", "(J)V", (jlong)context->instance);
192 return TRUE;
193}
194
195static BOOL android_rail_window_state(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
196 const WINDOW_STATE_ORDER* windowState)
197{
198 if (!context || !orderInfo || !windowState)
199 return FALSE;
200
201 const UINT32 flags = orderInfo->fieldFlags;
202 const BOOL isNew = (flags & WINDOW_ORDER_STATE_NEW) != 0;
203 const BOOL isHidden =
204 (flags & WINDOW_ORDER_FIELD_SHOW) && windowState->showState == WINDOW_HIDE;
205
206 if (isHidden)
207 {
208 freerdp_callback("OnRailWindowHide", "(JJ)V", (jlong)context->instance,
209 (jlong)orderInfo->windowId);
210 return TRUE;
211 }
212
213 const BOOL isShownAgain = !isNew && (flags & WINDOW_ORDER_FIELD_SHOW) && !isHidden &&
214 !(flags & WINDOW_ORDER_FIELD_WND_OFFSET);
215 if (isShownAgain)
216 {
217 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
218 (jlong)orderInfo->windowId, (jint)-1, (jint)-1, (jint)-1, (jint)-1);
219 return TRUE;
220 }
221
222 if (isNew)
223 {
224 const BOOL hasOffset = (flags & WINDOW_ORDER_FIELD_WND_OFFSET) != 0;
225 const BOOL hasOffsetAndSize = hasOffset && (flags & WINDOW_ORDER_FIELD_WND_SIZE) != 0;
226 const INT32 x = hasOffset ? windowState->windowOffsetX : 0;
227 const INT32 y = hasOffset ? windowState->windowOffsetY : 0;
228 const INT32 w = hasOffsetAndSize ? (INT32)windowState->windowWidth : -1;
229 const INT32 h = hasOffsetAndSize ? (INT32)windowState->windowHeight : -1;
230 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
231 (jlong)orderInfo->windowId, (jint)x, (jint)y, (jint)w, (jint)h);
232 }
233 else if (flags & WINDOW_ORDER_FIELD_WND_OFFSET)
234 {
235 const BOOL hasSize = (flags & WINDOW_ORDER_FIELD_WND_SIZE) != 0;
236 freerdp_callback("OnRailWindowMove", "(JJIIII)V", (jlong)context->instance,
237 (jlong)orderInfo->windowId, (jint)windowState->windowOffsetX,
238 (jint)windowState->windowOffsetY,
239 hasSize ? (jint)windowState->windowWidth : (jint)-1,
240 hasSize ? (jint)windowState->windowHeight : (jint)-1);
241 }
242 return TRUE;
243}
244
245static BOOL android_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
246{
247 if (!context || !orderInfo)
248 return FALSE;
249
250 freerdp_callback("OnRailWindowDestroy", "(JJ)V", (jlong)context->instance,
251 (jlong)orderInfo->windowId);
252 return TRUE;
253}
254
255BOOL android_rail_init(androidContext* afc, RailClientContext* rail)
256{
257 if (!afc || !rail)
258 return FALSE;
259
260 afc->rail = rail;
261 afc->railExecSent = FALSE;
262 rail->custom = (void*)afc;
263 rail->ServerSystemParam = android_rail_server_system_param;
264 rail->ServerExecuteResult = android_rail_server_execute_result;
265 rail->ServerLocalMoveSize = android_rail_server_local_move_size;
266 rail->ServerMinMaxInfo = android_rail_server_min_max_info;
267 rail->ServerZOrderSync = android_rail_server_z_order_sync;
268 rail->ServerCloak = android_rail_server_cloak;
269 rail->ServerLanguageBarInfo = android_rail_server_language_bar_info;
270 rail->ServerGetAppIdResponse = android_rail_server_get_appid_response;
271
272 rdpContext* context = (rdpContext*)afc;
273 context->update->window->MonitoredDesktop = android_rail_monitored_desktop;
274 context->update->window->NonMonitoredDesktop = android_rail_non_monitored_desktop;
275 context->update->window->WindowCreate = android_rail_window_state;
276 context->update->window->WindowUpdate = android_rail_window_state;
277 context->update->window->WindowDelete = android_rail_window_delete;
278 return TRUE;
279}
280
281BOOL android_rail_uninit(androidContext* afc, RailClientContext* rail)
282{
283 if (!afc || !rail)
284 return FALSE;
285
286 rail->custom = NULL;
287 afc->rail = NULL;
288 afc->railExecSent = FALSE;
289 return TRUE;
290}
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.