| /* |
| * 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 "android/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) |
| |
| #define TRACE_ON 1 |
| |
| #if TRACE_ON |
| #define T(...) VERBOSE_PRINT(mtscreen,__VA_ARGS__) |
| #else |
| #define T(...) |
| #endif |
| |
| |
| /* 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; |
| /* 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; |
| /* Indicates direction in which lines are arranged in the framebuffer. If |
| * this value is negative, lines are arranged in bottom-up format (i.e. the |
| * bottom line is at the beginning of the buffer). */ |
| int ydir; |
| /* Current framebuffer pointer. */ |
| uint8_t* current_fb; |
| } MTSState; |
| |
| /* Default multi-touch screen descriptor */ |
| static MTSState _MTSState = { 0 }; |
| |
| /* 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 |
| *******************************************************************************/ |
| |
| /* Multi-touch service initialization flag. */ |
| static int _is_mt_initialized = 0; |
| |
| /* Callback that is invoked when framebuffer update has been transmitted to the |
| * device. */ |
| static AsyncIOAction |
| _on_fb_sent(void* opaque, SDKCtlDirectPacket* packet, AsyncIOState status) |
| { |
| MTSState* const mts_state = (MTSState*)opaque; |
| |
| if (status == ASIO_STATE_SUCCEEDED) { |
| /* 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 && |
| !mts_state->fb_transfer_in_progress) { |
| mts_state->fb_transfer_in_progress = 1; |
| /* Send accumulated updates. */ |
| if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header, |
| mts_state->current_fb, _on_fb_sent, mts_state, |
| mts_state->ydir)) { |
| mts_state->fb_transfer_in_progress = 0; |
| } |
| } |
| } |
| |
| return ASIO_ACTION_DONE; |
| } |
| |
| /* Common handler for framebuffer updates invoked by both, software, and OpenGLES |
| * renderers. |
| */ |
| static void |
| _mt_fb_common_update(MTSState* mts_state, int x, int y, int w, int h) |
| { |
| 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; |
| } |
| |
| /* 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, |
| mts_state->current_fb, _on_fb_sent, mts_state, |
| mts_state->ydir)) { |
| mts_state->fb_transfer_in_progress = 0; |
| } |
| } |
| } |
| |
| /* A callback invoked on framebuffer updates by software renderer. |
| * 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; |
| |
| T("Multi-touch: Software renderer framebuffer update: %d:%d -> %dx%d", |
| x, y, w, h); |
| |
| /* TODO: For sofware renderer 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; |
| mts_state->current_fb = surface->data; |
| mts_state->ydir = 1; |
| |
| _mt_fb_common_update(mts_state, x, y, w, h); |
| } |
| |
| void |
| multitouch_opengles_fb_update(void* context, |
| int w, int h, int ydir, |
| int format, int type, |
| unsigned char* pixels) |
| { |
| MTSState* const mts_state = &_MTSState; |
| |
| /* Make sure MT port is initialized. */ |
| if (!_is_mt_initialized) { |
| return; |
| } |
| |
| T("Multi-touch: openGLES framebuffer update: 0:0 -> %dx%d", w, h); |
| |
| /* GLES format is always RGBA8888 */ |
| mts_state->fb_header.bpp = 4; |
| mts_state->fb_header.bpl = 4 * w; |
| mts_state->fb_header.disp_width = w; |
| mts_state->fb_header.disp_height = h; |
| mts_state->current_fb = pixels; |
| mts_state->ydir = ydir; |
| |
| /* GLES emulator alwas update the entire framebuffer. */ |
| _mt_fb_common_update(mts_state, 0, 0, w, h); |
| } |
| |
| void |
| multitouch_refresh_screen(void) |
| { |
| MTSState* const mts_state = &_MTSState; |
| |
| /* Make sure MT port is initialized. */ |
| if (!_is_mt_initialized) { |
| return; |
| } |
| |
| /* Lets see if any updates have been received so far. */ |
| if (NULL != mts_state->current_fb) { |
| _mt_fb_common_update(mts_state, 0, 0, mts_state->fb_header.disp_width, |
| mts_state->fb_header.disp_height); |
| } |
| } |
| |
| void |
| multitouch_fb_updated(void) |
| { |
| MTSState* const mts_state = &_MTSState; |
| |
| /* This concludes framebuffer update. */ |
| mts_state->fb_transfer_in_progress = 0; |
| |
| /* 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) { |
| mts_state->fb_transfer_in_progress = 1; |
| /* Send accumulated updates. */ |
| if (mts_port_send_frame(mts_state->mtsp, &mts_state->fb_header, |
| mts_state->current_fb, _on_fb_sent, mts_state, |
| mts_state->ydir)) { |
| mts_state->fb_transfer_in_progress = 0; |
| } |
| } |
| } |
| |
| void |
| multitouch_init(AndroidMTSPort* mtsp) |
| { |
| 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->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; |
| } |