// // "$Id$" // // Android screen interface for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2018 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // http://www.fltk.org/COPYING.php // // Please report all bugs and problems on the following page: // // http://www.fltk.org/str.php // #include "../../config_lib.h" #include "Fl_Android_Screen_Driver.H" #include "Fl_Android_Application.H" #include "Fl_Android_Graphics_Font.H" #include #include #include #include #include #include #include #include static void nothing() {} void (*fl_unlock_function)() = nothing; void (*fl_lock_function)() = nothing; static void timer_do_callback(int timerIndex); /* Creates a driver that manages all screen and display related calls. This function must be implemented once for every platform. */ Fl_Screen_Driver *Fl_Screen_Driver::newScreenDriver() { return new Fl_Android_Screen_Driver(); } extern int fl_send_system_handlers(void *e); int Fl_Android_Screen_Driver::handle_app_command() { // get the command int8_t cmd = Fl_Android_Application::read_cmd(); // setup the Android glue and prepare all settings for calling into FLTK Fl_Android_Application::pre_exec_cmd(cmd); // call all registered FLTK system handlers Fl::e_number = ((uint32_t)(cmd-Fl_Android_Application::APP_CMD_INPUT_CHANGED)) + FL_ANDROID_EVENT_INPUT_CHANGED; fl_send_system_handlers(nullptr); // fixup and finalize application wide command handling Fl_Android_Application::post_exec_cmd(cmd); return 1; } int Fl_Android_Screen_Driver::handle_input_event() { AInputQueue *queue = Fl_Android_Application::input_event_queue(); AInputEvent *event = nullptr; if (AInputQueue_getEvent(queue, &event) >= 0) { if (AInputQueue_preDispatchEvent(queue, event)==0) { int consumed = 0; switch (AInputEvent_getType(event)) { case AINPUT_EVENT_TYPE_KEY: consumed = handle_keyboard_event(queue, event); break; case AINPUT_EVENT_TYPE_MOTION: consumed = handle_mouse_event(queue, event); break; default: // don't do anything. There may be additional event types in the future AInputQueue_finishEvent(queue, event, consumed); break; } // TODO: handle all events here // AInputQueue_finishEvent(queue, event, consumed); } } return 0; } int Fl_Android_Screen_Driver::handle_mouse_event(AInputQueue *queue, AInputEvent *event) { int ex = Fl::e_x_root = (int)(AMotionEvent_getX(event, 0) * 600 / ANativeWindow_getWidth(Fl_Android_Application::native_window())); int ey = Fl::e_y_root = (int)(AMotionEvent_getY(event, 0) * 800 / ANativeWindow_getHeight(Fl_Android_Application::native_window())); // FIXME: find the window in which the event happened Fl_Window *win = Fl::grab(); if (!win) { win = Fl::first_window(); if (win && !win->modal()) { while (win) { if (ex >= win->x() && ex < win->x() + win->w() && ey >= win->y() && ey < win->y() + win->h()) break; win = Fl::next_window(win); } } } if (!win) { AInputQueue_finishEvent(queue, event, 0); return 0; } if (win) { Fl::e_x = ex-win->x(); Fl::e_y = ey-win->y(); } else { Fl::e_x = ex; Fl::e_y = ey; } Fl::e_state = FL_BUTTON1; Fl::e_keysym = FL_Button + 1; if (AMotionEvent_getAction(event) == AMOTION_EVENT_ACTION_DOWN) { AInputQueue_finishEvent(queue, event, 1); Fl::e_is_click = 1; if (win) Fl::handle(FL_PUSH, win); // do NOT send a push event into the "Desktop" Fl_Android_Application::log_i("Mouse push %d at %d, %d", Fl::event_button(), Fl::event_x(), Fl::event_y()); } else if (AMotionEvent_getAction(event) == AMOTION_EVENT_ACTION_MOVE) { AInputQueue_finishEvent(queue, event, 1); Fl::handle(FL_DRAG, win); } else if (AMotionEvent_getAction(event) == AMOTION_EVENT_ACTION_UP) { AInputQueue_finishEvent(queue, event, 1); Fl::e_state = 0; Fl::handle(FL_RELEASE, win); } else { AInputQueue_finishEvent(queue, event, 0); } return 1; } /** * Handle all events in the even queue. * * FIXME: what should this function return? * * @param time_to_wait * @return we do not know */ int Fl_Android_Screen_Driver::handle_queued_events(double time_to_wait) { int ret = 0; // Read all pending events. int ident; int events; int delay_millis = time_to_wait*1000; bool done = false; int delay = Fl::damage() ? 0 : delay_millis; while (!done) { ident = ALooper_pollOnce(delay, nullptr, &events, nullptr); switch (ident) { case Fl_Android_Application::LOOPER_ID_MAIN: ret = handle_app_command(); break; case Fl_Android_Application::LOOPER_ID_INPUT: ret = handle_input_event(); break; case Fl_Android_Application::LOOPER_ID_TIMER: timer_do_callback(Fl_Android_Application::receive_timer_index()); break; case ALOOPER_POLL_WAKE: Fl_Android_Application::log_e("Someone woke up ALooper_pollOnce."); done = true; break; case ALOOPER_POLL_CALLBACK: Fl_Android_Application::log_e( "Someone added a callback to ALooper_pollOnce."); done = true; break; case ALOOPER_POLL_TIMEOUT: done = true; // timer expired, return to FLTK break; case ALOOPER_POLL_ERROR: Fl_Android_Application::log_e( "Something caused an ERROR in ALooper_pollOnce."); done = true; // return to the app to find the error break; default: Fl_Android_Application::log_e( "Unknown return value from ALooper_pollOnce."); done = true; // return to the app, just in case break; } // we need to repeat this as long as there are messages in the queue, or any // change in the graphical interface will trigger a redraw immediately. To // save time and energy, we want to collect graphics changes and execute // them as soon as no more events are pending. // Setting delay to zero on the second round makes sure that all events // are handled first, and the call returns only when no more // events are pending. delay = 0; } return ret; } /** * Wait for a maximum of `time_to_wait` until something happens. * @param time_to_wait in seconds * @return We really do not know; check other platforms to see what is * consistent here. * FIXME: return the remaining time to reach 'time_to_wait' */ double Fl_Android_Screen_Driver::wait(double time_to_wait) { Fl::run_checks(); static int in_idle = 0; if (Fl::idle) { if (!in_idle) { in_idle = 1; Fl::idle(); in_idle = 0; } // the idle function may turn off idle, we can then wait: if (Fl::idle) time_to_wait = 0.0; } if (time_to_wait==0.0) { // if there is no wait time, handle the event and show the results right away fl_unlock_function(); handle_queued_events(time_to_wait); fl_lock_function(); Fl::flush(); } else { // if there is wait time, show the pending changes and then handle the events Fl::flush(); if (Fl::idle && !in_idle) // 'idle' may have been set within flush() time_to_wait = 0.0; fl_unlock_function(); handle_queued_events(time_to_wait); fl_lock_function(); } return 0.0; } /** * On Android, we currently write into a memory buffer and copy * the content to the screen. */ void Fl_Android_Screen_Driver::flush() { Fl_Screen_Driver::flush(); // FIXME: do this only if anything actually changed on screen (need to optimize)! if (pScreenContentChanged) { if (Fl_Android_Application::copy_screen()) pScreenContentChanged = false; } } // ---- timers ----------------------------------------------------------------- struct TimerData { timer_t handle; struct sigevent sigevent; Fl_Timeout_Handler callback; void *data; bool used; bool triggered; struct itimerspec timeout; }; static TimerData* timerData = nullptr; static int NTimerData = 0; static int nTimerData = 0; static int allocate_more_timers() { if (NTimerData == 0) { NTimerData = 8; } if (NTimerData>256) { // out of timers return -1; } NTimerData *= 2; timerData = (TimerData*)realloc(timerData, sizeof(TimerData) * NTimerData); return nTimerData; } static void timer_signal_handler(union sigval data) { int timerIndex = data.sival_int; Fl_Android_Application::send_timer_index(timerIndex); } static void timer_do_callback(int timerIndex) { TimerData& t = timerData[timerIndex]; t.triggered = false; if (t.callback) { t.callback(t.data); // TODO: should we release the timer at this point? } } void Fl_Android_Screen_Driver::add_timeout(double time, Fl_Timeout_Handler cb, void *data) { repeat_timeout(time, cb, data); } void Fl_Android_Screen_Driver::repeat_timeout(double time, Fl_Timeout_Handler cb, void *data) { int ret = -1; int timerIndex = -1; // first, find the timer associated with this handler for (int i = 0; i < nTimerData; ++i) { TimerData& t = timerData[i]; if ( (t.used) && (t.callback==cb) && (t.data==data) ) { timerIndex = i; break; } } // if we did not have a timer yet, find a free slot if (timerIndex==-1) { for (int i = 0; i < nTimerData; ++i) { if (!timerData[i].used) timerIndex = i; break; } } // if that didn't work, allocate more timers if (timerIndex==-1) { if (nTimerData==NTimerData) allocate_more_timers(); timerIndex = nTimerData++; } // if that didn;t work either, we ran out of timers if (timerIndex==-1) { Fl::error("FLTK ran out of timer slots."); return; } TimerData& t = timerData[timerIndex]; if (!t.used) { t.data = data; t.callback = cb; memset(&t.sigevent, 0, sizeof(struct sigevent)); t.sigevent.sigev_notify = SIGEV_THREAD; t.sigevent.sigev_notify_function = timer_signal_handler; t.sigevent.sigev_value.sival_int = timerIndex; ret = timer_create(CLOCK_MONOTONIC, &t.sigevent, &t.handle); if (ret==-1) { Fl_Android_Application::log_e("Can't create timer: %s", strerror(errno)); return; } t.used = true; } double ff; t.timeout = { { 0, 0 }, { (time_t)floor(time), (long)(modf(time, &ff)*1000000000) } }; ret = timer_settime(t.handle, 0, &t.timeout, nullptr); if (ret==-1) { Fl_Android_Application::log_e("Can't launch timer: %s", strerror(errno)); return; } t.triggered = true; } int Fl_Android_Screen_Driver::has_timeout(Fl_Timeout_Handler cb, void *data) { for (int i = 0; i < nTimerData; ++i) { TimerData& t = timerData[i]; if ( (t.used) && (t.callback==cb) && (t.data==data) ) { return 1; } } return 0; } void Fl_Android_Screen_Driver::remove_timeout(Fl_Timeout_Handler cb, void *data) { for (int i = 0; i < nTimerData; ++i) { TimerData& t = timerData[i]; if ( t.used && (t.callback==cb) && ( (t.data==data) || (data==nullptr) ) ) { if (t.used) timer_delete(t.handle); t.triggered = t.used = false; } } } // // End of "$Id$". //