/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=4 sw=4 sts=4 tw=80 et: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Gonk. * * The Initial Developer of the Original Code is * the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Michael Wu * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "nscore.h" #include "mozilla/FileUtils.h" #include "mozilla/Services.h" #include "nsAppShell.h" #include "nsGkAtoms.h" #include "nsGUIEvent.h" #include "nsIObserverService.h" #include "nsWindow.h" #include "android/log.h" #ifndef ABS_MT_TOUCH_MAJOR // Taken from include/linux/input.h // XXX update the bionic input.h so we don't have to do this! #define ABS_X 0x00 #define ABS_Y 0x01 // ... #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ #define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */ #define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */ #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ #define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ #define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ #define SYN_MT_REPORT 2 #endif #define LOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) #ifdef VERBOSE_LOG_ENABLED # define VERBOSE_LOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) #else # define VERBOSE_LOG(args...) \ (void)0 #endif using namespace mozilla; bool gDrawRequest = false; static nsAppShell *gAppShell = NULL; static int epollfd = 0; static int signalfds[2] = {0}; namespace mozilla { bool ProcessNextEvent() { return gAppShell->ProcessNextNativeEvent(true); } void NotifyEvent() { gAppShell->NotifyNativeEvent(); } } static void pipeHandler(int fd, FdHandler *data) { ssize_t len; do { char tmp[32]; len = read(fd, tmp, sizeof(tmp)); } while (len > 0); } static PRUint64 timevalToMS(const struct timeval &time) { return time.tv_sec * 1000 + time.tv_usec / 1000; } static void sendMouseEvent(PRUint32 msg, struct timeval& time, int x, int y) { nsMouseEvent event(true, msg, NULL, nsMouseEvent::eReal, nsMouseEvent::eNormal); event.refPoint.x = x; event.refPoint.y = y; event.time = timevalToMS(time); event.isShift = false; event.isControl = false; event.isMeta = false; event.isAlt = false; event.button = nsMouseEvent::eLeftButton; if (msg != NS_MOUSE_MOVE) event.clickCount = 1; nsWindow::DispatchInputEvent(event); } static nsEventStatus sendKeyEventWithMsg(PRUint32 keyCode, PRUint32 msg, const timeval &time, PRUint32 flags) { nsKeyEvent event(true, msg, NULL); event.keyCode = keyCode; event.time = timevalToMS(time); event.flags |= flags; return nsWindow::DispatchInputEvent(event); } static void sendKeyEvent(PRUint32 keyCode, bool down, const timeval &time) { nsEventStatus status = sendKeyEventWithMsg(keyCode, down ? NS_KEY_DOWN : NS_KEY_UP, time, 0); if (down) { sendKeyEventWithMsg(keyCode, NS_KEY_PRESS, time, status == nsEventStatus_eConsumeNoDefault ? NS_EVENT_FLAG_NO_DEFAULT : 0); } } static void sendSpecialKeyEvent(nsIAtom *command, const timeval &time) { nsCommandEvent event(true, nsGkAtoms::onAppCommand, command, NULL); event.time = timevalToMS(time); nsWindow::DispatchInputEvent(event); } static void maybeSendKeyEvent(int keyCode, bool pressed, const timeval& time) { switch (keyCode) { case KEY_BACK: sendKeyEvent(NS_VK_ESCAPE, pressed, time); break; case KEY_MENU: if (!pressed) sendSpecialKeyEvent(nsGkAtoms::Menu, time); break; case KEY_SEARCH: if (pressed) sendSpecialKeyEvent(nsGkAtoms::Search, time); break; case KEY_HOME: sendKeyEvent(NS_VK_HOME, pressed, time); break; case KEY_POWER: sendKeyEvent(NS_VK_SLEEP, pressed, time); break; case KEY_VOLUMEUP: if (pressed) sendSpecialKeyEvent(nsGkAtoms::VolumeUp, time); break; case KEY_VOLUMEDOWN: if (pressed) sendSpecialKeyEvent(nsGkAtoms::VolumeDown, time); break; default: VERBOSE_LOG("Got unknown key event code. type 0x%04x code 0x%04x value %d", keyCode, pressed); } } static void maybeSendKeyEvent(const input_event& e) { if (e.type != EV_KEY) { VERBOSE_LOG("Got unknown key event type. type 0x%04x code 0x%04x value %d", e.type, e.code, e.value); return; } if (e.value != 0 && e.value != 1) { VERBOSE_LOG("Got unknown key event value. type 0x%04x code 0x%04x value %d", e.type, e.code, e.value); return; } bool pressed = e.value == 1; maybeSendKeyEvent(e.code, pressed, e.time); } static void configureVButtons(FdHandler& data) { char vbuttonsPath[PATH_MAX]; snprintf(vbuttonsPath, sizeof(vbuttonsPath), "/sys/board_properties/virtualkeys.%s", data.name); ScopedClose fd(open(vbuttonsPath, O_RDONLY)); if (0 > fd.mFd) { LOG("No vbuttons for mt device %s", data.name); return; } // This device has vbuttons. Process the configuration. char config[1024]; ssize_t nread; do { nread = read(fd.mFd, config, sizeof(config)); } while (-1 == nread && EINTR == errno); if (0 > nread) { LOG("Error reading virtualkey configuration"); return; } config[nread] = '\0'; LOG("Device %s has vbutton config '%s'", data.name, config); char* startStr = config; for (size_t i = 0; i < FdHandler::kMaxVButtons; ++i) { FdHandler::VButton& vbutton = data.vbuttons[i]; char* token; char* state; // XXX not clear what "0x01" is ... maybe a version // number? See InputManager.java. if (!(token = strtok_r(startStr, ":", &state)) || strcmp(token, "0x01")) { LOG(" magic 0x01 tag missing"); break; } startStr = NULL; if (!(token = strtok_r(NULL, ":", &state))) { LOG(" failed to read keycode"); break; } vbutton.keyCode = atoi(token); const char *centerX, *centerY, *width, *height; if (!((centerX = strtok_r(NULL, ":", &state)) && (centerY = strtok_r(NULL, ":", &state)) && (width = strtok_r(NULL, ":", &state)) && (height = strtok_r(NULL, ":", &state)))) { LOG(" failed to read bound params"); break; } // NB: these coordinates are in *screen* space, not input // space. That means the values in /sys/board_config make // assumptions about how the raw input events are mapped // ... le sigh. nsIntRect rect; rect.width = atoi(width); rect.height = atoi(height); rect.x = atoi(centerX) - rect.width / 2; rect.y = atoi(centerY) - rect.height / 2; vbutton.buttonRect = rect; LOG(" configured vbutton code=%d at ", vbutton.keyCode, rect.x, rect.y, rect.width, rect.height); } } static bool calibrateMultitouchDevice(FdHandler& data) { if (data.calibrated) return true; if (gScreenBounds.IsEmpty()) { // The framebuffer hasn't been initialized yet. We *could* // force it to be initialized here, but that's another patch. LOG("Deferring multitouch calibrate, fb not ready"); return false; } struct input_absinfo xInfo, yInfo; if (0 > ioctl(data.fd, EVIOCGABS(ABS_MT_POSITION_X), &xInfo) || 0 > ioctl(data.fd, EVIOCGABS(ABS_MT_POSITION_Y), &yInfo)) { LOG("Couldn't get absinfo for multitouch axes"); return false; } LOG("Input coordinate bounds: xmin=%d, xmax=%d, ymin=%d, ymax=%d", xInfo.minimum, xInfo.maximum, yInfo.minimum, yInfo.maximum); data.inputMinX = xInfo.minimum; data.inputMinY = yInfo.minimum; data.inputToScreenScaleX = float(gScreenBounds.width) / float(xInfo.maximum - xInfo.minimum); data.inputToScreenScaleY = float(gScreenBounds.height) / float(yInfo.maximum - yInfo.minimum); configureVButtons(data); data.calibrated = true; return true; } static void multitouchHandler(int fd, FdHandler *data) { if (!calibrateMultitouchDevice(*data)) return; // The Linux's input documentation (Documentation/input/input.txt) // says that we'll always read a multiple of sizeof(input_event) bytes here. input_event events[16]; int event_count = read(fd, events, sizeof(events)); if (event_count < 0) { LOG("Error reading in multitouchHandler"); return; } MOZ_ASSERT(event_count % sizeof(input_event) == 0); event_count /= sizeof(struct input_event); for (int i = 0; i < event_count; i++) { input_event *event = &events[i]; if (event->type == EV_ABS) { if (data->mtState == FdHandler::MT_IGNORE) continue; if (data->mtState == FdHandler::MT_START) data->mtState = FdHandler::MT_COLLECT; switch (event->code) { case ABS_MT_TOUCH_MAJOR: data->mtMajor = event->value; break; case ABS_MT_TOUCH_MINOR: case ABS_MT_WIDTH_MAJOR: case ABS_MT_WIDTH_MINOR: case ABS_MT_ORIENTATION: case ABS_MT_TOOL_TYPE: case ABS_MT_BLOB_ID: case ABS_MT_TRACKING_ID: case ABS_MT_PRESSURE: break; case ABS_MT_POSITION_X: data->mtX = data->inputXToScreenX(event->value); break; case ABS_MT_POSITION_Y: data->mtY = data->inputYToScreenY(event->value); break; default: VERBOSE_LOG("Got unknown mt event type 0x%04x with code 0x%04x and value %d", event->type, event->code, event->value); break; } } else if (event->type == EV_SYN) { switch (event->code) { case SYN_MT_REPORT: if (data->mtState == FdHandler::MT_COLLECT) data->mtState = FdHandler::MT_IGNORE; break; case SYN_REPORT: if (!data->mtMajor || data->mtState == FdHandler::MT_START) { data->mtDown = false; if (data->keyCode) { maybeSendKeyEvent(data->keyCode, data->mtDown, event->time); data->keyCode = 0; } else { sendMouseEvent(NS_MOUSE_BUTTON_UP, event->time, data->mtX, data->mtY); } } else if (!data->mtDown) { int x = data->mtX, y = data->mtY; bool isKeyEvent = false; if (!gScreenBounds.Contains(x, y)) { // Off-screen mt down. Should be a vbutton. for (size_t i = 0; i < FdHandler::kMaxVButtons; ++i) { const FdHandler::VButton& vbutton = data->vbuttons[i]; if (vbutton.buttonRect.IsEmpty()) break; if (vbutton.buttonRect.Contains(x, y)) { isKeyEvent = true; data->keyCode = vbutton.keyCode; break; } } } data->mtDown = true; if (isKeyEvent) { maybeSendKeyEvent(data->keyCode, data->mtDown, event->time); } else { sendMouseEvent(NS_MOUSE_BUTTON_DOWN, event->time, data->mtX, data->mtY); } } else if (!data->keyCode) { sendMouseEvent(NS_MOUSE_MOVE, event->time, data->mtX, data->mtY); data->mtDown = true; } data->mtState = FdHandler::MT_START; break; default: VERBOSE_LOG("Got unknown mt event type 0x%04x with code 0x%04x and value %d", event->type, event->code, event->value); } } else VERBOSE_LOG("Got unknown mt event type 0x%04x with code 0x%04x and value %d", event->type, event->code, event->value); } } static void singleTouchHandler(int fd, FdHandler *data) { // The Linux's input documentation (Documentation/input/input.txt) // says that we'll always read a multiple of sizeof(input_event) bytes here. input_event events[16]; int event_count = read(fd, events, sizeof(events)); if (event_count < 0) { LOG("Error reading in singleTouchHandler"); return; } MOZ_ASSERT(event_count % sizeof(input_event) == 0); event_count /= sizeof(struct input_event); for (int i = 0; i < event_count; i++) { input_event *event = &events[i]; if (event->type == EV_KEY) { switch (event->code) { case BTN_TOUCH: data->mtDown = event->value; break; default: maybeSendKeyEvent(*event); } } else if (event->type == EV_ABS) { switch (event->code) { case ABS_X: data->mtX = event->value; break; case ABS_Y: data->mtY = event->value; break; default: LOG("Got unknown st abs event type 0x%04x with code 0x%04x and value %d", event->type, event->code, event->value); } } else if (event->type == EV_SYN) { if (data->mtState == FdHandler::MT_START) { MOZ_ASSERT(data->mtDown); sendMouseEvent(NS_MOUSE_BUTTON_DOWN, event->time, data->mtX, data->mtY); data->mtState = FdHandler::MT_COLLECT; } else if (data->mtDown) { MOZ_ASSERT(data->mtDown); sendMouseEvent(NS_MOUSE_MOVE, event->time, data->mtX, data->mtY); } else { MOZ_ASSERT(!data->mtDown); sendMouseEvent(NS_MOUSE_BUTTON_UP, event->time, data->mtX, data->mtY); data->mtDown = false; data->mtState = FdHandler::MT_START; } } } } static void keyHandler(int fd, FdHandler *data) { input_event events[16]; ssize_t bytesRead = read(fd, events, sizeof(events)); if (bytesRead < 0) { LOG("Error reading in keyHandler"); return; } MOZ_ASSERT(bytesRead % sizeof(input_event) == 0); for (unsigned int i = 0; i < bytesRead / sizeof(struct input_event); i++) { const input_event &e = events[i]; if (e.type == EV_SYN) { // Ignore this event; it just signifies that a key was pressed. continue; } maybeSendKeyEvent(e); } } nsAppShell::nsAppShell() : mNativeCallbackRequest(false) , mHandlers() { gAppShell = this; } nsAppShell::~nsAppShell() { gAppShell = NULL; } nsresult nsAppShell::Init() { nsresult rv = nsBaseAppShell::Init(); NS_ENSURE_SUCCESS(rv, rv); epollfd = epoll_create(16); NS_ENSURE_TRUE(epollfd >= 0, NS_ERROR_UNEXPECTED); int ret = pipe2(signalfds, O_NONBLOCK); NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED); rv = AddFdHandler(signalfds[0], pipeHandler, ""); NS_ENSURE_SUCCESS(rv, rv); DIR *dir = opendir("/dev/input"); NS_ENSURE_TRUE(dir, NS_ERROR_UNEXPECTED); #define IS_BIT_SET(bit, flags) (flags[bit >> 3] & (1 << (bit & 0x7))) struct dirent *entry; while ((entry = readdir(dir))) { char entryName[64]; char entryPath[MAXPATHLEN]; if (snprintf(entryPath, sizeof(entryPath), "/dev/input/%s", entry->d_name) < 0) { LOG("Couldn't generate path while enumerating input devices!"); continue; } int fd = open(entryPath, O_RDONLY); if (ioctl(fd, EVIOCGNAME(sizeof(entryName)), entryName) >= 0) LOG("Found device %s - %s", entry->d_name, entryName); else continue; FdHandlerCallback handlerFunc = NULL; char flags[(NS_MAX(ABS_MAX, KEY_MAX) + 1) / 8]; if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(flags)), flags) >= 0 && IS_BIT_SET(ABS_MT_POSITION_X, flags)) { LOG("Found multitouch input device"); handlerFunc = multitouchHandler; } else if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(flags)), flags) >= 0 && IS_BIT_SET(ABS_X, flags)) { LOG("Found single touch input device"); handlerFunc = singleTouchHandler; } else if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(flags)), flags) >= 0) { LOG("Found key input device"); handlerFunc = keyHandler; } // Register the handler, if we have one. if (!handlerFunc) continue; rv = AddFdHandler(fd, handlerFunc, entryName); if (NS_FAILED(rv)) LOG("Failed to add fd to epoll fd"); } return rv; } nsresult nsAppShell::AddFdHandler(int fd, FdHandlerCallback handlerFunc, const char* deviceName) { epoll_event event = { EPOLLIN, { 0 } }; FdHandler *handler = mHandlers.AppendElement(); handler->fd = fd; strncpy(handler->name, deviceName, sizeof(handler->name) - 1); handler->func = handlerFunc; event.data.u32 = mHandlers.Length() - 1; return epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) ? NS_ERROR_UNEXPECTED : NS_OK; } void nsAppShell::ScheduleNativeEventCallback() { mNativeCallbackRequest = true; NotifyEvent(); } bool nsAppShell::ProcessNextNativeEvent(bool mayWait) { epoll_event events[16] = {{ 0 }}; int event_count; if ((event_count = epoll_wait(epollfd, events, 16, mayWait ? -1 : 0)) <= 0) return true; for (int i = 0; i < event_count; i++) mHandlers[events[i].data.u32].run(); // NativeEventCallback always schedules more if it needs it // so we can coalesce these. // See the implementation in nsBaseAppShell.cpp for more info if (mNativeCallbackRequest) { mNativeCallbackRequest = false; NativeEventCallback(); } if (gDrawRequest) { gDrawRequest = false; nsWindow::DoDraw(); } return true; } void nsAppShell::NotifyNativeEvent() { write(signalfds[1], "w", 1); }