1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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>
26 #include "utility/string.h"
27 #include "utility/wide_string.h"
29 BindingsConfiguration Bindings
;
31 Key
Key::noOp
= Key(ERR
, NCurses
);
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
);
83 Key
stringToKey(const std::string
&s
)
85 Key result
= stringToSpecialKey(s
);
86 if (result
== Key::noOp
)
88 std::wstring ws
= ToWString(s
);
90 result
= Key(ws
[0], Key::Standard
);
96 Actions::BaseAction
*parseActionLine(const std::string
&line
, F error
)
98 Actions::BaseAction
*result
= 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() };
113 result
= new Actions::PushCharacters(&Global::wFooter
, std::move(queue
));
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);
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
)
127 result
= new Actions::PushCharacters(&Global::wFooter
, std::move(queue
));
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
);
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
);
148 result
= new Actions::RequireRunnable(action
);
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
));
158 error() << "empty command passed to run_external_command\n";
166 Key
Key::read(NC::Window
&w
)
178 result
= Key(input
, NCurses
);
185 size_t conv_res
= mbrtowc(&wc
, tmp
.c_str(), MB_CUR_MAX
, 0);
186 if (conv_res
== size_t(-1)) // incomplete multibyte character
188 else if (conv_res
== size_t(-2)) // garbage character sequence
190 else // character complete
192 result
= Key(wc
, Standard
);
200 bool BindingsConfiguration::read(const std::string
&file
)
202 enum class InProgress
{ None
, Command
, Key
};
206 std::ifstream
f(file
);
211 InProgress in_progress
= InProgress::None
;
214 Binding::ActionChain actions
;
216 // def_key specific variables
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
;
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
)));
242 error() << "definition of command '" << cmd_name
<< "' cannot be empty\n";
246 else if (in_progress
== InProgress::Key
)
248 if (!actions
.empty())
256 error() << "definition of key '" << strkey
<< "' cannot be empty\n";
263 const char def_command
[] = "def_command";
264 const char def_key
[] = "def_key";
266 while (!f
.eof() && ++line_no
)
269 if (line
.empty() || line
[0] == '#')
272 // beginning of command definition
273 if (!line
.compare(0, const_strlen(def_command
), def_command
))
275 if (!bind_in_progress())
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";
284 if (m_commands
.find(cmd_name
) != m_commands
.end())
286 error() << "redefinition of command '" << cmd_name
<< "'\n";
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;
296 error() << "invalid type of command: '" << cmd_type
<< "'\n";
300 // beginning of key definition
301 else if (!line
.compare(0, const_strlen(def_key
), def_key
))
303 if (!bind_in_progress())
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";
314 else if (isspace(line
[0])) // name of action to be bound
317 auto action
= parseActionLine(line
, error
);
319 actions
.push_back(action
);
322 error() << "unknown action: '" << line
<< "'\n";
328 error() << "invalid line: '" << line
<< "'\n";
337 void BindingsConfiguration::generateDefaults()
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
);
544 if (notBound(k
= stringToKey("n")))
546 bind(k
, Actions::Type::MoveSortOrderDown
);
547 bind(k
, Actions::Type::MoveSelectedItemsDown
);
549 if (notBound(k
= stringToKey("M")))
550 bind(k
, Actions::Type::MoveSelectedItemsTo
);
551 if (notBound(k
= stringToKey("A")))
552 bind(k
, Actions::Type::Add
);
553 if (notBound(k
= stringToKey("S")))
554 bind(k
, Actions::Type::SavePlaylist
);
555 if (notBound(k
= stringToKey("o")))
556 bind(k
, Actions::Type::JumpToPlayingSong
);
557 if (notBound(k
= stringToKey("G")))
559 bind(k
, Actions::Type::JumpToBrowser
);
560 bind(k
, Actions::Type::JumpToPlaylistEditor
);
562 if (notBound(k
= stringToKey("~")))
563 bind(k
, Actions::Type::JumpToMediaLibrary
);
564 if (notBound(k
= stringToKey("E")))
565 bind(k
, Actions::Type::JumpToTagEditor
);
566 if (notBound(k
= stringToKey("U")))
567 bind(k
, Actions::Type::TogglePlayingSongCentering
);
568 if (notBound(k
= stringToKey("P")))
569 bind(k
, Actions::Type::ToggleDisplayMode
);
570 if (notBound(k
= stringToKey("\\")))
571 bind(k
, Actions::Type::ToggleInterface
);
572 if (notBound(k
= stringToKey("!")))
573 bind(k
, Actions::Type::ToggleSeparatorsBetweenAlbums
);
574 if (notBound(k
= stringToKey("L")))
575 bind(k
, Actions::Type::ToggleLyricsFetcher
);
576 if (notBound(k
= stringToKey("F")))
577 bind(k
, Actions::Type::ToggleFetchingLyricsInBackground
);
578 if (notBound(k
= stringToKey("ctrl_l")))
579 bind(k
, Actions::Type::ToggleScreenLock
);
580 if (notBound(k
= stringToKey("`")))
582 bind(k
, Actions::Type::ToggleBrowserSortMode
);
583 bind(k
, Actions::Type::ToggleLibraryTagType
);
584 bind(k
, Actions::Type::RefetchLyrics
);
585 bind(k
, Actions::Type::AddRandomItems
);
587 if (notBound(k
= stringToKey("ctrl_p")))
588 bind(k
, Actions::Type::SetSelectedItemsPriority
);
589 if (notBound(k
= stringToKey("q")))
590 bind(k
, Actions::Type::Quit
);
592 if (notBound(k
= stringToKey("-")))
593 bind(k
, Actions::Type::VolumeDown
);