From 8dd31e8e10fc3ca10192368acf19d2345eeddde7 Mon Sep 17 00:00:00 2001 From: Vladimir Chtchetkine Date: Wed, 15 Feb 2012 17:16:04 -0800 Subject: Multi-touch emulation support Change-Id: I311dc55fe10f41637775baa330a7c732ff317f51 --- android/multitouch-port.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 android/multitouch-port.c (limited to 'android/multitouch-port.c') diff --git a/android/multitouch-port.c b/android/multitouch-port.c new file mode 100644 index 0000000..c8e3ca5 --- /dev/null +++ b/android/multitouch-port.c @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 "qemu-common.h" +#include "utils/panic.h" +#include "android/hw-events.h" +#include "android/charmap.h" +#include "android/multitouch-screen.h" +#include "android/multitouch-port.h" +#include "android/globals.h" /* for android_hw */ +#include "android/utils/misc.h" +#include "android/utils/jpeg-compress.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(mtport,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(mtport) + +/* Query timeout in milliseconds. */ +#define MTSP_QUERY_TIMEOUT 3000 +#define MTSP_MAX_MSG 2048 +#define MTSP_MAX_EVENT 2048 + +/* Multi-touch port descriptor. */ +struct AndroidMTSPort { + /* Caller identifier. */ + void* opaque; + /* Connected android device. */ + AndroidDevice* device; + /* Initialized JPEG compressor instance. */ + AJPEGDesc* jpeg_compressor; + /* Connection status: 1 connected, 0 - disconnected. */ + int is_connected; + /* Buffer where to receive multitouch messages. */ + char mts_msg[MTSP_MAX_MSG]; + /* Buffer where to receive multitouch events. */ + char events[MTSP_MAX_EVENT]; +}; + +/* Destroys and frees the descriptor. */ +static void +_mts_port_free(AndroidMTSPort* mtsp) +{ + if (mtsp != NULL) { + if (mtsp->jpeg_compressor != NULL) { + jpeg_compressor_destroy(mtsp->jpeg_compressor); + } + if (mtsp->device != NULL) { + android_device_destroy(mtsp->device); + } + AFREE(mtsp); + } +} + +/******************************************************************************** + * Multi-touch action handlers + *******************************************************************************/ + +/* + * Although there are a lot of similarities in the way the handlers below are + * implemented, for the sake of tracing / debugging it's better to have a + * separate handler for each distinctive action. + */ + +/* First pointer down event handler. */ +static void +_on_action_down(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/* Last pointer up event handler. */ +static void +_on_action_up(int tracking_id) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, 0, 0, 0); +} + +/* Pointer down event handler. */ +static void +_on_action_pointer_down(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/* Pointer up event handler. */ +static void +_on_action_pointer_up(int tracking_id) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, 0, 0, 0); +} + +/* Pointer move event handler. */ +static void +_on_action_move(int tracking_id, int x, int y, int pressure) +{ + multitouch_update_pointer(MTES_DEVICE, tracking_id, x, y, pressure); +} + +/******************************************************************************** + * Multi-touch event handlers + *******************************************************************************/ + +/* Handles "pointer move" event. */ +static void +_on_move(const char* param) +{ + const char* pid = param; + D(">>> MOVE: %s", param); + while (pid && *pid) { + int pid_val, x, y, pressure = 0; + if (!get_token_value_int(pid, "pid", &pid_val) && + !get_token_value_int(pid, "x", &x) && + !get_token_value_int(pid, "y", &y)) { + get_token_value_int(pid, "pressure", &pressure); + _on_action_move(pid_val, x, y, pressure); + pid = strstr(pid + 1, "pid"); + } else { + break; + } + } +} + +/* Handles "first pointer down" event. */ +static void +_on_down(const char* param) +{ + int pid_val, x, y, pressure = 0; + D(">>> 1-ST DOWN: %s", param); + if (!get_token_value_int(param, "pid", &pid_val) && + !get_token_value_int(param, "x", &x) && + !get_token_value_int(param, "y", &y)) { + get_token_value_int(param, "pressure", &pressure); + _on_action_down(pid_val, x, y, pressure); + } else { + W("Invalid parameters '%s' for MTS 'down' event", param); + } +} + +/* Handles "last pointer up" event. */ +static void +_on_up(const char* param) +{ + int pid_val; + D(">>> LAST UP: %s", param); + if (!get_token_value_int(param, "pid", &pid_val)) { + _on_action_up(pid_val); + } else { + W("Invalid parameters '%s' for MTS 'up' event", param); + } +} + +/* Handles "next pointer down" event. */ +static void +_on_pdown(const char* param) +{ + int pid_val, x, y, pressure = 0; + D(">>> DOWN: %s", param); + if (!get_token_value_int(param, "pid", &pid_val) && + !get_token_value_int(param, "x", &x) && + !get_token_value_int(param, "y", &y)) { + get_token_value_int(param, "pressure", &pressure); + _on_action_pointer_down(pid_val, x, y, pressure); + } else { + W("Invalid parameters '%s' for MTS 'pointer down' event", param); + } +} + +/* Handles "next pointer up" event. */ +static void +_on_pup(const char* param) +{ + int pid_val; + D(">>> UP: %s", param); + if (!get_token_value_int(param, "pid", &pid_val)) { + _on_action_pointer_up(pid_val); + } else { + W("Invalid parameters '%s' for MTS 'up' event", param); + } +} + +/******************************************************************************** + * Device communication callbacks + *******************************************************************************/ + +/* Main event handler. + * This routine is invoked when an event message has been received from the + * device. + */ +static void +_on_event_received(void* opaque, AndroidDevice* ad, char* msg, int msgsize) +{ + char* action; + int res; + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + + if (errno) { + D("Multi-touch notification has failed: %s", strerror(errno)); + return; + } + + /* Dispatch the event to an appropriate handler. */ + res = get_token_value_alloc(msg, "action", &action); + if (!res) { + const char* param = strchr(msg, ' '); + if (param) { + param++; + } + if (!strcmp(action, "move")) { + _on_move(param); + } else if (!strcmp(action, "down")) { + _on_down(param); + } else if (!strcmp(action, "up")) { + _on_up(param); + } else if (!strcmp(action, "pdown")) { + _on_pdown(param); + } else if (!strcmp(action, "pup")) { + _on_pup(param); + } else { + D("Unknown multi-touch event action '%s'", action); + } + free(action); + } + + /* Listen to the next event. */ + android_device_listen(ad, mtsp->events, sizeof(mtsp->events), + _on_event_received); +} + +/* A callback that is invoked when android device is connected (i.e. both, + * command and event channels have been established). + * Param: + * opaque - AndroidMTSPort instance. + * ad - Android device used by this port. + * failure - Connections status. + */ +static void +_on_device_connected(void* opaque, AndroidDevice* ad, int failure) +{ + if (!failure) { + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + mtsp->is_connected = 1; + D("Multi-touch emulation has started"); + android_device_listen(mtsp->device, mtsp->events, sizeof(mtsp->events), + _on_event_received); + mts_port_start(mtsp); + } +} + +/* Invoked when an I/O failure occurs on a socket. + * Note that this callback will not be invoked on connection failures. + * Param: + * opaque - AndroidMTSPort instance. + * ad - Android device instance + * ads - Connection socket where failure has occured. + * failure - Contains 'errno' indicating the reason for failure. + */ +static void +_on_io_failure(void* opaque, AndroidDevice* ad, int failure) +{ + AndroidMTSPort* mtsp = (AndroidMTSPort*)opaque; + E("Multi-touch port got disconnected: %s", strerror(failure)); + mtsp->is_connected = 0; + android_device_disconnect(ad); + + /* Try to reconnect again. */ + android_device_connect_async(ad, _on_device_connected); +} + +/******************************************************************************** + * MTS port API + *******************************************************************************/ + +AndroidMTSPort* +mts_port_create(void* opaque) +{ + AndroidMTSPort* mtsp; + int res; + + ANEW0(mtsp); + mtsp->opaque = opaque; + mtsp->is_connected = 0; + + /* Initialize default MTS descriptor. */ + multitouch_init(mtsp); + + /* Create JPEG compressor. Put "$BLOB:%09d\0" + MTFrameHeader header in front + * of the compressed data. this way we will have entire query ready to be + * transmitted to the device. */ + mtsp->jpeg_compressor = jpeg_compressor_create(16 + sizeof(MTFrameHeader), 4096); + + mtsp->device = android_device_init(mtsp, AD_MULTITOUCH_PORT, _on_io_failure); + if (mtsp->device == NULL) { + _mts_port_free(mtsp); + return NULL; + } + + res = android_device_connect_async(mtsp->device, _on_device_connected); + if (res != 0) { + mts_port_destroy(mtsp); + return NULL; + } + + return mtsp; +} + +void +mts_port_destroy(AndroidMTSPort* mtsp) +{ + _mts_port_free(mtsp); +} + +int +mts_port_is_connected(AndroidMTSPort* mtsp) +{ + return mtsp->is_connected; +} + +int +mts_port_start(AndroidMTSPort* mtsp) +{ + char qresp[MTSP_MAX_MSG]; + char query[256]; + AndroidHwConfig* config = android_hw; + + /* Query the device to start capturing multi-touch events, also providing + * the device with width / height of the emulator's screen. This is required + * so device can properly adjust multi-touch event coordinates, and display + * emulator's framebuffer. */ + snprintf(query, sizeof(query), "start:%dx%d", + config->hw_lcd_width, config->hw_lcd_height); + int res = android_device_query(mtsp->device, query, qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + if (!res) { + /* By protocol device should reply with its view dimensions. */ + if (*qresp) { + int width, height; + if (sscanf(qresp, "%dx%d", &width, &height) == 2) { + multitouch_set_device_screen_size(width, height); + D("Multi-touch emulation has started. Device dims: %dx%d", + width, height); + } else { + E("Unexpected reply to MTS 'start' query: %s", qresp); + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + res = -1; + } + } else { + E("MTS protocol error: no reply to query 'start'"); + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + res = -1; + } + } else { + if (errno) { + D("Query 'start' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'start' failed on device: %s", qresp); + } + } + return res; +} + +int +mts_port_stop(AndroidMTSPort* mtsp) +{ + char qresp[MTSP_MAX_MSG]; + const int res = + android_device_query(mtsp->device, "stop", qresp, sizeof(qresp), + MTSP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query 'stop' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'stop' failed on device: %s", qresp); + } + } + + return res; +} + +/******************************************************************************** + * Handling framebuffer updates + *******************************************************************************/ + +/* Compresses a framebuffer region into JPEG image. + * Param: + * mtsp - Multi-touch port descriptor with initialized JPEG compressor. + * fmt Descriptor for framebuffer region to compress. + * fb Beginning of the framebuffer. + * jpeg_quality JPEG compression quality. A number from 1 to 100. Note that + * value 10 provides pretty decent image for the purpose of multi-touch + * emulation. + */ +static void +_fb_compress(const AndroidMTSPort* mtsp, + const MTFrameHeader* fmt, + const uint8_t* fb, + int jpeg_quality) +{ + jpeg_compressor_compress_fb(mtsp->jpeg_compressor, fmt->x, fmt->y, fmt->w, + fmt->h, fmt->bpp, fmt->bpl, fb, jpeg_quality); +} + +int +mts_port_send_frame(AndroidMTSPort* mtsp, + MTFrameHeader* fmt, + const uint8_t* fb, + async_send_cb cb, + void* cb_opaque) +{ + char* query; + int blob_size, off; + + /* Make sure that port is connected. */ + if (!mts_port_is_connected(mtsp)) { + return -1; + } + + /* Compress framebuffer region. 10% quality seems to be sufficient. */ + fmt->format = MTFB_JPEG; + _fb_compress(mtsp, fmt, fb, 10); + + /* Total size of the blob: header + JPEG image. */ + blob_size = sizeof(MTFrameHeader) + + jpeg_compressor_get_jpeg_size(mtsp->jpeg_compressor); + + /* Query starts at the beginning of the buffer allocated by the compressor's + * destination manager. */ + query = (char*)jpeg_compressor_get_buffer(mtsp->jpeg_compressor); + + /* Build the $BLOB query to transfer to the device. */ + snprintf(query, jpeg_compressor_get_header_size(mtsp->jpeg_compressor), + "$BLOB:%09d", blob_size); + off = strlen(query) + 1; + + /* Copy framebuffer update header to the query. */ + memcpy(query + off, fmt, sizeof(MTFrameHeader)); + + /* Zeroing the rectangle in the update header we indicate that it contains + * no updates. */ + fmt->x = fmt->y = fmt->w = fmt->h = 0; + + /* Initiate asynchronous transfer of the updated framebuffer rectangle. */ + if (android_device_send_async(mtsp->device, query, off + blob_size, 0, cb, cb_opaque)) { + D("Unable to send query '%s': %s", query, strerror(errno)); + return -1; + } + + return 0; +} -- cgit v1.1