Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,34 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Android Clipboard Redirection
*
* Copyright 2013 Felix Long
* Copyright 2023 Iordan Iordanov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CLIENT_IOS_CLIPRDR_H
#define FREERDP_CLIENT_IOS_CLIPRDR_H
#include <freerdp/client/cliprdr.h>
#include <freerdp/api.h>
#include "ios_freerdp.h"
FREERDP_LOCAL UINT ios_cliprdr_send_client_format_list(CliprdrClientContext* cliprdr);
FREERDP_LOCAL BOOL ios_cliprdr_init(mfContext* context, CliprdrClientContext* cliprdr);
FREERDP_LOCAL BOOL ios_cliprdr_uninit(mfContext* context, CliprdrClientContext* cliprdr);
#endif /* FREERDP_CLIENT_IOS_CLIPRDR_H */

View File

@@ -0,0 +1,499 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Android Clipboard Redirection
*
* Copyright 2013 Felix Long
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2023 Iordan Iordanov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <winpr/clipboard.h>
#include <freerdp/client/channels.h>
#include <freerdp/client/cliprdr.h>
#include "ios_cliprdr.h"
UINT ios_cliprdr_send_client_format_list(CliprdrClientContext *cliprdr)
{
UINT rc = ERROR_INTERNAL_ERROR;
UINT32 formatId;
UINT32 numFormats;
UINT32 *pFormatIds;
const char *formatName;
CLIPRDR_FORMAT *formats;
CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT;
if (!cliprdr)
return ERROR_INVALID_PARAMETER;
mfContext *afc = (mfContext *)cliprdr->custom;
if (!afc || !afc->cliprdr)
return ERROR_INVALID_PARAMETER;
pFormatIds = nullptr;
numFormats = ClipboardGetFormatIds(afc->clipboard, &pFormatIds);
formats = (CLIPRDR_FORMAT *)calloc(numFormats, sizeof(CLIPRDR_FORMAT));
if (!formats)
goto fail;
for (UINT32 index = 0; index < numFormats; index++)
{
formatId = pFormatIds[index];
formatName = ClipboardGetFormatName(afc->clipboard, formatId);
formats[index].formatId = formatId;
formats[index].formatName = nullptr;
if ((formatId > CF_MAX) && formatName)
{
formats[index].formatName = _strdup(formatName);
if (!formats[index].formatName)
goto fail;
}
}
formatList.common.msgFlags = 0;
formatList.numFormats = numFormats;
formatList.formats = formats;
formatList.common.msgType = CB_FORMAT_LIST;
if (!afc->cliprdr->ClientFormatList)
goto fail;
rc = afc->cliprdr->ClientFormatList(afc->cliprdr, &formatList);
fail:
free(pFormatIds);
free(formats);
return rc;
}
static UINT ios_cliprdr_send_client_format_data_request(CliprdrClientContext *cliprdr,
UINT32 formatId)
{
UINT rc = ERROR_INVALID_PARAMETER;
CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = WINPR_C_ARRAY_INIT;
mfContext *afc;
if (!cliprdr)
goto fail;
afc = (mfContext *)cliprdr->custom;
if (!afc || !afc->clipboardRequestEvent || !cliprdr->ClientFormatDataRequest)
goto fail;
formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
formatDataRequest.common.msgFlags = 0;
formatDataRequest.requestedFormatId = formatId;
afc->requestedFormatId = formatId;
(void)ResetEvent(afc->clipboardRequestEvent);
rc = cliprdr->ClientFormatDataRequest(cliprdr, &formatDataRequest);
fail:
return rc;
}
static UINT ios_cliprdr_send_client_capabilities(CliprdrClientContext *cliprdr)
{
CLIPRDR_CAPABILITIES capabilities;
CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
if (!cliprdr || !cliprdr->ClientCapabilities)
return ERROR_INVALID_PARAMETER;
capabilities.cCapabilitiesSets = 1;
capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
generalCapabilitySet.capabilitySetLength = 12;
generalCapabilitySet.version = CB_CAPS_VERSION_2;
generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
return cliprdr->ClientCapabilities(cliprdr, &capabilities);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT ios_cliprdr_monitor_ready(CliprdrClientContext *cliprdr,
const CLIPRDR_MONITOR_READY *monitorReady)
{
UINT rc;
mfContext *afc;
if (!cliprdr || !monitorReady)
return ERROR_INVALID_PARAMETER;
afc = (mfContext *)cliprdr->custom;
if (!afc)
return ERROR_INVALID_PARAMETER;
if ((rc = ios_cliprdr_send_client_capabilities(cliprdr)) != CHANNEL_RC_OK)
return rc;
if ((rc = ios_cliprdr_send_client_format_list(cliprdr)) != CHANNEL_RC_OK)
return rc;
afc->clipboardSync = TRUE;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT ios_cliprdr_server_capabilities(CliprdrClientContext *cliprdr,
const CLIPRDR_CAPABILITIES *capabilities)
{
CLIPRDR_CAPABILITY_SET *capabilitySet;
mfContext *afc;
if (!cliprdr || !capabilities)
return ERROR_INVALID_PARAMETER;
afc = (mfContext *)cliprdr->custom;
if (!afc)
return ERROR_INVALID_PARAMETER;
for (UINT32 index = 0; index < capabilities->cCapabilitiesSets; index++)
{
capabilitySet = &(capabilities->capabilitySets[index]);
if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) &&
(capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN))
{
CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet =
(CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySet;
afc->clipboardCapabilities = generalCapabilitySet->generalFlags;
break;
}
}
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT ios_cliprdr_server_format_list(CliprdrClientContext *cliprdr,
const CLIPRDR_FORMAT_LIST *formatList)
{
UINT rc;
CLIPRDR_FORMAT *format;
mfContext *afc;
if (!cliprdr || !formatList)
return ERROR_INVALID_PARAMETER;
afc = (mfContext *)cliprdr->custom;
if (!afc)
return ERROR_INVALID_PARAMETER;
if (afc->serverFormats)
{
for (UINT32 index = 0; index < afc->numServerFormats; index++)
free(afc->serverFormats[index].formatName);
free(afc->serverFormats);
afc->serverFormats = nullptr;
afc->numServerFormats = 0;
}
if (formatList->numFormats < 1)
return CHANNEL_RC_OK;
afc->numServerFormats = formatList->numFormats;
afc->serverFormats = (CLIPRDR_FORMAT *)calloc(afc->numServerFormats, sizeof(CLIPRDR_FORMAT));
if (!afc->serverFormats)
return CHANNEL_RC_NO_MEMORY;
for (UINT32 index = 0; index < afc->numServerFormats; index++)
{
afc->serverFormats[index].formatId = formatList->formats[index].formatId;
afc->serverFormats[index].formatName = nullptr;
if (formatList->formats[index].formatName)
{
afc->serverFormats[index].formatName = _strdup(formatList->formats[index].formatName);
if (!afc->serverFormats[index].formatName)
return CHANNEL_RC_NO_MEMORY;
}
}
BOOL unicode = FALSE;
BOOL text = FALSE;
for (UINT32 index = 0; index < afc->numServerFormats; index++)
{
format = &(afc->serverFormats[index]);
if (format->formatId == CF_UNICODETEXT)
unicode = TRUE;
else if (format->formatId == CF_TEXT)
text = TRUE;
}
if (unicode)
return ios_cliprdr_send_client_format_data_request(cliprdr, CF_UNICODETEXT);
if (text)
return ios_cliprdr_send_client_format_data_request(cliprdr, CF_TEXT);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_format_list_response(CliprdrClientContext *cliprdr,
const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
{
if (!cliprdr || !formatListResponse)
return ERROR_INVALID_PARAMETER;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_lock_clipboard_data(CliprdrClientContext *cliprdr,
const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData)
{
if (!cliprdr || !lockClipboardData)
return ERROR_INVALID_PARAMETER;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_unlock_clipboard_data(CliprdrClientContext *cliprdr,
const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData)
{
if (!cliprdr || !unlockClipboardData)
return ERROR_INVALID_PARAMETER;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_format_data_request(CliprdrClientContext *cliprdr,
const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
{
UINT rc;
BYTE *data;
UINT32 size;
UINT32 formatId;
CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT;
mfContext *afc;
if (!cliprdr || !formatDataRequest || !cliprdr->ClientFormatDataResponse)
return ERROR_INVALID_PARAMETER;
afc = (mfContext *)cliprdr->custom;
if (!afc)
return ERROR_INVALID_PARAMETER;
formatId = formatDataRequest->requestedFormatId;
data = (BYTE *)ClipboardGetData(afc->clipboard, formatId, &size);
response.common.msgFlags = CB_RESPONSE_OK;
response.common.dataLen = size;
response.requestedFormatData = data;
if (!data)
{
response.common.msgFlags = CB_RESPONSE_FAIL;
response.common.dataLen = 0;
response.requestedFormatData = nullptr;
}
rc = cliprdr->ClientFormatDataResponse(cliprdr, &response);
free(data);
return rc;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_format_data_response(CliprdrClientContext *cliprdr,
const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
{
BYTE *data;
UINT32 size;
UINT32 formatId;
CLIPRDR_FORMAT *format = nullptr;
mfContext *afc;
freerdp *instance;
if (!cliprdr || !formatDataResponse)
return ERROR_INVALID_PARAMETER;
afc = (mfContext *)cliprdr->custom;
if (!afc)
return ERROR_INVALID_PARAMETER;
instance = ((rdpContext *)afc)->instance;
if (!instance)
return ERROR_INVALID_PARAMETER;
for (UINT32 index = 0; index < afc->numServerFormats; index++)
{
if (afc->requestedFormatId == afc->serverFormats[index].formatId)
format = &(afc->serverFormats[index]);
}
if (!format)
{
(void)SetEvent(afc->clipboardRequestEvent);
return ERROR_INTERNAL_ERROR;
}
if (format->formatName)
formatId = ClipboardRegisterFormat(afc->clipboard, format->formatName);
else
formatId = format->formatId;
size = formatDataResponse->common.dataLen;
ClipboardLock(afc->clipboard);
if (!ClipboardSetData(afc->clipboard, formatId, formatDataResponse->requestedFormatData, size))
return ERROR_INTERNAL_ERROR;
(void)SetEvent(afc->clipboardRequestEvent);
if ((formatId == CF_TEXT) || (formatId == CF_UNICODETEXT))
{
formatId = ClipboardRegisterFormat(afc->clipboard, "UTF8_STRING");
data = (BYTE *)ClipboardGetData(afc->clipboard, formatId, &size);
size = (UINT32)strnlen(data, size);
if (afc->ServerCutText != nullptr)
{
afc->ServerCutText((rdpContext *)afc, (uint8_t *)data, size);
}
}
ClipboardUnlock(afc->clipboard);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
ios_cliprdr_server_file_contents_request(CliprdrClientContext *cliprdr,
const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
{
if (!cliprdr || !fileContentsRequest)
return ERROR_INVALID_PARAMETER;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT ios_cliprdr_server_file_contents_response(
CliprdrClientContext *cliprdr, const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
{
if (!cliprdr || !fileContentsResponse)
return ERROR_INVALID_PARAMETER;
return CHANNEL_RC_OK;
}
BOOL ios_cliprdr_init(mfContext *afc, CliprdrClientContext *cliprdr)
{
wClipboard *clipboard;
HANDLE hevent;
if (!afc || !cliprdr)
return FALSE;
if (!(hevent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
return FALSE;
if (!(clipboard = ClipboardCreate()))
{
(void)CloseHandle(hevent);
return FALSE;
}
afc->cliprdr = cliprdr;
afc->clipboard = clipboard;
afc->clipboardRequestEvent = hevent;
cliprdr->custom = (void *)afc;
cliprdr->MonitorReady = ios_cliprdr_monitor_ready;
cliprdr->ServerCapabilities = ios_cliprdr_server_capabilities;
cliprdr->ServerFormatList = ios_cliprdr_server_format_list;
cliprdr->ServerFormatListResponse = ios_cliprdr_server_format_list_response;
cliprdr->ServerLockClipboardData = ios_cliprdr_server_lock_clipboard_data;
cliprdr->ServerUnlockClipboardData = ios_cliprdr_server_unlock_clipboard_data;
cliprdr->ServerFormatDataRequest = ios_cliprdr_server_format_data_request;
cliprdr->ServerFormatDataResponse = ios_cliprdr_server_format_data_response;
cliprdr->ServerFileContentsRequest = ios_cliprdr_server_file_contents_request;
cliprdr->ServerFileContentsResponse = ios_cliprdr_server_file_contents_response;
return TRUE;
}
BOOL ios_cliprdr_uninit(mfContext *afc, CliprdrClientContext *cliprdr)
{
if (!afc || !cliprdr)
return FALSE;
cliprdr->custom = nullptr;
afc->cliprdr = nullptr;
ClipboardDestroy(afc->clipboard);
(void)CloseHandle(afc->clipboardRequestEvent);
return TRUE;
}

View File

@@ -0,0 +1,87 @@
/*
RDP run-loop
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
Copyright 2023 Iordan Iordanov
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import <CoreGraphics/CoreGraphics.h>
#import <freerdp/freerdp.h>
#import <freerdp/channels/channels.h>
#import "TSXTypes.h"
#import <winpr/clipboard.h>
#import <freerdp/client/cliprdr.h>
@class RDPSession, RDPSessionView;
typedef BOOL (*pServerCutText)(rdpContext *context, UINT8 *data, UINT32 size);
// FreeRDP extended structs
typedef struct mf_info mfInfo;
typedef struct mf_context
{
rdpContext _p;
mfInfo *mfi;
rdpSettings *settings;
BOOL clipboardSync;
wClipboard *clipboard;
UINT32 numServerFormats;
UINT32 requestedFormatId;
HANDLE clipboardRequestEvent;
CLIPRDR_FORMAT *serverFormats;
CliprdrClientContext *cliprdr;
UINT32 clipboardCapabilities;
WINPR_ATTR_NODISCARD pServerCutText ServerCutText;
} mfContext;
struct mf_info
{
// RDP
freerdp *instance;
mfContext *context;
rdpContext *_context;
// UI
RDPSession *session;
// Graphics
CGContextRef bitmap_context;
// Events
int event_pipe_producer;
int event_pipe_consumer;
HANDLE handle;
// Tracking connection state
volatile TSXConnectionState connection_state;
volatile BOOL
unwanted; // set when controlling Session no longer wants the connection to continue
};
#define MFI_FROM_INSTANCE(inst) (((mfContext *)((inst)->context))->mfi)
enum MF_EXIT_CODE
{
MF_EXIT_SUCCESS = 0,
MF_EXIT_CONN_FAILED = 128,
MF_EXIT_CONN_CANCELED = 129,
MF_EXIT_LOGON_TIMEOUT = 130,
MF_EXIT_UNKNOWN = 255
};
void ios_init_freerdp(void);
void ios_uninit_freerdp(void);
freerdp *ios_freerdp_new(void);
int ios_run_freerdp(freerdp *instance);
void ios_freerdp_free(freerdp *instance);
void ios_send_clipboard_data(void *context, const void *data, UINT32 size);

View File

@@ -0,0 +1,443 @@
/*
RDP run-loop
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include <winpr/assert.h>
#import <winpr/clipboard.h>
#import <freerdp/gdi/gdi.h>
#import <freerdp/channels/channels.h>
#import <freerdp/client/channels.h>
#import <freerdp/client/cmdline.h>
#import <freerdp/freerdp.h>
#import <freerdp/gdi/gfx.h>
#import <freerdp/client/cliprdr.h>
#import "ios_freerdp.h"
#import "ios_freerdp_ui.h"
#import "ios_freerdp_events.h"
#import "ios_cliprdr.h"
#import "RDPSession.h"
#import "Utils.h"
#include <errno.h>
#define TAG FREERDP_TAG("iOS")
#pragma mark Connection helpers
static void ios_OnChannelConnectedEventHandler(void *context, const ChannelConnectedEventArgs *e)
{
WLog_INFO(TAG, "ios_OnChannelConnectedEventHandler, channel %s", e->name);
rdpSettings *settings;
mfContext *afc;
if (!context || !e)
{
WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void *)e);
return;
}
afc = (mfContext *)context;
settings = afc->_p.settings;
if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
{
if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
{
gdi_graphics_pipeline_init(afc->_p.gdi, (RdpgfxClientContext *)e->pInterface);
}
else
{
WLog_WARN(TAG, "GFX without software GDI requested. "
" This is not supported, add /gdi:sw");
}
}
else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
{
ios_cliprdr_init(afc, (CliprdrClientContext *)e->pInterface);
}
}
static void ios_OnChannelDisconnectedEventHandler(void *context,
const ChannelDisconnectedEventArgs *e)
{
WLog_INFO(TAG, "ios_OnChannelConnectedEventHandler, channel %s", e->name);
rdpSettings *settings;
mfContext *afc;
if (!context || !e)
{
WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void *)e);
return;
}
afc = (mfContext *)context;
settings = afc->_p.settings;
if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
{
if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
{
gdi_graphics_pipeline_uninit(afc->_p.gdi, (RdpgfxClientContext *)e->pInterface);
}
else
{
WLog_WARN(TAG, "GFX without software GDI requested. "
" This is not supported, add /gdi:sw");
}
}
else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
{
ios_cliprdr_uninit(afc, (CliprdrClientContext *)e->pInterface);
}
}
static BOOL ios_pre_connect(freerdp *instance)
{
int rc;
rdpSettings *settings;
if (!instance || !instance->context)
return FALSE;
settings = instance->context->settings;
WINPR_ASSERT(settings);
const char *Password = freerdp_settings_get_string(settings, FreeRDP_Password);
if (!freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled,
Password && (Password && (strlen(Password) > 0))))
return FALSE;
// Verify screen width/height are sane
if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) < 64) ||
(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) < 64) ||
(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) > 4096) ||
(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) > 4096))
{
NSLog(@"%s: invalid dimensions %d %d", __func__,
freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
return FALSE;
}
rc = PubSub_SubscribeChannelConnected(instance->context->pubSub,
ios_OnChannelConnectedEventHandler);
if (rc != CHANNEL_RC_OK)
{
WLog_ERR(TAG, "Could not subscribe to connect event handler [%l08X]", rc);
return FALSE;
}
rc = PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
ios_OnChannelDisconnectedEventHandler);
if (rc != CHANNEL_RC_OK)
{
WLog_ERR(TAG, "Could not subscribe to disconnect event handler [%l08X]", rc);
return FALSE;
}
if (!freerdp_client_load_addins(instance->context->channels, settings))
{
WLog_ERR(TAG, "Failed to load addins [%l08X]", GetLastError());
return FALSE;
}
return TRUE;
}
static BOOL ios_Pointer_New(rdpContext *context, rdpPointer *pointer)
{
if (!context || !pointer || !context->gdi)
return FALSE;
return TRUE;
}
static void ios_Pointer_Free(rdpContext *context, rdpPointer *pointer)
{
if (!context || !pointer)
return;
}
static BOOL ios_Pointer_Set(rdpContext *context, rdpPointer *pointer)
{
if (!context)
return FALSE;
return TRUE;
}
static BOOL ios_Pointer_SetPosition(rdpContext *context, UINT32 x, UINT32 y)
{
if (!context)
return FALSE;
return TRUE;
}
static BOOL ios_Pointer_SetNull(rdpContext *context)
{
if (!context)
return FALSE;
return TRUE;
}
static BOOL ios_Pointer_SetDefault(rdpContext *context)
{
if (!context)
return FALSE;
return TRUE;
}
static BOOL ios_register_pointer(rdpGraphics *graphics)
{
rdpPointer pointer = WINPR_C_ARRAY_INIT;
if (!graphics)
return FALSE;
pointer.size = sizeof(pointer);
pointer.New = ios_Pointer_New;
pointer.Free = ios_Pointer_Free;
pointer.Set = ios_Pointer_Set;
pointer.SetNull = ios_Pointer_SetNull;
pointer.SetDefault = ios_Pointer_SetDefault;
pointer.SetPosition = ios_Pointer_SetPosition;
graphics_register_pointer(graphics, &pointer);
return TRUE;
}
static BOOL ios_post_connect(freerdp *instance)
{
mfInfo *mfi;
if (!instance)
return FALSE;
mfi = MFI_FROM_INSTANCE(instance);
if (!mfi)
return FALSE;
if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
return FALSE;
if (!ios_register_pointer(instance->context->graphics))
return FALSE;
ios_allocate_display_buffer(mfi);
instance->context->update->BeginPaint = ios_ui_begin_paint;
instance->context->update->EndPaint = ios_ui_end_paint;
instance->context->update->DesktopResize = ios_ui_resize_window;
[mfi->session performSelectorOnMainThread:@selector(sessionDidConnect)
withObject:nil
waitUntilDone:YES];
return TRUE;
}
static void ios_post_disconnect(freerdp *instance)
{
gdi_free(instance);
}
#pragma mark -
#pragma mark Running the connection
int ios_run_freerdp(freerdp *instance)
{
mfContext *context = (mfContext *)instance->context;
mfInfo *mfi = context->mfi;
rdpChannels *channels = instance->context->channels;
mfi->connection_state = TSXConnectionConnecting;
if (!freerdp_connect(instance))
{
NSLog(@"%s: inst->rdp_connect failed", __func__);
return mfi->unwanted ? MF_EXIT_CONN_CANCELED : MF_EXIT_CONN_FAILED;
}
if (mfi->unwanted)
return MF_EXIT_CONN_CANCELED;
mfi->connection_state = TSXConnectionConnected;
// Connection main loop
NSAutoreleasePool *pool;
while (!freerdp_shall_disconnect_context(instance->context))
{
DWORD status;
DWORD nCount = 0;
HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT;
pool = [[NSAutoreleasePool alloc] init];
nCount = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles));
if (nCount == 0)
{
NSLog(@"%s: freerdp_get_event_handles failed", __func__);
break;
}
handles[nCount++] = ios_events_get_handle(mfi);
status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
if (WAIT_FAILED == status)
{
NSLog(@"%s: WaitForMultipleObjects failed!", __func__);
break;
}
// Check the libfreerdp fds
if (!freerdp_check_event_handles(instance->context))
{
NSLog(@"%s: freerdp_check_event_handles failed.", __func__);
break;
}
// Check input event fds
if (ios_events_check_handle(mfi) != TRUE)
{
// This event will fail when the app asks for a disconnect.
// NSLog(@"%s: ios_events_check_fds failed: terminating connection.", __func__);
break;
}
[pool release];
pool = nil;
}
CGContextRelease(mfi->bitmap_context);
mfi->bitmap_context = nullptr;
mfi->connection_state = TSXConnectionDisconnected;
// Cleanup
freerdp_disconnect(instance);
gdi_free(instance);
cache_free(instance->context->cache);
[pool release];
pool = nil;
return MF_EXIT_SUCCESS;
}
#pragma mark -
#pragma mark Context callbacks
static BOOL ios_client_new(freerdp *instance, rdpContext *context)
{
mfContext *ctx = (mfContext *)context;
if (!instance || !context)
return FALSE;
if ((ctx->mfi = calloc(1, sizeof(mfInfo))) == nullptr)
return FALSE;
ctx->mfi->context = (mfContext *)context;
ctx->mfi->_context = context;
ctx->mfi->instance = instance;
if (!ios_events_create_pipe(ctx->mfi))
return FALSE;
instance->PreConnect = ios_pre_connect;
instance->PostConnect = ios_post_connect;
instance->PostDisconnect = ios_post_disconnect;
instance->Authenticate = ios_ui_authenticate;
instance->GatewayAuthenticate = ios_ui_gw_authenticate;
instance->VerifyCertificateEx = ios_ui_verify_certificate_ex;
instance->VerifyChangedCertificateEx = ios_ui_verify_changed_certificate_ex;
instance->LogonErrorInfo = nullptr;
return TRUE;
}
static void ios_client_free(freerdp *instance, rdpContext *context)
{
mfInfo *mfi;
if (!context)
return;
mfi = ((mfContext *)context)->mfi;
ios_events_free_pipe(mfi);
free(mfi);
}
static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS *pEntryPoints)
{
WINPR_ASSERT(pEntryPoints);
ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
pEntryPoints->GlobalInit = nullptr;
pEntryPoints->GlobalUninit = nullptr;
pEntryPoints->ContextSize = sizeof(mfContext);
pEntryPoints->ClientNew = ios_client_new;
pEntryPoints->ClientFree = ios_client_free;
pEntryPoints->ClientStart = nullptr;
pEntryPoints->ClientStop = nullptr;
return 0;
}
#pragma mark -
#pragma mark Initialization and cleanup
freerdp *ios_freerdp_new()
{
rdpContext *context;
RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
RdpClientEntry(&clientEntryPoints);
context = freerdp_client_context_new(&clientEntryPoints);
if (!context)
return nullptr;
return context->instance;
}
void ios_freerdp_free(freerdp *instance)
{
if (!instance || !instance->context)
return;
freerdp_client_context_free(instance->context);
}
void ios_init_freerdp()
{
signal(SIGPIPE, SIG_IGN);
}
void ios_uninit_freerdp()
{
}
/* compatibility functions */
size_t fwrite$UNIX2003(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
return fwrite(ptr, size, nmemb, stream);
}
void ios_send_clipboard_data(void *context, const void *data, UINT32 size)
{
mfContext *afc = (mfContext *)context;
ClipboardLock(afc->clipboard);
UINT32 formatId = ClipboardRegisterFormat(afc->clipboard, "UTF8_STRING");
if (size)
ClipboardSetData(afc->clipboard, formatId, data, size);
else
ClipboardEmpty(afc->clipboard);
ClipboardUnlock(afc->clipboard);
ios_cliprdr_send_client_format_list(afc->cliprdr);
}

View File

@@ -0,0 +1,27 @@
/*
RDP event queuing
Copyright 2013 Thincast Technologies GmbH, Author: Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#ifndef IOS_RDP_EVENT_H
#define IOS_RDP_EVENT_H
#import <Foundation/Foundation.h>
#import "ios_freerdp.h"
// For UI: use to send events
BOOL ios_events_send(mfInfo *mfi, NSDictionary *event_description);
// For connection runloop: use to poll for queued input events
HANDLE ios_events_get_handle(mfInfo *mfi);
BOOL ios_events_check_handle(mfInfo *mfi);
BOOL ios_events_create_pipe(mfInfo *mfi);
void ios_events_free_pipe(mfInfo *mfi);
#endif /* IOS_RDP_EVENT_H */

View File

@@ -0,0 +1,174 @@
/*
RDP event queuing
Copyright 2013 Thincast Technologies GmbH, Author: Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include <winpr/assert.h>
#include "ios_freerdp_events.h"
#pragma mark -
#pragma mark Sending compacted input events (from main thread)
// While this function may be called from any thread that has an autorelease pool allocated, it is
// not threadsafe: caller is responsible for synchronization
BOOL ios_events_send(mfInfo *mfi, NSDictionary *event_description)
{
NSData *encoded_description = [NSKeyedArchiver archivedDataWithRootObject:event_description];
WINPR_ASSERT(mfi);
if ([encoded_description length] > 32000 || (mfi->event_pipe_producer == -1))
return FALSE;
uint32_t archived_data_len = (uint32_t)[encoded_description length];
// NSLog(@"writing %d bytes to input event pipe", archived_data_len);
if (write(mfi->event_pipe_producer, &archived_data_len, 4) == -1)
{
NSLog(@"%s: Failed to write length descriptor to pipe.", __func__);
return FALSE;
}
if (write(mfi->event_pipe_producer, [encoded_description bytes], archived_data_len) == -1)
{
NSLog(@"%s: Failed to write %d bytes into the event queue (event type: %@).", __func__,
(int)[encoded_description length], [event_description objectForKey:@"type"]);
return FALSE;
}
return TRUE;
}
#pragma mark -
#pragma mark Processing compacted input events (from connection thread runloop)
static BOOL ios_events_handle_event(mfInfo *mfi, NSDictionary *event_description)
{
NSString *event_type = [event_description objectForKey:@"type"];
BOOL should_continue = TRUE;
rdpInput *input;
WINPR_ASSERT(mfi);
freerdp *instance = mfi->instance;
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
input = instance->context->input;
WINPR_ASSERT(input);
if ([event_type isEqualToString:@"mouse"])
{
input->MouseEvent(input, [[event_description objectForKey:@"flags"] unsignedShortValue],
[[event_description objectForKey:@"coord_x"] unsignedShortValue],
[[event_description objectForKey:@"coord_y"] unsignedShortValue]);
}
else if ([event_type isEqualToString:@"keyboard"])
{
if ([[event_description objectForKey:@"subtype"] isEqualToString:@"scancode"])
freerdp_input_send_keyboard_event(
input, [[event_description objectForKey:@"flags"] unsignedShortValue],
[[event_description objectForKey:@"scancode"] unsignedShortValue]);
else if ([[event_description objectForKey:@"subtype"] isEqualToString:@"unicode"])
freerdp_input_send_unicode_keyboard_event(
input, [[event_description objectForKey:@"flags"] unsignedShortValue],
[[event_description objectForKey:@"unicode_char"] unsignedShortValue]);
else
NSLog(@"%s: doesn't know how to send keyboard input with subtype %@", __func__,
[event_description objectForKey:@"subtype"]);
}
else if ([event_type isEqualToString:@"disconnect"])
should_continue = FALSE;
else
NSLog(@"%s: unrecognized event type: %@", __func__, event_type);
return should_continue;
}
BOOL ios_events_check_handle(mfInfo *mfi)
{
WINPR_ASSERT(mfi);
if (WaitForSingleObject(mfi->handle, 0) != WAIT_OBJECT_0)
return TRUE;
if (mfi->event_pipe_consumer == -1)
return TRUE;
uint32_t archived_data_length = 0;
ssize_t bytes_read;
// First, read the length of the blob
bytes_read = read(mfi->event_pipe_consumer, &archived_data_length, 4);
if (bytes_read == -1 || archived_data_length < 1 || archived_data_length > 32000)
{
NSLog(@"%s: just read length descriptor. bytes_read=%ld, archived_data_length=%u", __func__,
bytes_read, archived_data_length);
return FALSE;
}
// NSLog(@"reading %d bytes from input event pipe", archived_data_length);
NSMutableData *archived_object_data =
[[NSMutableData alloc] initWithLength:archived_data_length];
bytes_read =
read(mfi->event_pipe_consumer, [archived_object_data mutableBytes], archived_data_length);
if (bytes_read != archived_data_length)
{
NSLog(@"%s: attempted to read data; read %ld bytes but wanted %d bytes.", __func__,
bytes_read, archived_data_length);
[archived_object_data release];
return FALSE;
}
id unarchived_object_data = [NSKeyedUnarchiver unarchiveObjectWithData:archived_object_data];
[archived_object_data release];
return ios_events_handle_event(mfi, unarchived_object_data);
}
HANDLE ios_events_get_handle(mfInfo *mfi)
{
WINPR_ASSERT(mfi);
return mfi->handle;
}
// Sets up the event pipe
BOOL ios_events_create_pipe(mfInfo *mfi)
{
int pipe_fds[2];
WINPR_ASSERT(mfi);
if (pipe(pipe_fds) == -1)
{
NSLog(@"%s: pipe failed.", __func__);
return FALSE;
}
mfi->event_pipe_consumer = pipe_fds[0];
mfi->event_pipe_producer = pipe_fds[1];
mfi->handle = CreateFileDescriptorEvent(nullptr, FALSE, FALSE, mfi->event_pipe_consumer,
WINPR_FD_READ | WINPR_FD_WRITE);
return TRUE;
}
void ios_events_free_pipe(mfInfo *mfi)
{
WINPR_ASSERT(mfi);
int consumer_fd = mfi->event_pipe_consumer, producer_fd = mfi->event_pipe_producer;
mfi->event_pipe_consumer = mfi->event_pipe_producer = -1;
close(producer_fd);
close(consumer_fd);
(void)CloseHandle(mfi->handle);
}

View File

@@ -0,0 +1,29 @@
/*
RDP ui callbacks
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import "ios_freerdp.h"
BOOL ios_ui_begin_paint(rdpContext* context);
BOOL ios_ui_end_paint(rdpContext* context);
BOOL ios_ui_resize_window(rdpContext* context);
BOOL ios_ui_authenticate(freerdp* instance, char** username, char** password, char** domain);
BOOL ios_ui_gw_authenticate(freerdp* instance, char** username, char** password, char** domain);
DWORD ios_ui_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject, const char* issuer,
const char* fingerprint, DWORD flags);
DWORD ios_ui_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags);
void ios_allocate_display_buffer(mfInfo* mfi);

View File

@@ -0,0 +1,241 @@
/*
RDP ui callbacks
Copyright 2013 Thincast Technologies GmbH, Authors: Martin Fleisz, Dorian Johnson
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import <Foundation/Foundation.h>
#import <freerdp/gdi/gdi.h>
#import "ios_freerdp_ui.h"
#import "RDPSession.h"
#pragma mark -
#pragma mark Certificate authentication
static void ios_resize_display_buffer(mfInfo *mfi);
static BOOL ios_ui_authenticate_raw(freerdp *instance, char **username, char **password,
char **domain, const char *title)
{
mfInfo *mfi = MFI_FROM_INSTANCE(instance);
NSMutableDictionary *params = [NSMutableDictionary
dictionaryWithObjectsAndKeys:(*username) ? [NSString stringWithUTF8String:*username] : @"",
@"username",
(*password) ? [NSString stringWithUTF8String:*password] : @"",
@"password",
(*domain) ? [NSString stringWithUTF8String:*domain] : @"",
@"domain",
[NSString stringWithUTF8String:freerdp_settings_get_string(
instance->context->settings,
FreeRDP_ServerHostname)],
@"hostname", // used for the auth prompt message; not changed
nil];
// request auth UI
[mfi->session performSelectorOnMainThread:@selector(sessionRequestsAuthenticationWithParams:)
withObject:params
waitUntilDone:YES];
// wait for UI request to be completed
[[mfi->session uiRequestCompleted] lock];
[[mfi->session uiRequestCompleted] wait];
[[mfi->session uiRequestCompleted] unlock];
if (![[params valueForKey:@"result"] boolValue])
{
mfi->unwanted = YES;
return FALSE;
}
// Free old values
free(*username);
free(*password);
free(*domain);
// set values back
*username = _strdup([[params objectForKey:@"username"] UTF8String]);
*password = _strdup([[params objectForKey:@"password"] UTF8String]);
*domain = _strdup([[params objectForKey:@"domain"] UTF8String]);
if (!(*username) || !(*password) || !(*domain))
{
free(*username);
free(*password);
free(*domain);
return FALSE;
}
return TRUE;
}
BOOL ios_ui_authenticate(freerdp *instance, char **username, char **password, char **domain)
{
return ios_ui_authenticate_raw(instance, username, password, domain, "");
}
BOOL ios_ui_gw_authenticate(freerdp *instance, char **username, char **password, char **domain)
{
return ios_ui_authenticate_raw(instance, username, password, domain, "gateway");
}
DWORD ios_ui_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port,
const char *common_name, const char *subject, const char *issuer,
const char *fingerprint, DWORD flags)
{
// check whether we accept all certificates
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"security.accept_certificates"] == YES)
return 2;
mfInfo *mfi = MFI_FROM_INSTANCE(instance);
NSMutableDictionary *params = [NSMutableDictionary
dictionaryWithObjectsAndKeys:(subject) ? [NSString stringWithUTF8String:subject] : @"",
@"subject",
(issuer) ? [NSString stringWithUTF8String:issuer] : @"",
@"issuer",
(fingerprint) ? [NSString stringWithUTF8String:subject] : @"",
@"fingerprint", nil];
// request certificate verification UI
[mfi->session performSelectorOnMainThread:@selector(sessionVerifyCertificateWithParams:)
withObject:params
waitUntilDone:YES];
// wait for UI request to be completed
[[mfi->session uiRequestCompleted] lock];
[[mfi->session uiRequestCompleted] wait];
[[mfi->session uiRequestCompleted] unlock];
if (![[params valueForKey:@"result"] boolValue])
{
mfi->unwanted = YES;
return 0;
}
return 1;
}
DWORD ios_ui_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port,
const char *common_name, const char *subject,
const char *issuer, const char *fingerprint,
const char *old_subject, const char *old_issuer,
const char *old_fingerprint, DWORD flags)
{
return ios_ui_verify_certificate_ex(instance, host, port, common_name, subject, issuer,
fingerprint, flags);
}
#pragma mark -
#pragma mark Graphics updates
BOOL ios_ui_begin_paint(rdpContext *context)
{
WINPR_ASSERT(context);
rdpGdi *gdi = context->gdi;
WINPR_ASSERT(gdi);
WINPR_ASSERT(gdi->primary);
HGDI_DC hdc = gdi->primary->hdc;
WINPR_ASSERT(hdc);
if (!hdc->hwnd)
return TRUE;
HGDI_WND hwnd = hdc->hwnd;
if (!hwnd->invalid)
return TRUE;
hwnd->invalid->null = TRUE;
return TRUE;
}
BOOL ios_ui_end_paint(rdpContext *context)
{
WINPR_ASSERT(context);
mfInfo *mfi = MFI_FROM_INSTANCE(context->instance);
WINPR_ASSERT(mfi);
rdpGdi *gdi = context->gdi;
WINPR_ASSERT(gdi);
WINPR_ASSERT(gdi->primary);
HGDI_DC hdc = gdi->primary->hdc;
WINPR_ASSERT(hdc);
if (!hdc->hwnd)
return TRUE;
HGDI_WND hwnd = hdc->hwnd;
WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
if (hwnd->invalid->null)
return TRUE;
CGRect dirty_rect =
CGRectMake(hwnd->invalid->x, hwnd->invalid->y, hwnd->invalid->w, hwnd->invalid->h);
if (!hwnd->invalid->null)
[mfi->session performSelectorOnMainThread:@selector(setNeedsDisplayInRectAsValue:)
withObject:[NSValue valueWithCGRect:dirty_rect]
waitUntilDone:NO];
return TRUE;
}
BOOL ios_ui_resize_window(rdpContext *context)
{
rdpSettings *settings;
rdpGdi *gdi;
if (!context || !context->settings)
return FALSE;
settings = context->settings;
gdi = context->gdi;
if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
return FALSE;
ios_resize_display_buffer(MFI_FROM_INSTANCE(context->instance));
return TRUE;
}
#pragma mark -
#pragma mark Exported
static void ios_create_bitmap_context(mfInfo *mfi)
{
[mfi->session performSelectorOnMainThread:@selector(sessionBitmapContextWillChange)
withObject:nil
waitUntilDone:YES];
rdpGdi *gdi = mfi->instance->context->gdi;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (FreeRDPGetBytesPerPixel(gdi->dstFormat) == 2)
mfi->bitmap_context = CGBitmapContextCreate(
gdi->primary_buffer, gdi->width, gdi->height, 5, gdi->stride, colorSpace,
kCGBitmapByteOrder16Little | kCGImageAlphaNoneSkipFirst);
else
mfi->bitmap_context = CGBitmapContextCreate(
gdi->primary_buffer, gdi->width, gdi->height, 8, gdi->stride, colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(colorSpace);
[mfi->session performSelectorOnMainThread:@selector(sessionBitmapContextDidChange)
withObject:nil
waitUntilDone:YES];
}
void ios_allocate_display_buffer(mfInfo *mfi)
{
ios_create_bitmap_context(mfi);
}
void ios_resize_display_buffer(mfInfo *mfi)
{
// Release the old context in a thread-safe manner
CGContextRef old_context = mfi->bitmap_context;
mfi->bitmap_context = nullptr;
CGContextRelease(old_context);
// Create the new context
ios_create_bitmap_context(mfi);
}