1 /***************************************************************************
2  *   Copyright (C) 2008-2021 by Andrzej Rybczak                            *
3  *   andrzej@rybczak.net                                                   *
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  ***************************************************************************/
20 
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"
28 
29 BindingsConfiguration Bindings;
30 
31 namespace {
32 
warning(const char * msg)33 void warning(const char *msg)
34 {
35 	std::cerr << "WARNING: " << msg << "\n";
36 }
37 
38 NC::Key::Type stringToKey(const std::string &s);
39 
stringToSpecialKey(const std::string & s)40 NC::Key::Type stringToSpecialKey(const std::string &s)
41 {
42 	NC::Key::Type result = NC::Key::None;
43 	if (!s.compare(0, 4, "ctrl") && s.length() == 6 && s[4] == '-')
44 	{
45 		if (s[5] >= 'a' && s[5] <= 'z')
46 			result = NC::Key::Ctrl_A + (s[5] - 'a');
47 		else if (s[5] == '[')
48 			result = NC::Key::Ctrl_LeftBracket;
49 		else if (s[5] == '\\')
50 			result = NC::Key::Ctrl_Backslash;
51 		else if (s[5] == ']')
52 			result = NC::Key::Ctrl_RightBracket;
53 		else if (s[5] == '^')
54 			result = NC::Key::Ctrl_Caret;
55 		else if (s[5] == '_')
56 			result = NC::Key::Ctrl_Underscore;
57 	}
58 	else if (!s.compare(0, 3, "alt") && s.length() > 3 && s[3] == '-')
59 	{
60 		result = NC::Key::Alt | stringToKey(s.substr(4));
61 	}
62 	else if (!s.compare(0, 4, "ctrl") && s.length() > 4 && s[4] == '-')
63 	{
64 		result = NC::Key::Ctrl | stringToKey(s.substr(5));
65 	}
66 	else if (!s.compare(0, 5, "shift") && s.length() > 5 && s[5] == '-')
67 	{
68 		result = NC::Key::Shift | stringToKey(s.substr(6));
69 	}
70 	else if (!s.compare("escape"))
71 		result = NC::Key::Escape;
72 	else if (!s.compare("mouse"))
73 		result = NC::Key::Mouse;
74 	else if (!s.compare("up"))
75 		result = NC::Key::Up;
76 	else if (!s.compare("down"))
77 		result = NC::Key::Down;
78 	else if (!s.compare("page_up"))
79 		result = NC::Key::PageUp;
80 	else if (!s.compare("page_down"))
81 		result = NC::Key::PageDown;
82 	else if (!s.compare("home"))
83 		result = NC::Key::Home;
84 	else if (!s.compare("end"))
85 		result = NC::Key::End;
86 	else if (!s.compare("space"))
87 		result = NC::Key::Space;
88 	else if (!s.compare("enter"))
89 		result = NC::Key::Enter;
90 	else if (!s.compare("insert"))
91 		result = NC::Key::Insert;
92 	else if (!s.compare("delete"))
93 		result = NC::Key::Delete;
94 	else if (!s.compare("left"))
95 		result = NC::Key::Left;
96 	else if (!s.compare("right"))
97 		result = NC::Key::Right;
98 	else if (!s.compare("tab"))
99 		result = NC::Key::Tab;
100 	else if ((s.length() == 2 || s.length() == 3) && s[0] == 'f')
101 	{
102 		int n = atoi(s.c_str() + 1);
103 		if (n >= 1 && n <= 12)
104 			result = NC::Key::F1 + n - 1;
105 	}
106 	else if (!s.compare("backspace"))
107 		result = NC::Key::Backspace;
108 	return result;
109 }
110 
stringToKey(const std::string & s)111 NC::Key::Type stringToKey(const std::string &s)
112 {
113 	NC::Key::Type result = stringToSpecialKey(s);
114 	if (result == NC::Key::None)
115 	{
116 		std::wstring ws = ToWString(s);
117 		if (ws.length() == 1)
118 			result = ws[0];
119 	}
120 	return result;
121 }
122 
123 template <typename F>
parseActionLine(const std::string & line,F error)124 std::shared_ptr<Actions::BaseAction> parseActionLine(const std::string &line, F error)
125 {
126 	std::shared_ptr<Actions::BaseAction> result;
127 	size_t i = 0;
128 	for (; i < line.size() && !isspace(line[i]); ++i) { }
129 	if (i == line.size()) // only action name
130 	{
131 		if (line == "set_visualizer_sample_multiplier")
132 		{
133 			warning("action 'set_visualizer_sample_multiplier' is deprecated"
134 			        " and will be removed in 0.9");
135 			result = Actions::get_(Actions::Type::Dummy);
136 		}
137 		else
138 			result = Actions::get_(line);
139 	}
140 	else // there is something else
141 	{
142 		std::string action_name = line.substr(0, i);
143 		if (action_name == "push_character")
144 		{
145 			// push single character into input queue
146 			std::string arg = getEnclosedString(line, '"', '"', 0);
147 			NC::Key::Type k = stringToSpecialKey(arg);
148 			if (k != NC::Key::None)
149 				result = std::static_pointer_cast<Actions::BaseAction>(
150 					std::make_shared<Actions::PushCharacters>(
151 						&Global::wFooter,
152 						std::vector<NC::Key::Type>{k}));
153 			else
154 				error() << "invalid character passed to push_character: '" << arg << "'\n";
155 		}
156 		else if (action_name == "push_characters")
157 		{
158 			// push sequence of characters into input queue
159 			std::string arg = getEnclosedString(line, '"', '"', 0);
160 			if (!arg.empty())
161 			{
162 				// if char is signed, erase 1s from char -> int conversion
163 				for (auto it = arg.begin(); it != arg.end(); ++it)
164 					*it &= 0xff;
165 				result = std::static_pointer_cast<Actions::BaseAction>(
166 					std::make_shared<Actions::PushCharacters>(
167 						&Global::wFooter,
168 						std::vector<NC::Key::Type>{arg.begin(), arg.end()}));
169 			}
170 			else
171 				error() << "empty argument passed to push_characters\n";
172 		}
173 		else if (action_name == "require_screen")
174 		{
175 			// require screen of given type
176 			std::string arg = getEnclosedString(line, '"', '"', 0);
177 			ScreenType screen_type = stringToScreenType(arg);
178 			if (screen_type != ScreenType::Unknown)
179 				result = std::static_pointer_cast<Actions::BaseAction>(
180 					std::make_shared<Actions::RequireScreen>(screen_type));
181 			else
182 				error() << "unknown screen passed to require_screen: '" << arg << "'\n";
183 		}
184 		else if (action_name == "require_runnable")
185 		{
186 			// require that given action is runnable
187 			std::string arg = getEnclosedString(line, '"', '"', 0);
188 			auto action = Actions::get_(arg);
189 			if (action)
190 				result = std::static_pointer_cast<Actions::BaseAction>(
191 					std::make_shared<Actions::RequireRunnable>(action));
192 			else
193 				error() << "unknown action passed to require_runnable: '" << arg << "'\n";
194 		}
195 		else if (action_name == "run_external_command")
196 		{
197 			std::string command = getEnclosedString(line, '"', '"', 0);
198 			if (!command.empty())
199 				result = std::static_pointer_cast<Actions::BaseAction>(
200 					std::make_shared<Actions::RunExternalCommand>(std::move(command)));
201 			else
202 				error() << "empty command passed to run_external_command\n";
203 		}
204 		else if (action_name == "run_external_console_command")
205 		{
206 			std::string command = getEnclosedString(line, '"', '"', 0);
207 			if (!command.empty())
208 				result = std::static_pointer_cast<Actions::BaseAction>(
209 					std::make_shared<Actions::RunExternalConsoleCommand>(std::move(command)));
210 			else
211 				error() << "empty command passed to run_external_console_command\n";
212 		}
213 	}
214 	return result;
215 }
216 
217 }
218 
readKey(NC::Window & w)219 NC::Key::Type readKey(NC::Window &w)
220 {
221 	NC::Key::Type result = NC::Key::None;
222 	std::string tmp;
223 	NC::Key::Type input;
224 	bool alt_pressed = false;
225 	while (true)
226 	{
227 		input = w.readKey();
228 		if (input == NC::Key::None)
229 			break;
230 		if (input & NC::Key::Alt)
231 		{
232 			// Complete the key and reapply the mask at the end.
233 			alt_pressed = true;
234 			input &= ~NC::Key::Alt;
235 		}
236 		if (input > 255) // NC special character
237 		{
238 			result = input;
239 			break;
240 		}
241 		else
242 		{
243 			wchar_t wc;
244 			tmp += input;
245 			size_t conv_res = mbrtowc(&wc, tmp.c_str(), MB_CUR_MAX, 0);
246 			if (conv_res == size_t(-1)) // incomplete multibyte character
247 				continue;
248 			else if (conv_res == size_t(-2)) // garbage character sequence
249 				break;
250 			else // character complete
251 			{
252 				result = wc;
253 				break;
254 			}
255 		}
256 	}
257 	if (alt_pressed)
258 		result |= NC::Key::Alt;
259 	return result;
260 }
261 
keyToWString(const NC::Key::Type key)262 std::wstring keyToWString(const NC::Key::Type key)
263 {
264 	std::wstring result;
265 
266 	if (key == NC::Key::Tab)
267 		result += L"Tab";
268 	else if (key == NC::Key::Enter)
269 		result += L"Enter";
270 	else if (key == NC::Key::Escape)
271 		result += L"Escape";
272 	else if (key >= NC::Key::Ctrl_A && key <= NC::Key::Ctrl_Z)
273 	{
274 		result += L"Ctrl-";
275 		result += 'A' + (key - NC::Key::Ctrl_A);
276 	}
277 	else if (key == NC::Key::Ctrl_LeftBracket)
278 		result += L"Ctrl-[";
279 	else if (key == NC::Key::Ctrl_Backslash)
280 		result += L"Ctrl-\\";
281 	else if (key == NC::Key::Ctrl_RightBracket)
282 		result += L"Ctrl-]";
283 	else if (key == NC::Key::Ctrl_Caret)
284 		result += L"Ctrl-^";
285 	else if (key == NC::Key::Ctrl_Underscore)
286 		result += L"Ctrl-_";
287 	else if (key & NC::Key::Alt)
288 	{
289 		result += L"Alt-";
290 		result += keyToWString(key & ~NC::Key::Alt);
291 	}
292 	else if (key & NC::Key::Ctrl)
293 	{
294 		result += L"Ctrl-";
295 		result += keyToWString(key & ~NC::Key::Ctrl);
296 	}
297 	else if (key & NC::Key::Shift)
298 	{
299 		result += L"Shift-";
300 		result += keyToWString(key & ~NC::Key::Shift);
301 	}
302 	else if (key == NC::Key::Space)
303 		result += L"Space";
304 	else if (key == NC::Key::Backspace)
305 		result += L"Backspace";
306 	else if (key == NC::Key::Insert)
307 		result += L"Insert";
308 	else if (key == NC::Key::Delete)
309 		result += L"Delete";
310 	else if (key == NC::Key::Home)
311 		result += L"Home";
312 	else if (key == NC::Key::End)
313 		result += L"End";
314 	else if (key == NC::Key::PageUp)
315 		result += L"PageUp";
316 	else if (key == NC::Key::PageDown)
317 		result += L"PageDown";
318 	else if (key == NC::Key::Up)
319 		result += L"Up";
320 	else if (key == NC::Key::Down)
321 		result += L"Down";
322 	else if (key == NC::Key::Left)
323 		result += L"Left";
324 	else if (key == NC::Key::Right)
325 		result += L"Right";
326 	else if (key == NC::Key::EoF)
327 		result += L"EoF";
328 	else if (key >= NC::Key::F1 && key <= NC::Key::F9)
329 	{
330 		result += L"F";
331 		result += '1' + (key - NC::Key::F1);
332 	}
333 	else if (key >= NC::Key::F10 && key <= NC::Key::F12)
334 	{
335 		result += L"F1";
336 		result += '0' + (key - NC::Key::F10);
337 	}
338 	else
339 		result += std::wstring(1, key);
340 
341 	return result;
342 }
343 
read(const std::string & file)344 bool BindingsConfiguration::read(const std::string &file)
345 {
346 	enum class InProgress { None, Command, Key };
347 
348 	bool result = true;
349 
350 	std::ifstream f(file);
351 	if (!f.is_open())
352 		return result;
353 
354 	// shared variables
355 	InProgress in_progress = InProgress::None;
356 	size_t line_no = 0;
357 	std::string line;
358 	Binding::ActionChain actions;
359 
360 	// def_key specific variables
361 	NC::Key::Type key = NC::Key::None;
362 	std::string strkey;
363 
364 	// def_command specific variables
365 	bool cmd_immediate = false;
366 	std::string cmd_name;
367 
368 	auto error = [&]() -> std::ostream & {
369 		std::cerr << file << ":" << line_no << ": error: ";
370 		in_progress = InProgress::None;
371 		result = false;
372 		return std::cerr;
373 	};
374 
375 	auto bind_in_progress = [&]() -> bool {
376 		if (in_progress == InProgress::Command)
377 		{
378 			if (!actions.empty())
379 			{
380 				m_commands.insert(std::make_pair(cmd_name, Command(std::move(actions), cmd_immediate)));
381 				actions.clear();
382 				return true;
383 			}
384 			else
385 			{
386 				error() << "definition of command '" << cmd_name << "' cannot be empty\n";
387 				return false;
388 			}
389 		}
390 		else if (in_progress == InProgress::Key)
391 		{
392 			if (!actions.empty())
393 			{
394 				bind(key, actions);
395 				actions.clear();
396 				return true;
397 			}
398 			else
399 			{
400 				error() << "definition of key '" << strkey << "' cannot be empty\n";
401 				return false;
402 			}
403 		}
404 		return true;
405 	};
406 
407 	const char def_command[] = "def_command";
408 	const char def_key[] = "def_key";
409 
410 	while (!f.eof() && ++line_no)
411 	{
412 		getline(f, line);
413 		if (line.empty() || line[0] == '#')
414 			continue;
415 
416 		// beginning of command definition
417 		if (!line.compare(0, const_strlen(def_command), def_command))
418 		{
419 			if (!bind_in_progress())
420 				break;
421 			in_progress = InProgress::Command;
422 			cmd_name = getEnclosedString(line, '"', '"', 0);
423 			if (cmd_name.empty())
424 			{
425 				error() << "command must have non-empty name\n";
426 				break;
427 			}
428 			if (m_commands.find(cmd_name) != m_commands.end())
429 			{
430 				error() << "redefinition of command '" << cmd_name << "'\n";
431 				break;
432 			}
433 			std::string cmd_type = getEnclosedString(line, '[', ']', 0);
434 			if (cmd_type == "immediate")
435 				cmd_immediate = true;
436 			else if (cmd_type == "deferred")
437 				cmd_immediate = false;
438 			else
439 			{
440 				error() << "invalid type of command: '" << cmd_type << "'\n";
441 				break;
442 			}
443 		}
444 		// beginning of key definition
445 		else if (!line.compare(0, const_strlen(def_key), def_key))
446 		{
447 			if (!bind_in_progress())
448 				break;
449 			in_progress = InProgress::Key;
450 			strkey = getEnclosedString(line, '"', '"', 0);
451 			key = stringToKey(strkey);
452 			if (key == NC::Key::None)
453 			{
454 				error() << "invalid key: '" << strkey << "'\n";
455 				break;
456 			}
457 		}
458 		else if (isspace(line[0])) // name of action to be bound
459 		{
460 			boost::trim(line);
461 			auto action = parseActionLine(line, error);
462 			if (action)
463 				actions.push_back(action);
464 			else
465 			{
466 				error() << "unknown action: '" << line << "'\n";
467 				break;
468 			}
469 		}
470 		else
471 		{
472 			error() << "invalid line: '" << line << "'\n";
473 			break;
474 		}
475 	}
476 	bind_in_progress();
477 	f.close();
478 	return result;
479 }
480 
read(const std::vector<std::string> & binding_paths)481 bool BindingsConfiguration::read(const std::vector<std::string> &binding_paths)
482 {
483 	return std::all_of(
484 		binding_paths.begin(),
485 		binding_paths.end(),
486 		[&](const std::string &binding_path) {
487 			return read(binding_path);
488 		}
489 	);
490 }
491 
generateDefaults()492 void BindingsConfiguration::generateDefaults()
493 {
494 	NC::Key::Type k = NC::Key::None;
495 	bind(NC::Key::EoF, Actions::Type::Quit);
496 	if (notBound(k = stringToKey("mouse")))
497 		bind(k, Actions::Type::MouseEvent);
498 	if (notBound(k = stringToKey("up")))
499 		bind(k, Actions::Type::ScrollUp);
500 	if (notBound(k = stringToKey("shift-up")))
501 		bind(k, Binding::ActionChain({Actions::get_(Actions::Type::SelectItem), Actions::get_(Actions::Type::ScrollUp)}));
502 	if (notBound(k = stringToKey("down")))
503 		bind(k, Actions::Type::ScrollDown);
504 	if (notBound(k = stringToKey("shift-down")))
505 		bind(k, Binding::ActionChain({Actions::get_(Actions::Type::SelectItem), Actions::get_(Actions::Type::ScrollDown)}));
506 	if (notBound(k = stringToKey("[")))
507 		bind(k, Actions::Type::ScrollUpAlbum);
508 	if (notBound(k = stringToKey("]")))
509 		bind(k, Actions::Type::ScrollDownAlbum);
510 	if (notBound(k = stringToKey("{")))
511 		bind(k, Actions::Type::ScrollUpArtist);
512 	if (notBound(k = stringToKey("}")))
513 		bind(k, Actions::Type::ScrollDownArtist);
514 	if (notBound(k = stringToKey("page_up")))
515 		bind(k, Actions::Type::PageUp);
516 	if (notBound(k = stringToKey("page_down")))
517 		bind(k, Actions::Type::PageDown);
518 	if (notBound(k = stringToKey("home")))
519 		bind(k, Actions::Type::MoveHome);
520 	if (notBound(k = stringToKey("end")))
521 		bind(k, Actions::Type::MoveEnd);
522 	if (notBound(k = stringToKey("insert")))
523 		bind(k, Actions::Type::SelectItem);
524 	if (notBound(k = stringToKey("enter")))
525 	{
526 		bind(k, Actions::Type::EnterDirectory);
527 		bind(k, Actions::Type::ToggleOutput);
528 		bind(k, Actions::Type::RunAction);
529 		bind(k, Actions::Type::PlayItem);
530 	}
531 	if (notBound(k = stringToKey("space")))
532 	{
533 		bind(k, Actions::Type::AddItemToPlaylist);
534 		bind(k, Actions::Type::ToggleLyricsUpdateOnSongChange);
535 		bind(k, Actions::Type::ToggleVisualizationType);
536 	}
537 	if (notBound(k = stringToKey("delete")))
538 	{
539 		bind(k, Actions::Type::DeletePlaylistItems);
540 		bind(k, Actions::Type::DeleteBrowserItems);
541 		bind(k, Actions::Type::DeleteStoredPlaylist);
542 	}
543 	if (notBound(k = stringToKey("right")))
544 	{
545 		bind(k, Actions::Type::NextColumn);
546 		bind(k, Actions::Type::SlaveScreen);
547 		bind(k, Actions::Type::VolumeUp);
548 	}
549 	if (notBound(k = stringToKey("+")))
550 		bind(k, Actions::Type::VolumeUp);
551 	if (notBound(k = stringToKey("left")))
552 	{
553 		bind(k, Actions::Type::PreviousColumn);
554 		bind(k, Actions::Type::MasterScreen);
555 		bind(k, Actions::Type::VolumeDown);
556 	}
557 	if (notBound(k = stringToKey("-")))
558 		bind(k, Actions::Type::VolumeDown);
559 	if (notBound(k = stringToKey(":")))
560 		bind(k, Actions::Type::ExecuteCommand);
561 	if (notBound(k = stringToKey("tab")))
562 		bind(k, Actions::Type::NextScreen);
563 	if (notBound(k = stringToKey("shift-tab")))
564 		bind(k, Actions::Type::PreviousScreen);
565 	if (notBound(k = stringToKey("f1")))
566 		bind(k, Actions::Type::ShowHelp);
567 	if (notBound(k = stringToKey("1")))
568 		bind(k, Actions::Type::ShowPlaylist);
569 	if (notBound(k = stringToKey("2")))
570 	{
571 		bind(k, Actions::Type::ShowBrowser);
572 		bind(k, Actions::Type::ChangeBrowseMode);
573 	}
574 	if (notBound(k = stringToKey("3")))
575 	{
576 		bind(k, Actions::Type::ShowSearchEngine);
577 		bind(k, Actions::Type::ResetSearchEngine);
578 	}
579 	if (notBound(k = stringToKey("4")))
580 	{
581 		bind(k, Actions::Type::ShowMediaLibrary);
582 		bind(k, Actions::Type::ToggleMediaLibraryColumnsMode);
583 	}
584 	if (notBound(k = stringToKey("5")))
585 		bind(k, Actions::Type::ShowPlaylistEditor);
586 	if (notBound(k = stringToKey("6")))
587 		bind(k, Actions::Type::ShowTagEditor);
588 	if (notBound(k = stringToKey("7")))
589 		bind(k, Actions::Type::ShowOutputs);
590 	if (notBound(k = stringToKey("8")))
591 		bind(k, Actions::Type::ShowVisualizer);
592 	if (notBound(k = stringToKey("=")))
593 		bind(k, Actions::Type::ShowClock);
594 	if (notBound(k = stringToKey("@")))
595 		bind(k, Actions::Type::ShowServerInfo);
596 	if (notBound(k = stringToKey("s")))
597 		bind(k, Actions::Type::Stop);
598 	if (notBound(k = stringToKey("p")))
599 		bind(k, Actions::Type::Pause);
600 	if (notBound(k = stringToKey(">")))
601 		bind(k, Actions::Type::Next);
602 	if (notBound(k = stringToKey("<")))
603 		bind(k, Actions::Type::Previous);
604 	if (notBound(k = stringToKey("ctrl-h")))
605 	{
606 		bind(k, Actions::Type::JumpToParentDirectory);
607 		bind(k, Actions::Type::ReplaySong);
608 	}
609 	if (notBound(k = stringToKey("backspace")))
610 	{
611 		bind(k, Actions::Type::JumpToParentDirectory);
612 		bind(k, Actions::Type::ReplaySong);
613 		bind(k, Actions::Type::Play);
614 	}
615 	if (notBound(k = stringToKey("f")))
616 		bind(k, Actions::Type::SeekForward);
617 	if (notBound(k = stringToKey("b")))
618 		bind(k, Actions::Type::SeekBackward);
619 	if (notBound(k = stringToKey("r")))
620 		bind(k, Actions::Type::ToggleRepeat);
621 	if (notBound(k = stringToKey("z")))
622 		bind(k, Actions::Type::ToggleRandom);
623 	if (notBound(k = stringToKey("y")))
624 	{
625 		bind(k, Actions::Type::SaveTagChanges);
626 		bind(k, Actions::Type::StartSearching);
627 		bind(k, Actions::Type::ToggleSingle);
628 	}
629 	if (notBound(k = stringToKey("R")))
630 		bind(k, Actions::Type::ToggleConsume);
631 	if (notBound(k = stringToKey("Y")))
632 		bind(k, Actions::Type::ToggleReplayGainMode);
633 	if (notBound(k = stringToKey("T")))
634 		bind(k, Actions::Type::ToggleAddMode);
635 	if (notBound(k = stringToKey("|")))
636 		bind(k, Actions::Type::ToggleMouse);
637 	if (notBound(k = stringToKey("#")))
638 		bind(k, Actions::Type::ToggleBitrateVisibility);
639 	if (notBound(k = stringToKey("Z")))
640 		bind(k, Actions::Type::Shuffle);
641 	if (notBound(k = stringToKey("x")))
642 		bind(k, Actions::Type::ToggleCrossfade);
643 	if (notBound(k = stringToKey("X")))
644 		bind(k, Actions::Type::SetCrossfade);
645 	if (notBound(k = stringToKey("u")))
646 		bind(k, Actions::Type::UpdateDatabase);
647 	if (notBound(k = stringToKey("ctrl-s")))
648 	{
649 		bind(k, Actions::Type::SortPlaylist);
650 		bind(k, Actions::Type::ToggleBrowserSortMode);
651 		bind(k, Actions::Type::ToggleMediaLibrarySortMode);
652 	}
653 	if (notBound(k = stringToKey("ctrl-r")))
654 		bind(k, Actions::Type::ReversePlaylist);
655 	if (notBound(k = stringToKey("ctrl-f")))
656 		bind(k, Actions::Type::ApplyFilter);
657 	if (notBound(k = stringToKey("ctrl-_")))
658 		bind(k, Actions::Type::SelectFoundItems);
659 	if (notBound(k = stringToKey("/")))
660 	{
661 		bind(k, Actions::Type::Find);
662 		bind(k, Actions::Type::FindItemForward);
663 	}
664 	if (notBound(k = stringToKey("?")))
665 	{
666 		bind(k, Actions::Type::Find);
667 		bind(k, Actions::Type::FindItemBackward);
668 	}
669 	if (notBound(k = stringToKey(".")))
670 		bind(k, Actions::Type::NextFoundItem);
671 	if (notBound(k = stringToKey(",")))
672 		bind(k, Actions::Type::PreviousFoundItem);
673 	if (notBound(k = stringToKey("w")))
674 		bind(k, Actions::Type::ToggleFindMode);
675 	if (notBound(k = stringToKey("e")))
676 	{
677 		bind(k, Actions::Type::EditSong);
678 		bind(k, Actions::Type::EditLibraryTag);
679 		bind(k, Actions::Type::EditLibraryAlbum);
680 		bind(k, Actions::Type::EditDirectoryName);
681 		bind(k, Actions::Type::EditPlaylistName);
682 		bind(k, Actions::Type::EditLyrics);
683 	}
684 	if (notBound(k = stringToKey("i")))
685 		bind(k, Actions::Type::ShowSongInfo);
686 	if (notBound(k = stringToKey("I")))
687 		bind(k, Actions::Type::ShowArtistInfo);
688 	if (notBound(k = stringToKey("g")))
689 		bind(k, Actions::Type::JumpToPositionInSong);
690 	if (notBound(k = stringToKey("l")))
691 		bind(k, Actions::Type::ShowLyrics);
692 	if (notBound(k = stringToKey("ctrl-v")))
693 		bind(k, Actions::Type::SelectRange);
694 	if (notBound(k = stringToKey("v")))
695 		bind(k, Actions::Type::ReverseSelection);
696 	if (notBound(k = stringToKey("V")))
697 		bind(k, Actions::Type::RemoveSelection);
698 	if (notBound(k = stringToKey("B")))
699 		bind(k, Actions::Type::SelectAlbum);
700 	if (notBound(k = stringToKey("a")))
701 		bind(k, Actions::Type::AddSelectedItems);
702 	if (notBound(k = stringToKey("c")))
703 	{
704 		bind(k, Actions::Type::ClearPlaylist);
705 		bind(k, Actions::Type::ClearMainPlaylist);
706 	}
707 	if (notBound(k = stringToKey("C")))
708 	{
709 		bind(k, Actions::Type::CropPlaylist);
710 		bind(k, Actions::Type::CropMainPlaylist);
711 	}
712 	if (notBound(k = stringToKey("m")))
713 	{
714 		bind(k, Actions::Type::MoveSortOrderUp);
715 		bind(k, Actions::Type::MoveSelectedItemsUp);
716 	}
717 	if (notBound(k = stringToKey("n")))
718 	{
719 		bind(k, Actions::Type::MoveSortOrderDown);
720 		bind(k, Actions::Type::MoveSelectedItemsDown);
721 	}
722 	if (notBound(k = stringToKey("M")))
723 		bind(k, Actions::Type::MoveSelectedItemsTo);
724 	if (notBound(k = stringToKey("A")))
725 		bind(k, Actions::Type::Add);
726 	if (notBound(k = stringToKey("S")))
727 		bind(k, Actions::Type::SavePlaylist);
728 	if (notBound(k = stringToKey("o")))
729 		bind(k, Actions::Type::JumpToPlayingSong);
730 	if (notBound(k = stringToKey("G")))
731 	{
732 		bind(k, Actions::Type::JumpToBrowser);
733 		bind(k, Actions::Type::JumpToPlaylistEditor);
734 	}
735 	if (notBound(k = stringToKey("~")))
736 		bind(k, Actions::Type::JumpToMediaLibrary);
737 	if (notBound(k = stringToKey("E")))
738 		bind(k, Actions::Type::JumpToTagEditor);
739 	if (notBound(k = stringToKey("U")))
740 		bind(k, Actions::Type::TogglePlayingSongCentering);
741 	if (notBound(k = stringToKey("P")))
742 		bind(k, Actions::Type::ToggleDisplayMode);
743 	if (notBound(k = stringToKey("\\")))
744 		bind(k, Actions::Type::ToggleInterface);
745 	if (notBound(k = stringToKey("!")))
746 		bind(k, Actions::Type::ToggleSeparatorsBetweenAlbums);
747 	if (notBound(k = stringToKey("L")))
748 		bind(k, Actions::Type::ToggleLyricsFetcher);
749 	if (notBound(k = stringToKey("F")))
750 		bind(k, Actions::Type::FetchLyricsInBackground);
751 	if (notBound(k = stringToKey("alt-l")))
752 		bind(k, Actions::Type::ToggleFetchingLyricsInBackground);
753 	if (notBound(k = stringToKey("ctrl-l")))
754 		bind(k, Actions::Type::ToggleScreenLock);
755 	if (notBound(k = stringToKey("`")))
756 	{
757 		bind(k, Actions::Type::ToggleLibraryTagType);
758 		bind(k, Actions::Type::RefetchLyrics);
759 		bind(k, Actions::Type::AddRandomItems);
760 	}
761 	if (notBound(k = stringToKey("ctrl-p")))
762 		bind(k, Actions::Type::SetSelectedItemsPriority);
763 	if (notBound(k = stringToKey("q")))
764 		bind(k, Actions::Type::Quit);
765 }
766 
findCommand(const std::string & name)767 const Command *BindingsConfiguration::findCommand(const std::string &name)
768 {
769 	const Command *ptr = nullptr;
770 	auto it = m_commands.find(name);
771 	if (it != m_commands.end())
772 		ptr = &it->second;
773 	return ptr;
774 }
775 
get(const NC::Key::Type & k)776 BindingsConfiguration::BindingIteratorPair BindingsConfiguration::get(const NC::Key::Type &k)
777 {
778 	std::pair<BindingIterator, BindingIterator> result;
779 	auto it = m_bindings.find(k);
780 	if (it != m_bindings.end()) {
781 		result.first = it->second.begin();
782 		result.second = it->second.end();
783 	} else {
784 		auto list_end = m_bindings.begin()->second.end();
785 		result.first = list_end;
786 		result.second = list_end;
787 	}
788 	return result;
789 }
790