aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.common5
-rw-r--r--android/android-device.c74
-rw-r--r--android/avd/hardware-properties.ini7
-rw-r--r--android/cmdline-options.h2
-rw-r--r--android/help.c15
-rw-r--r--android/main.c24
-rw-r--r--android/multitouch-port.c464
-rw-r--r--android/multitouch-port.h132
-rw-r--r--android/multitouch-screen.c422
-rw-r--r--android/multitouch-screen.h68
-rw-r--r--android/qemulator.c3
-rw-r--r--android/utils/debug.h2
-rw-r--r--android/utils/jpeg-compress.c197
-rw-r--r--android/utils/jpeg-compress.h93
-rw-r--r--hw/goldfish_events_device.c88
-rw-r--r--linux_keycodes.h56
-rw-r--r--vl-android.c6
17 files changed, 1602 insertions, 56 deletions
diff --git a/Makefile.common b/Makefile.common
index 8b98df8..54ab704 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -442,7 +442,10 @@ CORE_MISC_SOURCES = \
android/camera/camera-service.c \
android/adb-server.c \
android/adb-qemud.c \
- android/snaphost-android.c
+ android/snaphost-android.c \
+ android/multitouch-screen.c \
+ android/multitouch-port.c \
+ android/utils/jpeg-compress.c
$(call gen-hw-config-defs)
diff --git a/android/android-device.c b/android/android-device.c
index 37af0c6..498e304 100644
--- a/android/android-device.c
+++ b/android/android-device.c
@@ -581,35 +581,60 @@ _android_dev_socket_recv(AndroidDevSocket* ads, char* buf, int bufsize)
return -1;
}
- iolooper_add_read(_ads_io_looper(ads), ads->fd);
- do {
- int res = socket_recv(ads->fd, buf + recvd, bufsize - recvd);
- if (res == 0) {
- /* Disconnection. */
- errno = ECONNRESET;
- recvd = -1;
- break;
+ /* XXX: This is a hack that implements a blocked line read on an async
+ * event socket! Redo this ASAP! */
+ if (ads->type == ADS_TYPE_EVENT) {
+ AndroidEventSocket* adsevent = (AndroidEventSocket*)ads;
+ asyncLineReader_init(&adsevent->alr, buf, bufsize, adsevent->io);
+ /* Default EOL for the line reader was '\n'. */
+ asyncLineReader_setEOL(&adsevent->alr, '\0');
+ AsyncStatus status = ASYNC_NEED_MORE;
+
+ while (status == ASYNC_NEED_MORE) {
+ status = asyncLineReader_read(&adsevent->alr);
+ if (status == ASYNC_COMPLETE) {
+ recvd = adsevent->alr.pos;
+ break;
+ } else if (status == ASYNC_ERROR) {
+ if (errno == ENOMEM) {
+ recvd = adsevent->alr.pos;
+ } else {
+ recvd = -1;
+ }
+ break;
+ }
}
-
- if (res < 0) {
- if (errno == EINTR) {
- /* loop on EINTR */
- continue;
+ } else {
+ iolooper_add_read(_ads_io_looper(ads), ads->fd);
+ do {
+ int res = socket_recv(ads->fd, buf + recvd, bufsize - recvd);
+ if (res == 0) {
+ /* Disconnection. */
+ errno = ECONNRESET;
+ recvd = -1;
+ break;
}
- if (errno == EWOULDBLOCK || errno == EAGAIN) {
- res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline);
- if (res > 0) {
- /* Ready to read. */
+ if (res < 0) {
+ if (errno == EINTR) {
+ /* loop on EINTR */
continue;
}
+
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline);
+ if (res > 0) {
+ /* Ready to read. */
+ continue;
+ }
+ }
+ recvd = -1;
+ break;
}
- recvd = -1;
- break;
- }
- recvd += res;
- } while (recvd < bufsize);
- iolooper_del_read(_ads_io_looper(ads), ads->fd);
+ recvd += res;
+ } while (recvd < bufsize);
+ iolooper_del_read(_ads_io_looper(ads), ads->fd);
+ }
/* In case of an I/O failure we have to invoke failure callback. Note that we
* report I/O failures only on registered sockets. */
@@ -1108,6 +1133,7 @@ _on_event_socket_io(void* opaque, int fd, unsigned events)
/* Continue reading data. */
status = asyncLineReader_read(&adsevent->alr);
if (status == ASYNC_COMPLETE) {
+ errno = 0;
_on_event_received(adsevent);
} else if (status == ASYNC_ERROR) {
D("I/O failure while reading from channel '%s'@%d: %s",
@@ -1153,6 +1179,7 @@ _on_event_socket_io(void* opaque, int fd, unsigned events)
return;
} else if (sent == to_send->data_remaining) {
/* All data is sent. */
+ errno = 0;
adsevent->send_pending = to_send->next;
_async_send_buffer_complete(to_send, ATR_SUCCESS);
} else {
@@ -1182,6 +1209,7 @@ _on_event_socket_connected(AndroidEventSocket* adsevent, int failure)
/* Complete event socket connection by identifying it as "event" socket with
* the application. */
res = _android_dev_socket_register(ads);
+
if (res) {
const int save_error = errno;
_android_event_socket_disconnect(adsevent);
diff --git a/android/avd/hardware-properties.ini b/android/avd/hardware-properties.ini
index ebf2450..4859680 100644
--- a/android/avd/hardware-properties.ini
+++ b/android/avd/hardware-properties.ini
@@ -53,6 +53,13 @@ default = yes
abstract = Touch-screen support
description = Whether there is a touch screen or not on the device.
+# Multi-touch screen support
+name = hw.multiTouch
+type = boolean
+default = no
+abstract = Multi-touch screen support
+description = Whether there is a multi-touch screen or not on the device.
+
# Hardware main keys (back/home)
name = hw.mainKeys
type = boolean
diff --git a/android/cmdline-options.h b/android/cmdline-options.h
index 0cf358f..d490e40 100644
--- a/android/cmdline-options.h
+++ b/android/cmdline-options.h
@@ -159,6 +159,8 @@ OPT_PARAM( gpu, "<mode>", "set hardware OpenGLES emulation mode" )
OPT_PARAM( fake_camera, "<mode>", "set fake camera emulation mode" )
OPT_LIST( webcam, "name=<name>[,dir=<direction>]", "setup web camera emulation" )
+OPT_PARAM( screen, "<mode>", "set emulated screen mode" )
+
#undef CFG_FLAG
#undef CFG_PARAM
#undef OPT_FLAG
diff --git a/android/help.c b/android/help.c
index 5f151ca..db46d21 100644
--- a/android/help.c
+++ b/android/help.c
@@ -1488,6 +1488,21 @@ help_webcam(stralloc_t* out)
);
}
+static void
+help_screen(stralloc_t* out)
+{
+ PRINTF(
+ " Use -screen <mode> to set the emulated screen mode.\n"
+ " Valid values for <mode> are:\n\n"
+
+ " touch -> emulate a touch screen\n"
+ " multi-touch -> emulate a multi-touch screen\n"
+ " off -> disable touch and multi-touch screen emulation\n\n"
+
+ " Default mode for screen emulation is 'touch'.\n\n"
+ );
+}
+
#define help_no_skin NULL
#define help_netspeed help_shaper
#define help_netdelay help_shaper
diff --git a/android/main.c b/android/main.c
index b7421a1..2723834 100644
--- a/android/main.c
+++ b/android/main.c
@@ -1271,6 +1271,30 @@ int main(int argc, char **argv)
args[n++] = "socket,vlan=1,mcast=230.0.0.10:1234";
}
+ /* Setup screen emulation */
+ if (opts->screen) {
+ if (!strcmp(opts->screen, "touch")) {
+ hw->hw_touchScreen = 1;
+ hw->hw_multiTouch = 0;
+ } else if (!strcmp(opts->screen, "multi-touch")) {
+ hw->hw_multiTouch = 1;
+ hw->hw_touchScreen = 0;
+ } else if (!strcmp(opts->screen, "off")) {
+ hw->hw_touchScreen = 0;
+ hw->hw_multiTouch = 0;
+ } else {
+ derror("Invalid value for -screen <mode> parameter: %s\n", opts->screen);
+ derror("Valid values are: touch, multi-touch, or off\n");
+ exit(1);
+ }
+ } else {
+ /* If both, touch and multitouch are set in hw.ini, choose multi-touch
+ * for screen emulation. */
+ if (hw->hw_touchScreen && hw->hw_multiTouch) {
+ hw->hw_touchScreen = 0;
+ }
+ }
+
while(argc-- > 0) {
args[n++] = *argv++;
}
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;
+}
diff --git a/android/multitouch-port.h b/android/multitouch-port.h
new file mode 100644
index 0000000..553617b
--- /dev/null
+++ b/android/multitouch-port.h
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_ANDROID_MULTITOUCH_PORT_H_
+#define ANDROID_ANDROID_MULTITOUCH_PORT_H_
+
+/*
+ * Encapsulates exchange protocol between the multi-touch screen emulator, and an
+ * application running on an Android device that provides touch events, and is
+ * connected to the host via USB.
+ */
+
+#include "android/android-device.h"
+
+/* TCP port reserved for multi-touch emulation. */
+#define AD_MULTITOUCH_PORT 1969
+
+/*
+ * Codes that define transmitted framebuffer format:
+ *
+ * NOTE: Application on the device side depends on these values. Any changes
+ * made here must be reflected in the app too. Application location is at
+ * 'sdk/apps/SdkController/SdkControllerMultitouch' root.
+ */
+
+/* Framebuffer is transmitted as JPEG. */
+#define MTFB_JPEG 1
+/* Framebuffer is transmitted as raw RGB565 bitmap. */
+#define MTFB_RGB565 2
+/* Framebuffer is transmitted as raw RGB888 bitmap. */
+#define MTFB_RGB888 3
+
+/* Framebuffer update descriptor.
+ * This descriptor is used to collect properties of the updated framebuffer
+ * region. This descriptor is also sent to the MT emulation application on the
+ * device, so it can properly redraw its screen.
+ *
+ * NOTE: Application on the device side depends on that structure. Any changes
+ * made here must be reflected in the app too. Application location is at
+ * 'sdk/apps/SdkController/SdkControllerMultitouch' root.
+ */
+typedef struct MTFrameHeader {
+ /* Size of the header. Must be always sizeof(MTFrameHeader). */
+ int header_size;
+ /* Display width */
+ int disp_width;
+ /* Display height */
+ int disp_height;
+ /* x, y, w, and h define framebuffer region that has been updated. */
+ int x;
+ int y;
+ int w;
+ int h;
+ /* Bytes per line in the framebufer. */
+ int bpl;
+ /* Bytes per pixel in the framebufer. */
+ int bpp;
+ /* Defines format in which framebuffer is transmitted to the device. */
+ int format;
+} MTFrameHeader;
+
+/* Declares multi-touch port descriptor. */
+typedef struct AndroidMTSPort AndroidMTSPort;
+
+/* Creates multi-touch port, and connects it to the device.
+ * Param:
+ * opaque - An opaque pointer that is passed back to the callback routines.
+ * Return:
+ * Initialized device descriptor on success, or NULL on failure. If failure is
+ * returned from this routine, 'errno' indicates the reason for failure. If this
+ * routine successeds, a connection is established with the sensor reading
+ * application on the device.
+ */
+extern AndroidMTSPort* mts_port_create(void* opaque);
+
+/* Disconnects from the multi-touch port, and destroys the descriptor. */
+extern void mts_port_destroy(AndroidMTSPort* amtp);
+
+/* Checks if port is connected to a MT-emulating application on the device.
+ * Note that connection can go out and then be restored at any time after
+ * mts_port_create API succeeded.
+ */
+extern int mts_port_is_connected(AndroidMTSPort* amtp);
+
+/* Queries the connected application to start delivering multi-touch events.
+ * Param:
+ * amtp - Android multi-touch port instance returned from mts_port_create.
+ * Return:
+ * Zero on success, failure otherwise.
+ */
+extern int mts_port_start(AndroidMTSPort* amtp);
+
+/* Queries the connected application to stop delivering multi-touch events.
+ * Param:
+ * amtp - Android multi-touch port instance returned from mts_port_create.
+ * Return:
+ * Zero on success, failure otherwise.
+ */
+extern int mts_port_stop(AndroidMTSPort* amtp);
+
+/* Sends framebuffer update to the multi-touch emulation application, running on
+ * the android device.
+ * Param:
+ * mtsp - Android multi-touch port instance returned from mts_port_create.
+ * fmt - Framebuffer update descriptor.
+ * fb - Beginning of the framebuffer.
+ * cb - Callback to invoke when update has been transferred to the MT-emulating
+ * application on the device.
+ * cb_opaque - An opaque parameter to pass back to the 'cb' callback.
+ * Return:
+ * 0 on success, or != 0 on failure.
+ */
+extern int mts_port_send_frame(AndroidMTSPort* mtsp,
+ MTFrameHeader* fmt,
+ const uint8_t* fb,
+ async_send_cb cb,
+ void* cb_opaque);
+
+#endif /* ANDROID_ANDROID_MULTITOUCH_PORT_H_ */
diff --git a/android/multitouch-screen.c b/android/multitouch-screen.c
new file mode 100644
index 0000000..e1a9a8a
--- /dev/null
+++ b/android/multitouch-screen.c
@@ -0,0 +1,422 @@
+/*
+ * 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 "user-events.h"
+#include "android/display-core.h"
+#include "android/hw-events.h"
+#include "android/charmap.h"
+#include "android/globals.h" /* for android_hw */
+#include "android/utils/misc.h"
+#include "android/utils/debug.h"
+#include "android/multitouch-screen.h"
+
+#define E(...) derror(__VA_ARGS__)
+#define W(...) dwarning(__VA_ARGS__)
+#define D(...) VERBOSE_PRINT(mtscreen,__VA_ARGS__)
+#define D_ACTIVE VERBOSE_CHECK(mtscreen)
+
+/* Maximum number of pointers, supported by multi-touch emulation. */
+#define MTS_POINTERS_NUM 10
+/* Signals that pointer is not tracked (or is "up"). */
+#define MTS_POINTER_UP -1
+/* Special tracking ID for a mouse pointer. */
+#define MTS_POINTER_MOUSE -2
+
+/* Describes state of a multi-touch pointer */
+typedef struct MTSPointerState {
+ /* Tracking ID assigned to the pointer by an app emulating multi-touch. */
+ int tracking_id;
+ /* X - coordinate of the tracked pointer. */
+ int x;
+ /* Y - coordinate of the tracked pointer. */
+ int y;
+ /* Current pressure value. */
+ int pressure;
+} MTSPointerState;
+
+/* Describes state of an emulated multi-touch screen. */
+typedef struct MTSState {
+ /* Multi-touch port connected to the device. */
+ AndroidMTSPort* mtsp;
+ /* Emulator's display state. */
+ DisplayState* ds;
+ /* Screen width of the device that emulates multi-touch. */
+ int device_width;
+ /* Screen height of the device that emulates multi-touch. */
+ int device_height;
+ /* Number of tracked pointers. */
+ int tracked_ptr_num;
+ /* Index in the 'tracked_pointers' array of the last pointer for which
+ * ABS_MT_SLOT was sent. -1 indicates that no slot selection has been made
+ * yet. */
+ int current_slot;
+ /* Accumulator for ABS_TOUCH_MAJOR value. */
+ int touch_major;
+ /* Array of multi-touch pointers. */
+ MTSPointerState tracked_pointers[MTS_POINTERS_NUM];
+ /* Header collecting framebuffer information and updates. */
+ MTFrameHeader fb_header;
+ /* Boolean value indicating if framebuffer updates are currently being
+ * transferred to the application running on the device. */
+ int fb_transfer_in_progress;
+} MTSState;
+
+/* Default multi-touch screen descriptor */
+static MTSState _MTSState;
+
+/* Pushes event to the event device. */
+static void
+_push_event(int type, int code, int value)
+{
+ user_event_generic(type, code, value);
+}
+
+/* Gets an index in the MTS's tracking pointers array MTS for the given
+ * tracking id.
+ * Return:
+ * Index of a matching entry in the MTS's tracking pointers array, or -1 if
+ * matching entry was not found.
+ */
+static int
+_mtsstate_get_pointer_index(const MTSState* mts_state, int tracking_id)
+{
+ int index;
+ for (index = 0; index < MTS_POINTERS_NUM; index++) {
+ if (mts_state->tracked_pointers[index].tracking_id == tracking_id) {
+ return index;
+ }
+ }
+ return -1;
+}
+
+/* Gets an index of the first untracking pointer in the MTS's tracking pointers
+ * array.
+ * Return:
+ * An index of the first untracking pointer, or -1 if all pointers are tracked.
+ */
+static int
+_mtsstate_get_available_pointer_index(const MTSState* mts_state)
+{
+ return _mtsstate_get_pointer_index(mts_state, MTS_POINTER_UP);
+}
+
+/* Handles a "pointer down" event
+ * Param:
+ * mts_state - MTS state descriptor.
+ * tracking_id - Tracking ID of the "downed" pointer.
+ * x, y - "Downed" pointer coordinates,
+ * pressure - Pressure value for the pointer.
+ */
+static void
+_mts_pointer_down(MTSState* mts_state, int tracking_id, int x, int y, int pressure)
+{
+ /* Get first available slot for the new pointer. */
+ const int slot_index = _mtsstate_get_available_pointer_index(mts_state);
+
+ /* Make sure there is a place for the pointer. */
+ if (slot_index >= 0) {
+ /* Initialize pointer's entry. */
+ mts_state->tracked_ptr_num++;
+ mts_state->tracked_pointers[slot_index].tracking_id = tracking_id;
+ mts_state->tracked_pointers[slot_index].x = x;
+ mts_state->tracked_pointers[slot_index].y = y;
+ mts_state->tracked_pointers[slot_index].pressure = pressure;
+
+ /* Send events indicating a "pointer down" to the EventHub */
+ /* Make sure that correct slot is selected. */
+ if (slot_index != mts_state->current_slot) {
+ _push_event(EV_ABS, ABS_MT_SLOT, slot_index);
+ }
+ _push_event(EV_ABS, ABS_MT_TRACKING_ID, slot_index);
+ _push_event(EV_ABS, ABS_MT_TOUCH_MAJOR, ++mts_state->touch_major);
+ _push_event(EV_ABS, ABS_MT_PRESSURE, pressure);
+ _push_event(EV_ABS, ABS_MT_POSITION_X, x);
+ _push_event(EV_ABS, ABS_MT_POSITION_Y, y);
+ _push_event(EV_SYN, SYN_REPORT, 0);
+ mts_state->current_slot = slot_index;
+ } else {
+ D("MTS pointer count is exceeded.");
+ return;
+ }
+}
+
+/* Handles a "pointer up" event
+ * Param:
+ * mts_state - MTS state descriptor.
+ * slot_index - Pointer's index in the MTS's array of tracked pointers.
+ */
+static void
+_mts_pointer_up(MTSState* mts_state, int slot_index)
+{
+ /* Make sure that correct slot is selected. */
+ if (slot_index != mts_state->current_slot) {
+ _push_event(EV_ABS, ABS_MT_SLOT, slot_index);
+ }
+
+ /* Send event indicating "pointer up" to the EventHub. */
+ _push_event(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ _push_event(EV_SYN, SYN_REPORT, 0);
+
+ /* Update MTS descriptor, removing the tracked pointer. */
+ mts_state->tracked_pointers[slot_index].tracking_id = MTS_POINTER_UP;
+ mts_state->tracked_pointers[slot_index].x = 0;
+ mts_state->tracked_pointers[slot_index].y = 0;
+ mts_state->tracked_pointers[slot_index].pressure = 0;
+
+ /* Since current slot is no longer tracked, make sure we will do a "select"
+ * next time we send events to the EventHub. */
+ mts_state->current_slot = -1;
+ mts_state->tracked_ptr_num--;
+ assert(mts_state->tracked_ptr_num >= 0);
+}
+
+/* Handles a "pointer move" event
+ * Param:
+ * mts_state - MTS state descriptor.
+ * slot_index - Pointer's index in the MTS's array of tracked pointers.
+ * x, y - New pointer coordinates,
+ * pressure - Pressure value for the pointer.
+ */
+static void
+_mts_pointer_move(MTSState* mts_state, int slot_index, int x, int y, int pressure)
+{
+ MTSPointerState* ptr_state = &mts_state->tracked_pointers[slot_index];
+
+ /* Make sure that coordinates have really changed. */
+ if (ptr_state->x == x && ptr_state->y == y) {
+ /* Coordinates didn't change. Bail out. */
+ return;
+ }
+
+ /* Make sure that the right slot is selected. */
+ if (slot_index != mts_state->current_slot) {
+ _push_event(EV_ABS, ABS_MT_SLOT, slot_index);
+ mts_state->current_slot = slot_index;
+ }
+
+ /* Push the changes down. */
+ if (ptr_state->pressure != pressure && pressure != 0) {
+ _push_event(EV_ABS, ABS_MT_PRESSURE, pressure);
+ ptr_state->pressure = pressure;
+ }
+ if (ptr_state->x != x) {
+ _push_event(EV_ABS, ABS_MT_POSITION_X, x);
+ ptr_state->x = x;
+ }
+ if (ptr_state->y != y) {
+ _push_event(EV_ABS, ABS_MT_POSITION_Y, y);
+ ptr_state->y = y;
+ }
+ _push_event(EV_SYN, SYN_REPORT, 0);
+}
+
+/********************************************************************************
+ * Multi-touch API
+ *******************************************************************************/
+
+/* Callback that is invoked when framebuffer update has been transmitted to the
+ * device. */
+static void
+_on_fb_sent(void* opaque, ATResult res, void* data, int size, int sent)
+{
+ MTSState* const mts_state = (MTSState*)opaque;
+
+ /* Lets see if we have accumulated more changes while transmission has been
+ * in progress. */
+ if (mts_state->fb_header.w && mts_state->fb_header.h) {
+ /* Send accumulated updates. */
+ if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header,
+ mts_state->ds->surface->data, _on_fb_sent,
+ mts_state)) {
+ mts_state->fb_transfer_in_progress = 0;
+ }
+ } else {
+ /* Framebuffer transfer is completed, and no more updates are pending. */
+ mts_state->fb_transfer_in_progress = 0;
+ }
+}
+
+/* A callback invoked on framebuffer updates.
+ * Param:
+ * opaque - MTSState instance.
+ * x, y, w, h - Defines an updated rectangle inside the framebuffer.
+ */
+static void
+_mt_fb_update(void* opaque, int x, int y, int w, int h)
+{
+ MTSState* const mts_state = (MTSState*)opaque;
+ const DisplaySurface* const surface = mts_state->ds->surface;
+
+ if (mts_state->fb_header.w == 0 && mts_state->fb_header.h == 0) {
+ /* First update after previous one has been transmitted to the device. */
+ mts_state->fb_header.x = x;
+ mts_state->fb_header.y = y;
+ mts_state->fb_header.w = w;
+ mts_state->fb_header.h = h;
+ } else {
+ /*
+ * Accumulate framebuffer changes in the header.
+ */
+
+ /* "right" and "bottom" coordinates of the current update. */
+ int right = mts_state->fb_header.x + mts_state->fb_header.w;
+ int bottom = mts_state->fb_header.y + mts_state->fb_header.h;
+
+ /* "right" and "bottom" coordinates of the new update. */
+ const int new_right = x + w;
+ const int new_bottom = y + h;
+
+ /* Accumulate changed rectangle coordinates in the header. */
+ if (mts_state->fb_header.x > x) {
+ mts_state->fb_header.x = x;
+ }
+ if (mts_state->fb_header.y > y) {
+ mts_state->fb_header.y = y;
+ }
+ if (right < new_right) {
+ right = new_right;
+ }
+ if (bottom < new_bottom) {
+ bottom = new_bottom;
+ }
+ mts_state->fb_header.w = right - mts_state->fb_header.x;
+ mts_state->fb_header.h = bottom - mts_state->fb_header.y;
+ }
+
+ /* TODO: Looks like general framebuffer properties can change on the fly.
+ * Find a callback that can catch that. For now, just copy FB properties
+ * over in every FB update. */
+ mts_state->fb_header.bpp = surface->pf.bytes_per_pixel;
+ mts_state->fb_header.bpl = surface->linesize;
+ mts_state->fb_header.disp_width = surface->width;
+ mts_state->fb_header.disp_height = surface->height;
+
+ /* We will send updates to the device only after previous transmission is
+ * completed. */
+ if (!mts_state->fb_transfer_in_progress) {
+ mts_state->fb_transfer_in_progress = 1;
+ if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header,
+ surface->data, _on_fb_sent, mts_state)) {
+ mts_state->fb_transfer_in_progress = 0;
+ }
+ }
+}
+
+void
+multitouch_init(AndroidMTSPort* mtsp)
+{
+ /* Multi-touch service initialization flag. */
+ static int _is_mt_initialized = 0;
+
+ if (!_is_mt_initialized) {
+ MTSState* const mts_state = &_MTSState;
+ DisplayState* const ds = get_displaystate();
+ DisplayUpdateListener* dul;
+ int index;
+
+ /*
+ * Initialize the descriptor.
+ */
+
+ memset(mts_state, 0, sizeof(MTSState));
+ mts_state->tracked_ptr_num = 0;
+ mts_state->current_slot = -1;
+ for (index = 0; index < MTS_POINTERS_NUM; index++) {
+ mts_state->tracked_pointers[index].tracking_id = MTS_POINTER_UP;
+ }
+ mts_state->device_width = android_hw->hw_lcd_width;
+ mts_state->device_height = android_hw->hw_lcd_height;
+ mts_state->mtsp = mtsp;
+ mts_state->fb_header.header_size = sizeof(MTFrameHeader);
+ mts_state->fb_transfer_in_progress = 0;
+
+ /*
+ * Set framebuffer update listener.
+ */
+
+ ANEW0(dul);
+ dul->opaque = &_MTSState;
+ dul->dpy_update = _mt_fb_update;
+
+ /* Initialize framebuffer information in the screen descriptor. */
+ mts_state->ds = ds;
+ mts_state->fb_header.disp_width = ds->surface->width;
+ mts_state->fb_header.disp_height = ds->surface->height;
+ mts_state->fb_header.x = mts_state->fb_header.y = 0;
+ mts_state->fb_header.w = mts_state->fb_header.h = 0;
+ mts_state->fb_header.bpp = ds->surface->pf.bytes_per_pixel;
+ mts_state->fb_header.bpl = ds->surface->linesize;
+ mts_state->fb_transfer_in_progress = 0;
+
+ register_displayupdatelistener(ds, dul);
+
+ _is_mt_initialized = 1;
+ }
+}
+
+void
+multitouch_update_pointer(MTESource source,
+ int tracking_id,
+ int x,
+ int y,
+ int pressure)
+{
+ MTSState* const mts_state = &_MTSState;
+
+ /* Assign a fixed tracking ID to the mouse pointer. */
+ if (source == MTES_MOUSE) {
+ tracking_id = MTS_POINTER_MOUSE;
+ }
+
+ /* Find the tracked pointer for the tracking ID. */
+ const int slot_index = _mtsstate_get_pointer_index(mts_state, tracking_id);
+ if (slot_index < 0) {
+ /* This is the first time the pointer is seen. Must be "pressed",
+ * otherwise it's "hoovering", which we don't support yet. */
+ if (pressure == 0) {
+ if (tracking_id != MTS_POINTER_MOUSE) {
+ D("Unexpected MTS pointer update for tracking id: %d",
+ tracking_id);
+ }
+ return;
+ }
+
+ /* This is a "pointer down" event */
+ _mts_pointer_down(mts_state, tracking_id, x, y, pressure);
+ } else if (pressure == 0) {
+ /* This is a "pointer up" event */
+ _mts_pointer_up(mts_state, slot_index);
+ } else {
+ /* This is a "pointer move" event */
+ _mts_pointer_move(mts_state, slot_index, x, y, pressure);
+ }
+}
+
+int
+multitouch_get_max_slot()
+{
+ return MTS_POINTERS_NUM - 1;
+}
+
+void
+multitouch_set_device_screen_size(int width, int height)
+{
+ MTSState* const mts_state = &_MTSState;
+
+ mts_state->device_width = width;
+ mts_state->device_height = height;
+}
diff --git a/android/multitouch-screen.h b/android/multitouch-screen.h
new file mode 100644
index 0000000..9288073
--- /dev/null
+++ b/android/multitouch-screen.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MULTITOUCH_SCREEN_H_
+#define ANDROID_MULTITOUCH_SCREEN_H_
+
+#include "android/multitouch-port.h"
+
+/*
+ * Encapsulates functionality of multi-touch screen. Main task of this component
+ * is to report touch events to the emulated system via event device (see
+ * hw/goldfish_events_device.c) The source of touch events can be a mouse, or an
+ * actual android device that is used for multi-touch emulation. Note that since
+ * we need to simultaneousely support a mouse and a device as event source, we
+ * need to know which one has sent us a touch event. This is important for proper
+ * tracking of pointer IDs when multitouch is in play.
+ */
+
+/* Defines a source of multi-touch event. This is used to properly track
+ * pointer IDs.
+ */
+typedef enum MTESource {
+ /* The event is associated with a mouse. */
+ MTES_MOUSE,
+ /* The event is associated with an actual android device. */
+ MTES_DEVICE,
+} MTESource;
+
+/* Initializes MTSState instance.
+ * Param:
+ * mtsp - Instance of the multi-touch port connected to the device.
+ */
+extern void multitouch_init(AndroidMTSPort* mtsp);
+
+/* Handles a MT pointer event.
+ * Param:
+ * source - Identifies the source of the event (mouse or a device).
+ * tracking_id - Tracking ID of the pointer.
+ * x, y - Pointer coordinates,
+ * pressure - Pressure value for the pointer.
+ */
+extern void multitouch_update_pointer(MTESource source,
+ int tracking_id,
+ int x,
+ int y,
+ int pressure);
+
+/* Gets maximum slot index available for the multi-touch emulation. */
+extern int multitouch_get_max_slot();
+
+/* Saves screen size reported by the device that emulates multi-touch. */
+extern void multitouch_set_device_screen_size(int width, int height);
+
+#endif /* ANDROID_MULTITOUCH_SCREEN_H_ */
+
diff --git a/android/qemulator.c b/android/qemulator.c
index 35587ff..cd97510 100644
--- a/android/qemulator.c
+++ b/android/qemulator.c
@@ -81,7 +81,8 @@ qemulator_setup( QEmulator* emulator )
qemulator_set_title(emulator);
- skin_window_enable_touch ( emulator->window, android_hw->hw_touchScreen != 0 );
+ skin_window_enable_touch ( emulator->window, android_hw->hw_touchScreen != 0 ||
+ android_hw->hw_multiTouch != 0);
skin_window_enable_dpad ( emulator->window, android_hw->hw_dPad != 0 );
skin_window_enable_qwerty( emulator->window, android_hw->hw_keyboard != 0 );
skin_window_enable_trackball( emulator->window, android_hw->hw_trackBall != 0 );
diff --git a/android/utils/debug.h b/android/utils/debug.h
index ec3094d..e97ec43 100644
--- a/android/utils/debug.h
+++ b/android/utils/debug.h
@@ -38,6 +38,8 @@
_VERBOSE_TAG(camera, "camera") \
_VERBOSE_TAG(adevice, "android device connected via port forwarding") \
_VERBOSE_TAG(sensors_port, "sensors emulator connected to android device") \
+ _VERBOSE_TAG(mtport, "multi-touch emulator connected to android device") \
+ _VERBOSE_TAG(mtscreen, "multi-touch screen emulation") \
_VERBOSE_TAG(gles, "hardware OpenGLES emulation") \
_VERBOSE_TAG(adbserver, "ADB server") \
_VERBOSE_TAG(adbclient, "ADB QEMU client") \
diff --git a/android/utils/jpeg-compress.c b/android/utils/jpeg-compress.c
new file mode 100644
index 0000000..2b42a00
--- /dev/null
+++ b/android/utils/jpeg-compress.c
@@ -0,0 +1,197 @@
+/* Copyright (C) 2011 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+#include <stdint.h>
+#include "jinclude.h"
+#include "jpeglib.h"
+#include "jpeg-compress.h"
+#include "panic.h"
+
+/* Implements JPEG destination manager's init_destination routine. */
+static void _on_init_destination(j_compress_ptr cinfo);
+/* Implements JPEG destination manager's empty_output_buffer routine. */
+static boolean _on_empty_output_buffer(j_compress_ptr cinfo);
+/* Implements JPEG destination manager's term_destination routine. */
+static void _on_term_destination(j_compress_ptr cinfo);
+
+/* JPEG compression descriptor. */
+struct AJPEGDesc {
+ /* Common JPEG compression destination manager header. */
+ struct jpeg_destination_mgr common;
+ /* Buffer where to save compressed output. */
+ uint8_t* jpeg_buf;
+ /* Byte size of the 'jpeg_buf' */
+ int size;
+ /* Chunk size to increment the 'jpeg_buf' with on each allocation request. */
+ int chunk_size;
+ /* Size of the header to put in front of the compressed data. */
+ int header_size;
+};
+
+/********************************************************************************
+ * jpeglib callbacks.
+ *******************************************************************************/
+
+/* Implements JPEG destination manager's init_destination routine. */
+static void
+_on_init_destination(j_compress_ptr cinfo)
+{
+ AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest;
+ if (dst->jpeg_buf == NULL) {
+ /* This is the first time our destination manager is initialized.
+ * Allocate minimal buffer. */
+ dst->size = dst->chunk_size;
+ dst->jpeg_buf = malloc(dst->size);
+ if (dst->jpeg_buf == NULL) {
+ APANIC("Unable to allocate %d bytes for JPEG compression", dst->size);
+ }
+ }
+ /* Initialize common header with entire destination buffer. */
+ dst->common.next_output_byte = dst->jpeg_buf + dst->header_size;
+ dst->common.free_in_buffer = dst->size - dst->header_size;
+}
+
+/* Implements JPEG destination manager's empty_output_buffer routine.
+ * Name is a bit misleading here. This routine is called by the compressor when
+ * output buffer doesn't have enough free space to contain the next chunk of the
+ * compressed data. So, here we should reallocate the output buffer, rather than
+ * "empty" it.
+ */
+static boolean
+_on_empty_output_buffer(j_compress_ptr cinfo)
+{
+ AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest;
+ /* Save already compressed data size. */
+ const int accumulated = jpeg_compressor_get_jpeg_size(dst);
+
+ /* Reallocate output buffer. */
+ dst->size += dst->chunk_size;
+ dst->jpeg_buf = realloc(dst->jpeg_buf, dst->size);
+ if (dst->jpeg_buf == NULL) {
+ APANIC("Unable to allocate %d bytes for JPEG compression", dst->size);
+ }
+
+ /* Update common header. */
+ dst->common.next_output_byte = dst->jpeg_buf + accumulated + dst->header_size;
+ dst->common.free_in_buffer = dst->size - accumulated - dst->header_size;
+
+ return TRUE;
+}
+
+/* Implements JPEG destination manager's term_destination routine.
+ * We don't do anything here. All the cleanup will be performed when the user
+ * calls jpeg_compressor_destroy. */
+static void
+_on_term_destination(j_compress_ptr cinfo)
+{
+}
+
+/********************************************************************************
+ * JPEG compressor API.
+ *******************************************************************************/
+
+AJPEGDesc*
+jpeg_compressor_create(int header_size, int chunk_size)
+{
+ AJPEGDesc* dsc = (AJPEGDesc*)malloc(sizeof(AJPEGDesc));
+ if (dsc == NULL) {
+ APANIC("Unable to allocate JPEG compression descriptor.");
+ }
+
+ dsc->common.next_output_byte = NULL;
+ dsc->common.free_in_buffer = 0;
+ dsc->common.init_destination = _on_init_destination;
+ dsc->common.empty_output_buffer = _on_empty_output_buffer;
+ dsc->common.term_destination = _on_term_destination;
+ dsc->jpeg_buf = NULL;
+ dsc->size = 0;
+ dsc->chunk_size = chunk_size;
+ dsc->header_size = header_size;
+ return dsc;
+}
+
+void
+jpeg_compressor_destroy(AJPEGDesc* dsc)
+{
+ if (dsc != NULL) {
+ if (dsc->jpeg_buf != NULL) {
+ free(dsc->jpeg_buf);
+ }
+ free(dsc);
+ }
+}
+
+int
+jpeg_compressor_get_jpeg_size(const AJPEGDesc* dsc)
+{
+ return (dsc->jpeg_buf == NULL) ? 0 :
+ (uint8_t*)dsc->common.next_output_byte - dsc->jpeg_buf - dsc->header_size;
+}
+
+void*
+jpeg_compressor_get_buffer(const AJPEGDesc* dsc)
+{
+ return dsc->jpeg_buf;
+}
+
+int
+jpeg_compressor_get_header_size(const AJPEGDesc* dsc)
+{
+ return dsc->header_size;
+}
+
+void
+jpeg_compressor_compress_fb(AJPEGDesc* dsc,
+ int x, int y, int w, int h,
+ int bpp, int bpl,
+ const uint8_t* fb,
+ int jpeg_quality){
+ struct jpeg_compress_struct cinfo = {0};
+ struct jpeg_error_mgr err_mgr;
+
+ /*
+ * Initialize compressin information structure, and start compression
+ */
+
+ cinfo.err = jpeg_std_error(&err_mgr);
+ jpeg_create_compress(&cinfo);
+ cinfo.dest = &dsc->common;
+ cinfo.image_width = w;
+ cinfo.image_height = h;
+
+ /* Decode framebuffer's pixel format. There can be only three:
+ * - RGB565,
+ * - RGBA8888,
+ * - RGBX8888 */
+ if (bpp == 2) {
+ /* This is RGB565 - most commonly used pixel format for framebuffer. */
+ cinfo.input_components = 2;
+ cinfo.in_color_space = JCS_RGB_565;
+ } else {
+ /* RGBA8888, or RGBX8888 - makes no difference here. */
+ cinfo.input_components = 4;
+ cinfo.in_color_space = JCS_RGBA_8888;
+ }
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, jpeg_quality, TRUE);
+ jpeg_start_compress(&cinfo, TRUE);
+
+ /* Line by line compress the region. */
+ while (cinfo.next_scanline < cinfo.image_height) {
+ JSAMPROW rgb = (JSAMPROW)(fb + (cinfo.next_scanline + y) * bpl + x * bpp);
+ jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&rgb, 1);
+ }
+
+ /* Complete the compression. */
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+}
diff --git a/android/utils/jpeg-compress.h b/android/utils/jpeg-compress.h
new file mode 100644
index 0000000..4e0e61a
--- /dev/null
+++ b/android/utils/jpeg-compress.h
@@ -0,0 +1,93 @@
+/* Copyright (C) 2011 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+#ifndef _ANDROID_UTILS_JPEG_COMPRESS_H
+#define _ANDROID_UTILS_JPEG_COMPRESS_H
+
+/*
+ * Contains declaration of utility routines that compress an RGB bitmap into
+ * a JPEG image.
+ *
+ * NOTE: This code uses a jpeglib library located in distrib/jpeg-6b. It's a
+ * 3-rd party library that uses its own type definitions that are different from
+ * the ones that are use elsewhere in the emulator code. For instance, in the
+ * emulator built for Windows, sizeof(bool) = 1, while in the jpeglib sizeof(bool) = 4.
+ * So, to simplify dealing with these issues, all the code that uses jpeglib should
+ * be compiled separately, and should include only headers that are used to compile
+ * jpeglib.
+ */
+
+
+/* Declares descriptor for a JPEG compression. */
+typedef struct AJPEGDesc AJPEGDesc;
+
+/* Creates a descriptor that will be used for compression.
+ * Param:
+ * header_size - Number of bytes to allocate for a custom header that should
+ * preceed the actual JPEG buffer. This is useful when sending JPEG
+ * somewhere else along with some extra data about the compressed image.
+ * cunk_size - Number of bytes to increment the compressed buffer with each time
+ * compressor requests more memory.
+ * Return:
+ * Initialized compression descriptor.
+ */
+extern AJPEGDesc* jpeg_compressor_create(int header_size, int chunk_size);
+
+/* Destroys compressor descriptor.
+ * Param:
+ * dsc - Compressin descriptor, obtained with jpeg_compressor_create.
+ */
+extern void jpeg_compressor_destroy(AJPEGDesc* dsc);
+
+/* Returns compressed data size.
+ * Param:
+ * dsc - Compression descriptor, obtained with jpeg_compressor_create.
+ * Return:
+ * Compressed data size.
+ */
+extern int jpeg_compressor_get_jpeg_size(const AJPEGDesc* dsc);
+
+/* Returns compressed buffer.
+ * Param:
+ * dsc - Compression descriptor, obtained with jpeg_compressor_create.
+ * Return:
+ * Compressed buffer. NOTE: if 'header_size' parameter passed to the jpeg_compressor_create
+ * for this descriptor was not zero, this routine returns a pointer to the custom
+ * header. Compressed data follows immediately after that header.
+ */
+extern void* jpeg_compressor_get_buffer(const AJPEGDesc* dsc);
+
+/* Returns size of the custom header placed before the compressed data.
+ * Param:
+ * dsc - Compression descriptor, obtained with jpeg_compressor_create.
+ * Return:
+ * Size of the custom header placed before the compressed data.
+ */
+extern int jpeg_compressor_get_header_size(const AJPEGDesc* dsc);
+
+/* Compresses a framebuffer region into JPEG image.
+ * Param:
+ * dsc - Compression descriptor, obtained with jpeg_compressor_create.
+ * x, y, w, h - Coordinates and sizes of framebuffer region to compress.
+ * bpp - Number of bytes per pixel in the framebuffer.
+ * bpl - Number of bytes per line in the framebuffer.
+ * 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.
+ */
+extern void jpeg_compressor_compress_fb(AJPEGDesc* dsc,
+ int x, int y, int w, int h,
+ int bpp, int bpl,
+ const uint8_t* fb,
+ int jpeg_quality);
+
+#endif /* _ANDROID_UTILS_JPEG_COMPRESS_H */
diff --git a/hw/goldfish_events_device.c b/hw/goldfish_events_device.c
index a5b2a21..264d15e 100644
--- a/hw/goldfish_events_device.c
+++ b/hw/goldfish_events_device.c
@@ -13,6 +13,7 @@
#include "android/hw-events.h"
#include "android/charmap.h"
#include "android/globals.h" /* for android_hw */
+#include "android/multitouch-screen.h"
#include "irq.h"
#include "user-events.h"
#include "console.h"
@@ -68,6 +69,18 @@ typedef struct
size_t abs_info_count;
} events_state;
+/* An entry in the array of ABS_XXX values */
+typedef struct ABSEntry {
+ /* Minimum ABS_XXX value. */
+ uint32_t min;
+ /* Maximum ABS_XXX value. */
+ uint32_t max;
+ /* 'fuzz;, and 'flat' ABS_XXX values are always zero here. */
+ uint32_t fuzz;
+ uint32_t flat;
+} ABSEntry;
+
+
/* modify this each time you change the events_device structure. you
* will also need to upadte events_state_load and events_state_save
*/
@@ -258,20 +271,27 @@ static void events_put_mouse(void *opaque, int dx, int dy, int dz, int buttons_s
* in android/skin/trackball.c and android/skin/window.c
*/
if (dz == 0) {
- enqueue_event(s, EV_ABS, ABS_X, dx);
- enqueue_event(s, EV_ABS, ABS_Y, dy);
- enqueue_event(s, EV_ABS, ABS_Z, dz);
- enqueue_event(s, EV_KEY, BTN_TOUCH, buttons_state&1);
+ if (android_hw->hw_multiTouch) {
+ /* Convert mouse event into multi-touch event */
+ multitouch_update_pointer(MTES_MOUSE, 0, dx, dy,
+ (buttons_state & 1) ? 0x81 : 0);
+ } else if (android_hw->hw_touchScreen) {
+ enqueue_event(s, EV_ABS, ABS_X, dx);
+ enqueue_event(s, EV_ABS, ABS_Y, dy);
+ enqueue_event(s, EV_ABS, ABS_Z, dz);
+ enqueue_event(s, EV_KEY, BTN_TOUCH, buttons_state&1);
+ enqueue_event(s, EV_SYN, 0, 0);
+ }
} else {
enqueue_event(s, EV_REL, REL_X, dx);
enqueue_event(s, EV_REL, REL_Y, dy);
+ enqueue_event(s, EV_SYN, 0, 0);
}
- enqueue_event(s, EV_SYN, 0, 0);
}
static void events_put_generic(void* opaque, int type, int code, int value)
{
- events_state *s = (events_state *) opaque;
+ events_state *s = (events_state *) opaque;
enqueue_event(s, type, code, value);
}
@@ -431,13 +451,13 @@ void events_dev_init(uint32_t base, qemu_irq irq)
*
* EV_ABS events are sent when the touchscreen is pressed
*/
- if (config->hw_touchScreen) {
- int32_t* values;
+ if (config->hw_touchScreen || config->hw_multiTouch) {
+ ABSEntry* abs_values;
events_set_bit (s, EV_SYN, EV_ABS );
events_set_bits(s, EV_ABS, ABS_X, ABS_Z);
/* Allocate the absinfo to report the min/max bounds for each
- * absolute dimension. The array must contain 3 tuples
+ * absolute dimension. The array must contain 3, or ABS_MAX tuples
* of (min,max,fuzz,flat) 32-bit values.
*
* min and max are the bounds
@@ -448,28 +468,34 @@ void events_dev_init(uint32_t base, qemu_irq irq)
* There is no need to save/restore this array in a snapshot
* since the values only depend on the hardware configuration.
*/
- s->abs_info_count = 3*4;
- s->abs_info = values = malloc(sizeof(uint32_t)*s->abs_info_count);
-
- /* ABS_X min/max/fuzz/flat */
- values[0] = 0;
- values[1] = config->hw_lcd_width-1;
- values[2] = 0;
- values[3] = 0;
- values += 4;
-
- /* ABS_Y */
- values[0] = 0;
- values[1] = config->hw_lcd_height-1;
- values[2] = 0;
- values[3] = 0;
- values += 4;
-
- /* ABS_Z */
- values[0] = 0;
- values[1] = 1;
- values[2] = 0;
- values[3] = 0;
+ s->abs_info_count = config->hw_multiTouch ? ABS_MAX * 4 : 3 * 4;
+ const int abs_size = sizeof(uint32_t) * s->abs_info_count;
+ s->abs_info = malloc(abs_size);
+ memset(s->abs_info, 0, abs_size);
+ abs_values = (ABSEntry*)s->abs_info;
+
+ abs_values[ABS_X].max = config->hw_lcd_width-1;
+ abs_values[ABS_Y].max = config->hw_lcd_height-1;
+ abs_values[ABS_Z].max = 1;
+
+ if (config->hw_multiTouch) {
+ /*
+ * Setup multitouch.
+ */
+ events_set_bit(s, EV_ABS, ABS_MT_SLOT);
+ events_set_bit(s, EV_ABS, ABS_MT_POSITION_X);
+ events_set_bit(s, EV_ABS, ABS_MT_POSITION_Y);
+ events_set_bit(s, EV_ABS, ABS_MT_TRACKING_ID);
+ events_set_bit(s, EV_ABS, ABS_MT_TOUCH_MAJOR);
+ events_set_bit(s, EV_ABS, ABS_MT_PRESSURE);
+
+ abs_values[ABS_MT_SLOT].max = multitouch_get_max_slot();
+ abs_values[ABS_MT_TRACKING_ID].max = abs_values[ABS_MT_SLOT].max + 1;
+ abs_values[ABS_MT_POSITION_X].max = abs_values[ABS_X].max;
+ abs_values[ABS_MT_POSITION_Y].max = abs_values[ABS_Y].max;
+ abs_values[ABS_MT_TOUCH_MAJOR].max = 0x7fffffff; // TODO: Make it less random
+ abs_values[ABS_MT_PRESSURE].max = 0x100; // TODO: Make it less random
+ }
}
/* configure EV_SW array
diff --git a/linux_keycodes.h b/linux_keycodes.h
index 3875028..195797e 100644
--- a/linux_keycodes.h
+++ b/linux_keycodes.h
@@ -449,4 +449,60 @@
#define KEY_MIN_INTERESTING KEY_MUTE
#define KEY_MAX 0x1ff
+#ifndef ABS_MT_SLOT
+#define ABS_MT_SLOT 0x2f /* MT slot being modified */
+#endif
+#ifndef ABS_MT_TOUCH_MAJOR
+#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
+#endif
+#ifndef ABS_MT_TOUCH_MINOR
+#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
+#endif
+#ifndef ABS_MT_WIDTH_MAJOR
+#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
+#endif
+#ifndef ABS_MT_WIDTH_MINOR
+#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
+#endif
+#ifndef ABS_MT_ORIENTATION
+#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
+#endif
+#ifndef ABS_MT_POSITION_X
+#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
+#endif
+#ifndef ABS_MT_POSITION_Y
+#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
+#endif
+#ifndef ABS_MT_TOOL_TYPE
+#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
+#endif
+#ifndef ABS_MT_BLOB_ID
+#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
+#endif
+#ifndef ABS_MT_TRACKING_ID
+#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
+#endif
+#ifndef ABS_MT_PRESSURE
+#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
+#endif
+#ifndef ABS_MT_DISTANCE
+#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
+#endif
+#ifndef ABS_MAX
+#define ABS_MAX 0x3f
+#endif
+
+#ifndef SYN_REPORT
+#define SYN_REPORT 0
+#endif
+#ifndef SYN_CONFIG
+#define SYN_CONFIG 1
+#endif
+#ifndef SYN_MT_REPORT
+#define SYN_MT_REPORT 2
+#endif
+#ifndef SYN_DROPPED
+#define SYN_DROPPED 3
+#endif
+
#endif
diff --git a/vl-android.c b/vl-android.c
index 46f869a..171d763 100644
--- a/vl-android.c
+++ b/vl-android.c
@@ -55,6 +55,7 @@
#include "android/hw-pipe-net.h"
#include "android/hw-qemud.h"
#include "android/camera/camera-service.h"
+#include "android/multitouch-port.h"
#include "android/charmap.h"
#include "android/globals.h"
#include "android/utils/bufprint.h"
@@ -4258,6 +4259,11 @@ int main(int argc, char **argv, char **envp)
initrd_filename,
cpu_model);
+ /* Initialize multi-touch emulation. */
+ if (android_hw->hw_multiTouch) {
+ mts_port_create(NULL);
+ }
+
stralloc_reset(kernel_params);
stralloc_reset(kernel_config);
}