1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 // * Redistributions of source code must retain the above copyright notice,
11 // this list of conditions and the following disclaimer.
12 //
13 // * Redistributions in binary form must reproduce the above copyright
14 // notice, this list of conditions and the following disclaimer in the
15 // documentation and/or other materials provided with the distribution.
16 //
17 // * Neither the name of the author nor the names of other contributors may
18 // be used to endorse or promote products derived from this software
19 // without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34
35 #include <stdafx.h>
36 #include "Hotkeys.h"
37 #include <musikcore/support/Preferences.h>
38 #include <app/util/Playback.h>
39 #include <unordered_map>
40 #include <unordered_set>
41 #include <iostream>
42
43 using namespace musik::cube;
44 using namespace musik::core;
45
46 #define ENSURE_LOADED() { if (!preferences) { loadPreferences(); } }
47
48 using Id = Hotkeys::Id;
49
50 /* sigh: http://stackoverflow.com/a/24847480 */
51 struct EnumHasher {
52 template <typename T>
operator ()EnumHasher53 std::size_t operator()(T t) const noexcept {
54 return static_cast<std::size_t>(t);
55 }
56 };
57
58 /* map from internal ID to user-friendly JSON key name */
59 static std::unordered_map<std::string, Id> NAME_TO_ID = {
60 { "key_up", Id::Up },
61 { "key_down", Id::Down },
62 { "key_left", Id::Left },
63 { "key_right", Id::Right },
64 { "key_page_up", Id::PageUp },
65 { "key_page_down", Id::PageDown },
66 { "key_home", Id::Home },
67 { "key_end", Id::End },
68
69 { "navigate_library", Id::NavigateLibrary },
70 { "navigate_library_browse", Id::NavigateLibraryBrowse },
71 { "navigate_library_browse_artists", Id::NavigateLibraryBrowseArtists },
72 { "navigate_library_browse_albums", Id::NavigateLibraryBrowseAlbums },
73 { "navigate_library_browse_genres", Id::NavigateLibraryBrowseGenres },
74 { "navigate_library_album_artists", Id::NavigateLibraryBrowseAlbumArtists },
75 { "navigate_library_playlists", Id::NavigateLibraryBrowsePlaylists },
76 { "navigate_library_choose_category", Id::NavigateLibraryBrowseChooseCategory },
77 { "navigate_library_browse_directories", Id::NavigateLibraryBrowseDirectories },
78 { "navigate_library_filter", Id::NavigateLibraryFilter },
79 { "navigate_library_tracks", Id::NavigateLibraryTracks },
80 { "navigate_library_play_queue", Id::NavigateLibraryPlayQueue },
81 { "navigate_settings", Id::NavigateSettings },
82 { "navigate_console", Id::NavigateConsole },
83 { "navigate_lyrics", Id::NavigateLyrics },
84 { "navigate_hotkeys", Id::NavigateHotkeys},
85 { "navigate_jump_to_playing", Id::NavigateJumpToPlaying },
86
87 { "play_queue_move_up", Id::PlayQueueMoveUp },
88 { "play_queue_move_down", Id::PlayQueueMoveDown },
89 { "play_queue_delete", Id::PlayQueueDelete },
90 { "play_queue_playlist_load", Id::PlayQueuePlaylistLoad },
91 { "play_queue_playlist_save", Id::PlayQueuePlaylistSave },
92 { "play_queue_playlist_rename", Id::PlayQueuePlaylistRename },
93 { "play_queue_playlist_delete", Id::PlayQueuePlaylistDelete },
94 { "play_queue_hot_swap", Id::PlayQueueHotSwap },
95 { "play_queue_clear", Id::PlayQueueClear },
96
97 { "browse_playlists_new", Id::BrowsePlaylistsNew },
98 { "browse_playlists_save", Id::BrowsePlaylistsSave },
99 { "browse_playlists_rename", Id::BrowsePlaylistsRename },
100 { "browse_playlists_delete", Id::BrowsePlaylistsDelete },
101
102 { "track_list_rate_track", Id::TrackListRateTrack },
103 { "track_list_change_sort_order", Id::TrackListChangeSortOrder },
104 { "track_list_next_group", Id::TrackListNextGroup },
105 { "track_list_previous_group", Id::TrackListPreviousGroup },
106 { "track_list_play_from_top", Id::TrackListPlayFromTop },
107
108 { "search_input_toggle_match_type", Id::SearchInputToggleMatchType },
109
110 { "lyrics_retry", Id::LyricsRetry },
111
112 { "playback_toggle_mute", Id::ToggleMute },
113 { "playback_toggle_pause", Id::TogglePause },
114 { "playback_next", Id::Next },
115 { "playback_previous", Id::Previous },
116 { "playback_volume_up", Id::VolumeUp },
117 { "playback_volume_down", Id::VolumeDown },
118 { "playback_seek_forward", Id::SeekForward },
119 { "playback_seek_forward_proportional", Id::SeekForwardProportional },
120 { "playback_seek_back_proportional", Id::SeekBackProportional },
121 { "playback_seek_back", Id::SeekBack },
122 { "playback_toggle_repeat", Id::ToggleRepeat },
123 { "playback_toggle_shuffle", Id::ToggleShuffle },
124 { "playback_stop", Id::Stop },
125
126 { "view_refresh", Id::ViewRefresh },
127
128 { "toggle_visualizer", Id::ToggleVisualizer },
129
130 { "show_equalizer", Id::ShowEqualizer },
131
132 { "metadata_rescan", Id::RescanMetadata },
133
134 { "hotkeys_reset_to_default", Id::HotkeysResetToDefault },
135 { "hotkeys_backup", Id::HotkeysBackup },
136
137 { "context_menu", Id::ContextMenu }
138 };
139
140 /* default hotkeys */
141 static std::unordered_map<Id, std::string, EnumHasher> ID_TO_DEFAULT = {
142 { Id::Up, "KEY_UP" },
143 { Id::Down, "KEY_DOWN" },
144 { Id::Left, "KEY_LEFT" },
145 { Id::Right, "KEY_RIGHT" },
146 { Id::PageUp, "KEY_PPAGE" },
147 { Id::PageDown, "KEY_NPAGE" },
148 { Id::Home, "KEY_HOME" },
149 { Id::End, "KEY_END" },
150
151 { Id::NavigateLibrary, "a" },
152 { Id::NavigateLibraryBrowse, "b" },
153 { Id::NavigateLibraryBrowseArtists, "1" },
154 { Id::NavigateLibraryBrowseAlbums, "2" },
155 { Id::NavigateLibraryBrowseGenres, "3" },
156 { Id::NavigateLibraryBrowseAlbumArtists, "4" },
157 { Id::NavigateLibraryBrowsePlaylists, "5" },
158 { Id::NavigateLibraryBrowseChooseCategory, "6" },
159 { Id::NavigateLibraryBrowseDirectories, "d" },
160
161 { Id::NavigateLibraryFilter, "f" },
162 { Id::NavigateLibraryTracks, "t" },
163 { Id::NavigateLibraryPlayQueue, "n" },
164 { Id::NavigateSettings, "s" },
165 { Id::NavigateConsole, "`" },
166 { Id::NavigateLyrics, "^L" },
167 { Id::NavigateHotkeys, "?" },
168 { Id::NavigateJumpToPlaying, "x" },
169
170 #ifdef __APPLE__
171 /* M-up, M-down don't seem to work on iTerm2, and the delete
172 key doesn't exist on most macbooks. ugly special mappings here. */
173 { Id::PlayQueueMoveUp, "CTL_UP" },
174 { Id::PlayQueueMoveDown, "CTL_DOWN" },
175 { Id::PlayQueueDelete, "KEY_BACKSPACE" },
176 #else
177 { Id::PlayQueueMoveUp, "M-up" },
178 { Id::PlayQueueMoveDown, "M-down" },
179 { Id::PlayQueueDelete, "KEY_DC" },
180 #endif
181 { Id::PlayQueuePlaylistLoad, "M-l" },
182 { Id::PlayQueuePlaylistSave, "M-s" },
183 { Id::PlayQueuePlaylistRename, "M-r" },
184 { Id::PlayQueuePlaylistDelete, "M-x" },
185 { Id::PlayQueueHotSwap, "M-a" },
186 { Id::PlayQueueClear, "X" },
187
188 { Id::BrowsePlaylistsSave, "M-s" },
189 { Id::BrowsePlaylistsNew, "M-n" },
190 { Id::BrowsePlaylistsRename, "M-r" },
191 #ifdef __APPLE__
192 { Id::BrowsePlaylistsDelete, "KEY_BACKSPACE" },
193 #else
194 { Id::BrowsePlaylistsDelete, "KEY_DC" },
195 #endif
196
197 { Id::TrackListRateTrack, "r" },
198 { Id::TrackListChangeSortOrder, "M-s" },
199 { Id::TrackListNextGroup, "]" },
200 { Id::TrackListPreviousGroup, "[" },
201 { Id::TrackListPlayFromTop, "M-P" },
202
203 { Id::SearchInputToggleMatchType, "M-m" },
204
205 { Id::LyricsRetry, "r" },
206
207 { Id::ToggleMute, "m" },
208 { Id::TogglePause, "^P" },
209 { Id::Next, "l" },
210 { Id::Previous, "j" },
211 { Id::VolumeUp, "i" },
212 { Id::VolumeDown, "k" },
213 { Id::SeekForward, "o" },
214 { Id::SeekBack, "u" },
215 { Id::SeekForwardProportional, "p" },
216 { Id::SeekBackProportional, "y" },
217 { Id::ToggleRepeat, "." },
218 { Id::ToggleShuffle, "," },
219 { Id::Stop, "^X" },
220
221 { Id::ViewRefresh, "KEY_F(5)" },
222
223 { Id::ToggleVisualizer, "v" },
224
225 { Id::ShowEqualizer, "^E" },
226
227 { Id::RescanMetadata, "^R"},
228
229 { Id::HotkeysResetToDefault, "M-r" },
230 { Id::HotkeysBackup, "M-b" },
231
232 { Id::ContextMenu, "M-enter" }
233 };
234
235 /* custom keys */
236 static std::unordered_map<Id, std::string, EnumHasher> customIdToKey;
237
238 /* preferences file */
239 static std::shared_ptr<Preferences> preferences;
240
savePreferences()241 static void savePreferences() {
242 for (const auto& pair : NAME_TO_ID) {
243 preferences->SetString(
244 pair.first.c_str(),
245 Hotkeys::Get(pair.second).c_str());
246 }
247
248 preferences->Save();
249 }
250
loadPreferences()251 static void loadPreferences() {
252 preferences = Preferences::ForComponent("hotkeys", Preferences::ModeReadWrite);
253
254 try {
255 if (preferences) {
256 std::vector<std::string> names;
257 preferences->GetKeys(names);
258 for (auto n : names) {
259 const auto it = NAME_TO_ID.find(n);
260 if (it != NAME_TO_ID.end()) {
261 customIdToKey[it->second] = preferences->GetString(n);
262 }
263 }
264 }
265
266 /* write back to disk; this way any new hotkey defaults are picked
267 up and saved so the user can edit them easily. */
268 savePreferences();
269 }
270 catch (...) {
271 std::cerr << "failed to load hotkeys.json! default hotkeys selected.";
272 customIdToKey.clear();
273 }
274 }
275
Is(Id id,const std::string & kn)276 bool Hotkeys::Is(Id id, const std::string& kn) {
277 ENSURE_LOADED()
278
279 /* see if the user has specified a custom value for this hotkey. if
280 they have, compare it to the custom value. */
281 const auto custom = customIdToKey.find(id);
282 if (custom != customIdToKey.end()) {
283 return (custom->second == kn);
284 }
285
286 /* otherwise, let's compare against the default key */
287 const auto it = ID_TO_DEFAULT.find(id);
288 if (it != ID_TO_DEFAULT.end() && it->second == kn) {
289 return true;
290 }
291
292 return false;
293 }
294
295 template <typename T>
find(Id id,T & map)296 std::string find(Id id, T& map) {
297 const auto it = map.find(id);
298 if (it != map.end()) {
299 return it->second;
300 }
301 return "";
302 }
303
304 template <typename T>
find(const std::string & kn,T & map)305 Hotkeys::Id find(const std::string& kn, T& map) {
306 for (auto it : map) {
307 if (it.second == kn) {
308 return it.first;
309 }
310 }
311 return Hotkeys::COUNT;
312 }
313
Default(Id id)314 std::string Hotkeys::Default(Id id) {
315 ENSURE_LOADED()
316 return find(id, ID_TO_DEFAULT);
317 }
318
Custom(Id id)319 std::string Hotkeys::Custom(Id id) {
320 ENSURE_LOADED()
321 return find(id, customIdToKey);
322 }
323
Get(Id id)324 std::string Hotkeys::Get(Id id) {
325 auto kn = Custom(id);
326 return kn.size() ? kn : Default(id);
327 }
328
Set(Id id,const std::string & kn)329 void Hotkeys::Set(Id id, const std::string& kn) {
330 customIdToKey[id] = kn;
331 savePreferences();
332 }
333
Reset()334 void Hotkeys::Reset() {
335 customIdToKey.clear();
336 savePreferences();
337 loadPreferences();
338 }
339
Existing(const std::string & kn)340 std::string Hotkeys::Existing(const std::string& kn) {
341 auto id = find(kn, customIdToKey);
342 if (id == Hotkeys::COUNT) {
343 id = find(kn, ID_TO_DEFAULT);
344 if (customIdToKey.find(id) != customIdToKey.end()) {
345 /* we found a default key for this one, but that default
346 binding has already been overridden! ensure we return
347 that it's available. */
348 id = Hotkeys::COUNT;
349 }
350 }
351 return id != Hotkeys::COUNT ? Name(id) : "";
352 }
353
Name(Id id)354 std::string Hotkeys::Name(Id id) {
355 for (auto entry : NAME_TO_ID) {
356 if (entry.second == id) {
357 return entry.first;
358 }
359 }
360 return "<error>";
361 }
362
363 class NavigationKeysImpl : public cursespp::INavigationKeys {
364 public:
Up(const std::string & key)365 bool Up(const std::string& key) override { return Up() == key; }
Down(const std::string & key)366 bool Down(const std::string& key) override { return Down() == key; }
Left(const std::string & key)367 bool Left(const std::string& key) override { return Left() == key; }
Right(const std::string & key)368 bool Right(const std::string& key) override { return Right() == key; }
PageUp(const std::string & key)369 bool PageUp(const std::string& key) override { return PageUp() == key; }
PageDown(const std::string & key)370 bool PageDown(const std::string& key) override { return PageDown() == key; }
Home(const std::string & key)371 bool Home(const std::string& key) override { return Home() == key; }
End(const std::string & key)372 bool End(const std::string& key) override { return End() == key; }
Next(const std::string & key)373 bool Next(const std::string& key) override { return Next() == key; }
Prev(const std::string & key)374 bool Prev(const std::string& key) override { return Prev() == key; }
Mode(const std::string & key)375 bool Mode(const std::string& key) override { return Mode() == key; }
376
Up()377 std::string Up() override { return Hotkeys::Get(Hotkeys::Up); }
Down()378 std::string Down() override { return Hotkeys::Get(Hotkeys::Down); }
Left()379 std::string Left() override { return Hotkeys::Get(Hotkeys::Left); }
Right()380 std::string Right() override { return Hotkeys::Get(Hotkeys::Right); }
PageUp()381 std::string PageUp() override { return Hotkeys::Get(Hotkeys::PageUp); }
PageDown()382 std::string PageDown() override { return Hotkeys::Get(Hotkeys::PageDown); }
Home()383 std::string Home() override { return Hotkeys::Get(Hotkeys::Home); }
End()384 std::string End() override { return Hotkeys::Get(Hotkeys::End); }
Next()385 std::string Next() override { return "KEY_TAB"; }
Prev()386 std::string Prev() override { return "KEY_BTAB"; }
Mode()387 std::string Mode() override { return "^["; }
388 };
389
NavigationKeys()390 std::shared_ptr<cursespp::INavigationKeys> Hotkeys::NavigationKeys() {
391 return std::make_shared<NavigationKeysImpl>();
392 }
393