/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 Jonathan Gordon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include "config.h" #include "lang.h" #include "appevents.h" #include "button.h" #include "action.h" #include "kernel.h" #include "debug.h" #include "splash.h" #include "settings.h" #include "pcmbuf.h" #include "misc.h" #if defined(HAVE_LCD_BITMAP) && !defined(BOOTLOADER) #include "language.h" #endif #include "viewport.h" #ifdef HAVE_TOUCHSCREEN #include "statusbar-skinned.h" #endif static int last_button = BUTTON_NONE|BUTTON_REL; /* allow the ipod wheel to work on startup */ static intptr_t last_data = 0; static int last_action = ACTION_NONE; static bool repeated = false; static bool wait_for_release = false; #ifdef HAVE_TOUCHSCREEN static bool short_press = false; #endif #define REPEAT_WINDOW_TICKS HZ/4 static int last_action_tick = 0; /* software keylock stuff */ #ifndef HAS_BUTTON_HOLD static bool keys_locked = false; static int unlock_combo = BUTTON_NONE; static bool screen_has_lock = false; #endif /* HAVE_SOFTWARE_KEYLOCK */ /* * do_button_check is the worker function for get_default_action. * returns ACTION_UNKNOWN or the requested return value from the list. */ static inline int do_button_check(const struct button_mapping *items, int button, int last_button, int *start) { int i = 0; int ret = ACTION_UNKNOWN; while (items[i].button_code != BUTTON_NONE) { if (items[i].button_code == button) { if ((items[i].pre_button_code == BUTTON_NONE) || (items[i].pre_button_code == last_button)) { ret = items[i].action_code; break; } } i++; } *start = i; return ret; } #if defined(HAVE_LCD_BITMAP) && !defined(BOOTLOADER) /* * button is horizontally inverted to support RTL language if the given language * and context combination require that */ static int button_flip_horizontally(int context, int button) { int newbutton; if (!(lang_is_rtl() && ((context == CONTEXT_STD) || (context == CONTEXT_TREE) || (context == CONTEXT_LIST) || (context == CONTEXT_MAINMENU)))) { return button; } newbutton = button & ~(BUTTON_LEFT | BUTTON_RIGHT #if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD) && \ !defined(SIMULATOR) | BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD #endif #if defined(BUTTON_MINUS) && defined(BUTTON_PLUS) && \ !defined(SIMULATOR) | BUTTON_MINUS | BUTTON_PLUS #endif ); if (button & BUTTON_LEFT) newbutton |= BUTTON_RIGHT; if (button & BUTTON_RIGHT) newbutton |= BUTTON_LEFT; #if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD) && \ !defined(SIMULATOR) if (button & BUTTON_SCROLL_BACK) newbutton |= BUTTON_SCROLL_FWD; if (button & BUTTON_SCROLL_FWD) newbutton |= BUTTON_SCROLL_BACK; #endif #if defined(BUTTON_MINUS) && defined(BUTTON_PLUS) && \ !defined(SIMULATOR) if (button & BUTTON_MINUS) newbutton |= BUTTON_PLUS; if (button & BUTTON_PLUS) newbutton |= BUTTON_MINUS; #endif return newbutton; } #endif static inline int get_next_context(const struct button_mapping *items, int i) { while (items[i].button_code != BUTTON_NONE) i++; return (items[i].action_code == ACTION_NONE ) ? CONTEXT_STD : items[i].action_code; } #if defined(HAVE_GUI_BOOST) && defined(HAVE_ADJUSTABLE_CPU_FREQ) /* Timeout for gui boost in seconds. */ #define GUI_BOOST_TIMEOUT (HZ) /* Helper function which is called to boost / unboost CPU. This function * avoids to increase boost_count with each call of gui_boost(). */ static void gui_boost(bool want_to_boost) { static bool boosted = false; if (want_to_boost && !boosted) { cpu_boost(true); boosted = true; } else if (!want_to_boost && boosted) { cpu_boost(false); boosted = false; } } /* gui_unboost_callback() is called GUI_BOOST_TIMEOUT seconds after the * last wheel scrolling event. */ static int gui_unboost_callback(struct timeout *tmo) { (void)tmo; gui_boost(false); return 0; } #endif /* * int get_action_worker(int context, struct button_mapping *user_mappings, int timeout) This function searches the button list for the given context for the just pressed button. If there is a match it returns the value from the list. If there is no match.. the last item in the list "points" to the next context in a chain so the "chain" is followed until the button is found. putting ACTION_NONE will get CONTEXT_STD which is always the last list checked. Timeout can be TIMEOUT_NOBLOCK to return immediatly TIMEOUT_BLOCK to wait for a button press Any number >0 to wait that many ticks for a press */ static int get_action_worker(int context, int timeout, const struct button_mapping* (*get_context_map)(int) ) { const struct button_mapping *items = NULL; int button; int i=0; int ret = ACTION_UNKNOWN; static int last_context = CONTEXT_STD; send_event(GUI_EVENT_ACTIONUPDATE, NULL); if (timeout == TIMEOUT_NOBLOCK) button = button_get(false); else if (timeout == TIMEOUT_BLOCK) button = button_get(true); else button = button_get_w_tmo(timeout); #if defined(HAVE_GUI_BOOST) && defined(HAVE_ADJUSTABLE_CPU_FREQ) static struct timeout gui_unboost; /* Boost the CPU in case of wheel scrolling activity in the defined contexts. * Call unboost with a timeout of GUI_BOOST_TIMEOUT. */ if ((button&(BUTTON_SCROLL_BACK|BUTTON_SCROLL_FWD)) && (context == CONTEXT_STD || context == CONTEXT_LIST || context == CONTEXT_MAINMENU || context == CONTEXT_TREE)) { gui_boost(true); timeout_register(&gui_unboost, gui_unboost_callback, GUI_BOOST_TIMEOUT, 0); } #endif /* Data from sys events can be pulled with button_get_data * multimedia button presses don't go through the action system */ if (button == BUTTON_NONE || button & (SYS_EVENT|BUTTON_MULTIMEDIA)) return button; /* Don't send any buttons through untill we see the release event */ if (wait_for_release) { if (button&BUTTON_REL) { /* remember the button for the below button eating on context * change */ last_button = button; wait_for_release = false; } return ACTION_NONE; } if ((context != last_context) && ((last_button & BUTTON_REL) == 0) #ifdef HAVE_SCROLLWHEEL /* Scrollwheel doesn't generate release events */ && !(last_button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD)) #endif ) { if (button & BUTTON_REL) { last_button = button; last_action = ACTION_NONE; } /* eat all buttons until the previous button was |BUTTON_REL (also eat the |BUTTON_REL button) */ return ACTION_NONE; /* "safest" return value */ } last_context = context; #ifndef HAS_BUTTON_HOLD screen_has_lock = ((context & ALLOW_SOFTLOCK) == ALLOW_SOFTLOCK); if (is_keys_locked()) { if (button == unlock_combo) { last_button = BUTTON_NONE; keys_locked = false; splash(HZ/2, str(LANG_KEYLOCK_OFF)); return ACTION_REDRAW; } else #if (BUTTON_REMOTE != 0) if ((button & BUTTON_REMOTE) == 0) #endif { if ((button & BUTTON_REL)) splash(HZ/2, str(LANG_KEYLOCK_ON)); return ACTION_REDRAW; } } context &= ~ALLOW_SOFTLOCK; #endif /* HAS_BUTTON_HOLD */ #ifdef HAVE_TOUCHSCREEN if (button & BUTTON_TOUCHSCREEN) { repeated = false; short_press = false; if (last_button & BUTTON_TOUCHSCREEN) { if ((button & BUTTON_REL) && ((last_button & BUTTON_REPEAT)==0)) { short_press = true; } else if (button & BUTTON_REPEAT) repeated = true; } last_button = button; return ACTION_TOUCHSCREEN; } #endif #if defined(HAVE_LCD_BITMAP) && !defined(BOOTLOADER) button = button_flip_horizontally(context, button); #endif /* logf("%x,%x",last_button,button); */ while (1) { /* logf("context = %x",context); */ #if (BUTTON_REMOTE != 0) if (button & BUTTON_REMOTE) context |= CONTEXT_REMOTE; #endif if ((context & CONTEXT_PLUGIN) && get_context_map) items = get_context_map(context); else items = get_context_mapping(context); if (items == NULL) break; ret = do_button_check(items,button,last_button,&i); if (ret == ACTION_UNKNOWN) { context = get_next_context(items,i); if (context != (int)CONTEXT_STOPSEARCHING) { i = 0; continue; } } /* Action was found or STOPSEARCHING was specified */ break; } /* DEBUGF("ret = %x\n",ret); */ #ifndef HAS_BUTTON_HOLD if (screen_has_lock && (ret == ACTION_STD_KEYLOCK)) { unlock_combo = button; keys_locked = true; splash(HZ/2, str(LANG_KEYLOCK_ON)); button_clear_queue(); return ACTION_REDRAW; } #endif if ((current_tick - last_action_tick < REPEAT_WINDOW_TICKS) && (ret == last_action)) repeated = true; else repeated = false; last_button = button; last_action = ret; last_data = button_get_data(); last_action_tick = current_tick; #if CONFIG_CODEC == SWCODEC /* Produce keyclick */ keyclick_click(0, ret); #endif return ret; } int get_action(int context, int timeout) { int button = get_action_worker(context,timeout,NULL); #ifdef HAVE_TOUCHSCREEN if (button == ACTION_TOUCHSCREEN) button = sb_touch_to_button(context); #endif return button; } int get_custom_action(int context,int timeout, const struct button_mapping* (*get_context_map)(int)) { return get_action_worker(context,timeout,get_context_map); } bool action_userabort(int timeout) { int action = get_action_worker(CONTEXT_STD,timeout,NULL); bool ret = (action == ACTION_STD_CANCEL); if(!ret) { default_event_handler(action); } return ret; } #ifndef HAS_BUTTON_HOLD bool is_keys_locked(void) { return (screen_has_lock && keys_locked); } #endif intptr_t get_action_data(void) { return last_data; } int get_action_statuscode(int *button) { int ret = 0; if (button) *button = last_button; if (last_button & BUTTON_REMOTE) ret |= ACTION_REMOTE; if (repeated) ret |= ACTION_REPEAT; return ret; } #ifdef HAVE_TOUCHSCREEN /* return BUTTON_NONE on error * BUTTON_REPEAT if repeated press * BUTTON_REPEAT|BUTTON_REL if release after repeated press * BUTTON_REL if it's a short press = release after press * BUTTON_TOUCHSCREEN if press */ int action_get_touchscreen_press(short *x, short *y) { static int last_data = 0; int data; if ((last_button & BUTTON_TOUCHSCREEN) == 0) return BUTTON_NONE; data = button_get_data(); if (last_button & BUTTON_REL) { *x = (last_data&0xffff0000)>>16; *y = (last_data&0xffff); } else { *x = (data&0xffff0000)>>16; *y = (data&0xffff); } last_data = data; if (repeated) return BUTTON_REPEAT; if (short_press) return BUTTON_REL; /* This is to return a BUTTON_REL after a BUTTON_REPEAT. */ if (last_button & BUTTON_REL) return BUTTON_REPEAT|BUTTON_REL; return BUTTON_TOUCHSCREEN; } int action_get_touchscreen_press_in_vp(short *x1, short *y1, struct viewport *vp) { short x, y; int ret; ret = action_get_touchscreen_press(&x, &y); if (ret != BUTTON_NONE && viewport_point_within_vp(vp, x, y)) { *x1 = x - vp->x; *y1 = y - vp->y; return ret; } if (ret & BUTTON_TOUCHSCREEN) return ACTION_UNKNOWN; return BUTTON_NONE; } #endif /* Don't let get_action*() return any ACTION_* values until the current buttons * have been released. SYS_* and BUTTON_NONE will go through. * Any actions relying on _RELEASE won't get seen. */ void action_wait_for_release(void) { wait_for_release = true; }