make sure to include boost posix_time where needed
[ncmpcpp.git] / src / bindings.cpp
blobd70f83f008ea5eaffb346d51db3d211549a31dc0
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <boost/algorithm/string/trim.hpp>
22 #include <fstream>
23 #include <iostream>
24 #include "global.h"
25 #include "bindings.h"
26 #include "utility/string.h"
27 #include "utility/wide_string.h"
29 BindingsConfiguration Bindings;
31 Key Key::noOp = Key(ERR, NCurses);
33 namespace {//
35 Key stringToSpecialKey(const std::string &s)
37 Key result = Key::noOp;
38 if (!s.compare("mouse"))
39 result = Key(KEY_MOUSE, Key::NCurses);
40 else if (!s.compare("up"))
41 result = Key(KEY_UP, Key::NCurses);
42 else if (!s.compare("down"))
43 result = Key(KEY_DOWN, Key::NCurses);
44 else if (!s.compare("page_up"))
45 result = Key(KEY_PPAGE, Key::NCurses);
46 else if (!s.compare("page_down"))
47 result = Key(KEY_NPAGE, Key::NCurses);
48 else if (!s.compare("home"))
49 result = Key(KEY_HOME, Key::NCurses);
50 else if (!s.compare("end"))
51 result = Key(KEY_END, Key::NCurses);
52 else if (!s.compare("space"))
53 result = Key(KEY_SPACE, Key::Standard);
54 else if (!s.compare("enter"))
55 result = Key(KEY_ENTER, Key::Standard);
56 else if (!s.compare("insert"))
57 result = Key(KEY_IC, Key::NCurses);
58 else if (!s.compare("delete"))
59 result = Key(KEY_DC, Key::NCurses);
60 else if (!s.compare("left"))
61 result = Key(KEY_LEFT, Key::NCurses);
62 else if (!s.compare("right"))
63 result = Key(KEY_RIGHT, Key::NCurses);
64 else if (!s.compare("tab"))
65 result = Key(KEY_TAB, Key::Standard);
66 else if (!s.compare("shift_tab"))
67 result = Key(KEY_SHIFT_TAB, Key::NCurses);
68 else if (!s.compare(0, 5, "ctrl_") && s.length() > 5 && s[5] >= 'a' && s[5] <= 'z')
69 result = Key(KEY_CTRL_A + (s[5] - 'a'), Key::Standard);
70 else if (s.length() > 1 && s[0] == 'f')
72 int n = atoi(s.c_str() + 1);
73 if (n >= 1 && n <= 12)
74 result = Key(KEY_F1 + n - 1, Key::NCurses);
76 else if (!s.compare("backspace"))
77 result = Key(KEY_BACKSPACE, Key::NCurses);
78 else if (!s.compare("backspace_2"))
79 result = Key(KEY_BACKSPACE_2, Key::Standard);
80 return result;
83 Key stringToKey(const std::string &s)
85 Key result = stringToSpecialKey(s);
86 if (result == Key::noOp)
88 std::wstring ws = ToWString(s);
89 if (ws.length() == 1)
90 result = Key(ws[0], Key::Standard);
92 return result;
95 template <typename F>
96 Actions::BaseAction *parseActionLine(const std::string &line, F error)
98 Actions::BaseAction *result = 0;
99 size_t i = 0;
100 for (; i < line.size() && !isspace(line[i]); ++i) { }
101 if (i == line.size()) // only action name
102 result = Actions::get(line);
103 else // there is something else
105 std::string action_name = line.substr(0, i);
106 if (action_name == "push_character")
108 // push single character into input queue
109 std::string arg = getEnclosedString(line, '"', '"', 0);
110 Key k = stringToSpecialKey(arg);
111 auto queue = std::vector<int>{ k.getChar() };
112 if (k != Key::noOp)
113 result = new Actions::PushCharacters(&Global::wFooter, std::move(queue));
114 else
115 error() << "invalid character passed to push_character: '" << arg << "'\n";
117 else if (action_name == "push_characters")
119 // push sequence of characters into input queue
120 std::string arg = getEnclosedString(line, '"', '"', 0);
121 if (!arg.empty())
123 std::vector<int> queue(arg.begin(), arg.end());
124 // if char is signed, erase 1s from char -> int conversion
125 for (auto it = arg.begin(); it != arg.end(); ++it)
126 *it &= 0xff;
127 result = new Actions::PushCharacters(&Global::wFooter, std::move(queue));
129 else
130 error() << "empty argument passed to push_characters\n";
132 else if (action_name == "require_screen")
134 // require screen of given type
135 std::string arg = getEnclosedString(line, '"', '"', 0);
136 ScreenType screen_type = stringToScreenType(arg);
137 if (screen_type != ScreenType::Unknown)
138 result = new Actions::RequireScreen(screen_type);
139 else
140 error() << "unknown screen passed to require_screen: '" << arg << "'\n";
142 else if (action_name == "require_runnable")
144 // require that given action is runnable
145 std::string arg = getEnclosedString(line, '"', '"', 0);
146 auto action = Actions::get(arg);
147 if (action)
148 result = new Actions::RequireRunnable(action);
149 else
150 error() << "unknown action passed to require_runnable: '" << arg << "'\n";
152 else if (action_name == "run_external_command")
154 std::string command = getEnclosedString(line, '"', '"', 0);
155 if (!command.empty())
156 result = new Actions::RunExternalCommand(std::move(command));
157 else
158 error() << "empty command passed to run_external_command\n";
161 return result;
166 Key Key::read(NC::Window &w)
168 Key result = noOp;
169 std::string tmp;
170 int input;
171 while (true)
173 input = w.readKey();
174 if (input == ERR)
175 break;
176 if (input > 255)
178 result = Key(input, NCurses);
179 break;
181 else
183 wchar_t wc;
184 tmp += input;
185 size_t conv_res = mbrtowc(&wc, tmp.c_str(), MB_CUR_MAX, 0);
186 if (conv_res == size_t(-1)) // incomplete multibyte character
187 continue;
188 else if (conv_res == size_t(-2)) // garbage character sequence
189 break;
190 else // character complete
192 result = Key(wc, Standard);
193 break;
197 return result;
200 bool BindingsConfiguration::read(const std::string &file)
202 enum class InProgress { None, Command, Key };
204 bool result = true;
206 std::ifstream f(file);
207 if (!f.is_open())
208 return result;
210 // shared variables
211 InProgress in_progress = InProgress::None;
212 size_t line_no = 0;
213 std::string line;
214 Binding::ActionChain actions;
216 // def_key specific variables
217 Key key = Key::noOp;
218 std::string strkey;
220 // def_command specific variables
221 bool cmd_immediate = false;
222 std::string cmd_name;
224 auto error = [&]() -> std::ostream & {
225 std::cerr << file << ":" << line_no << ": error: ";
226 in_progress = InProgress::None;
227 result = false;
228 return std::cerr;
231 auto bind_in_progress = [&]() -> bool {
232 if (in_progress == InProgress::Command)
234 if (!actions.empty())
236 m_commands.insert(std::make_pair(cmd_name, Command(std::move(actions), cmd_immediate)));
237 actions.clear();
238 return true;
240 else
242 error() << "definition of command '" << cmd_name << "' cannot be empty\n";
243 return false;
246 else if (in_progress == InProgress::Key)
248 if (!actions.empty())
250 bind(key, actions);
251 actions.clear();
252 return true;
254 else
256 error() << "definition of key '" << strkey << "' cannot be empty\n";
257 return false;
260 return true;
263 const char def_command[] = "def_command";
264 const char def_key[] = "def_key";
266 while (!f.eof() && ++line_no)
268 getline(f, line);
269 if (line.empty() || line[0] == '#')
270 continue;
272 // beginning of command definition
273 if (!line.compare(0, const_strlen(def_command), def_command))
275 if (!bind_in_progress())
276 break;
277 in_progress = InProgress::Command;
278 cmd_name = getEnclosedString(line, '"', '"', 0);
279 if (cmd_name.empty())
281 error() << "command must have non-empty name\n";
282 break;
284 if (m_commands.find(cmd_name) != m_commands.end())
286 error() << "redefinition of command '" << cmd_name << "'\n";
287 break;
289 std::string cmd_type = getEnclosedString(line, '[', ']', 0);
290 if (cmd_type == "immediate")
291 cmd_immediate = true;
292 else if (cmd_type == "deferred")
293 cmd_immediate = false;
294 else
296 error() << "invalid type of command: '" << cmd_type << "'\n";
297 break;
300 // beginning of key definition
301 else if (!line.compare(0, const_strlen(def_key), def_key))
303 if (!bind_in_progress())
304 break;
305 in_progress = InProgress::Key;
306 strkey = getEnclosedString(line, '"', '"', 0);
307 key = stringToKey(strkey);
308 if (key == Key::noOp)
310 error() << "invalid key: '" << strkey << "'\n";
311 break;
314 else if (isspace(line[0])) // name of action to be bound
316 boost::trim(line);
317 auto action = parseActionLine(line, error);
318 if (action)
319 actions.push_back(action);
320 else
322 error() << "unknown action: '" << line << "'\n";
323 break;
326 else
328 error() << "invalid line: '" << line << "'\n";
329 break;
332 bind_in_progress();
333 f.close();
334 return result;
337 void BindingsConfiguration::generateDefaults()
339 Key k = Key::noOp;
340 if (notBound(k = stringToKey("mouse")))
341 bind(k, Actions::Type::MouseEvent);
342 if (notBound(k = stringToKey("up")))
343 bind(k, Actions::Type::ScrollUp);
344 if (notBound(k = stringToKey("down")))
345 bind(k, Actions::Type::ScrollDown);
346 if (notBound(k = stringToKey("[")))
347 bind(k, Actions::Type::ScrollUpAlbum);
348 if (notBound(k = stringToKey("]")))
349 bind(k, Actions::Type::ScrollDownAlbum);
350 if (notBound(k = stringToKey("{")))
351 bind(k, Actions::Type::ScrollUpArtist);
352 if (notBound(k = stringToKey("}")))
353 bind(k, Actions::Type::ScrollDownArtist);
354 if (notBound(k = stringToKey("page_up")))
355 bind(k, Actions::Type::PageUp);
356 if (notBound(k = stringToKey("page_down")))
357 bind(k, Actions::Type::PageDown);
358 if (notBound(k = stringToKey("home")))
359 bind(k, Actions::Type::MoveHome);
360 if (notBound(k = stringToKey("end")))
361 bind(k, Actions::Type::MoveEnd);
362 if (notBound(k = stringToKey("space")))
363 bind(k, Actions::Type::PressSpace);
364 if (notBound(k = stringToKey("enter")))
365 bind(k, Actions::Type::PressEnter);
366 if (notBound(k = stringToKey("delete")))
368 bind(k, Actions::Type::DeletePlaylistItems);
369 bind(k, Actions::Type::DeleteStoredPlaylist);
371 if (notBound(k = stringToKey("right")))
373 bind(k, Actions::Type::NextColumn);
374 bind(k, Actions::Type::SlaveScreen);
375 bind(k, Actions::Type::VolumeUp);
377 if (notBound(k = stringToKey("+")))
378 bind(k, Actions::Type::VolumeUp);
379 if (notBound(k = stringToKey("left")))
381 bind(k, Actions::Type::PreviousColumn);
382 bind(k, Actions::Type::MasterScreen);
383 bind(k, Actions::Type::VolumeDown);
385 if (notBound(k = stringToKey("-")))
386 bind(k, Actions::Type::VolumeDown);
387 if (notBound(k = stringToKey(":")))
388 bind(k, Actions::Type::ExecuteCommand);
389 if (notBound(k = stringToKey("tab")))
390 bind(k, Actions::Type::NextScreen);
391 if (notBound(k = stringToKey("shift_tab")))
392 bind(k, Actions::Type::PreviousScreen);
393 if (notBound(k = stringToKey("f1")))
394 bind(k, Actions::Type::ShowHelp);
395 if (notBound(k = stringToKey("1")))
396 bind(k, Actions::Type::ShowPlaylist);
397 if (notBound(k = stringToKey("2")))
399 bind(k, Actions::Type::ShowBrowser);
400 bind(k, Actions::Type::ChangeBrowseMode);
402 if (notBound(k = stringToKey("3")))
404 bind(k, Actions::Type::ShowSearchEngine);
405 bind(k, Actions::Type::ResetSearchEngine);
407 if (notBound(k = stringToKey("4")))
409 bind(k, Actions::Type::ShowMediaLibrary);
410 bind(k, Actions::Type::ToggleMediaLibraryColumnsMode);
412 if (notBound(k = stringToKey("5")))
413 bind(k, Actions::Type::ShowPlaylistEditor);
414 if (notBound(k = stringToKey("6")))
415 bind(k, Actions::Type::ShowTagEditor);
416 if (notBound(k = stringToKey("7")))
417 bind(k, Actions::Type::ShowOutputs);
418 if (notBound(k = stringToKey("8")))
419 bind(k, Actions::Type::ShowVisualizer);
420 if (notBound(k = stringToKey("=")))
421 bind(k, Actions::Type::ShowClock);
422 if (notBound(k = stringToKey("@")))
423 bind(k, Actions::Type::ShowServerInfo);
424 if (notBound(k = stringToKey("s")))
425 bind(k, Actions::Type::Stop);
426 if (notBound(k = stringToKey("p")))
427 bind(k, Actions::Type::Pause);
428 if (notBound(k = stringToKey(">")))
429 bind(k, Actions::Type::Next);
430 if (notBound(k = stringToKey("<")))
431 bind(k, Actions::Type::Previous);
432 if (notBound(k = stringToKey("ctrl_h")))
434 bind(k, Actions::Type::JumpToParentDirectory);
435 bind(k, Actions::Type::ReplaySong);
437 if (notBound(k = stringToKey("backspace")))
439 bind(k, Actions::Type::JumpToParentDirectory);
440 bind(k, Actions::Type::ReplaySong);
442 if (notBound(k = stringToKey("backspace_2")))
444 bind(k, Actions::Type::JumpToParentDirectory);
445 bind(k, Actions::Type::ReplaySong);
447 if (notBound(k = stringToKey("f")))
448 bind(k, Actions::Type::SeekForward);
449 if (notBound(k = stringToKey("b")))
450 bind(k, Actions::Type::SeekBackward);
451 if (notBound(k = stringToKey("r")))
452 bind(k, Actions::Type::ToggleRepeat);
453 if (notBound(k = stringToKey("z")))
454 bind(k, Actions::Type::ToggleRandom);
455 if (notBound(k = stringToKey("y")))
457 bind(k, Actions::Type::SaveTagChanges);
458 bind(k, Actions::Type::StartSearching);
459 bind(k, Actions::Type::ToggleSingle);
461 if (notBound(k = stringToKey("R")))
462 bind(k, Actions::Type::ToggleConsume);
463 if (notBound(k = stringToKey("Y")))
464 bind(k, Actions::Type::ToggleReplayGainMode);
465 if (notBound(k = stringToKey("t")))
466 bind(k, Actions::Type::ToggleSpaceMode);
467 if (notBound(k = stringToKey("T")))
468 bind(k, Actions::Type::ToggleAddMode);
469 if (notBound(k = stringToKey("|")))
470 bind(k, Actions::Type::ToggleMouse);
471 if (notBound(k = stringToKey("#")))
472 bind(k, Actions::Type::ToggleBitrateVisibility);
473 if (notBound(k = stringToKey("Z")))
474 bind(k, Actions::Type::Shuffle);
475 if (notBound(k = stringToKey("x")))
476 bind(k, Actions::Type::ToggleCrossfade);
477 if (notBound(k = stringToKey("X")))
478 bind(k, Actions::Type::SetCrossfade);
479 if (notBound(k = stringToKey("u")))
480 bind(k, Actions::Type::UpdateDatabase);
481 if (notBound(k = stringToKey("ctrl_v")))
482 bind(k, Actions::Type::SortPlaylist);
483 if (notBound(k = stringToKey("ctrl_r")))
484 bind(k, Actions::Type::ReversePlaylist);
485 if (notBound(k = stringToKey("ctrl_f")))
486 bind(k, Actions::Type::ApplyFilter);
487 if (notBound(k = stringToKey("/")))
489 bind(k, Actions::Type::Find);
490 bind(k, Actions::Type::FindItemForward);
492 if (notBound(k = stringToKey("?")))
494 bind(k, Actions::Type::Find);
495 bind(k, Actions::Type::FindItemBackward);
497 if (notBound(k = stringToKey(".")))
498 bind(k, Actions::Type::NextFoundItem);
499 if (notBound(k = stringToKey(",")))
500 bind(k, Actions::Type::PreviousFoundItem);
501 if (notBound(k = stringToKey("w")))
502 bind(k, Actions::Type::ToggleFindMode);
503 if (notBound(k = stringToKey("e")))
505 bind(k, Actions::Type::EditSong);
506 bind(k, Actions::Type::EditLibraryTag);
507 bind(k, Actions::Type::EditLibraryAlbum);
508 bind(k, Actions::Type::EditDirectoryName);
509 bind(k, Actions::Type::EditPlaylistName);
510 bind(k, Actions::Type::EditLyrics);
512 if (notBound(k = stringToKey("i")))
513 bind(k, Actions::Type::ShowSongInfo);
514 if (notBound(k = stringToKey("I")))
515 bind(k, Actions::Type::ShowArtistInfo);
516 if (notBound(k = stringToKey("g")))
517 bind(k, Actions::Type::JumpToPositionInSong);
518 if (notBound(k = stringToKey("l")))
519 bind(k, Actions::Type::ShowLyrics);
520 if (notBound(k = stringToKey("v")))
521 bind(k, Actions::Type::ReverseSelection);
522 if (notBound(k = stringToKey("V")))
523 bind(k, Actions::Type::RemoveSelection);
524 if (notBound(k = stringToKey("B")))
525 bind(k, Actions::Type::SelectAlbum);
526 if (notBound(k = stringToKey("a")))
527 bind(k, Actions::Type::AddSelectedItems);
528 if (notBound(k = stringToKey("c")))
530 bind(k, Actions::Type::ClearPlaylist);
531 bind(k, Actions::Type::ClearMainPlaylist);
533 if (notBound(k = stringToKey("C")))
535 bind(k, Actions::Type::CropPlaylist);
536 bind(k, Actions::Type::CropMainPlaylist);
538 if (notBound(k = stringToKey("m")))
540 bind(k, Actions::Type::MoveSortOrderUp);
541 bind(k, Actions::Type::MoveSelectedItemsUp);
542 bind(k, Actions::Type::ToggleMediaLibrarySortMode);
543 bind(k, Actions::Type::SetVisualizerSampleMultiplier);
545 if (notBound(k = stringToKey("n")))
547 bind(k, Actions::Type::MoveSortOrderDown);
548 bind(k, Actions::Type::MoveSelectedItemsDown);
550 if (notBound(k = stringToKey("M")))
551 bind(k, Actions::Type::MoveSelectedItemsTo);
552 if (notBound(k = stringToKey("A")))
553 bind(k, Actions::Type::Add);
554 if (notBound(k = stringToKey("S")))
555 bind(k, Actions::Type::SavePlaylist);
556 if (notBound(k = stringToKey("o")))
557 bind(k, Actions::Type::JumpToPlayingSong);
558 if (notBound(k = stringToKey("G")))
560 bind(k, Actions::Type::JumpToBrowser);
561 bind(k, Actions::Type::JumpToPlaylistEditor);
563 if (notBound(k = stringToKey("~")))
564 bind(k, Actions::Type::JumpToMediaLibrary);
565 if (notBound(k = stringToKey("E")))
566 bind(k, Actions::Type::JumpToTagEditor);
567 if (notBound(k = stringToKey("U")))
568 bind(k, Actions::Type::TogglePlayingSongCentering);
569 if (notBound(k = stringToKey("P")))
570 bind(k, Actions::Type::ToggleDisplayMode);
571 if (notBound(k = stringToKey("\\")))
572 bind(k, Actions::Type::ToggleInterface);
573 if (notBound(k = stringToKey("!")))
574 bind(k, Actions::Type::ToggleSeparatorsBetweenAlbums);
575 if (notBound(k = stringToKey("L")))
576 bind(k, Actions::Type::ToggleLyricsFetcher);
577 if (notBound(k = stringToKey("F")))
578 bind(k, Actions::Type::ToggleFetchingLyricsInBackground);
579 if (notBound(k = stringToKey("ctrl_l")))
580 bind(k, Actions::Type::ToggleScreenLock);
581 if (notBound(k = stringToKey("`")))
583 bind(k, Actions::Type::ToggleBrowserSortMode);
584 bind(k, Actions::Type::ToggleLibraryTagType);
585 bind(k, Actions::Type::RefetchLyrics);
586 bind(k, Actions::Type::AddRandomItems);
588 if (notBound(k = stringToKey("ctrl_p")))
589 bind(k, Actions::Type::SetSelectedItemsPriority);
590 if (notBound(k = stringToKey("q")))
591 bind(k, Actions::Type::Quit);
593 if (notBound(k = stringToKey("-")))
594 bind(k, Actions::Type::VolumeDown);