blob: c8e3ca52cb6760ef6fd7b0da994e2b0e9ea0ce38 [file] [log] [blame]
/*
* 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;
}