diff --git a/client/Android/FreeRDPCore/jni/CMakeLists.txt b/client/Android/FreeRDPCore/jni/CMakeLists.txt index 58d96f6c7bd3..5aec24ef198a 100644 --- a/client/Android/FreeRDPCore/jni/CMakeLists.txt +++ b/client/Android/FreeRDPCore/jni/CMakeLists.txt @@ -33,6 +33,8 @@ set(${MODULE_PREFIX}_SRCS android_event.h android_freerdp.c android_freerdp.h + android_jni_utils.c + android_jni_utils.h android_jni_callback.c android_jni_callback.h) @@ -40,6 +42,12 @@ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} generated/android_freerdp_jni.c generated/android_freerdp_jni.h) +if(WITH_CLIENT_CHANNELS) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + android_cliprdr.c + android_cliprdr.h) +endif() + add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS}) set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client) diff --git a/client/Android/FreeRDPCore/jni/android_cliprdr.c b/client/Android/FreeRDPCore/jni/android_cliprdr.c new file mode 100644 index 000000000000..1c62d2b4c9ac --- /dev/null +++ b/client/Android/FreeRDPCore/jni/android_cliprdr.c @@ -0,0 +1,619 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Clipboard Redirection + * + * Copyright 2013 Felix Long + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include +#include +#include + +#include "android_debug.h" +#include "android_cliprdr.h" +#include "android_jni_utils.h" +#include "android_jni_callback.h" + +typedef struct clipboard_context clipboardContext; +struct clipboard_context +{ + freerdp* instance; + rdpChannels* channels; + + /* server->client data */ + UINT32* formats; + int num_formats; + BYTE* data; + UINT32 data_format; + int data_length; + + /* client->server data */ + UINT32* android_formats; + int android_num_formats; + BYTE* android_data; + int android_data_length; +}; + +static BYTE* lf2crlf(BYTE* data, int* size) +{ + BYTE c; + BYTE* outbuf; + BYTE* out; + BYTE* in_end; + BYTE* in; + int out_size; + + out_size = (*size) * 2 + 1; + outbuf = (BYTE*) malloc(out_size); + ZeroMemory(outbuf, out_size); + + out = outbuf; + in = data; + in_end = data + (*size); + + while (in < in_end) + { + c = *in++; + if (c == '\n') + { + *out++ = '\r'; + *out++ = '\n'; + } + else + { + *out++ = c; + } + } + + *out++ = 0; + *size = out - outbuf; + + return outbuf; +} + +static void crlf2lf(BYTE* data, int* size) +{ + BYTE c; + BYTE* out; + BYTE* in; + BYTE* in_end; + + out = data; + in = data; + in_end = data + (*size); + + while (in < in_end) + { + c = *in++; + + if (c != '\r') + *out++ = c; + } + + *size = out - data; +} + +static void be2le(BYTE* data, int size) +{ + BYTE c; + + while (size >= 2) + { + c = data[0]; + data[0] = data[1]; + data[1] = c; + + data += 2; + size -= 2; + } +} + +void android_cliprdr_init(freerdp* inst) +{ + androidContext* ctx = (androidContext*)inst->context; + clipboardContext* cb; + + cb = (clipboardContext*)malloc(sizeof(clipboardContext)); + ZeroMemory(cb, sizeof(clipboardContext)); + cb->instance = inst; + cb->channels = inst->context->channels; + + cb->android_formats = (UINT32*)malloc(sizeof(UINT32) * 3); + cb->android_formats[0] = CB_FORMAT_TEXT; + cb->android_formats[1] = CB_FORMAT_UNICODETEXT; + cb->android_formats[2] = CB_FORMAT_HTML; + cb->android_num_formats = 3; + +#if 0 + cb->android_data = strdup("ANDROID_CLIPBOARD_TEST"); + cb->android_data_length = strlen(cb->android_data); +#endif + + ctx->clipboard_context = cb; +} + +void android_cliprdr_uninit(freerdp* inst) +{ + androidContext* ctx = (androidContext*)inst->context; + clipboardContext* cb = (clipboardContext*)ctx->clipboard_context; + + if (cb) + { + if (cb->formats) + free(cb->formats); + if (cb->data) + free(cb->data); + if (cb->android_formats) + free(cb->android_formats); + if (cb->android_data) + free(cb->android_data); + free(cb); + ctx->clipboard_context = NULL; + } +} + +static void android_cliprdr_send_null_format_list(clipboardContext* cb) +{ + RDP_CB_FORMAT_LIST_EVENT* event; + + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(CliprdrChannel_Class, + CliprdrChannel_FormatList, NULL, NULL); + + event->num_formats = 0; + + freerdp_channels_send_event(cb->channels, (wMessage*) event); +} + +static void android_cliprdr_send_supported_format_list(clipboardContext* cb) +{ + int i; + RDP_CB_FORMAT_LIST_EVENT* event; + + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(CliprdrChannel_Class, + CliprdrChannel_FormatList, NULL, NULL); + + event->formats = (UINT32*) malloc(sizeof(UINT32) * cb->android_num_formats); + event->num_formats = cb->android_num_formats; + + for (i = 0; i < cb->android_num_formats; i++) + { + event->formats[i] = cb->android_formats[i]; + } + + freerdp_channels_send_event(cb->channels, (wMessage*) event); +} + +static void android_cliprdr_send_format_list(clipboardContext* cb) +{ + if (cb->android_data) + { + android_cliprdr_send_supported_format_list(cb); + } + else + { + android_cliprdr_send_null_format_list(cb); + } +} + +static void android_cliprdr_send_data_request(clipboardContext* cb, UINT32 format) +{ + RDP_CB_DATA_REQUEST_EVENT* event; + + event = (RDP_CB_DATA_REQUEST_EVENT*) freerdp_event_new(CliprdrChannel_Class, + CliprdrChannel_DataRequest, NULL, NULL); + + event->format = format; + + freerdp_channels_send_event(cb->channels, (wMessage*) event); +} + +static void android_cliprdr_send_data_response(clipboardContext* cb, BYTE* data, int size) +{ + RDP_CB_DATA_RESPONSE_EVENT* event; + + event = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new(CliprdrChannel_Class, + CliprdrChannel_DataResponse, NULL, NULL); + + event->data = data; + event->size = size; + + freerdp_channels_send_event(cb->channels, (wMessage*) event); +} + +static void android_cliprdr_send_null_data_response(clipboardContext* cb) +{ + android_cliprdr_send_data_response(cb, NULL, 0); +} + +static void android_cliprdr_process_cb_monitor_ready_event(clipboardContext* cb) +{ + android_cliprdr_send_format_list(cb); +} + +static BYTE* android_cliprdr_process_requested_unicodetext(BYTE* data, int* size) +{ + char* inbuf; + WCHAR* outbuf = NULL; + int out_size; + + inbuf = (char*) lf2crlf(data, size); + out_size = ConvertToUnicode(CP_UTF8, 0, inbuf, -1, &outbuf, 0); + free(inbuf); + + *size = (int) ((out_size + 1) * 2); + + return (BYTE*) outbuf; +} + +static BYTE* android_cliprdr_process_requested_text(BYTE* data, int* size) +{ + BYTE* outbuf; + + outbuf = lf2crlf(data, size); + + return outbuf; +} + +static BYTE* android_cliprdr_process_requested_html(BYTE* data, int* size) +{ + char* inbuf; + BYTE* in; + BYTE* outbuf; + char num[11]; + + inbuf = NULL; + + if (*size > 2) + { + if ((BYTE) data[0] == 0xFE && (BYTE) data[1] == 0xFF) + { + be2le(data, *size); + } + + if ((BYTE) data[0] == 0xFF && (BYTE) data[1] == 0xFE) + { + ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) (data + 2), (*size - 2) / 2, &inbuf, 0, NULL, NULL); + } + } + + if (inbuf == NULL) + { + inbuf = malloc(*size + 1); + ZeroMemory(inbuf, *size + 1); + + memcpy(inbuf, data, *size); + } + + outbuf = (BYTE*) malloc(*size + 200); + ZeroMemory(outbuf, *size + 200); + + strcpy((char*) outbuf, + "Version:0.9\r\n" + "StartHTML:0000000000\r\n" + "EndHTML:0000000000\r\n" + "StartFragment:0000000000\r\n" + "EndFragment:0000000000\r\n"); + + in = (BYTE*) strstr((char*) inbuf, ""); + } + strcat((char*) outbuf, ""); + /* StartFragment */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 69, num, 10); + strcat((char*) outbuf, (char*) inbuf); + /* EndFragment */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 93, num, 10); + strcat((char*) outbuf, ""); + if (in == NULL) + { + strcat((char*) outbuf, ""); + } + /* EndHTML */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 43, num, 10); + + *size = strlen((char*) outbuf) + 1; + free(inbuf); + + return outbuf; +} + +static void android_cliprdr_process_cb_data_request_event(clipboardContext* cb, RDP_CB_DATA_REQUEST_EVENT* event) +{ + int i; + + DEBUG_ANDROID("format %d", event->format); + + for(i = 0; i < cb->android_num_formats; i++) + { + if (event->format == cb->android_formats[i]) + break; + } + + if (i >= cb->android_num_formats) + { + DEBUG_ANDROID("unsupported format requested"); + android_cliprdr_send_null_data_response(cb); + } + else if (!cb->android_data) + { + DEBUG_ANDROID("no android clipdata"); + android_cliprdr_send_null_data_response(cb); + } + else + { + BYTE* outbuf; + int size = cb->android_data_length; + + switch (event->format) + { + case CB_FORMAT_RAW: + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + case CB_FORMAT_GIF: + case CB_FORMAT_DIB: + default: + DEBUG_ANDROID("unsupported format %x\n", event->format); + outbuf = NULL; + break; + + case CB_FORMAT_UNICODETEXT: + outbuf = android_cliprdr_process_requested_unicodetext(cb->android_data, &size); + break; + + case CB_FORMAT_TEXT: + outbuf = android_cliprdr_process_requested_text(cb->android_data, &size); + break; + + case CB_FORMAT_HTML: + outbuf = android_cliprdr_process_requested_html(cb->android_data, &size); + break; + } + if (outbuf) + android_cliprdr_send_data_response(cb, outbuf, size); + else + android_cliprdr_send_null_data_response(cb); + } + + /* Resend the format list, otherwise the server won't request again for the next paste */ + android_cliprdr_send_format_list(cb); +} + +static BOOL android_cliprdr_has_format(UINT32* formats, int num_formats, UINT32 format) +{ + int i; + for(i = 0; i < num_formats; i++) + { + if (formats[i] == format) + return TRUE; + } + return FALSE; +} + +static void android_cliprdr_process_cb_format_list_event(clipboardContext* cb, RDP_CB_FORMAT_LIST_EVENT* event) +{ + if (cb->data) + { + free(cb->data); + cb->data = NULL; + cb->data_length = 0; + } + + if (cb->formats) + free(cb->formats); + + cb->data_format = CB_FORMAT_RAW; + cb->formats = event->formats; + cb->num_formats = event->num_formats; + event->formats = NULL; + event->num_formats = 0; + + if (android_cliprdr_has_format(cb->formats, cb->num_formats, CB_FORMAT_TEXT)) + { + cb->data_format = CB_FORMAT_TEXT; + android_cliprdr_send_data_request(cb, CB_FORMAT_TEXT); + } + else if (android_cliprdr_has_format(cb->formats, cb->num_formats, CB_FORMAT_UNICODETEXT)) + { + cb->data_format = CB_FORMAT_UNICODETEXT; + android_cliprdr_send_data_request(cb, CB_FORMAT_UNICODETEXT); + } + else if (android_cliprdr_has_format(cb->formats, cb->num_formats, CB_FORMAT_HTML)) + { + cb->data_format = CB_FORMAT_HTML; + android_cliprdr_send_data_request(cb, CB_FORMAT_HTML); + } +} + +static void android_cliprdr_process_text(clipboardContext* cb, BYTE* data, int size) +{ + if (size > 0 && data) + { + cb->data = (BYTE*) malloc(size + 1); + memcpy(cb->data, data, size); + cb->data[size] = 0; + cb->data_length = size; + } +} + +static void android_cliprdr_process_unicodetext(clipboardContext* cb, BYTE* data, int size) +{ + cb->data_length = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) data, size / 2, (CHAR**) &(cb->data), 0, NULL, NULL); + crlf2lf(cb->data, &cb->data_length); +} + +static void android_cliprdr_process_html(clipboardContext* cb, BYTE* data, int size) +{ + char* start_str; + char* end_str; + int start; + int end; + + start_str = strstr((char*) data, "StartHTML:"); + end_str = strstr((char*) data, "EndHTML:"); + if (start_str == NULL || end_str == NULL) + { + DEBUG_ANDROID("invalid HTML clipboard format"); + return; + } + start = atoi(start_str + 10); + end = atoi(end_str + 8); + if (start > size || end > size || start >= end) + { + DEBUG_ANDROID("invalid HTML offset"); + return; + } + + cb->data = (BYTE*) malloc(end - start + 1); + memcpy(cb->data, data + start, end - start); + cb->data[end - start] = 0; + cb->data_length = end - start; +} + +static void android_cliprdr_process_cb_data_response_event(clipboardContext* cb, RDP_CB_DATA_RESPONSE_EVENT* event) +{ + DEBUG_ANDROID("size=%d", event->size); + + if (event->size > 0) + { + if (cb->data) + { + free(cb->data); + cb->data = NULL; + cb->data_length = 0; + } + switch (cb->data_format) + { + case CB_FORMAT_RAW: + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + case CB_FORMAT_GIF: + case CB_FORMAT_DIB: + default: + DEBUG_ANDROID("unsupported format\n"); + break; + + case CB_FORMAT_TEXT: + android_cliprdr_process_text(cb, event->data, event->size - 1); + break; + + case CB_FORMAT_UNICODETEXT: + android_cliprdr_process_unicodetext(cb, event->data, event->size - 2); + break; + + case CB_FORMAT_HTML: + android_cliprdr_process_html(cb, event->data, event->size); + break; + } + DEBUG_ANDROID("computer_clipboard_data %s ", (char*)cb->data); + if (cb->data) + { //CALLBACK + JNIEnv* env; + jboolean attached = jni_attach_thread(&env); + jstring jdata = jniNewStringUTF(env, cb->data, cb->data_length); + + freerdp_callback("OnRemoteClipboardChanged", "(ILjava/lang/String;)V", cb->instance, jdata); + + (*env)->DeleteLocalRef(env, jdata); + if(attached == JNI_TRUE) + { + jni_detach_thread(); + } + } + } +} + +void android_process_cliprdr_event(freerdp* inst, wMessage* event) +{ + androidContext* ctx = (androidContext*)inst->context; + clipboardContext* cb = (clipboardContext*) ctx->clipboard_context; + + if (!cb) + { + return; + } + + switch (GetMessageType(event->id)) + { + case CliprdrChannel_MonitorReady: + android_cliprdr_process_cb_monitor_ready_event(cb); + break; + + case CliprdrChannel_FormatList: + android_cliprdr_process_cb_format_list_event(cb, (RDP_CB_FORMAT_LIST_EVENT*) event); + break; + + case CliprdrChannel_DataRequest: + android_cliprdr_process_cb_data_request_event(cb, (RDP_CB_DATA_REQUEST_EVENT*) event); + break; + + case CliprdrChannel_DataResponse: + android_cliprdr_process_cb_data_response_event(cb, (RDP_CB_DATA_RESPONSE_EVENT*) event); + break; + + default: + DEBUG_ANDROID("unknown event type %d", event->event_type); + break; + } +} + +void android_process_cliprdr_send_clipboard_data(freerdp* inst, void* data, int len) +{ + androidContext* ctx = (androidContext*)inst->context; + clipboardContext* cb = (clipboardContext*) ctx->clipboard_context; + + DEBUG_ANDROID("android_clipboard_data %s ", (char*)data); + + if (cb && (data == NULL || cb->android_data == NULL || len != cb->android_data_length || memcmp(data, cb->android_data, len))) + { + if (cb->android_data) + { + free(cb->android_data); + cb->android_data = NULL; + cb->android_data_length = 0; + } + if (data) + { + cb->android_data = (BYTE*)malloc(len + 1); + memcpy(cb->android_data, data, len); + cb->android_data[len] = 0; + cb->android_data_length = len; + } + android_cliprdr_send_format_list(cb); + } +} + diff --git a/client/Android/FreeRDPCore/jni/android_cliprdr.h b/client/Android/FreeRDPCore/jni/android_cliprdr.h new file mode 100644 index 000000000000..233bff38020b --- /dev/null +++ b/client/Android/FreeRDPCore/jni/android_cliprdr.h @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Clipboard Redirection + * + * Copyright 2013 Felix Long + * + * 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 __ANDROID_CLIPRDR_H__ +#define __ANDROID_CLIPRDR_H__ + +#include "android_freerdp.h" + +void android_cliprdr_init(freerdp* inst); +void android_cliprdr_uninit(freerdp* inst); +void android_process_cliprdr_send_clipboard_data(freerdp* inst, void* data, int len); +void android_process_cliprdr_event(freerdp* inst, wMessage* event); + +#endif /* __ANDROID_CLIPRDR_H__ */ diff --git a/client/Android/FreeRDPCore/jni/android_event.c b/client/Android/FreeRDPCore/jni/android_event.c index bb0f55bb1653..c25582cfc202 100644 --- a/client/Android/FreeRDPCore/jni/android_event.c +++ b/client/Android/FreeRDPCore/jni/android_event.c @@ -26,6 +26,7 @@ #endif #include "android_freerdp.h" +#include "android_cliprdr.h" int android_is_event_set(ANDROID_EVENT_QUEUE * queue) { @@ -137,6 +138,12 @@ int android_process_event(ANDROID_EVENT_QUEUE * queue, freerdp * inst) inst->input->MouseEvent(inst->input, cursor_event->flags, cursor_event->x, cursor_event->y); android_event_cursor_free(cursor_event); } + else if (event->type == EVENT_TYPE_CLIPBOARD) + { + ANDROID_EVENT_CLIPBOARD* clipboard_event = (ANDROID_EVENT_CLIPBOARD*)event; + android_process_cliprdr_send_clipboard_data(inst, clipboard_event->data, clipboard_event->data_length); + android_event_clipboard_free(clipboard_event); + } else if (event->type == EVENT_TYPE_DISCONNECT) { android_event_disconnect_free(event); @@ -254,6 +261,36 @@ void android_event_disconnect_free(ANDROID_EVENT* event) free(event); } +ANDROID_EVENT_CLIPBOARD* android_event_clipboard_new(void* data, int data_length) +{ + ANDROID_EVENT_CLIPBOARD* event; + + event = (ANDROID_EVENT_CLIPBOARD*) malloc(sizeof(ANDROID_EVENT_CLIPBOARD)); + memset(event, 0, sizeof(ANDROID_EVENT_CLIPBOARD)); + + event->type = EVENT_TYPE_CLIPBOARD; + if (data) + { + event->data = malloc(data_length); + memcpy(event->data, data, data_length); + event->data_length = data_length; + } + + return event; +} + +void android_event_clipboard_free(ANDROID_EVENT_CLIPBOARD* event) +{ + if (event != NULL) + { + if (event->data) + { + free(event->data); + } + free(event); + } +} + void android_event_queue_init(freerdp * inst) { androidContext* aCtx = (androidContext*)inst->context; diff --git a/client/Android/FreeRDPCore/jni/android_event.h b/client/Android/FreeRDPCore/jni/android_event.h index 7a3b6e599ca0..7872ec770d91 100644 --- a/client/Android/FreeRDPCore/jni/android_event.h +++ b/client/Android/FreeRDPCore/jni/android_event.h @@ -17,6 +17,7 @@ #define EVENT_TYPE_CURSOR 2 #define EVENT_TYPE_DISCONNECT 3 #define EVENT_TYPE_KEY_UNICODE 4 +#define EVENT_TYPE_CLIPBOARD 5 struct _ANDROID_EVENT { @@ -41,6 +42,14 @@ struct _ANDROID_EVENT_CURSOR }; typedef struct _ANDROID_EVENT_CURSOR ANDROID_EVENT_CURSOR; +struct _ANDROID_EVENT_CLIPBOARD +{ + int type; + void* data; + int data_length; +}; +typedef struct _ANDROID_EVENT_CLIPBOARD ANDROID_EVENT_CLIPBOARD; + struct _ANDROID_EVENT_QUEUE { int size; @@ -64,10 +73,12 @@ ANDROID_EVENT_KEY* android_event_key_new(int flags, UINT16 scancode); ANDROID_EVENT_KEY* android_event_unicodekey_new(UINT16 key); ANDROID_EVENT_CURSOR* android_event_cursor_new(UINT16 flags, UINT16 x, UINT16 y); ANDROID_EVENT* android_event_disconnect_new(void); +ANDROID_EVENT_CLIPBOARD* android_event_clipboard_new(void* data, int data_length); void android_event_key_free(ANDROID_EVENT_KEY* event); void android_event_unicodekey_free(ANDROID_EVENT_KEY* event); void android_event_cursor_free(ANDROID_EVENT_CURSOR* event); void android_event_disconnect_free(ANDROID_EVENT* event); +void android_event_clipboard_free(ANDROID_EVENT_CLIPBOARD* event); void android_event_queue_init(freerdp * inst); void android_event_queue_uninit(freerdp * inst); diff --git a/client/Android/FreeRDPCore/jni/android_freerdp.c b/client/Android/FreeRDPCore/jni/android_freerdp.c index 215c5518e73f..10aa209263c0 100644 --- a/client/Android/FreeRDPCore/jni/android_freerdp.c +++ b/client/Android/FreeRDPCore/jni/android_freerdp.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -28,11 +27,12 @@ #include #include -#include #include "android_freerdp.h" #include "android_jni_callback.h" +#include "android_jni_utils.h" #include "android_debug.h" +#include "android_cliprdr.h" struct thread_data { @@ -143,10 +143,7 @@ BOOL android_post_connect(freerdp* instance) instance->update->EndPaint = android_end_paint; instance->update->DesktopResize = android_desktop_resize; - //ai->rail = rail_new(instance->settings); - //instance->update->rail = (void*) ai->rail; - //rail_register_update_callbacks(xfi->rail, instance->update); - //android_rail_register_callbacks(xfi, xfi->rail); + android_cliprdr_init(instance); freerdp_channels_post_connect(instance->context->channels, instance); @@ -156,74 +153,6 @@ BOOL android_post_connect(freerdp* instance) return TRUE; } -jobject create_string_builder(JNIEnv *env, char* initialStr) -{ - jclass cls; - jmethodID methodId; - jobject obj; - - // get class - cls = (*env)->FindClass(env, "java/lang/StringBuilder"); - if(!cls) - return NULL; - - if(initialStr) - { - // get method id for constructor - methodId = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;)V"); - if(!methodId) - return NULL; - - // create string that holds our initial string - jstring jstr = (*env)->NewStringUTF(env, initialStr); - - // construct new StringBuilder - obj = (*env)->NewObject(env, cls, methodId, jstr); - } - else - { - // get method id for constructor - methodId = (*env)->GetMethodID(env, cls, "", "()V"); - if(!methodId) - return NULL; - - // construct new StringBuilder - obj = (*env)->NewObject(env, cls, methodId); - } - - return obj; -} - -char* get_string_from_string_builder(JNIEnv* env, jobject strBuilder) -{ - jclass cls; - jmethodID methodId; - jstring strObj; - const jbyte* native_str; - char* result; - - // get class - cls = (*env)->FindClass(env, "java/lang/StringBuilder"); - if(!cls) - return NULL; - - // get method id for constructor - methodId = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); - if(!methodId) - return NULL; - - // get jstring representation of our buffer - strObj = (*env)->CallObjectMethod(env, strBuilder, methodId); - - // read string - native_str = (*env)->GetStringUTFChars(env, strObj, NULL); - result = strdup(native_str); - (*env)->ReleaseStringUTFChars(env, strObj, native_str); - - return result; -} - - BOOL android_authenticate(freerdp* instance, char** username, char** password, char** domain) { DEBUG_ANDROID("Authenticate user:"); @@ -291,19 +220,6 @@ BOOL android_verify_changed_certificate(freerdp* instance, char* subject, char* return android_verify_certificate(instance, subject, issuer, new_fingerprint); } - -/* -int xf_process_plugin_args(rdpSettings* settings, const char* name, RDP_PLUGIN_DATA* plugin_data, void* user_data) -{ - rdpChanMan* chanman = (rdpChanMan*) user_data; - - printf("loading plugin %s\n", name); - freerdp_chanman_load_plugin(chanman, settings, name, plugin_data); - - return 1; -} -*/ - int android_receive_channel_data(freerdp* instance, int channelId, UINT8* data, int size, int flags, int total_size) { return freerdp_channels_data(instance, channelId, data, size, flags, total_size); @@ -317,25 +233,16 @@ void android_process_channel_event(rdpChannels* channels, freerdp* instance) if (event) { -/* switch (event->event_class) + switch(GetMessageClass(event->id)) { - case RailChannel_Class: - xf_process_rail_event(ai, chanman, event); + case CliprdrChannel_Class: + android_process_cliprdr_event(instance, event); break; default: break; } - switch (event->event_type) - { - case RDP_EVENT_TYPE_CB_SYNC: - android_process_cb_sync_event(chanman, instance); - break; - default: - break; - } -*/ freerdp_event_free(event); } } @@ -437,6 +344,7 @@ int android_freerdp_run(freerdp* instance) freerdp_disconnect(instance); gdi_free(instance); cache_free(instance->context->cache); + android_cliprdr_uninit(instance); freerdp_callback("OnDisconnected", "(I)V", instance); return 0; @@ -456,20 +364,6 @@ void* android_thread_func(void* param) return NULL; } -jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - - DEBUG_ANDROID("JNI_OnLoad"); - - jint res = init_callback_environment(vm); - - setlocale(LC_ALL, ""); - - freerdp_channels_global_init(); - - return res; -} - JNIEXPORT jint JNICALL jni_freerdp_new(JNIEnv *env, jclass cls) { freerdp* instance; @@ -657,7 +551,7 @@ JNIEXPORT void JNICALL jni_freerdp_set_performance_flags( { freerdp* inst = (freerdp*)instance; rdpSettings * settings = inst->settings; - + DEBUG_ANDROID("remotefx: %d", (remotefx == JNI_TRUE) ? 1 : 0); if (remotefx == JNI_TRUE) { @@ -748,6 +642,16 @@ JNIEXPORT void JNICALL jni_freerdp_set_drive_redirection(JNIEnv *env, jclass cls (*env)->ReleaseStringUTFChars(env, jpath, path); } +JNIEXPORT void JNICALL jni_freerdp_set_clipboard_redirection(JNIEnv *env, jclass cls, jint instance, jboolean enable) +{ + freerdp* inst = (freerdp*)instance; + rdpSettings * settings = inst->settings; + + DEBUG_ANDROID("clipboard redirect: %s", enable ? "TRUE" : "FALSE"); + + settings->RedirectClipboard = enable ? TRUE : FALSE; +} + void copy_pixel_buffer(UINT8* dstBuf, UINT8* srcBuf, int x, int y, int width, int height, int wBuf, int hBuf, int bpp) { int i, j; @@ -860,6 +764,22 @@ JNIEXPORT void JNICALL jni_freerdp_send_cursor_event( DEBUG_ANDROID("send_cursor_event: (%d, %d), %d", x, y, flags); } +JNIEXPORT void JNICALL jni_freerdp_send_clipboard_data(JNIEnv *env, jclass cls, jint instance, jstring jdata) +{ + ANDROID_EVENT* event; + freerdp* inst = (freerdp*)instance; + const jbyte *data = jdata != NULL ? (*env)->GetStringUTFChars(env, jdata, NULL) : NULL; + int data_length = data ? strlen(data) : 0; + + event = (ANDROID_EVENT*) android_event_clipboard_new((void*)data, data_length); + android_push_event(inst, event); + + DEBUG_ANDROID("send_clipboard_data: (%s)", data); + + if (data) + (*env)->ReleaseStringUTFChars(env, jdata, data); +} + JNIEXPORT jstring JNICALL jni_freerdp_get_version(JNIEnv *env, jclass cls) { return (*env)->NewStringUTF(env, GIT_REVISION); diff --git a/client/Android/FreeRDPCore/jni/android_freerdp.h b/client/Android/FreeRDPCore/jni/android_freerdp.h index 32753ad78995..f4c660b3dffd 100644 --- a/client/Android/FreeRDPCore/jni/android_freerdp.h +++ b/client/Android/FreeRDPCore/jni/android_freerdp.h @@ -23,6 +23,8 @@ struct android_context ANDROID_EVENT_QUEUE* event_queue; pthread_t thread; BOOL is_connected; + + void* clipboard_context; }; typedef struct android_context androidContext; @@ -43,11 +45,13 @@ JNIEXPORT void JNICALL jni_freerdp_set_performance_flags(JNIEnv *env, jclass cls jboolean disableMenuAnimations, jboolean disableTheming, jboolean enableFontSmoothing, jboolean enableDesktopComposition); JNIEXPORT void JNICALL jni_freerdp_set_advanced_settings(JNIEnv *env, jclass cls, jint instance, jstring jRemoteProgram, jstring jWorkDir); JNIEXPORT void JNICALL jni_freerdp_set_drive_redirection(JNIEnv *env, jclass cls, jint instance, jstring jpath); +JNIEXPORT void JNICALL jni_freerdp_set_clipboard_redirection(JNIEnv *env, jclass cls, jint instance, jboolean enable); JNIEXPORT void JNICALL jni_freerdp_set_data_directory(JNIEnv *env, jclass cls, jint instance, jstring jdirectory); JNIEXPORT jboolean JNICALL jni_freerdp_update_graphics(JNIEnv *env, jclass cls, jint instance, jobject bitmap, jint x, jint y, jint width, jint height); JNIEXPORT void JNICALL jni_freerdp_send_cursor_event(JNIEnv *env, jclass cls, jint instance, jint x, jint y, jint flags); JNIEXPORT void JNICALL jni_freerdp_send_key_event(JNIEnv *env, jclass cls, jint instance, jint keycode, jboolean down); JNIEXPORT void JNICALL jni_freerdp_send_unicodekey_event(JNIEnv *env, jclass cls, jint instance, jint keycode); +JNIEXPORT void JNICALL jni_freerdp_send_clipboard_data(JNIEnv *env, jclass cls, jint instance, jstring jdata); JNIEXPORT jstring JNICALL jni_freerdp_get_version(JNIEnv *env, jclass cls); #endif /* __ANDROID_FREERDP_H */ diff --git a/client/Android/FreeRDPCore/jni/android_jni_utils.c b/client/Android/FreeRDPCore/jni/android_jni_utils.c new file mode 100644 index 000000000000..0424c501aad5 --- /dev/null +++ b/client/Android/FreeRDPCore/jni/android_jni_utils.c @@ -0,0 +1,211 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2013 Felix Long + * Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz + * + * 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 "android_jni_utils.h" + +#include +#include + +#include "android_debug.h" +#include "android_jni_callback.h" + + +JavaVM *g_JavaVm; + + +JavaVM* getJavaVM() +{ + return g_JavaVm; +} + +JNIEnv* getJNIEnv() +{ + JNIEnv* env = NULL; + if ((*g_JavaVm)->GetEnv(g_JavaVm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) + { + DEBUG_ANDROID("Failed to obtain JNIEnv"); + return NULL; + } + return env; +} + +jobject create_string_builder(JNIEnv *env, char* initialStr) +{ + jclass cls; + jmethodID methodId; + jobject obj; + + // get class + cls = (*env)->FindClass(env, "java/lang/StringBuilder"); + if(!cls) + return NULL; + + if(initialStr) + { + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;)V"); + if(!methodId) + return NULL; + + // create string that holds our initial string + jstring jstr = (*env)->NewStringUTF(env, initialStr); + + // construct new StringBuilder + obj = (*env)->NewObject(env, cls, methodId, jstr); + } + else + { + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "", "()V"); + if(!methodId) + return NULL; + + // construct new StringBuilder + obj = (*env)->NewObject(env, cls, methodId); + } + + return obj; +} + +char* get_string_from_string_builder(JNIEnv* env, jobject strBuilder) +{ + jclass cls; + jmethodID methodId; + jstring strObj; + const jbyte* native_str; + char* result; + + // get class + cls = (*env)->FindClass(env, "java/lang/StringBuilder"); + if(!cls) + return NULL; + + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); + if(!methodId) + return NULL; + + // get jstring representation of our buffer + strObj = (*env)->CallObjectMethod(env, strBuilder, methodId); + + // read string + native_str = (*env)->GetStringUTFChars(env, strObj, NULL); + result = strdup(native_str); + (*env)->ReleaseStringUTFChars(env, strObj, native_str); + + return result; +} + +jstring jniNewStringUTF(JNIEnv* env, const char* in, int len) +{ + jstring out = NULL; + jchar* unicode = NULL; + jint result_size = 0; + jint i; + unsigned char* utf8 = (unsigned char*)in; + + if (!in) + { + return NULL; + } + if (len < 0) + len = strlen(in); + + unicode = (jchar*)malloc(sizeof(jchar) * (len + 1)); + if (!unicode) + { + return NULL; + } + + for(i = 0; i < len; i++) + { + unsigned char one = utf8[i]; + switch(one >> 4) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + unicode[result_size++] = one; + break; + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + //case 0x0f: + /* + * Bit pattern 10xx or 1111, which are illegal start bytes. + * Note: 1111 is valid for normal UTF-8, but not the + * modified UTF-8 used here. + */ + break; + case 0x0f: + case 0x0e: + // Bit pattern 111x, so there are two additional bytes. + if (i < (len - 2)) + { + unsigned char two = utf8[i+1]; + unsigned char three = utf8[i+2]; + if ((two & 0xc0) == 0x80 && (three & 0xc0) == 0x80) + { + i += 2; + unicode[result_size++] = + ((one & 0x0f) << 12) + | ((two & 0x3f) << 6) + | (three & 0x3f); + } + } + break; + case 0x0c: + case 0x0d: + // Bit pattern 110x, so there is one additional byte. + if (i < (len - 1)) + { + unsigned char two = utf8[i+1]; + if ((two & 0xc0) == 0x80) + { + i += 1; + unicode[result_size++] = + ((one & 0x1f) << 6) + | (two & 0x3f); + } + } + break; + } + } + + out = (*env)->NewString(env, unicode, result_size); + free(unicode); + + return out; +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + jint result = JNI_ERR; + + DEBUG_ANDROID("JNI_OnLoad"); + + g_JavaVm = vm; + result = init_callback_environment(vm); + + setlocale(LC_ALL, ""); + + freerdp_channels_global_init(); + + return result; +} + diff --git a/client/Android/FreeRDPCore/jni/android_jni_utils.h b/client/Android/FreeRDPCore/jni/android_jni_utils.h new file mode 100644 index 000000000000..f5ac12e364e6 --- /dev/null +++ b/client/Android/FreeRDPCore/jni/android_jni_utils.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2013 Felix Long + * Copyright 2013 Thinstuff Technologies GmbH, Author: Martin Fleisz + * + * 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 _ANDROID_JNI_UTILS_H_ +#define _ANDROID_JNI_UTILS_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +JNIEnv* getJNIEnv(); + +JavaVM* getJavaVM(); + +char* get_string_from_string_builder(JNIEnv* env, jobject strBuilder); +jobject create_string_builder(JNIEnv *env, char* initialStr); +jstring jniNewStringUTF(JNIEnv* env, const char* in, int len); + +#ifdef __cplusplus +} +#endif + +#endif /* _ANDROID_JNI_UTILS_H_ */ diff --git a/client/Android/FreeRDPCore/jni/generated/android_freerdp_jni.c b/client/Android/FreeRDPCore/jni/generated/android_freerdp_jni.c index b263b1d66162..861a9087ad85 100644 --- a/client/Android/FreeRDPCore/jni/generated/android_freerdp_jni.c +++ b/client/Android/FreeRDPCore/jni/generated/android_freerdp_jni.c @@ -70,6 +70,12 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_ jni_freerdp_set_performance_flags(env, cls, instance, remotefx, disableWallpaper, disableFullWindowDrag, disableMenuAnimations, disableTheming, enableFontSmoothing, enableDesktopComposition); } +JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1clipboard_1redirection + (JNIEnv *env, jclass cls, jint inst, jboolean enable) +{ + jni_freerdp_set_clipboard_redirection(env, cls, inst, enable); +} + JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1drive_1redirection (JNIEnv *env, jclass cls, jint inst, jstring path) { @@ -100,6 +106,12 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_ jni_freerdp_send_unicodekey_event(env, cls, instance, keycode); } +JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1clipboard_1data + (JNIEnv *env, jclass cls, jint instance, jstring data) +{ + jni_freerdp_send_clipboard_data(env, cls, instance, data); +} + JNIEXPORT jstring JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1version(JNIEnv *env, jclass cls) { return jni_freerdp_get_version(env, cls); diff --git a/client/Android/FreeRDPCore/jni/generated/com_freerdp_freerdpcore_services_LibFreeRDP.h b/client/Android/FreeRDPCore/jni/generated/com_freerdp_freerdpcore_services_LibFreeRDP.h index a5c30412b0f7..08188f700e65 100644 --- a/client/Android/FreeRDPCore/jni/generated/com_freerdp_freerdpcore_services_LibFreeRDP.h +++ b/client/Android/FreeRDPCore/jni/generated/com_freerdp_freerdpcore_services_LibFreeRDP.h @@ -79,6 +79,14 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1data_1directory (JNIEnv *, jclass, jint, jstring); +/* + * Class: com_freerdp_freerdpcore_services_LibFreeRDP + * Method: freerdp_set_clipboard_redirection + * Signature: (IZ)V + */ +JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1set_1clipboard_1redirection + (JNIEnv *, jclass, jint, jboolean); + /* * Class: com_freerdp_freerdpcore_services_LibFreeRDP * Method: freerdp_set_drive_redirection @@ -119,6 +127,14 @@ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_ JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1unicodekey_1event (JNIEnv *, jclass, jint, jint); +/* + * Class: com_freerdp_freerdpcore_services_LibFreeRDP + * Method: freerdp_send_clipboard_data + * Signature: (ILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1clipboard_1data + (JNIEnv *, jclass, jint, jstring); + /* * Class: com_freerdp_freerdpcore_services_LibFreeRDP * Method: freerdp_get_version diff --git a/client/Android/FreeRDPCore/project.properties b/client/Android/FreeRDPCore/project.properties index db721fd89784..b4af3b428c0a 100644 --- a/client/Android/FreeRDPCore/project.properties +++ b/client/Android/FreeRDPCore/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-8 +target=android-11 android.library=true diff --git a/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/presentation/SessionActivity.java b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/presentation/SessionActivity.java index 3d1c5adc155f..055b48b73c94 100644 --- a/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/presentation/SessionActivity.java +++ b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/presentation/SessionActivity.java @@ -22,6 +22,7 @@ import com.freerdp.freerdpcore.services.LibFreeRDP; import com.freerdp.freerdpcore.utils.KeyboardMapper; import com.freerdp.freerdpcore.utils.Mouse; +import com.freerdp.freerdpcore.utils.ClipboardManagerProxy; import android.app.Activity; import android.app.Dialog; @@ -59,7 +60,8 @@ public class SessionActivity extends Activity implements LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener, ScrollView2D.ScrollView2DListener, - KeyboardMapper.KeyProcessingListener, SessionView.SessionViewListener, TouchPointerView.TouchPointerListener + KeyboardMapper.KeyProcessingListener, SessionView.SessionViewListener, TouchPointerView.TouchPointerListener, + ClipboardManagerProxy.OnClipboardChangedListener { private class UIHandler extends Handler { @@ -328,6 +330,8 @@ private void OnDisconnected(Context context) private static final int SEND_MOVE_EVENT_TIMEOUT = 150; private int discardedMoveEvents = 0; + private ClipboardManagerProxy mClipboardManager; + private void createDialogs() { // build verify certificate dialog @@ -477,6 +481,9 @@ public void onClick(View v) { IntentFilter filter = new IntentFilter(); filter.addAction(GlobalApp.ACTION_EVENT_FREERDP); registerReceiver(libFreeRDPBroadcastReceiver, filter); + + mClipboardManager = ClipboardManagerProxy.getClipboardManager(this); + mClipboardManager.addClipboardChangedListener(this); } @Override @@ -519,7 +526,10 @@ protected void onDestroy() { // unregister freerdp events broadcast receiver unregisterReceiver(libFreeRDPBroadcastReceiver); - + + // remove clipboard listener + mClipboardManager.removeClipboardboardChangedListener(this); + // free session GlobalApp.freeSession(session.getInstance()); session = null; @@ -1013,7 +1023,13 @@ public boolean OnVerifiyCertificate(String subject, String issuer, String finger return callbackDialogResult; } - + @Override + public void OnRemoteClipboardChanged(String data) + { + Log.v(TAG, "OnRemoteClipboardChanged: " + data); + mClipboardManager.setClipboardData(data); + } + // **************************************************************************** // ScrollView2DListener implementation private void resetZoomControlsAutoHideTimeout() { @@ -1131,5 +1147,13 @@ public void onTouchPointerToggleExtKeyboard() { public void onTouchPointerResetScrollZoom() { sessionView.setZoom(1.0f); scrollView.scrollTo(0, 0); + } + + // **************************************************************************** + // ClipboardManagerProxy.OnClipboardChangedListener + @Override + public void onClipboardChanged(String data) { + Log.v(TAG, "onClipboardChanged: " + data); + LibFreeRDP.sendClipboardData(session.getInstance(), data); } } diff --git a/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/services/LibFreeRDP.java b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/services/LibFreeRDP.java index 248e9ac6fa00..455d0a3796f7 100644 --- a/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/services/LibFreeRDP.java +++ b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/services/LibFreeRDP.java @@ -39,6 +39,7 @@ private static native void freerdp_set_performance_flags(int inst, private static native void freerdp_set_data_directory(int inst, String directory); + private static native void freerdp_set_clipboard_redirection(int inst, boolean enable); private static native void freerdp_set_drive_redirection(int inst, String path); private static native boolean freerdp_update_graphics(int inst, @@ -47,7 +48,8 @@ private static native boolean freerdp_update_graphics(int inst, private static native void freerdp_send_cursor_event(int inst, int x, int y, int flags); private static native void freerdp_send_key_event(int inst, int keycode, boolean down); private static native void freerdp_send_unicodekey_event(int inst, int keycode); - + private static native void freerdp_send_clipboard_data(int inst, String data); + private static native String freerdp_get_version(); private static final String TAG = "LibFreeRDP"; @@ -67,6 +69,7 @@ public static interface UIEventListener boolean OnVerifiyCertificate(String subject, String issuer, String fingerprint); void OnGraphicsUpdate(int x, int y, int width, int height); void OnGraphicsResize(int width, int height, int bpp); + void OnRemoteClipboardChanged(String data); } private static EventListener listener; @@ -149,6 +152,9 @@ public static boolean setConnectionInfo(int inst, BookmarkBase bookmark) if (advancedSettings.getRedirectSDCard()) freerdp_set_drive_redirection(inst, android.os.Environment.getExternalStorageDirectory().getPath()); + // always enable clipboard redirection + freerdp_set_clipboard_redirection(inst, true); + return true; } @@ -177,6 +183,11 @@ public static void sendUnicodeKeyEvent(int inst, int keycode) freerdp_send_unicodekey_event(inst, keycode); } + public static void sendClipboardData(int inst, String data) + { + freerdp_send_clipboard_data(inst, data); + } + private static void OnConnectionSuccess(int inst) { if (listener != null) @@ -252,7 +263,17 @@ private static void OnGraphicsResize(int inst, int width, int height, int bpp) if (uiEventListener != null) uiEventListener.OnGraphicsResize(width, height, bpp); } - + + private static void OnRemoteClipboardChanged(int inst, String data) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnRemoteClipboardChanged(data); + } + public static String getVersion() { return freerdp_get_version(); diff --git a/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java new file mode 100644 index 000000000000..83f6ca5623b2 --- /dev/null +++ b/client/Android/FreeRDPCore/src/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java @@ -0,0 +1,89 @@ +package com.freerdp.freerdpcore.utils; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; + +public abstract class ClipboardManagerProxy { + + public static ClipboardManagerProxy getClipboardManager(Context ctx) { + if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + return new PreHCClipboardManager(ctx); + else + return new HCClipboardManager(ctx); + } + + public static interface OnClipboardChangedListener { + void onClipboardChanged(String data); + } + + public abstract void setClipboardData(String data); + public abstract void addClipboardChangedListener(OnClipboardChangedListener listener); + public abstract void removeClipboardboardChangedListener(OnClipboardChangedListener listener); + + private static class PreHCClipboardManager extends ClipboardManagerProxy { + + public PreHCClipboardManager(Context ctx) { + + } + + @Override + public void setClipboardData(String data) { + } + + @Override + public void addClipboardChangedListener( + OnClipboardChangedListener listener) { + } + + @Override + public void removeClipboardboardChangedListener( + OnClipboardChangedListener listener) { + } + } + + @TargetApi(11) + private static class HCClipboardManager extends ClipboardManagerProxy implements ClipboardManager.OnPrimaryClipChangedListener { + private ClipboardManager mClipboardManager; + private OnClipboardChangedListener mListener; + + public HCClipboardManager(Context ctx) { + mClipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); + } + + @Override + public void setClipboardData(String data) { + mClipboardManager.setPrimaryClip(ClipData.newPlainText("rdp-clipboard", data == null ? "" : data)); + } + + @Override + public void onPrimaryClipChanged() { + ClipData clip = mClipboardManager.getPrimaryClip(); + String data = null; + + if (clip != null && clip.getItemCount() > 0) { + data = clip.getItemAt(0).getText().toString(); + } + if (mListener != null) { + mListener.onClipboardChanged(data); + } + } + + @Override + public void addClipboardChangedListener( + OnClipboardChangedListener listener) { + mListener = listener; + mClipboardManager.addPrimaryClipChangedListener(this); + } + + @Override + public void removeClipboardboardChangedListener( + OnClipboardChangedListener listener) { + mListener = null; + mClipboardManager.removePrimaryClipChangedListener(this); + } + } +} diff --git a/client/Android/aFreeRDP/project.properties b/client/Android/aFreeRDP/project.properties index 857d31de6331..4507b18461f1 100644 --- a/client/Android/aFreeRDP/project.properties +++ b/client/Android/aFreeRDP/project.properties @@ -8,6 +8,6 @@ # project structure. # Project target. -target=android-8 +target=android-11 android.library.reference.1=../FreeRDPCore manifestmerger.enabled=true