/*************************************************************************** * Copyright (C) 2008-2012 by Andrzej Rybczak * * electricityispower@gmail.com * * * * 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 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include "global.h" #include "bindings.h" #include "utility/string.h" #include "utility/wide_string.h" BindingsConfiguration Bindings; Key Key::noOp = Key(ERR, NCurses); namespace {// Key stringToSpecialKey(const std::string &s) { Key result = Key::noOp; if (!s.compare("mouse")) result = Key(KEY_MOUSE, Key::NCurses); else if (!s.compare("up")) result = Key(KEY_UP, Key::NCurses); else if (!s.compare("down")) result = Key(KEY_DOWN, Key::NCurses); else if (!s.compare("page_up")) result = Key(KEY_PPAGE, Key::NCurses); else if (!s.compare("page_down")) result = Key(KEY_NPAGE, Key::NCurses); else if (!s.compare("home")) result = Key(KEY_HOME, Key::NCurses); else if (!s.compare("end")) result = Key(KEY_END, Key::NCurses); else if (!s.compare("space")) result = Key(KEY_SPACE, Key::Standard); else if (!s.compare("enter")) result = Key(KEY_ENTER, Key::Standard); else if (!s.compare("insert")) result = Key(KEY_IC, Key::NCurses); else if (!s.compare("delete")) result = Key(KEY_DC, Key::NCurses); else if (!s.compare("left")) result = Key(KEY_LEFT, Key::NCurses); else if (!s.compare("right")) result = Key(KEY_RIGHT, Key::NCurses); else if (!s.compare("tab")) result = Key(KEY_TAB, Key::Standard); else if (!s.compare("shift_tab")) result = Key(KEY_SHIFT_TAB, Key::NCurses); else if (!s.compare(0, 5, "ctrl_") && s.length() > 5 && s[5] >= 'a' && s[5] <= 'z') result = Key(KEY_CTRL_A + (s[5] - 'a'), Key::Standard); else if (s.length() > 1 && s[0] == 'f') { int n = atoi(s.c_str() + 1); if (n >= 1 && n <= 12) result = Key(KEY_F1 + n - 1, Key::NCurses); } else if (!s.compare("backspace")) result = Key(KEY_BACKSPACE, Key::NCurses); else if (!s.compare("backspace_2")) result = Key(KEY_BACKSPACE_2, Key::Standard); return result; } Key stringToKey(const std::string &s) { Key result = stringToSpecialKey(s); if (result == Key::noOp) { std::wstring ws = ToWString(s); if (ws.length() == 1) result = Key(ws[0], Key::Standard); } return result; } template Action *parseActionLine(const std::string &line, F error) { Action *result = 0; size_t i = 0; for (; i < line.size() && !isspace(line[i]); ++i) { } if (i == line.size()) // only action name result = Action::get(line); else // there is something else { std::string action_name = line.substr(0, i); if (action_name == "push_character") { // push single character into input queue std::string arg = getEnclosedString(line, '"', '"', 0); Key k = stringToSpecialKey(arg); if (k != Key::noOp) result = new PushCharacters(&Global::wFooter, std::vector{ k.getChar() }); else error() << "invalid character passed to push_character: '" << arg << "'\n"; } else if (action_name == "push_characters") { // push sequence of characters into input queue std::string arg = getEnclosedString(line, '"', '"', 0); if (!arg.empty()) { std::vector queue(arg.begin(), arg.end()); // if char is signed, erase 1s from char -> int conversion for (auto it = arg.begin(); it != arg.end(); ++it) *it &= 0xff; result = new PushCharacters(&Global::wFooter, std::move(queue)); } else error() << "empty argument passed to push_characters\n"; } else if (action_name == "require_screen") { // require screen of given type std::string arg = getEnclosedString(line, '"', '"', 0); ScreenType screen_type = stringToScreenType(arg); if (screen_type != ScreenType::Unknown) result = new RequireScreen(screen_type); else error() << "unknown screen passed to require_screen: '" << arg << "'\n"; } else if (action_name == "require_runnable") { // require that given action is runnable std::string arg = getEnclosedString(line, '"', '"', 0); Action *action = Action::get(arg); if (action) result = new RequireRunnable(action); else error() << "unknown action passed to require_runnable: '" << arg << "'\n"; } else if (action_name == "run_external_command") { std::string command = getEnclosedString(line, '"', '"', 0); if (!command.empty()) result = new RunExternalCommand(std::move(command)); else error() << "empty command passed to run_external_command\n"; } } return result; } } Key Key::read(NC::Window &w) { Key result = noOp; std::string tmp; int input; while (true) { input = w.readKey(); if (input == ERR) break; if (input > 255) { result = Key(input, NCurses); break; } else { wchar_t wc; tmp += input; size_t conv_res = mbrtowc(&wc, tmp.c_str(), MB_CUR_MAX, 0); if (conv_res == size_t(-1)) // incomplete multibyte character continue; else if (conv_res == size_t(-2)) // garbage character sequence break; else // character complete { result = Key(wc, Standard); break; } } } return result; } bool BindingsConfiguration::read(const std::string &file) { enum class InProgress { None, Command, Key }; bool result = true; std::ifstream f(file); if (!f.is_open()) return result; // shared variables InProgress in_progress = InProgress::None; size_t line_no = 0; std::string line; Binding::ActionChain actions; // def_key specific variables Key key = Key::noOp; std::string strkey; // def_command specific variables bool cmd_immediate = false; std::string cmd_name; auto error = [&]() -> std::ostream & { std::cerr << file << ":" << line_no << ": error: "; in_progress = InProgress::None; result = false; return std::cerr; }; auto bind_in_progress = [&]() -> bool { if (in_progress == InProgress::Command) { if (!actions.empty()) { m_commands.insert(std::make_pair(cmd_name, Command(actions, cmd_immediate))); actions.clear(); return true; } else { error() << "definition of command '" << cmd_name << "' cannot be empty\n"; return false; } } else if (in_progress == InProgress::Key) { if (!actions.empty()) { bind(key, actions); actions.clear(); return true; } else { error() << "definition of key '" << strkey << "' cannot be empty\n"; return false; } } return true; }; const char def_command[] = "def_command"; const char def_key[] = "def_key"; while (!f.eof() && ++line_no) { getline(f, line); if (line.empty() || line[0] == '#') continue; // beginning of command definition if (!line.compare(0, const_strlen(def_command), def_command)) { if (!bind_in_progress()) break; in_progress = InProgress::Command; cmd_name = getEnclosedString(line, '"', '"', 0); if (cmd_name.empty()) { error() << "command must have non-empty name\n"; break; } if (m_commands.find(cmd_name) != m_commands.end()) { error() << "redefinition of command '" << cmd_name << "'\n"; break; } std::string cmd_type = getEnclosedString(line, '[', ']', 0); if (cmd_type == "immediate") cmd_immediate = true; else if (cmd_type == "deferred") cmd_immediate = false; else { error() << "invalid type of command: '" << cmd_type << "'\n"; break; } } // beginning of key definition else if (!line.compare(0, const_strlen(def_key), def_key)) { if (!bind_in_progress()) break; in_progress = InProgress::Key; strkey = getEnclosedString(line, '"', '"', 0); key = stringToKey(strkey); if (key == Key::noOp) { error() << "invalid key: '" << strkey << "'\n"; break; } } else if (isspace(line[0])) // name of action to be bound { trim(line); Action *action = parseActionLine(line, error); if (action) actions.push_back(action); else { error() << "unknown action: '" << line << "'\n"; break; } } else { error() << "invalid line: '" << line << "'\n"; break; } } bind_in_progress(); f.close(); return result; } void BindingsConfiguration::generateDefaults() { Key k = Key::noOp; if (notBound(k = stringToKey("mouse"))) bind(k, aMouseEvent); if (notBound(k = stringToKey("up"))) bind(k, aScrollUp); if (notBound(k = stringToKey("down"))) bind(k, aScrollDown); if (notBound(k = stringToKey("["))) bind(k, aScrollUpAlbum); if (notBound(k = stringToKey("]"))) bind(k, aScrollDownAlbum); if (notBound(k = stringToKey("{"))) bind(k, aScrollUpArtist); if (notBound(k = stringToKey("}"))) bind(k, aScrollDownArtist); if (notBound(k = stringToKey("page_up"))) bind(k, aPageUp); if (notBound(k = stringToKey("page_down"))) bind(k, aPageDown); if (notBound(k = stringToKey("home"))) bind(k, aMoveHome); if (notBound(k = stringToKey("end"))) bind(k, aMoveEnd); if (notBound(k = stringToKey("space"))) bind(k, aPressSpace); if (notBound(k = stringToKey("enter"))) bind(k, aPressEnter); if (notBound(k = stringToKey("delete"))) { bind(k, aDeletePlaylistItems); bind(k, aDeleteStoredPlaylist); } if (notBound(k = stringToKey("right"))) { bind(k, aNextColumn); bind(k, aSlaveScreen); bind(k, aVolumeUp); } if (notBound(k = stringToKey("+"))) bind(k, aVolumeUp); if (notBound(k = stringToKey("left"))) { bind(k, aPreviousColumn); bind(k, aMasterScreen); bind(k, aVolumeDown); } if (notBound(k = stringToKey("-"))) bind(k, aVolumeDown); if (notBound(k = stringToKey(":"))) bind(k, aExecuteCommand); if (notBound(k = stringToKey("tab"))) bind(k, aNextScreen); if (notBound(k = stringToKey("shift_tab"))) bind(k, aPreviousScreen); if (notBound(k = stringToKey("f1"))) bind(k, aShowHelp); if (notBound(k = stringToKey("1"))) bind(k, aShowPlaylist); if (notBound(k = stringToKey("2"))) { bind(k, aShowBrowser); bind(k, aChangeBrowseMode); } if (notBound(k = stringToKey("3"))) { bind(k, aShowSearchEngine); bind(k, aResetSearchEngine); } if (notBound(k = stringToKey("4"))) { bind(k, aShowMediaLibrary); bind(k, aToggleMediaLibraryColumnsMode); } if (notBound(k = stringToKey("5"))) bind(k, aShowPlaylistEditor); if (notBound(k = stringToKey("6"))) bind(k, aShowTagEditor); if (notBound(k = stringToKey("7"))) bind(k, aShowOutputs); if (notBound(k = stringToKey("8"))) bind(k, aShowVisualizer); if (notBound(k = stringToKey("="))) bind(k, aShowClock); if (notBound(k = stringToKey("@"))) bind(k, aShowServerInfo); if (notBound(k = stringToKey("s"))) bind(k, aStop); if (notBound(k = stringToKey("p"))) bind(k, aPause); if (notBound(k = stringToKey(">"))) bind(k, aNext); if (notBound(k = stringToKey("<"))) bind(k, aPrevious); if (notBound(k = stringToKey("ctrl_h"))) { bind(k, aJumpToParentDirectory); bind(k, aReplaySong); } if (notBound(k = stringToKey("backspace"))) { bind(k, aJumpToParentDirectory); bind(k, aReplaySong); } if (notBound(k = stringToKey("backspace_2"))) { bind(k, aJumpToParentDirectory); bind(k, aReplaySong); } if (notBound(k = stringToKey("f"))) bind(k, aSeekForward); if (notBound(k = stringToKey("b"))) bind(k, aSeekBackward); if (notBound(k = stringToKey("r"))) bind(k, aToggleRepeat); if (notBound(k = stringToKey("z"))) bind(k, aToggleRandom); if (notBound(k = stringToKey("y"))) { bind(k, aSaveTagChanges); bind(k, aStartSearching); bind(k, aToggleSingle); } if (notBound(k = stringToKey("R"))) bind(k, aToggleConsume); if (notBound(k = stringToKey("Y"))) bind(k, aToggleReplayGainMode); if (notBound(k = stringToKey("t"))) bind(k, aToggleSpaceMode); if (notBound(k = stringToKey("T"))) bind(k, aToggleAddMode); if (notBound(k = stringToKey("|"))) bind(k, aToggleMouse); if (notBound(k = stringToKey("#"))) bind(k, aToggleBitrateVisibility); if (notBound(k = stringToKey("Z"))) bind(k, aShuffle); if (notBound(k = stringToKey("x"))) bind(k, aToggleCrossfade); if (notBound(k = stringToKey("X"))) bind(k, aSetCrossfade); if (notBound(k = stringToKey("u"))) bind(k, aUpdateDatabase); if (notBound(k = stringToKey("ctrl_v"))) bind(k, aSortPlaylist); if (notBound(k = stringToKey("ctrl_r"))) bind(k, aReversePlaylist); if (notBound(k = stringToKey("ctrl_f"))) bind(k, aApplyFilter); if (notBound(k = stringToKey("/"))) { bind(k, aFind); bind(k, aFindItemForward); } if (notBound(k = stringToKey("?"))) { bind(k, aFind); bind(k, aFindItemBackward); } if (notBound(k = stringToKey("."))) bind(k, aNextFoundItem); if (notBound(k = stringToKey(","))) bind(k, aPreviousFoundItem); if (notBound(k = stringToKey("w"))) bind(k, aToggleFindMode); if (notBound(k = stringToKey("e"))) { bind(k, aEditSong); bind(k, aEditLibraryTag); bind(k, aEditLibraryAlbum); bind(k, aEditDirectoryName); bind(k, aEditPlaylistName); bind(k, aEditLyrics); } if (notBound(k = stringToKey("i"))) bind(k, aShowSongInfo); if (notBound(k = stringToKey("I"))) bind(k, aShowArtistInfo); if (notBound(k = stringToKey("g"))) bind(k, aJumpToPositionInSong); if (notBound(k = stringToKey("l"))) bind(k, aShowLyrics); if (notBound(k = stringToKey("v"))) bind(k, aReverseSelection); if (notBound(k = stringToKey("V"))) bind(k, aRemoveSelection); if (notBound(k = stringToKey("B"))) bind(k, aSelectAlbum); if (notBound(k = stringToKey("a"))) bind(k, aAddSelectedItems); if (notBound(k = stringToKey("c"))) { bind(k, aClearPlaylist); bind(k, aClearMainPlaylist); } if (notBound(k = stringToKey("C"))) { bind(k, aCropPlaylist); bind(k, aCropMainPlaylist); } if (notBound(k = stringToKey("m"))) { bind(k, aMoveSortOrderUp); bind(k, aMoveSelectedItemsUp); bind(k, aToggleMediaLibrarySortMode); } if (notBound(k = stringToKey("n"))) { bind(k, aMoveSortOrderDown); bind(k, aMoveSelectedItemsDown); } if (notBound(k = stringToKey("M"))) bind(k, aMoveSelectedItemsTo); if (notBound(k = stringToKey("A"))) bind(k, aAdd); if (notBound(k = stringToKey("S"))) bind(k, aSavePlaylist); if (notBound(k = stringToKey("o"))) bind(k, aJumpToPlayingSong); if (notBound(k = stringToKey("G"))) { bind(k, aJumpToBrowser); bind(k, aJumpToPlaylistEditor); } if (notBound(k = stringToKey("~"))) bind(k, aJumpToMediaLibrary); if (notBound(k = stringToKey("E"))) bind(k, aJumpToTagEditor); if (notBound(k = stringToKey("U"))) bind(k, aTogglePlayingSongCentering); if (notBound(k = stringToKey("P"))) bind(k, aToggleDisplayMode); if (notBound(k = stringToKey("\\"))) bind(k, aToggleInterface); if (notBound(k = stringToKey("!"))) bind(k, aToggleSeparatorsBetweenAlbums); if (notBound(k = stringToKey("L"))) bind(k, aToggleLyricsFetcher); if (notBound(k = stringToKey("F"))) bind(k, aToggleFetchingLyricsInBackground); if (notBound(k = stringToKey("ctrl_l"))) bind(k, aToggleScreenLock); if (notBound(k = stringToKey("`"))) { bind(k, aToggleBrowserSortMode); bind(k, aToggleLibraryTagType); bind(k, aRefetchLyrics); bind(k, aRefetchArtistInfo); bind(k, aAddRandomItems); } if (notBound(k = stringToKey("ctrl_p"))) bind(k, aSetSelectedItemsPriority); if (notBound(k = stringToKey("q"))) bind(k, aQuit); if (notBound(k = stringToKey("-"))) bind(k, aVolumeDown); }