diff options
Diffstat (limited to 'src/input/ButtonTranslator.cpp')
-rw-r--r-- | src/input/ButtonTranslator.cpp | 1687 |
1 files changed, 1687 insertions, 0 deletions
diff --git a/src/input/ButtonTranslator.cpp b/src/input/ButtonTranslator.cpp new file mode 100644 index 0000000000..c297408f36 --- /dev/null +++ b/src/input/ButtonTranslator.cpp @@ -0,0 +1,1687 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * 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, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "system.h" +#include "interfaces/Builtins.h" +#include "ButtonTranslator.h" +#include "profiles/ProfilesManager.h" +#include "utils/URIUtils.h" +#include "guilib/Key.h" +#include "guilib/WindowIDs.h" +#include "input/XBMC_keysym.h" +#include "input/XBMC_keytable.h" +#include "filesystem/File.h" +#include "filesystem/Directory.h" +#include "FileItem.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "utils/XBMCTinyXML.h" +#include "utils/RegExp.h" +#include "XBIRRemote.h" +#include "Util.h" +#include <boost/shared_ptr.hpp> + +#if defined(TARGET_WINDOWS) +#include "input/windows/WINJoystick.h" +#elif defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER) +#include "SDLJoystick.h" +#endif + +#define JOYSTICK_DEFAULT_MAP "_xbmc_" + +using namespace std; +using namespace XFILE; + +typedef struct +{ + const char* name; + int action; +} ActionMapping; + +typedef struct +{ + int origin; + int target; +} WindowMapping; + +static const ActionMapping actions[] = +{ + {"left" , ACTION_MOVE_LEFT }, + {"right" , ACTION_MOVE_RIGHT}, + {"up" , ACTION_MOVE_UP }, + {"down" , ACTION_MOVE_DOWN }, + {"pageup" , ACTION_PAGE_UP }, + {"pagedown" , ACTION_PAGE_DOWN}, + {"select" , ACTION_SELECT_ITEM}, + {"highlight" , ACTION_HIGHLIGHT_ITEM}, + {"parentdir" , ACTION_NAV_BACK}, // backward compatibility + {"parentfolder" , ACTION_PARENT_DIR}, + {"back" , ACTION_NAV_BACK}, + {"previousmenu" , ACTION_PREVIOUS_MENU}, + {"info" , ACTION_SHOW_INFO}, + {"pause" , ACTION_PAUSE}, + {"stop" , ACTION_STOP}, + {"skipnext" , ACTION_NEXT_ITEM}, + {"skipprevious" , ACTION_PREV_ITEM}, + {"fullscreen" , ACTION_SHOW_GUI}, + {"aspectratio" , ACTION_ASPECT_RATIO}, + {"stepforward" , ACTION_STEP_FORWARD}, + {"stepback" , ACTION_STEP_BACK}, + {"bigstepforward" , ACTION_BIG_STEP_FORWARD}, + {"bigstepback" , ACTION_BIG_STEP_BACK}, + {"chapterorbigstepforward", ACTION_CHAPTER_OR_BIG_STEP_FORWARD}, + {"chapterorbigstepback" , ACTION_CHAPTER_OR_BIG_STEP_BACK}, + {"osd" , ACTION_SHOW_OSD}, + {"showsubtitles" , ACTION_SHOW_SUBTITLES}, + {"nextsubtitle" , ACTION_NEXT_SUBTITLE}, + {"cyclesubtitle" , ACTION_CYCLE_SUBTITLE}, + {"codecinfo" , ACTION_SHOW_CODEC}, + {"nextpicture" , ACTION_NEXT_PICTURE}, + {"previouspicture" , ACTION_PREV_PICTURE}, + {"zoomout" , ACTION_ZOOM_OUT}, + {"zoomin" , ACTION_ZOOM_IN}, + {"playlist" , ACTION_SHOW_PLAYLIST}, + {"queue" , ACTION_QUEUE_ITEM}, + {"zoomnormal" , ACTION_ZOOM_LEVEL_NORMAL}, + {"zoomlevel1" , ACTION_ZOOM_LEVEL_1}, + {"zoomlevel2" , ACTION_ZOOM_LEVEL_2}, + {"zoomlevel3" , ACTION_ZOOM_LEVEL_3}, + {"zoomlevel4" , ACTION_ZOOM_LEVEL_4}, + {"zoomlevel5" , ACTION_ZOOM_LEVEL_5}, + {"zoomlevel6" , ACTION_ZOOM_LEVEL_6}, + {"zoomlevel7" , ACTION_ZOOM_LEVEL_7}, + {"zoomlevel8" , ACTION_ZOOM_LEVEL_8}, + {"zoomlevel9" , ACTION_ZOOM_LEVEL_9}, + {"nextcalibration" , ACTION_CALIBRATE_SWAP_ARROWS}, + {"resetcalibration" , ACTION_CALIBRATE_RESET}, + {"analogmove" , ACTION_ANALOG_MOVE}, + {"rotate" , ACTION_ROTATE_PICTURE_CW}, + {"rotateccw" , ACTION_ROTATE_PICTURE_CCW}, + {"close" , ACTION_NAV_BACK}, // backwards compatibility + {"subtitledelayminus", ACTION_SUBTITLE_DELAY_MIN}, + {"subtitledelay" , ACTION_SUBTITLE_DELAY}, + {"subtitledelayplus" , ACTION_SUBTITLE_DELAY_PLUS}, + {"audiodelayminus" , ACTION_AUDIO_DELAY_MIN}, + {"audiodelay" , ACTION_AUDIO_DELAY}, + {"audiodelayplus" , ACTION_AUDIO_DELAY_PLUS}, + {"subtitleshiftup" , ACTION_SUBTITLE_VSHIFT_UP}, + {"subtitleshiftdown" , ACTION_SUBTITLE_VSHIFT_DOWN}, + {"subtitlealign" , ACTION_SUBTITLE_ALIGN}, + {"audionextlanguage" , ACTION_AUDIO_NEXT_LANGUAGE}, + {"verticalshiftup" , ACTION_VSHIFT_UP}, + {"verticalshiftdown" , ACTION_VSHIFT_DOWN}, + {"nextresolution" , ACTION_CHANGE_RESOLUTION}, + {"audiotoggledigital", ACTION_TOGGLE_DIGITAL_ANALOG}, + {"number0" , REMOTE_0}, + {"number1" , REMOTE_1}, + {"number2" , REMOTE_2}, + {"number3" , REMOTE_3}, + {"number4" , REMOTE_4}, + {"number5" , REMOTE_5}, + {"number6" , REMOTE_6}, + {"number7" , REMOTE_7}, + {"number8" , REMOTE_8}, + {"number9" , REMOTE_9}, + {"osdleft" , ACTION_OSD_SHOW_LEFT}, + {"osdright" , ACTION_OSD_SHOW_RIGHT}, + {"osdup" , ACTION_OSD_SHOW_UP}, + {"osddown" , ACTION_OSD_SHOW_DOWN}, + {"osdselect" , ACTION_OSD_SHOW_SELECT}, + {"osdvalueplus" , ACTION_OSD_SHOW_VALUE_PLUS}, + {"osdvalueminus" , ACTION_OSD_SHOW_VALUE_MIN}, + {"smallstepback" , ACTION_SMALL_STEP_BACK}, + {"fastforward" , ACTION_PLAYER_FORWARD}, + {"rewind" , ACTION_PLAYER_REWIND}, + {"play" , ACTION_PLAYER_PLAY}, + {"playpause" , ACTION_PLAYER_PLAYPAUSE}, + {"switchplayer" , ACTION_SWITCH_PLAYER}, + {"delete" , ACTION_DELETE_ITEM}, + {"copy" , ACTION_COPY_ITEM}, + {"move" , ACTION_MOVE_ITEM}, + {"mplayerosd" , ACTION_SHOW_MPLAYER_OSD}, + {"hidesubmenu" , ACTION_OSD_HIDESUBMENU}, + {"screenshot" , ACTION_TAKE_SCREENSHOT}, + {"rename" , ACTION_RENAME_ITEM}, + {"togglewatched" , ACTION_TOGGLE_WATCHED}, + {"scanitem" , ACTION_SCAN_ITEM}, + {"reloadkeymaps" , ACTION_RELOAD_KEYMAPS}, + {"volumeup" , ACTION_VOLUME_UP}, + {"volumedown" , ACTION_VOLUME_DOWN}, + {"mute" , ACTION_MUTE}, + {"backspace" , ACTION_BACKSPACE}, + {"scrollup" , ACTION_SCROLL_UP}, + {"scrolldown" , ACTION_SCROLL_DOWN}, + {"analogfastforward" , ACTION_ANALOG_FORWARD}, + {"analogrewind" , ACTION_ANALOG_REWIND}, + {"moveitemup" , ACTION_MOVE_ITEM_UP}, + {"moveitemdown" , ACTION_MOVE_ITEM_DOWN}, + {"contextmenu" , ACTION_CONTEXT_MENU}, + {"shift" , ACTION_SHIFT}, + {"symbols" , ACTION_SYMBOLS}, + {"cursorleft" , ACTION_CURSOR_LEFT}, + {"cursorright" , ACTION_CURSOR_RIGHT}, + {"showtime" , ACTION_SHOW_OSD_TIME}, + {"analogseekforward" , ACTION_ANALOG_SEEK_FORWARD}, + {"analogseekback" , ACTION_ANALOG_SEEK_BACK}, + {"showpreset" , ACTION_VIS_PRESET_SHOW}, + {"nextpreset" , ACTION_VIS_PRESET_NEXT}, + {"previouspreset" , ACTION_VIS_PRESET_PREV}, + {"lockpreset" , ACTION_VIS_PRESET_LOCK}, + {"randompreset" , ACTION_VIS_PRESET_RANDOM}, + {"increasevisrating" , ACTION_VIS_RATE_PRESET_PLUS}, + {"decreasevisrating" , ACTION_VIS_RATE_PRESET_MINUS}, + {"showvideomenu" , ACTION_SHOW_VIDEOMENU}, + {"enter" , ACTION_ENTER}, + {"increaserating" , ACTION_INCREASE_RATING}, + {"decreaserating" , ACTION_DECREASE_RATING}, + {"togglefullscreen" , ACTION_TOGGLE_FULLSCREEN}, + {"nextscene" , ACTION_NEXT_SCENE}, + {"previousscene" , ACTION_PREV_SCENE}, + {"nextletter" , ACTION_NEXT_LETTER}, + {"prevletter" , ACTION_PREV_LETTER}, + {"jumpsms2" , ACTION_JUMP_SMS2}, + {"jumpsms3" , ACTION_JUMP_SMS3}, + {"jumpsms4" , ACTION_JUMP_SMS4}, + {"jumpsms5" , ACTION_JUMP_SMS5}, + {"jumpsms6" , ACTION_JUMP_SMS6}, + {"jumpsms7" , ACTION_JUMP_SMS7}, + {"jumpsms8" , ACTION_JUMP_SMS8}, + {"jumpsms9" , ACTION_JUMP_SMS9}, + {"filter" , ACTION_FILTER}, + {"filterclear" , ACTION_FILTER_CLEAR}, + {"filtersms2" , ACTION_FILTER_SMS2}, + {"filtersms3" , ACTION_FILTER_SMS3}, + {"filtersms4" , ACTION_FILTER_SMS4}, + {"filtersms5" , ACTION_FILTER_SMS5}, + {"filtersms6" , ACTION_FILTER_SMS6}, + {"filtersms7" , ACTION_FILTER_SMS7}, + {"filtersms8" , ACTION_FILTER_SMS8}, + {"filtersms9" , ACTION_FILTER_SMS9}, + {"firstpage" , ACTION_FIRST_PAGE}, + {"lastpage" , ACTION_LAST_PAGE}, + {"guiprofile" , ACTION_GUIPROFILE_BEGIN}, + {"red" , ACTION_TELETEXT_RED}, + {"green" , ACTION_TELETEXT_GREEN}, + {"yellow" , ACTION_TELETEXT_YELLOW}, + {"blue" , ACTION_TELETEXT_BLUE}, + {"increasepar" , ACTION_INCREASE_PAR}, + {"decreasepar" , ACTION_DECREASE_PAR}, + {"volampup" , ACTION_VOLAMP_UP}, + {"volampdown" , ACTION_VOLAMP_DOWN}, + {"createbookmark" , ACTION_CREATE_BOOKMARK}, + {"createepisodebookmark" , ACTION_CREATE_EPISODE_BOOKMARK}, + {"settingsreset" , ACTION_SETTINGS_RESET}, + {"settingslevelchange", ACTION_SETTINGS_LEVEL_CHANGE}, + + // 3D movie playback/GUI + {"stereomode" , ACTION_STEREOMODE_SELECT}, // cycle 3D modes, for now an alias for next + {"nextstereomode" , ACTION_STEREOMODE_NEXT}, + {"previousstereomode" , ACTION_STEREOMODE_PREVIOUS}, + {"togglestereomode" , ACTION_STEREOMODE_TOGGLE}, + {"stereomodetomono" , ACTION_STEREOMODE_TOMONO}, + + // PVR actions + {"channelup" , ACTION_CHANNEL_UP}, + {"channeldown" , ACTION_CHANNEL_DOWN}, + {"previouschannelgroup" , ACTION_PREVIOUS_CHANNELGROUP}, + {"nextchannelgroup" , ACTION_NEXT_CHANNELGROUP}, + {"playpvr" , ACTION_PVR_PLAY}, + {"playpvrtv" , ACTION_PVR_PLAY_TV}, + {"playpvrradio" , ACTION_PVR_PLAY_RADIO}, + {"record" , ACTION_RECORD}, + + // Mouse actions + {"leftclick" , ACTION_MOUSE_LEFT_CLICK}, + {"rightclick" , ACTION_MOUSE_RIGHT_CLICK}, + {"middleclick" , ACTION_MOUSE_MIDDLE_CLICK}, + {"doubleclick" , ACTION_MOUSE_DOUBLE_CLICK}, + {"longclick" , ACTION_MOUSE_LONG_CLICK}, + {"wheelup" , ACTION_MOUSE_WHEEL_UP}, + {"wheeldown" , ACTION_MOUSE_WHEEL_DOWN}, + {"mousedrag" , ACTION_MOUSE_DRAG}, + {"mousemove" , ACTION_MOUSE_MOVE}, + + // Touch + {"tap" , ACTION_TOUCH_TAP}, + {"longpress" , ACTION_TOUCH_LONGPRESS}, + {"pangesture" , ACTION_GESTURE_PAN}, + {"zoomgesture" , ACTION_GESTURE_ZOOM}, + {"rotategesture" , ACTION_GESTURE_ROTATE}, + {"swipeleft" , ACTION_GESTURE_SWIPE_LEFT}, + {"swiperight" , ACTION_GESTURE_SWIPE_RIGHT}, + {"swipeup" , ACTION_GESTURE_SWIPE_UP}, + {"swipedown" , ACTION_GESTURE_SWIPE_DOWN}, + + // Do nothing action + { "noop" , ACTION_NOOP} +}; + +static const ActionMapping windows[] = + {{"home" , WINDOW_HOME}, + {"programs" , WINDOW_PROGRAMS}, + {"pictures" , WINDOW_PICTURES}, + {"filemanager" , WINDOW_FILES}, + {"files" , WINDOW_FILES}, // backward compat + {"settings" , WINDOW_SETTINGS_MENU}, + {"music" , WINDOW_MUSIC}, + {"video" , WINDOW_VIDEOS}, + {"videos" , WINDOW_VIDEO_NAV}, + {"pvr" , WINDOW_TV_CHANNELS}, // backward compat + {"tvchannels" , WINDOW_TV_CHANNELS}, + {"tvrecordings" , WINDOW_TV_RECORDINGS}, + {"tvguide" , WINDOW_TV_GUIDE}, + {"tvtimers" , WINDOW_TV_TIMERS}, + {"tvsearch" , WINDOW_TV_SEARCH}, + {"radiochannels" , WINDOW_RADIO_CHANNELS}, + {"radiorecordings" , WINDOW_RADIO_RECORDINGS}, + {"radioguide" , WINDOW_RADIO_GUIDE}, + {"radiotimers" , WINDOW_RADIO_TIMERS}, + {"radiosearch" , WINDOW_RADIO_SEARCH}, + {"pvrguideinfo" , WINDOW_DIALOG_PVR_GUIDE_INFO}, + {"pvrrecordinginfo" , WINDOW_DIALOG_PVR_RECORDING_INFO}, + {"pvrtimersetting" , WINDOW_DIALOG_PVR_TIMER_SETTING}, + {"pvrgroupmanager" , WINDOW_DIALOG_PVR_GROUP_MANAGER}, + {"pvrchannelmanager" , WINDOW_DIALOG_PVR_CHANNEL_MANAGER}, + {"pvrguidesearch" , WINDOW_DIALOG_PVR_GUIDE_SEARCH}, + {"pvrchannelscan" , WINDOW_DIALOG_PVR_CHANNEL_SCAN}, + {"pvrupdateprogress" , WINDOW_DIALOG_PVR_UPDATE_PROGRESS}, + {"pvrosdchannels" , WINDOW_DIALOG_PVR_OSD_CHANNELS}, + {"pvrosdguide" , WINDOW_DIALOG_PVR_OSD_GUIDE}, + {"pvrosddirector" , WINDOW_DIALOG_PVR_OSD_DIRECTOR}, + {"pvrosdcutter" , WINDOW_DIALOG_PVR_OSD_CUTTER}, + {"pvrosdteletext" , WINDOW_DIALOG_OSD_TELETEXT}, + {"systeminfo" , WINDOW_SYSTEM_INFORMATION}, + {"testpattern" , WINDOW_TEST_PATTERN}, + {"screencalibration" , WINDOW_SCREEN_CALIBRATION}, + {"guicalibration" , WINDOW_SCREEN_CALIBRATION}, // backward compat + {"picturessettings" , WINDOW_SETTINGS_MYPICTURES}, + {"programssettings" , WINDOW_SETTINGS_MYPROGRAMS}, + {"weathersettings" , WINDOW_SETTINGS_MYWEATHER}, + {"musicsettings" , WINDOW_SETTINGS_MYMUSIC}, + {"systemsettings" , WINDOW_SETTINGS_SYSTEM}, + {"videossettings" , WINDOW_SETTINGS_MYVIDEOS}, + {"networksettings" , WINDOW_SETTINGS_SERVICE}, // backward compat + {"servicesettings" , WINDOW_SETTINGS_SERVICE}, + {"appearancesettings" , WINDOW_SETTINGS_APPEARANCE}, + {"pvrsettings" , WINDOW_SETTINGS_MYPVR}, + {"tvsettings" , WINDOW_SETTINGS_MYPVR}, // backward compat + {"scripts" , WINDOW_PROGRAMS}, // backward compat + {"videofiles" , WINDOW_VIDEO_FILES}, + {"videolibrary" , WINDOW_VIDEO_NAV}, + {"videoplaylist" , WINDOW_VIDEO_PLAYLIST}, + {"loginscreen" , WINDOW_LOGIN_SCREEN}, + {"profiles" , WINDOW_SETTINGS_PROFILES}, + {"skinsettings" , WINDOW_SKIN_SETTINGS}, + {"addonbrowser" , WINDOW_ADDON_BROWSER}, + {"yesnodialog" , WINDOW_DIALOG_YES_NO}, + {"progressdialog" , WINDOW_DIALOG_PROGRESS}, + {"virtualkeyboard" , WINDOW_DIALOG_KEYBOARD}, + {"volumebar" , WINDOW_DIALOG_VOLUME_BAR}, + {"submenu" , WINDOW_DIALOG_SUB_MENU}, + {"favourites" , WINDOW_DIALOG_FAVOURITES}, + {"contextmenu" , WINDOW_DIALOG_CONTEXT_MENU}, + {"infodialog" , WINDOW_DIALOG_KAI_TOAST}, + {"numericinput" , WINDOW_DIALOG_NUMERIC}, + {"gamepadinput" , WINDOW_DIALOG_GAMEPAD}, + {"shutdownmenu" , WINDOW_DIALOG_BUTTON_MENU}, + {"mutebug" , WINDOW_DIALOG_MUTE_BUG}, + {"playercontrols" , WINDOW_DIALOG_PLAYER_CONTROLS}, + {"seekbar" , WINDOW_DIALOG_SEEK_BAR}, + {"musicosd" , WINDOW_DIALOG_MUSIC_OSD}, + {"addonsettings" , WINDOW_DIALOG_ADDON_SETTINGS}, + {"visualisationsettings" , WINDOW_DIALOG_ADDON_SETTINGS}, // backward compat + {"visualisationpresetlist" , WINDOW_DIALOG_VIS_PRESET_LIST}, + {"osdvideosettings" , WINDOW_DIALOG_VIDEO_OSD_SETTINGS}, + {"osdaudiosettings" , WINDOW_DIALOG_AUDIO_OSD_SETTINGS}, + {"videobookmarks" , WINDOW_DIALOG_VIDEO_BOOKMARKS}, + {"filebrowser" , WINDOW_DIALOG_FILE_BROWSER}, + {"networksetup" , WINDOW_DIALOG_NETWORK_SETUP}, + {"mediasource" , WINDOW_DIALOG_MEDIA_SOURCE}, + {"profilesettings" , WINDOW_DIALOG_PROFILE_SETTINGS}, + {"locksettings" , WINDOW_DIALOG_LOCK_SETTINGS}, + {"contentsettings" , WINDOW_DIALOG_CONTENT_SETTINGS}, + {"songinformation" , WINDOW_DIALOG_SONG_INFO}, + {"smartplaylisteditor" , WINDOW_DIALOG_SMART_PLAYLIST_EDITOR}, + {"smartplaylistrule" , WINDOW_DIALOG_SMART_PLAYLIST_RULE}, + {"busydialog" , WINDOW_DIALOG_BUSY}, + {"pictureinfo" , WINDOW_DIALOG_PICTURE_INFO}, + {"accesspoints" , WINDOW_DIALOG_ACCESS_POINTS}, + {"fullscreeninfo" , WINDOW_DIALOG_FULLSCREEN_INFO}, + {"karaokeselector" , WINDOW_DIALOG_KARAOKE_SONGSELECT}, + {"karaokelargeselector" , WINDOW_DIALOG_KARAOKE_SELECTOR}, + {"sliderdialog" , WINDOW_DIALOG_SLIDER}, + {"addoninformation" , WINDOW_DIALOG_ADDON_INFO}, + {"subtitlesearch" , WINDOW_DIALOG_SUBTITLES}, + {"musicplaylist" , WINDOW_MUSIC_PLAYLIST}, + {"musicfiles" , WINDOW_MUSIC_FILES}, + {"musiclibrary" , WINDOW_MUSIC_NAV}, + {"musicplaylisteditor" , WINDOW_MUSIC_PLAYLIST_EDITOR}, + {"teletext" , WINDOW_DIALOG_OSD_TELETEXT}, + {"selectdialog" , WINDOW_DIALOG_SELECT}, + {"musicinformation" , WINDOW_DIALOG_MUSIC_INFO}, + {"okdialog" , WINDOW_DIALOG_OK}, + {"movieinformation" , WINDOW_DIALOG_VIDEO_INFO}, + {"textviewer" , WINDOW_DIALOG_TEXT_VIEWER}, + {"fullscreenvideo" , WINDOW_FULLSCREEN_VIDEO}, + {"fullscreenlivetv" , WINDOW_FULLSCREEN_LIVETV}, // virtual window/keymap section for PVR specific bindings in fullscreen playback (which internally uses WINDOW_FULLSCREEN_VIDEO) + {"fullscreenradio" , WINDOW_FULLSCREEN_RADIO}, // virtual window for fullscreen radio, uses WINDOW_VISUALISATION as fallback + {"visualisation" , WINDOW_VISUALISATION}, + {"slideshow" , WINDOW_SLIDESHOW}, + {"filestackingdialog" , WINDOW_DIALOG_FILESTACKING}, + {"karaoke" , WINDOW_KARAOKELYRICS}, + {"weather" , WINDOW_WEATHER}, + {"screensaver" , WINDOW_SCREENSAVER}, + {"videoosd" , WINDOW_DIALOG_VIDEO_OSD}, + {"videomenu" , WINDOW_VIDEO_MENU}, + {"videotimeseek" , WINDOW_VIDEO_TIME_SEEK}, + {"musicoverlay" , WINDOW_DIALOG_MUSIC_OVERLAY}, + {"videooverlay" , WINDOW_DIALOG_VIDEO_OVERLAY}, + {"startwindow" , WINDOW_START}, + {"startup" , WINDOW_STARTUP_ANIM}, + {"peripherals" , WINDOW_DIALOG_PERIPHERAL_MANAGER}, + {"peripheralsettings" , WINDOW_DIALOG_PERIPHERAL_SETTINGS}, + {"extendedprogressdialog" , WINDOW_DIALOG_EXT_PROGRESS}, + {"mediafilter" , WINDOW_DIALOG_MEDIA_FILTER}, + {"addon" , WINDOW_ADDON_START}}; + +static const ActionMapping mousekeys[] = +{ + { "click", KEY_MOUSE_CLICK }, + { "leftclick", KEY_MOUSE_CLICK }, + { "rightclick", KEY_MOUSE_RIGHTCLICK }, + { "middleclick", KEY_MOUSE_MIDDLECLICK }, + { "doubleclick", KEY_MOUSE_DOUBLE_CLICK }, + { "longclick", KEY_MOUSE_LONG_CLICK }, + { "wheelup", KEY_MOUSE_WHEEL_UP }, + { "wheeldown", KEY_MOUSE_WHEEL_DOWN }, + { "mousedrag", KEY_MOUSE_DRAG }, + { "mousemove", KEY_MOUSE_MOVE } +}; + +static const ActionMapping touchcommands[] = +{ + { "tap", ACTION_TOUCH_TAP }, + { "longpress", ACTION_TOUCH_LONGPRESS }, + { "pan", ACTION_GESTURE_PAN }, + { "zoom", ACTION_GESTURE_ZOOM }, + { "rotate", ACTION_GESTURE_ROTATE }, + { "swipeleft", ACTION_GESTURE_SWIPE_LEFT }, + { "swiperight", ACTION_GESTURE_SWIPE_RIGHT }, + { "swipeup", ACTION_GESTURE_SWIPE_UP }, + { "swipedown", ACTION_GESTURE_SWIPE_DOWN } +}; + +static const WindowMapping fallbackWindows[] = +{ + { WINDOW_FULLSCREEN_LIVETV, WINDOW_FULLSCREEN_VIDEO }, + { WINDOW_DIALOG_FULLSCREEN_INFO, WINDOW_FULLSCREEN_VIDEO }, + { WINDOW_FULLSCREEN_RADIO, WINDOW_VISUALISATION } +}; + +#ifdef TARGET_WINDOWS +static const ActionMapping appcommands[] = +{ + { "browser_back", APPCOMMAND_BROWSER_BACKWARD }, + { "browser_forward", APPCOMMAND_BROWSER_FORWARD }, + { "browser_refresh", APPCOMMAND_BROWSER_REFRESH }, + { "browser_stop", APPCOMMAND_BROWSER_STOP }, + { "browser_search", APPCOMMAND_BROWSER_SEARCH }, + { "browser_favorites", APPCOMMAND_BROWSER_FAVORITES }, + { "browser_home", APPCOMMAND_BROWSER_HOME }, + { "volume_mute", APPCOMMAND_VOLUME_MUTE }, + { "volume_down", APPCOMMAND_VOLUME_DOWN }, + { "volume_up", APPCOMMAND_VOLUME_UP }, + { "next_track", APPCOMMAND_MEDIA_NEXTTRACK }, + { "prev_track", APPCOMMAND_MEDIA_PREVIOUSTRACK }, + { "stop", APPCOMMAND_MEDIA_STOP }, + { "play_pause", APPCOMMAND_MEDIA_PLAY_PAUSE }, + { "launch_mail", APPCOMMAND_LAUNCH_MAIL }, + { "launch_media_select", APPCOMMAND_LAUNCH_MEDIA_SELECT }, + { "launch_app1", APPCOMMAND_LAUNCH_APP1 }, + { "launch_app2", APPCOMMAND_LAUNCH_APP2 }, + { "play", APPCOMMAND_MEDIA_PLAY }, + { "pause", APPCOMMAND_MEDIA_PAUSE }, + { "fastforward", APPCOMMAND_MEDIA_FAST_FORWARD }, + { "rewind", APPCOMMAND_MEDIA_REWIND }, + { "channelup", APPCOMMAND_MEDIA_CHANNEL_UP }, + { "channeldown", APPCOMMAND_MEDIA_CHANNEL_DOWN } +}; +#endif + +CButtonTranslator& CButtonTranslator::GetInstance() +{ + static CButtonTranslator sl_instance; + return sl_instance; +} + +CButtonTranslator::CButtonTranslator() +{ + m_deviceList.clear(); + m_Loaded = false; +} + +#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE) +void CButtonTranslator::ClearLircButtonMapEntries() +{ + vector<lircButtonMap*> maps; + for (map<std::string,lircButtonMap*>::iterator it = lircRemotesMap.begin(); + it != lircRemotesMap.end();++it) + maps.push_back(it->second); + sort(maps.begin(),maps.end()); + vector<lircButtonMap*>::iterator itend = unique(maps.begin(),maps.end()); + for (vector<lircButtonMap*>::iterator it = maps.begin(); it != itend;++it) + delete *it; +} +#endif + +CButtonTranslator::~CButtonTranslator() +{ +#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE) + ClearLircButtonMapEntries(); +#endif +} + +// Add the supplied device name to the list of connected devices +void CButtonTranslator::AddDevice(std::string& strDevice) +{ + // Only add the device if it isn't already in the list + std::list<std::string>::iterator it; + for (it = m_deviceList.begin(); it != m_deviceList.end(); it++) + if (*it == strDevice) + return; + + // Add the device + m_deviceList.push_back(strDevice); + m_deviceList.sort(); + + // New device added so reload the key mappings + Load(); +} + +void CButtonTranslator::RemoveDevice(std::string& strDevice) +{ + // Find the device + std::list<std::string>::iterator it; + for (it = m_deviceList.begin(); it != m_deviceList.end(); it++) + if (*it == strDevice) + break; + if (it == m_deviceList.end()) + return; + + // Remove the device + m_deviceList.remove(strDevice); + + // Device removed so reload the key mappings + Load(); +} + +bool CButtonTranslator::Load(bool AlwaysLoad) +{ + m_translatorMap.clear(); + + // Directories to search for keymaps. They're applied in this order, + // so keymaps in profile/keymaps/ override e.g. system/keymaps + static const char* DIRS_TO_CHECK[] = { + "special://xbmc/system/keymaps/", + "special://masterprofile/keymaps/", + "special://profile/keymaps/" + }; + bool success = false; + + for (unsigned int dirIndex = 0; dirIndex < ARRAY_SIZE(DIRS_TO_CHECK); ++dirIndex) + { + if (XFILE::CDirectory::Exists(DIRS_TO_CHECK[dirIndex])) + { + CFileItemList files; + XFILE::CDirectory::GetDirectory(DIRS_TO_CHECK[dirIndex], files, ".xml"); + // Sort the list for filesystem based priorities, e.g. 01-keymap.xml, 02-keymap-overrides.xml + files.Sort(SortByFile, SortOrderAscending); + for(int fileIndex = 0; fileIndex<files.Size(); ++fileIndex) + { + if (!files[fileIndex]->m_bIsFolder) + success |= LoadKeymap(files[fileIndex]->GetPath()); + } + + // Load mappings for any HID devices we have connected + std::list<std::string>::iterator it; + for (it = m_deviceList.begin(); it != m_deviceList.end(); it++) + { + std::string devicedir = DIRS_TO_CHECK[dirIndex]; + devicedir.append(*it); + devicedir.append("/"); + if( XFILE::CDirectory::Exists(devicedir) ) + { + CFileItemList files; + XFILE::CDirectory::GetDirectory(devicedir, files, ".xml"); + // Sort the list for filesystem based priorities, e.g. 01-keymap.xml, 02-keymap-overrides.xml + files.Sort(SortByFile, SortOrderAscending); + for(int fileIndex = 0; fileIndex<files.Size(); ++fileIndex) + { + if (!files[fileIndex]->m_bIsFolder) + success |= LoadKeymap(files[fileIndex]->GetPath()); + } + } + } + } + } + + if (!success) + { + CLog::Log(LOGERROR, "Error loading keymaps from: %s or %s or %s", DIRS_TO_CHECK[0], DIRS_TO_CHECK[1], DIRS_TO_CHECK[2]); + return false; + } + +#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE) +#ifdef TARGET_POSIX +#define REMOTEMAP "Lircmap.xml" +#else +#define REMOTEMAP "IRSSmap.xml" +#endif + std::string lircmapPath = URIUtils::AddFileToFolder("special://xbmc/system/", REMOTEMAP); + lircRemotesMap.clear(); + if(CFile::Exists(lircmapPath)) + success |= LoadLircMap(lircmapPath); + else + CLog::Log(LOGDEBUG, "CButtonTranslator::Load - no system %s found, skipping", REMOTEMAP); + + lircmapPath = CProfilesManager::Get().GetUserDataItem(REMOTEMAP); + if(CFile::Exists(lircmapPath)) + success |= LoadLircMap(lircmapPath); + else + CLog::Log(LOGDEBUG, "CButtonTranslator::Load - no userdata %s found, skipping", REMOTEMAP); + + if (!success) + CLog::Log(LOGERROR, "CButtonTranslator::Load - unable to load remote map %s", REMOTEMAP); + // don't return false - it is to only indicate a fatal error (which this is not) +#endif + + // Done! + m_Loaded = true; + return true; +} + +bool CButtonTranslator::LoadKeymap(const std::string &keymapPath) +{ + CXBMCTinyXML xmlDoc; + + CLog::Log(LOGINFO, "Loading %s", keymapPath.c_str()); + if (!xmlDoc.LoadFile(keymapPath)) + { + CLog::Log(LOGERROR, "Error loading keymap: %s, Line %d\n%s", keymapPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + return false; + } + TiXmlElement* pRoot = xmlDoc.RootElement(); + if (!pRoot) + { + CLog::Log(LOGERROR, "Error getting keymap root: %s", keymapPath.c_str()); + return false; + } + std::string strValue = pRoot->Value(); + if ( strValue != "keymap") + { + CLog::Log(LOGERROR, "%s Doesn't contain <keymap>", keymapPath.c_str()); + return false; + } + // run through our window groups + TiXmlNode* pWindow = pRoot->FirstChild(); + while (pWindow) + { + if (pWindow->Type() == TiXmlNode::TINYXML_ELEMENT) + { + int windowID = WINDOW_INVALID; + const char *szWindow = pWindow->Value(); + if (szWindow) + { + if (strcmpi(szWindow, "global") == 0) + windowID = -1; + else + windowID = TranslateWindow(szWindow); + } + MapWindowActions(pWindow, windowID); + } + pWindow = pWindow->NextSibling(); + } + + return true; +} + +bool CButtonTranslator::LoadLircMap(const std::string &lircmapPath) +{ +#ifdef TARGET_POSIX +#define REMOTEMAPTAG "lircmap" +#else +#define REMOTEMAPTAG "irssmap" +#endif + // load our xml file, and fill up our mapping tables + CXBMCTinyXML xmlDoc; + + // Load the config file + CLog::Log(LOGINFO, "Loading %s", lircmapPath.c_str()); + if (!xmlDoc.LoadFile(lircmapPath)) + { + CLog::Log(LOGERROR, "%s, Line %d\n%s", lircmapPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + return false; // This is so people who don't have the file won't fail, just warn + } + + TiXmlElement* pRoot = xmlDoc.RootElement(); + std::string strValue = pRoot->Value(); + if (strValue != REMOTEMAPTAG) + { + CLog::Log(LOGERROR, "%sl Doesn't contain <%s>", lircmapPath.c_str(), REMOTEMAPTAG); + return false; + } + + // run through our window groups + TiXmlNode* pRemote = pRoot->FirstChild(); + while (pRemote) + { + if (pRemote->Type() == TiXmlNode::TINYXML_ELEMENT) + { + const char *szRemote = pRemote->Value(); + if (szRemote) + { + TiXmlAttribute* pAttr = pRemote->ToElement()->FirstAttribute(); + if (pAttr) + MapRemote(pRemote, pAttr->Value()); + } + } + pRemote = pRemote->NextSibling(); + } + + return true; +} + +void CButtonTranslator::MapRemote(TiXmlNode *pRemote, const char* szDevice) +{ + CLog::Log(LOGINFO, "* Adding remote mapping for device '%s'", szDevice); + vector<string> RemoteNames; + map<std::string, lircButtonMap*>::iterator it = lircRemotesMap.find(szDevice); + if (it == lircRemotesMap.end()) + lircRemotesMap[szDevice] = new lircButtonMap; + lircButtonMap& buttons = *lircRemotesMap[szDevice]; + + TiXmlElement *pButton = pRemote->FirstChildElement(); + while (pButton) + { + if (!pButton->NoChildren()) + { + if (pButton->ValueStr() == "altname") + RemoteNames.push_back(pButton->FirstChild()->ValueStr()); + else + buttons[pButton->FirstChild()->ValueStr()] = pButton->ValueStr(); + } + pButton = pButton->NextSiblingElement(); + } + for (vector<string>::iterator it = RemoteNames.begin(); + it != RemoteNames.end();++it) + { + CLog::Log(LOGINFO, "* Linking remote mapping for '%s' to '%s'", szDevice, it->c_str()); + lircRemotesMap[*it] = &buttons; + } +} + +int CButtonTranslator::TranslateLircRemoteString(const char* szDevice, const char *szButton) +{ + // Find the device + map<std::string, lircButtonMap*>::iterator it = lircRemotesMap.find(szDevice); + if (it == lircRemotesMap.end()) + return 0; + + // Find the button + lircButtonMap::iterator it2 = (*it).second->find(szButton); + if (it2 == (*it).second->end()) + return 0; + + // Convert the button to code + if (strnicmp((*it2).second.c_str(), "obc", 3) == 0) + return TranslateUniversalRemoteString((*it2).second.c_str()); + + return TranslateRemoteString((*it2).second.c_str()); +} + +#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER) +void CButtonTranslator::MapJoystickActions(int windowID, TiXmlNode *pJoystick) +{ + string joyname = JOYSTICK_DEFAULT_MAP; // default global map name + vector<boost::shared_ptr<CRegExp> > joynames; + map<int, string> buttonMap; + map<int, string> axisMap; + AxesConfig axesConfig; + ActionMap hatMap; + + TiXmlElement *pJoy = pJoystick->ToElement(); + if (pJoy && pJoy->Attribute("name")) + joyname = pJoy->Attribute("name"); + else + CLog::Log(LOGNOTICE, "No Joystick name specified, loading default map"); + + boost::shared_ptr<CRegExp> re(new CRegExp(true, CRegExp::asciiOnly)); + if (!re->RegComp(JoynameToRegex(joyname), CRegExp::StudyRegExp)) + { + CLog::Log(LOGNOTICE, "Invalid joystick regex specified: '%s'", joyname.c_str()); + return; + } + else + joynames.push_back(re); + + // parse map + TiXmlElement *pButton = pJoystick->FirstChildElement(); + int id = 0; + while (pButton) + { + const std::string &type = pButton->ValueStr(); + std::string action; + if (!pButton->NoChildren()) + action = pButton->FirstChild()->ValueStr(); + + if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id>=0 && id<=256) + { + if (type == "button") + { + buttonMap[id] = action; + } + else if (type == "axis") + { + int limit = 0; + if (pButton->QueryIntAttribute("limit", &limit) == TIXML_SUCCESS) + { + if (limit==-1) + axisMap[-id] = action; + else if (limit==1) + axisMap[id] = action; + else if (limit==0) + axisMap[id|0xFFFF0000] = action; + else + { + axisMap[id] = action; + axisMap[-id] = action; + CLog::Log(LOGERROR, "Error in joystick map, invalid limit specified %d for axis %d", limit, id); + } + } + else + { + axisMap[id] = action; + axisMap[-id] = action; + } + + if (windowID == -1) { + // in <global> we can override the rest state value of axes and whether they are triggers + bool trigger = false; + int restStateValue = 0; + pButton->QueryBoolAttribute("trigger", &trigger); + pButton->QueryIntAttribute("rest", &restStateValue); + // if it deviates from the defaults + if (trigger || restStateValue != 0) + axesConfig.push_back(AxisConfig(id, trigger, restStateValue)); + } + } + else if (type == "hat") + { + string position; + if (pButton->QueryValueAttribute("position", &position) == TIXML_SUCCESS) + { + uint32_t hatID = id|0xFFF00000; + if (position.compare("up") == 0) + hatMap[(JACTIVE_HAT_UP<<16)|hatID] = action; + else if (position.compare("down") == 0) + hatMap[(JACTIVE_HAT_DOWN<<16)|hatID] = action; + else if (position.compare("right") == 0) + hatMap[(JACTIVE_HAT_RIGHT<<16)|hatID] = action; + else if (position.compare("left") == 0) + hatMap[(JACTIVE_HAT_LEFT<<16)|hatID] = action; + else + CLog::Log(LOGERROR, "Error in joystick map, invalid position specified %s for axis %d", position.c_str(), id); + } + } + else + CLog::Log(LOGERROR, "Error reading joystick map element, unknown button type: %s", type.c_str()); + } + else if (type == "altname") + { + boost::shared_ptr<CRegExp> altRe(new CRegExp(true, CRegExp::asciiOnly)); + if (!altRe->RegComp(JoynameToRegex(action), CRegExp::StudyRegExp)) + CLog::Log(LOGNOTICE, "Ignoring invalid joystick altname regex: '%s'", action.c_str()); + else + joynames.push_back(altRe); + } + else + CLog::Log(LOGERROR, "Error reading joystick map element, Invalid id: %d", id); + + pButton = pButton->NextSiblingElement(); + } + vector<boost::shared_ptr<CRegExp> >::iterator it = joynames.begin(); + while (it!=joynames.end()) + { + MergeMap(*it, &m_joystickButtonMap, windowID, buttonMap); + MergeMap(*it, &m_joystickAxisMap, windowID, axisMap); + MergeMap(*it, &m_joystickHatMap, windowID, hatMap); + if (windowID == -1) + m_joystickAxesConfigs[*it] = axesConfig; +// CLog::Log(LOGDEBUG, "Found Joystick map for window %d using %s", windowID, it->c_str()); + it++; + } +} + +std::string CButtonTranslator::JoynameToRegex(const std::string& joyName) const +{ + if (joyName.empty()) + return joyName; + + // names already presented as regex are identified by a leading / + else if (joyName[0] == '/') + return joyName.substr(1); + + // the others we'll have to escape + return "\\Q" + joyName + "\\E"; +} + +void CButtonTranslator::MergeMap(boost::shared_ptr<CRegExp> joyName, JoystickMap *joystick, int windowID, const ActionMap &map) +{ + // find or create WindowMap entry, match on pattern equality + JoystickMap::iterator jit; + for (jit = joystick->begin(); jit != joystick->end(); jit++) + { + if (jit->first->GetPattern() == joyName->GetPattern()) + break; + } + WindowMap *w = (jit == joystick->end()) ? &(*joystick)[joyName] : &jit->second; + + // find or create ActionMap, and merge/overwrite new entries + ActionMap *a = &(*w)[windowID]; + for (ActionMap::const_iterator it = map.begin(); it != map.end(); it++) + (*a)[it->first] = it->second; +} + +CButtonTranslator::JoystickMap::const_iterator CButtonTranslator::FindWindowMap(const std::string& joyName, const JoystickMap &maps) const +{ + JoystickMap::const_iterator it; + for (it = maps.begin(); it != maps.end(); it++) + { + if (it->first->RegFind(joyName) >= 0) + { + // CLog::Log(LOGDEBUG, "Regex %s matches joystick %s", it->first->GetPattern().c_str(), joyName.c_str()); + break; + } + // CLog::Log(LOGDEBUG, "No match: %s for joystick %s", it->first->GetPattern().c_str(), joyName.c_str()); + } + return it; +} + +bool CButtonTranslator::TranslateJoystickString(int window, const std::string& joyName, int id, short inputType, int& action, std::string& strAction, bool &fullrange) +{ + fullrange = false; + + // resolve the correct JoystickMap + JoystickMap *jmap; + if (inputType == JACTIVE_AXIS) + jmap = &m_joystickAxisMap; + else if (inputType == JACTIVE_BUTTON) + jmap = &m_joystickButtonMap; + else if (inputType == JACTIVE_HAT) + jmap = &m_joystickHatMap; + else + { + CLog::Log(LOGERROR, "Error reading joystick input type '%i'", (int) inputType); + return false; + } + + JoystickMap::const_iterator it = FindWindowMap(joyName, *jmap); + if (it==jmap->end()) + { + it = FindWindowMap(JOYSTICK_DEFAULT_MAP, *jmap); // default global map name + if (it==jmap->end()) + return false; + } + + const WindowMap *wmap = &it->second; + + // try to get the action from the current window + action = GetActionCode(window, id, *wmap, strAction, fullrange); + + // if it's invalid, try to get it from a fallback window or the global map + if (action == 0) + { + int fallbackWindow = GetFallbackWindow(window); + if (fallbackWindow > -1) + action = GetActionCode(fallbackWindow, id, *wmap, strAction, fullrange); + // still no valid action? use global map + if (action == 0) + action = GetActionCode(-1, id, *wmap, strAction, fullrange); + } + + return (action > 0); +} + +bool CButtonTranslator::TranslateTouchAction(int window, int touchAction, int touchPointers, int &action) +{ + action = 0; + if (touchPointers <= 0) + touchPointers = 1; + + touchAction += touchPointers - 1; + touchAction |= KEY_TOUCH; + + action = GetTouchActionCode(window, touchAction); + if (action <= 0) + { + int fallbackWindow = GetFallbackWindow(window); + if (fallbackWindow > -1) + action = GetTouchActionCode(fallbackWindow, touchAction); + if (action <= 0) + action = GetTouchActionCode(-1, touchAction); + } + + return action > 0; +} + +int CButtonTranslator::GetActionCode(int window, int action) +{ + map<int, buttonMap>::const_iterator it = m_translatorMap.find(window); + if (it == m_translatorMap.end()) + return 0; + + buttonMap::const_iterator it2 = it->second.find(action); + if (it2 == it->second.end()) + return 0; + + return it2->second.id; +} + +/* + * Translates a joystick input to an action code + */ +int CButtonTranslator::GetActionCode(int window, int id, const WindowMap &wmap, std::string &strAction, bool &fullrange) const +{ + int action = 0; + bool found = false; + + WindowMap::const_iterator it = wmap.find(window); + if (it != wmap.end()) + { + const map<int, string> &windowbmap = it->second; + map<int, string>::const_iterator it2 = windowbmap.find(id); + if (it2 != windowbmap.end()) + { + strAction = (it2->second).c_str(); + found = true; + } + + it2 = windowbmap.find(abs(id)|0xFFFF0000); + if (it2 != windowbmap.end()) + { + strAction = (it2->second).c_str(); + found = true; + fullrange = true; + } + + // Hats joystick + it2 = windowbmap.find(id|0xFFF00000); + if (it2 != windowbmap.end()) + { + strAction = (it2->second).c_str(); + found = true; + } + } + + if (found) + TranslateActionString(strAction.c_str(), action); + return action; +} +#endif + +void CButtonTranslator::GetActions(std::vector<std::string> &actionList) +{ + unsigned int size = sizeof(actions) / sizeof(ActionMapping); + actionList.clear(); + actionList.reserve(size); + for (unsigned int index = 0; index < size; index++) + actionList.push_back(actions[index].name); +} + +void CButtonTranslator::GetWindows(std::vector<std::string> &windowList) +{ + unsigned int size = sizeof(windows) / sizeof(ActionMapping); + windowList.clear(); + windowList.reserve(size); + for (unsigned int index = 0; index < size; index++) + windowList.push_back(windows[index].name); +} + +int CButtonTranslator::GetFallbackWindow(int windowID) +{ + for (unsigned int index = 0; index < ARRAY_SIZE(fallbackWindows); ++index) + { + if (fallbackWindows[index].origin == windowID) + return fallbackWindows[index].target; + } + // for addon windows use WINDOW_ADDON_START + // because id is dynamic + if (windowID >= WINDOW_ADDON_START && windowID <= WINDOW_ADDON_END) + return WINDOW_ADDON_START; + + return -1; +} + +CAction CButtonTranslator::GetAction(int window, const CKey &key, bool fallback) +{ + std::string strAction; + // try to get the action from the current window + int actionID = GetActionCode(window, key, strAction); + // if it's invalid, try to get it from the global map + if (actionID == 0 && fallback) + { + int fallbackWindow = GetFallbackWindow(window); + if (fallbackWindow > -1) + actionID = GetActionCode(fallbackWindow, key, strAction); + // still no valid action? use global map + if (actionID == 0) + actionID = GetActionCode( -1, key, strAction); + } + // Now fill our action structure + CAction action(actionID, strAction, key); + return action; +} + +int CButtonTranslator::GetActionCode(int window, const CKey &key, std::string &strAction) const +{ + uint32_t code = key.GetButtonCode(); + + map<int, buttonMap>::const_iterator it = m_translatorMap.find(window); + if (it == m_translatorMap.end()) + return 0; + buttonMap::const_iterator it2 = (*it).second.find(code); + int action = 0; + if (it2 != (*it).second.end()) + { + action = (*it2).second.id; + strAction = (*it2).second.strID; + } +#ifdef TARGET_POSIX + // Some buttoncodes changed in Hardy + if (action == 0 && (code & KEY_VKEY) == KEY_VKEY && (code & 0x0F00)) + { + CLog::Log(LOGDEBUG, "%s: Trying Hardy keycode for %#04x", __FUNCTION__, code); + code &= ~0x0F00; + it2 = (*it).second.find(code); + if (it2 != (*it).second.end()) + { + action = (*it2).second.id; + strAction = (*it2).second.strID; + } + } +#endif + return action; +} + +void CButtonTranslator::MapAction(uint32_t buttonCode, const char *szAction, buttonMap &map) +{ + int action = ACTION_NONE; + if (!TranslateActionString(szAction, action) || !buttonCode) + return; // no valid action, or an invalid buttoncode + + // have a valid action, and a valid button - map it. + // check to see if we've already got this (button,action) pair defined + buttonMap::iterator it = map.find(buttonCode); + if (it == map.end() || (*it).second.id != action || (*it).second.strID != szAction) + { + // NOTE: This multimap is only being used as a normal map at this point (no support + // for multiple actions per key) + if (it != map.end()) + map.erase(it); + CButtonAction button; + button.id = action; + button.strID = szAction; + map.insert(pair<uint32_t, CButtonAction>(buttonCode, button)); + } +} + +bool CButtonTranslator::HasDeviceType(TiXmlNode *pWindow, std::string type) +{ + return pWindow->FirstChild(type) != NULL; +} + +void CButtonTranslator::MapWindowActions(TiXmlNode *pWindow, int windowID) +{ + if (!pWindow || windowID == WINDOW_INVALID) + return; + + TiXmlNode* pDevice; + + const char* types[] = {"gamepad", "remote", "universalremote", "keyboard", "mouse", "appcommand", NULL}; + for (int i = 0; types[i]; ++i) + { + std::string type(types[i]); + if (HasDeviceType(pWindow, type)) + { + buttonMap map; + std::map<int, buttonMap>::iterator it = m_translatorMap.find(windowID); + if (it != m_translatorMap.end()) + { + map = it->second; + m_translatorMap.erase(it); + } + + pDevice = pWindow->FirstChild(type); + + TiXmlElement *pButton = pDevice->FirstChildElement(); + + while (pButton) + { + uint32_t buttonCode=0; + if (type == "gamepad") + buttonCode = TranslateGamepadString(pButton->Value()); + else if (type == "remote") + buttonCode = TranslateRemoteString(pButton->Value()); + else if (type == "universalremote") + buttonCode = TranslateUniversalRemoteString(pButton->Value()); + else if (type == "keyboard") + buttonCode = TranslateKeyboardButton(pButton); + else if (type == "mouse") + buttonCode = TranslateMouseCommand(pButton); + else if (type == "appcommand") + buttonCode = TranslateAppCommand(pButton->Value()); + + if (buttonCode && pButton->FirstChild()) + MapAction(buttonCode, pButton->FirstChild()->Value(), map); + pButton = pButton->NextSiblingElement(); + } + + // add our map to our table + if (!map.empty()) + m_translatorMap.insert(pair<int, buttonMap>( windowID, map)); + } + } + +#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER) + if ((pDevice = pWindow->FirstChild("joystick")) != NULL) + { + // map joystick actions + while (pDevice) + { + MapJoystickActions(windowID, pDevice); + pDevice = pDevice->NextSibling("joystick"); + } + } +#endif + + if ((pDevice = pWindow->FirstChild("touch")) != NULL) + { + // map touch actions + while (pDevice) + { + MapTouchActions(windowID, pDevice); + pDevice = pDevice->NextSibling("touch"); + } + } +} + +bool CButtonTranslator::TranslateActionString(const char *szAction, int &action) +{ + action = ACTION_NONE; + std::string strAction = szAction; + StringUtils::ToLower(strAction); + if (CBuiltins::HasCommand(strAction)) + action = ACTION_BUILT_IN_FUNCTION; + + for (unsigned int index=0;index < ARRAY_SIZE(actions);++index) + { + if (strAction == actions[index].name) + { + action = actions[index].action; + break; + } + } + + if (action == ACTION_NONE) + { + CLog::Log(LOGERROR, "Keymapping error: no such action '%s' defined", strAction.c_str()); + return false; + } + + return true; +} + +std::string CButtonTranslator::TranslateWindow(int windowID) +{ + for (unsigned int index = 0; index < ARRAY_SIZE(windows); ++index) + { + if (windows[index].action == windowID) + return windows[index].name; + } + return ""; +} + +int CButtonTranslator::TranslateWindow(const std::string &window) +{ + std::string strWindow(window); + if (strWindow.empty()) + return WINDOW_INVALID; + StringUtils::ToLower(strWindow); + // eliminate .xml + if (StringUtils::EndsWith(strWindow, ".xml")) + strWindow = strWindow.substr(0, strWindow.size() - 4); + + // window12345, for custom window to be keymapped + if (strWindow.length() > 6 && StringUtils::StartsWithNoCase(strWindow, "window")) + strWindow = strWindow.substr(6); + if (StringUtils::StartsWithNoCase(strWindow, "my")) // drop "my" prefix + strWindow = strWindow.substr(2); + if (StringUtils::IsNaturalNumber(strWindow)) + { + // allow a full window id or a delta id + int iWindow = atoi(strWindow.c_str()); + if (iWindow > WINDOW_INVALID) + return iWindow; + return WINDOW_HOME + iWindow; + } + + // run through the window structure + for (unsigned int index = 0; index < ARRAY_SIZE(windows); ++index) + { + if (strWindow == windows[index].name) + return windows[index].action; + } + + CLog::Log(LOGERROR, "Window Translator: Can't find window %s", strWindow.c_str()); + return WINDOW_INVALID; +} + +uint32_t CButtonTranslator::TranslateGamepadString(const char *szButton) +{ + if (!szButton) + return 0; + uint32_t buttonCode = 0; + std::string strButton = szButton; + StringUtils::ToLower(strButton); + if (strButton == "a") buttonCode = KEY_BUTTON_A; + else if (strButton == "b") buttonCode = KEY_BUTTON_B; + else if (strButton == "x") buttonCode = KEY_BUTTON_X; + else if (strButton == "y") buttonCode = KEY_BUTTON_Y; + else if (strButton == "white") buttonCode = KEY_BUTTON_WHITE; + else if (strButton == "black") buttonCode = KEY_BUTTON_BLACK; + else if (strButton == "start") buttonCode = KEY_BUTTON_START; + else if (strButton == "back") buttonCode = KEY_BUTTON_BACK; + else if (strButton == "leftthumbbutton") buttonCode = KEY_BUTTON_LEFT_THUMB_BUTTON; + else if (strButton == "rightthumbbutton") buttonCode = KEY_BUTTON_RIGHT_THUMB_BUTTON; + else if (strButton == "leftthumbstick") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK; + else if (strButton == "leftthumbstickup") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_UP; + else if (strButton == "leftthumbstickdown") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_DOWN; + else if (strButton == "leftthumbstickleft") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_LEFT; + else if (strButton == "leftthumbstickright") buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_RIGHT; + else if (strButton == "rightthumbstick") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK; + else if (strButton == "rightthumbstickup") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_UP; + else if (strButton =="rightthumbstickdown") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_DOWN; + else if (strButton == "rightthumbstickleft") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_LEFT; + else if (strButton == "rightthumbstickright") buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT; + else if (strButton == "lefttrigger") buttonCode = KEY_BUTTON_LEFT_TRIGGER; + else if (strButton == "righttrigger") buttonCode = KEY_BUTTON_RIGHT_TRIGGER; + else if (strButton == "leftanalogtrigger") buttonCode = KEY_BUTTON_LEFT_ANALOG_TRIGGER; + else if (strButton == "rightanalogtrigger") buttonCode = KEY_BUTTON_RIGHT_ANALOG_TRIGGER; + else if (strButton == "dpadleft") buttonCode = KEY_BUTTON_DPAD_LEFT; + else if (strButton == "dpadright") buttonCode = KEY_BUTTON_DPAD_RIGHT; + else if (strButton == "dpadup") buttonCode = KEY_BUTTON_DPAD_UP; + else if (strButton == "dpaddown") buttonCode = KEY_BUTTON_DPAD_DOWN; + else CLog::Log(LOGERROR, "Gamepad Translator: Can't find button %s", strButton.c_str()); + return buttonCode; +} + +uint32_t CButtonTranslator::TranslateRemoteString(const char *szButton) +{ + if (!szButton) + return 0; + uint32_t buttonCode = 0; + std::string strButton = szButton; + StringUtils::ToLower(strButton); + if (strButton == "left") buttonCode = XINPUT_IR_REMOTE_LEFT; + else if (strButton =="right") buttonCode = XINPUT_IR_REMOTE_RIGHT; + else if (strButton =="up") buttonCode = XINPUT_IR_REMOTE_UP; + else if (strButton == "down") buttonCode = XINPUT_IR_REMOTE_DOWN; + else if (strButton == "select") buttonCode = XINPUT_IR_REMOTE_SELECT; + else if (strButton == "back") buttonCode = XINPUT_IR_REMOTE_BACK; + else if (strButton == "menu") buttonCode = XINPUT_IR_REMOTE_MENU; + else if (strButton == "info") buttonCode = XINPUT_IR_REMOTE_INFO; + else if (strButton == "display") buttonCode = XINPUT_IR_REMOTE_DISPLAY; + else if (strButton == "title") buttonCode = XINPUT_IR_REMOTE_TITLE; + else if (strButton == "play") buttonCode = XINPUT_IR_REMOTE_PLAY; + else if (strButton == "pause") buttonCode = XINPUT_IR_REMOTE_PAUSE; + else if (strButton == "reverse") buttonCode = XINPUT_IR_REMOTE_REVERSE; + else if (strButton == "forward") buttonCode = XINPUT_IR_REMOTE_FORWARD; + else if (strButton == "skipplus") buttonCode = XINPUT_IR_REMOTE_SKIP_PLUS; + else if (strButton == "skipminus") buttonCode = XINPUT_IR_REMOTE_SKIP_MINUS; + else if (strButton == "stop") buttonCode = XINPUT_IR_REMOTE_STOP; + else if (strButton == "zero") buttonCode = XINPUT_IR_REMOTE_0; + else if (strButton == "one") buttonCode = XINPUT_IR_REMOTE_1; + else if (strButton == "two") buttonCode = XINPUT_IR_REMOTE_2; + else if (strButton == "three") buttonCode = XINPUT_IR_REMOTE_3; + else if (strButton == "four") buttonCode = XINPUT_IR_REMOTE_4; + else if (strButton == "five") buttonCode = XINPUT_IR_REMOTE_5; + else if (strButton == "six") buttonCode = XINPUT_IR_REMOTE_6; + else if (strButton == "seven") buttonCode = XINPUT_IR_REMOTE_7; + else if (strButton == "eight") buttonCode = XINPUT_IR_REMOTE_8; + else if (strButton == "nine") buttonCode = XINPUT_IR_REMOTE_9; + // additional keys from the media center extender for xbox remote + else if (strButton == "power") buttonCode = XINPUT_IR_REMOTE_POWER; + else if (strButton == "mytv") buttonCode = XINPUT_IR_REMOTE_MY_TV; + else if (strButton == "mymusic") buttonCode = XINPUT_IR_REMOTE_MY_MUSIC; + else if (strButton == "mypictures") buttonCode = XINPUT_IR_REMOTE_MY_PICTURES; + else if (strButton == "myvideo") buttonCode = XINPUT_IR_REMOTE_MY_VIDEOS; + else if (strButton == "record") buttonCode = XINPUT_IR_REMOTE_RECORD; + else if (strButton == "start") buttonCode = XINPUT_IR_REMOTE_START; + else if (strButton == "volumeplus") buttonCode = XINPUT_IR_REMOTE_VOLUME_PLUS; + else if (strButton == "volumeminus") buttonCode = XINPUT_IR_REMOTE_VOLUME_MINUS; + else if (strButton == "channelplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS; + else if (strButton == "channelminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS; + else if (strButton == "pageplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS; + else if (strButton == "pageminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS; + else if (strButton == "mute") buttonCode = XINPUT_IR_REMOTE_MUTE; + else if (strButton == "recordedtv") buttonCode = XINPUT_IR_REMOTE_RECORDED_TV; + else if (strButton == "guide") buttonCode = XINPUT_IR_REMOTE_GUIDE; + else if (strButton == "livetv") buttonCode = XINPUT_IR_REMOTE_LIVE_TV; + else if (strButton == "liveradio") buttonCode = XINPUT_IR_REMOTE_LIVE_RADIO; + else if (strButton == "epgsearch") buttonCode = XINPUT_IR_REMOTE_EPG_SEARCH; + else if (strButton == "star") buttonCode = XINPUT_IR_REMOTE_STAR; + else if (strButton == "hash") buttonCode = XINPUT_IR_REMOTE_HASH; + else if (strButton == "clear") buttonCode = XINPUT_IR_REMOTE_CLEAR; + else if (strButton == "enter") buttonCode = XINPUT_IR_REMOTE_ENTER; + else if (strButton == "xbox") buttonCode = XINPUT_IR_REMOTE_DISPLAY; // same as display + else if (strButton == "playlist") buttonCode = XINPUT_IR_REMOTE_PLAYLIST; + else if (strButton == "guide") buttonCode = XINPUT_IR_REMOTE_GUIDE; + else if (strButton == "teletext") buttonCode = XINPUT_IR_REMOTE_TELETEXT; + else if (strButton == "red") buttonCode = XINPUT_IR_REMOTE_RED; + else if (strButton == "green") buttonCode = XINPUT_IR_REMOTE_GREEN; + else if (strButton == "yellow") buttonCode = XINPUT_IR_REMOTE_YELLOW; + else if (strButton == "blue") buttonCode = XINPUT_IR_REMOTE_BLUE; + else if (strButton == "subtitle") buttonCode = XINPUT_IR_REMOTE_SUBTITLE; + else if (strButton == "language") buttonCode = XINPUT_IR_REMOTE_LANGUAGE; + else if (strButton == "eject") buttonCode = XINPUT_IR_REMOTE_EJECT; + else if (strButton == "contentsmenu") buttonCode = XINPUT_IR_REMOTE_CONTENTS_MENU; + else if (strButton == "rootmenu") buttonCode = XINPUT_IR_REMOTE_ROOT_MENU; + else if (strButton == "topmenu") buttonCode = XINPUT_IR_REMOTE_TOP_MENU; + else if (strButton == "dvdmenu") buttonCode = XINPUT_IR_REMOTE_DVD_MENU; + else if (strButton == "print") buttonCode = XINPUT_IR_REMOTE_PRINT; + else CLog::Log(LOGERROR, "Remote Translator: Can't find button %s", strButton.c_str()); + return buttonCode; +} + +uint32_t CButtonTranslator::TranslateUniversalRemoteString(const char *szButton) +{ + if (!szButton || strlen(szButton) < 4 || strnicmp(szButton, "obc", 3)) + return 0; + const char *szCode = szButton + 3; + // Button Code is 255 - OBC (Original Button Code) of the button + uint32_t buttonCode = 255 - atol(szCode); + if (buttonCode > 255) + buttonCode = 0; + return buttonCode; +} + +uint32_t CButtonTranslator::TranslateKeyboardString(const char *szButton) +{ + uint32_t buttonCode = 0; + XBMCKEYTABLE keytable; + + // Look up the key name + if (KeyTableLookupName(szButton, &keytable)) + { + buttonCode = keytable.vkey; + } + + // The lookup failed i.e. the key name wasn't found + else + { + CLog::Log(LOGERROR, "Keyboard Translator: Can't find button %s", szButton); + } + + buttonCode |= KEY_VKEY; + + return buttonCode; +} + +uint32_t CButtonTranslator::TranslateKeyboardButton(TiXmlElement *pButton) +{ + uint32_t button_id = 0; + const char *szButton = pButton->Value(); + + if (!szButton) + return 0; + const std::string strKey = szButton; + if (strKey == "key") + { + std::string strID; + if (pButton->QueryValueAttribute("id", &strID) == TIXML_SUCCESS) + { + const char *str = strID.c_str(); + char *endptr; + long int id = strtol(str, &endptr, 0); + if (endptr - str != (int)strlen(str) || id <= 0 || id > 0x00FFFFFF) + CLog::Log(LOGDEBUG, "%s - invalid key id %s", __FUNCTION__, strID.c_str()); + else + button_id = (uint32_t) id; + } + else + CLog::Log(LOGERROR, "Keyboard Translator: `key' button has no id"); + } + else + button_id = TranslateKeyboardString(szButton); + + // Process the ctrl/shift/alt modifiers + std::string strMod; + if (pButton->QueryValueAttribute("mod", &strMod) == TIXML_SUCCESS) + { + StringUtils::ToLower(strMod); + + vector<string> modArray = StringUtils::Split(strMod, ","); + for (vector<string>::const_iterator i = modArray.begin(); i != modArray.end(); ++i) + { + string substr = *i; + StringUtils::Trim(substr); + + if (substr == "ctrl" || substr == "control") + button_id |= CKey::MODIFIER_CTRL; + else if (substr == "shift") + button_id |= CKey::MODIFIER_SHIFT; + else if (substr == "alt") + button_id |= CKey::MODIFIER_ALT; + else if (substr == "super" || substr == "win") + button_id |= CKey::MODIFIER_SUPER; + else if (substr == "meta" || substr == "cmd") + button_id |= CKey::MODIFIER_META; + else + CLog::Log(LOGERROR, "Keyboard Translator: Unknown key modifier %s in %s", substr.c_str(), strMod.c_str()); + } + } + + return button_id; +} + +uint32_t CButtonTranslator::TranslateAppCommand(const char *szButton) +{ +#ifdef TARGET_WINDOWS + std::string strAppCommand = szButton; + StringUtils::ToLower(strAppCommand); + + for (int i = 0; i < ARRAY_SIZE(appcommands); i++) + if (strAppCommand == appcommands[i].name) + return appcommands[i].action | KEY_APPCOMMAND; + + CLog::Log(LOGERROR, "%s: Can't find appcommand %s", __FUNCTION__, szButton); +#endif + + return 0; +} + +uint32_t CButtonTranslator::TranslateMouseCommand(TiXmlElement *pButton) +{ + uint32_t buttonId = 0; + + if (pButton) + { + std::string szKey = pButton->ValueStr(); + if (!szKey.empty()) + { + StringUtils::ToLower(szKey); + for (unsigned int i = 0; i < ARRAY_SIZE(mousekeys); i++) + { + if (szKey == mousekeys[i].name) + { + buttonId = mousekeys[i].action; + break; + } + } + if (!buttonId) + { + CLog::Log(LOGERROR, "Unknown mouse action (%s), skipping", pButton->Value()); + } + else + { + int id = 0; + if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id>=0 && id<=4) + { + buttonId += id; + } + } + } + } + + return buttonId; +} + +void CButtonTranslator::Clear() +{ + m_translatorMap.clear(); +#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE) + ClearLircButtonMapEntries(); + lircRemotesMap.clear(); +#endif + +#if defined(HAS_SDL_JOYSTICK) || defined(HAS_EVENT_SERVER) + m_joystickButtonMap.clear(); + m_joystickAxisMap.clear(); + m_joystickHatMap.clear(); +#endif + + m_Loaded = false; +} + +uint32_t CButtonTranslator::TranslateTouchCommand(TiXmlElement *pButton, CButtonAction &action) +{ + const char *szButton = pButton->Value(); + if (szButton == NULL || pButton->FirstChild() == NULL) + return ACTION_NONE; + + const char *szAction = pButton->FirstChild()->Value(); + if (szAction == NULL) + return ACTION_NONE; + + std::string strTouchCommand = szButton; + StringUtils::ToLower(strTouchCommand); + + const char *attrVal = pButton->Attribute("direction"); + if (attrVal != NULL) + strTouchCommand += attrVal; + + uint32_t actionId = ACTION_NONE; + for (unsigned int i = 0; i < ARRAY_SIZE(touchcommands); i++) + { + if (strTouchCommand == touchcommands[i].name) + { + actionId = touchcommands[i].action; + break; + } + } + + if (actionId <= ACTION_NONE) + { + CLog::Log(LOGERROR, "%s: Can't find touch command %s", __FUNCTION__, szButton); + return ACTION_NONE; + } + + attrVal = pButton->Attribute("pointers"); + if (attrVal != NULL) + { + int pointers = (int)strtol(attrVal, NULL, 0); + if (pointers >= 1) + actionId += pointers - 1; + } + + action.strID = szAction; + if (!TranslateActionString(szAction, action.id) || action.id <= ACTION_NONE) + return ACTION_NONE; + + return actionId | KEY_TOUCH; +} + +void CButtonTranslator::MapTouchActions(int windowID, TiXmlNode *pTouch) +{ + if (pTouch == NULL) + return; + + buttonMap map; + // check if there already is a touch map for the window ID + std::map<int, buttonMap>::iterator it = m_touchMap.find(windowID); + if (it != m_touchMap.end()) + { + // get the existing touch map and remove it from the window mapping + // as it will be inserted later on + map = it->second; + m_touchMap.erase(it); + } + + uint32_t actionId = 0; + TiXmlElement *pTouchElem = pTouch->ToElement(); + if (pTouchElem == NULL) + return; + + TiXmlElement *pButton = pTouchElem->FirstChildElement(); + while (pButton != NULL) + { + CButtonAction action; + actionId = TranslateTouchCommand(pButton, action); + if (actionId > 0) + { + // check if there already is a mapping for the parsed action + // and remove it if necessary + buttonMap::iterator actionIt = map.find(actionId); + if (actionIt != map.end()) + map.erase(actionIt); + + map.insert(std::make_pair(actionId, action)); + } + + pButton = pButton->NextSiblingElement(); + } + + // add the modified touch map with the window ID + if (!map.empty()) + m_touchMap.insert(std::pair<int, buttonMap>(windowID, map)); +} + +int CButtonTranslator::GetTouchActionCode(int window, int action) +{ + std::map<int, buttonMap>::const_iterator windowIt = m_touchMap.find(window); + if (windowIt == m_touchMap.end()) + return ACTION_NONE; + + buttonMap::const_iterator touchIt = windowIt->second.find(action); + if (touchIt == windowIt->second.end()) + return ACTION_NONE; + + return touchIt->second.id; +} |