| /* |
| * 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; |
| } |