aboutsummaryrefslogtreecommitdiffstats
path: root/android/multitouch-port.c
diff options
context:
space:
mode:
authorVladimir Chtchetkine <vchtchetkine@google.com>2012-02-15 17:16:04 -0800
committerVladimir Chtchetkine <vchtchetkine@google.com>2012-03-05 14:14:39 -0800
commit8dd31e8e10fc3ca10192368acf19d2345eeddde7 (patch)
tree7c469657616d59c3a2723ec446f528d4bbaee56d /android/multitouch-port.c
parentbf45fc2a26358b7ec52cb17bb9d6ed67d6483b01 (diff)
downloadexternal_qemu-8dd31e8e10fc3ca10192368acf19d2345eeddde7.zip
external_qemu-8dd31e8e10fc3ca10192368acf19d2345eeddde7.tar.gz
external_qemu-8dd31e8e10fc3ca10192368acf19d2345eeddde7.tar.bz2
Multi-touch emulation support
Change-Id: I311dc55fe10f41637775baa330a7c732ff317f51
Diffstat (limited to 'android/multitouch-port.c')
-rw-r--r--android/multitouch-port.c464
1 files changed, 464 insertions, 0 deletions
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;
+}