"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.0/Telegram/SourceFiles/core/shortcuts.cpp" (23 Feb 2021, 16632 Bytes) of package /linux/misc/tdesktop-2.6.0.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "shortcuts.cpp" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
2.5.1_vs_2.5.6.
1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "core/shortcuts.h"
9
10 #include "mainwindow.h"
11 #include "mainwidget.h"
12 #include "window/window_controller.h"
13 #include "core/application.h"
14 #include "media/player/media_player_instance.h"
15 #include "base/platform/base_platform_info.h"
16 #include "platform/platform_specific.h"
17 #include "base/parse_helper.h"
18 #include "facades.h"
19
20 #include <QtWidgets/QShortcut>
21 #include <QtCore/QJsonDocument>
22 #include <QtCore/QJsonObject>
23 #include <QtCore/QJsonArray>
24
25 namespace Shortcuts {
26 namespace {
27
28 constexpr auto kCountLimit = 256; // How many shortcuts can be in json file.
29
30 rpl::event_stream<not_null<Request*>> RequestsStream;
31
32 const auto AutoRepeatCommands = base::flat_set<Command>{
33 Command::MediaPrevious,
34 Command::MediaNext,
35 Command::ChatPrevious,
36 Command::ChatNext,
37 Command::ChatFirst,
38 Command::ChatLast,
39 };
40
41 const auto MediaCommands = base::flat_set<Command>{
42 Command::MediaPlay,
43 Command::MediaPause,
44 Command::MediaPlayPause,
45 Command::MediaStop,
46 Command::MediaPrevious,
47 Command::MediaNext,
48 };
49
50 const auto SupportCommands = base::flat_set<Command>{
51 Command::SupportReloadTemplates,
52 Command::SupportToggleMuted,
53 Command::SupportScrollToCurrent,
54 Command::SupportHistoryBack,
55 Command::SupportHistoryForward,
56 };
57
58 const auto CommandByName = base::flat_map<QString, Command>{
59 { qsl("close_telegram") , Command::Close },
60 { qsl("lock_telegram") , Command::Lock },
61 { qsl("minimize_telegram") , Command::Minimize },
62 { qsl("quit_telegram") , Command::Quit },
63
64 { qsl("media_play") , Command::MediaPlay },
65 { qsl("media_pause") , Command::MediaPause },
66 { qsl("media_playpause") , Command::MediaPlayPause },
67 { qsl("media_stop") , Command::MediaStop },
68 { qsl("media_previous") , Command::MediaPrevious },
69 { qsl("media_next") , Command::MediaNext },
70
71 { qsl("search") , Command::Search },
72
73 { qsl("previous_chat") , Command::ChatPrevious },
74 { qsl("next_chat") , Command::ChatNext },
75 { qsl("first_chat") , Command::ChatFirst },
76 { qsl("last_chat") , Command::ChatLast },
77 { qsl("self_chat") , Command::ChatSelf },
78
79 { qsl("previous_folder") , Command::FolderPrevious },
80 { qsl("next_folder") , Command::FolderNext },
81 { qsl("all_chats") , Command::ShowAllChats },
82
83 { qsl("folder1") , Command::ShowFolder1 },
84 { qsl("folder2") , Command::ShowFolder2 },
85 { qsl("folder3") , Command::ShowFolder3 },
86 { qsl("folder4") , Command::ShowFolder4 },
87 { qsl("folder5") , Command::ShowFolder5 },
88 { qsl("folder6") , Command::ShowFolder6 },
89 { qsl("last_folder") , Command::ShowFolderLast },
90
91 { qsl("show_archive") , Command::ShowArchive },
92 { qsl("show_contacts") , Command::ShowContacts },
93
94 { qsl("read_chat") , Command::ReadChat },
95
96 // Shortcuts that have no default values.
97 { qsl("message") , Command::JustSendMessage },
98 { qsl("message_silently") , Command::SendSilentMessage },
99 { qsl("message_scheduled") , Command::ScheduleMessage },
100 //
101 };
102
103 const auto CommandNames = base::flat_map<Command, QString>{
104 { Command::Close , qsl("close_telegram") },
105 { Command::Lock , qsl("lock_telegram") },
106 { Command::Minimize , qsl("minimize_telegram") },
107 { Command::Quit , qsl("quit_telegram") },
108
109 { Command::MediaPlay , qsl("media_play") },
110 { Command::MediaPause , qsl("media_pause") },
111 { Command::MediaPlayPause , qsl("media_playpause") },
112 { Command::MediaStop , qsl("media_stop") },
113 { Command::MediaPrevious , qsl("media_previous") },
114 { Command::MediaNext , qsl("media_next") },
115
116 { Command::Search , qsl("search") },
117
118 { Command::ChatPrevious , qsl("previous_chat") },
119 { Command::ChatNext , qsl("next_chat") },
120 { Command::ChatFirst , qsl("first_chat") },
121 { Command::ChatLast , qsl("last_chat") },
122 { Command::ChatSelf , qsl("self_chat") },
123
124 { Command::FolderPrevious , qsl("previous_folder") },
125 { Command::FolderNext , qsl("next_folder") },
126 { Command::ShowAllChats , qsl("all_chats") },
127
128 { Command::ShowFolder1 , qsl("folder1") },
129 { Command::ShowFolder2 , qsl("folder2") },
130 { Command::ShowFolder3 , qsl("folder3") },
131 { Command::ShowFolder4 , qsl("folder4") },
132 { Command::ShowFolder5 , qsl("folder5") },
133 { Command::ShowFolder6 , qsl("folder6") },
134 { Command::ShowFolderLast , qsl("last_folder") },
135
136 { Command::ShowArchive , qsl("show_archive") },
137 { Command::ShowContacts , qsl("show_contacts") },
138
139 { Command::ReadChat , qsl("read_chat") },
140 };
141
142 class Manager {
143 public:
144 void fill();
145 void clear();
146
147 [[nodiscard]] std::vector<Command> lookup(int shortcutId) const;
148 void toggleMedia(bool toggled);
149 void toggleSupport(bool toggled);
150
151 const QStringList &errors() const;
152
153 private:
154 void fillDefaults();
155 void writeDefaultFile();
156 bool readCustomFile();
157
158 void set(const QString &keys, Command command, bool replace = false);
159 void remove(const QString &keys);
160 void unregister(base::unique_qptr<QShortcut> shortcut);
161
162 QStringList _errors;
163
164 base::flat_map<QKeySequence, base::unique_qptr<QShortcut>> _shortcuts;
165 base::flat_multi_map<int, Command> _commandByShortcutId;
166
167 base::flat_set<QShortcut*> _mediaShortcuts;
168 base::flat_set<QShortcut*> _supportShortcuts;
169
170 };
171
172 QString DefaultFilePath() {
173 return cWorkingDir() + qsl("tdata/shortcuts-default.json");
174 }
175
176 QString CustomFilePath() {
177 return cWorkingDir() + qsl("tdata/shortcuts-custom.json");
178 }
179
180 bool DefaultFileIsValid() {
181 QFile file(DefaultFilePath());
182 if (!file.open(QIODevice::ReadOnly)) {
183 return false;
184 }
185 auto error = QJsonParseError{ 0, QJsonParseError::NoError };
186 const auto document = QJsonDocument::fromJson(
187 base::parse::stripComments(file.readAll()),
188 &error);
189 file.close();
190
191 if (error.error != QJsonParseError::NoError || !document.isArray()) {
192 return false;
193 }
194 const auto shortcuts = document.array();
195 if (shortcuts.isEmpty() || !(*shortcuts.constBegin()).isObject()) {
196 return false;
197 }
198 const auto versionObject = (*shortcuts.constBegin()).toObject();
199 const auto version = versionObject.constFind(qsl("version"));
200 if (version == versionObject.constEnd()
201 || !(*version).isString()
202 || (*version).toString() != QString::number(AppVersion)) {
203 return false;
204 }
205 return true;
206 }
207
208 void WriteDefaultCustomFile() {
209 const auto path = CustomFilePath();
210 auto input = QFile(":/misc/default_shortcuts-custom.json");
211 auto output = QFile(path);
212 if (input.open(QIODevice::ReadOnly) && output.open(QIODevice::WriteOnly)) {
213 output.write(input.readAll());
214 }
215 }
216
217 void Manager::fill() {
218 fillDefaults();
219
220 if (!DefaultFileIsValid()) {
221 writeDefaultFile();
222 }
223 if (!readCustomFile()) {
224 WriteDefaultCustomFile();
225 }
226 }
227
228 void Manager::clear() {
229 _errors.clear();
230 _shortcuts.clear();
231 _commandByShortcutId.clear();
232 _mediaShortcuts.clear();
233 _supportShortcuts.clear();
234 }
235
236 const QStringList &Manager::errors() const {
237 return _errors;
238 }
239
240 std::vector<Command> Manager::lookup(int shortcutId) const {
241 auto result = std::vector<Command>();
242 auto i = _commandByShortcutId.findFirst(shortcutId);
243 const auto end = _commandByShortcutId.end();
244 for (; i != end && (i->first == shortcutId); ++i) {
245 result.push_back(i->second);
246 }
247 return result;
248 }
249
250 void Manager::toggleMedia(bool toggled) {
251 for (const auto shortcut : _mediaShortcuts) {
252 shortcut->setEnabled(toggled);
253 }
254 }
255
256 void Manager::toggleSupport(bool toggled) {
257 for (const auto shortcut : _supportShortcuts) {
258 shortcut->setEnabled(toggled);
259 }
260 }
261
262 bool Manager::readCustomFile() {
263 // read custom shortcuts from file if it exists or write an empty custom shortcuts file
264 QFile file(CustomFilePath());
265 if (!file.exists()) {
266 return false;
267 }
268 const auto guard = gsl::finally([&] {
269 if (!_errors.isEmpty()) {
270 _errors.push_front(qsl("While reading file '%1'..."
271 ).arg(file.fileName()));
272 }
273 });
274 if (!file.open(QIODevice::ReadOnly)) {
275 _errors.push_back(qsl("Could not read the file!"));
276 return true;
277 }
278 auto error = QJsonParseError{ 0, QJsonParseError::NoError };
279 const auto document = QJsonDocument::fromJson(
280 base::parse::stripComments(file.readAll()),
281 &error);
282 file.close();
283
284 if (error.error != QJsonParseError::NoError) {
285 _errors.push_back(qsl("Failed to parse! Error: %2"
286 ).arg(error.errorString()));
287 return true;
288 } else if (!document.isArray()) {
289 _errors.push_back(qsl("Failed to parse! Error: array expected"));
290 return true;
291 }
292 const auto shortcuts = document.array();
293 auto limit = kCountLimit;
294 for (auto i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) {
295 if (!(*i).isObject()) {
296 _errors.push_back(qsl("Bad entry! Error: object expected"));
297 continue;
298 }
299 const auto entry = (*i).toObject();
300 const auto keys = entry.constFind(qsl("keys"));
301 const auto command = entry.constFind(qsl("command"));
302 if (keys == entry.constEnd()
303 || command == entry.constEnd()
304 || !(*keys).isString()
305 || (!(*command).isString() && !(*command).isNull())) {
306 _errors.push_back(qsl("Bad entry! "
307 "{\"keys\": \"...\", \"command\": [ \"...\" | null ]} "
308 "expected."));
309 } else if ((*command).isNull()) {
310 remove((*keys).toString());
311 } else {
312 const auto name = (*command).toString();
313 const auto i = CommandByName.find(name);
314 if (i != end(CommandByName)) {
315 set((*keys).toString(), i->second, true);
316 } else {
317 LOG(("Shortcut Warning: "
318 "could not find shortcut command handler '%1'"
319 ).arg(name));
320 }
321 }
322 if (!--limit) {
323 _errors.push_back(qsl("Too many entries! Limit is %1"
324 ).arg(kCountLimit));
325 break;
326 }
327 }
328 return true;
329 }
330
331 void Manager::fillDefaults() {
332 const auto ctrl = Platform::IsMac() ? qsl("meta") : qsl("ctrl");
333
334 set(qsl("ctrl+w"), Command::Close);
335 set(qsl("ctrl+f4"), Command::Close);
336 set(qsl("ctrl+l"), Command::Lock);
337 set(qsl("ctrl+m"), Command::Minimize);
338 set(qsl("ctrl+q"), Command::Quit);
339
340 set(qsl("media play"), Command::MediaPlay);
341 set(qsl("media pause"), Command::MediaPause);
342 set(qsl("toggle media play/pause"), Command::MediaPlayPause);
343 set(qsl("media stop"), Command::MediaStop);
344 set(qsl("media previous"), Command::MediaPrevious);
345 set(qsl("media next"), Command::MediaNext);
346
347 set(qsl("ctrl+f"), Command::Search);
348 set(qsl("search"), Command::Search);
349 set(qsl("find"), Command::Search);
350
351 set(qsl("ctrl+pgdown"), Command::ChatNext);
352 set(qsl("alt+down"), Command::ChatNext);
353 set(qsl("ctrl+pgup"), Command::ChatPrevious);
354 set(qsl("alt+up"), Command::ChatPrevious);
355
356 set(qsl("%1+tab").arg(ctrl), Command::ChatNext);
357 set(qsl("%1+shift+tab").arg(ctrl), Command::ChatPrevious);
358 set(qsl("%1+backtab").arg(ctrl), Command::ChatPrevious);
359
360 set(qsl("ctrl+alt+home"), Command::ChatFirst);
361 set(qsl("ctrl+alt+end"), Command::ChatLast);
362
363 set(qsl("f5"), Command::SupportReloadTemplates);
364 set(qsl("ctrl+delete"), Command::SupportToggleMuted);
365 set(qsl("ctrl+insert"), Command::SupportScrollToCurrent);
366 set(qsl("ctrl+shift+x"), Command::SupportHistoryBack);
367 set(qsl("ctrl+shift+c"), Command::SupportHistoryForward);
368
369 set(qsl("ctrl+1"), Command::ChatPinned1);
370 set(qsl("ctrl+2"), Command::ChatPinned2);
371 set(qsl("ctrl+3"), Command::ChatPinned3);
372 set(qsl("ctrl+4"), Command::ChatPinned4);
373 set(qsl("ctrl+5"), Command::ChatPinned5);
374
375 auto &&folders = ranges::view::zip(
376 kShowFolder,
377 ranges::view::ints(1, ranges::unreachable));
378
379 for (const auto [command, index] : folders) {
380 set(qsl("%1+%2").arg(ctrl).arg(index), command);
381 }
382
383 set(qsl("%1+shift+down").arg(ctrl), Command::FolderNext);
384 set(qsl("%1+shift+up").arg(ctrl), Command::FolderPrevious);
385
386 set(qsl("ctrl+0"), Command::ChatSelf);
387
388 set(qsl("ctrl+9"), Command::ShowArchive);
389 set(qsl("ctrl+j"), Command::ShowContacts);
390
391 set(qsl("ctrl+r"), Command::ReadChat);
392 }
393
394 void Manager::writeDefaultFile() {
395 auto file = QFile(DefaultFilePath());
396 if (!file.open(QIODevice::WriteOnly)) {
397 return;
398 }
399 const char *defaultHeader = R"HEADER(
400 // This is a list of default shortcuts for Telegram Desktop
401 // Please don't modify it, its content is not used in any way
402 // You can place your own shortcuts in the 'shortcuts-custom.json' file
403
404 )HEADER";
405 file.write(defaultHeader);
406
407 auto shortcuts = QJsonArray();
408 auto version = QJsonObject();
409 version.insert(qsl("version"), QString::number(AppVersion));
410 shortcuts.push_back(version);
411
412 for (const auto &[sequence, shortcut] : _shortcuts) {
413 const auto shortcutId = shortcut->id();
414 auto i = _commandByShortcutId.findFirst(shortcutId);
415 const auto end = _commandByShortcutId.end();
416 for (; i != end && i->first == shortcutId; ++i) {
417 const auto j = CommandNames.find(i->second);
418 if (j != CommandNames.end()) {
419 QJsonObject entry;
420 entry.insert(qsl("keys"), sequence.toString().toLower());
421 entry.insert(qsl("command"), j->second);
422 shortcuts.append(entry);
423 }
424 }
425 }
426
427 auto document = QJsonDocument();
428 document.setArray(shortcuts);
429 file.write(document.toJson(QJsonDocument::Indented));
430 }
431
432 void Manager::set(const QString &keys, Command command, bool replace) {
433 if (keys.isEmpty()) {
434 return;
435 }
436
437 const auto result = QKeySequence(keys, QKeySequence::PortableText);
438 if (result.isEmpty()) {
439 _errors.push_back(qsl("Could not derive key sequence '%1'!"
440 ).arg(keys));
441 return;
442 }
443 auto shortcut = base::make_unique_q<QShortcut>(
444 result,
445 Core::App().activeWindow()->widget().get(),
446 nullptr,
447 nullptr,
448 Qt::ApplicationShortcut);
449 if (!AutoRepeatCommands.contains(command)) {
450 shortcut->setAutoRepeat(false);
451 }
452 const auto isMediaShortcut = MediaCommands.contains(command);
453 const auto isSupportShortcut = SupportCommands.contains(command);
454 if (isMediaShortcut || isSupportShortcut) {
455 shortcut->setEnabled(false);
456 }
457 auto id = shortcut->id();
458 auto i = _shortcuts.find(result);
459 if (i == end(_shortcuts)) {
460 i = _shortcuts.emplace(result, std::move(shortcut)).first;
461 } else if (replace) {
462 unregister(std::exchange(i->second, std::move(shortcut)));
463 } else {
464 id = i->second->id();
465 }
466 if (!id) {
467 _errors.push_back(qsl("Could not create shortcut '%1'!").arg(keys));
468 return;
469 }
470 _commandByShortcutId.emplace(id, command);
471 if (!shortcut && isMediaShortcut) {
472 _mediaShortcuts.emplace(i->second.get());
473 }
474 if (!shortcut && isSupportShortcut) {
475 _supportShortcuts.emplace(i->second.get());
476 }
477 }
478
479 void Manager::remove(const QString &keys) {
480 if (keys.isEmpty()) {
481 return;
482 }
483
484 const auto result = QKeySequence(keys, QKeySequence::PortableText);
485 if (result.isEmpty()) {
486 _errors.push_back(qsl("Could not derive key sequence '%1'!"
487 ).arg(keys));
488 return;
489 }
490 const auto i = _shortcuts.find(result);
491 if (i != end(_shortcuts)) {
492 unregister(std::move(i->second));
493 _shortcuts.erase(i);
494 }
495 }
496
497 void Manager::unregister(base::unique_qptr<QShortcut> shortcut) {
498 if (shortcut) {
499 _commandByShortcutId.erase(shortcut->id());
500 _mediaShortcuts.erase(shortcut.get());
501 _supportShortcuts.erase(shortcut.get());
502 }
503 }
504
505 Manager Data;
506
507 } // namespace
508
509 Request::Request(std::vector<Command> commands)
510 : _commands(std::move(commands)) {
511 }
512
513 bool Request::check(Command command, int priority) {
514 if (ranges::contains(_commands, command)
515 && priority > _handlerPriority) {
516 _handlerPriority = priority;
517 return true;
518 }
519 return false;
520 }
521
522 bool Request::handle(FnMut<bool()> handler) {
523 _handler = std::move(handler);
524 return true;
525 }
526
527 FnMut<bool()> RequestHandler(std::vector<Command> commands) {
528 auto request = Request(std::move(commands));
529 RequestsStream.fire(&request);
530 return std::move(request._handler);
531 }
532
533 FnMut<bool()> RequestHandler(Command command) {
534 return RequestHandler(std::vector<Command>{ command });
535 }
536
537 bool Launch(Command command) {
538 if (auto handler = RequestHandler(command)) {
539 return handler();
540 }
541 return false;
542 }
543
544 bool Launch(std::vector<Command> commands) {
545 if (auto handler = RequestHandler(std::move(commands))) {
546 return handler();
547 }
548 return false;
549 }
550
551 rpl::producer<not_null<Request*>> Requests() {
552 return RequestsStream.events();
553 }
554
555 void Start() {
556 Assert(Global::started());
557
558 Data.fill();
559 }
560
561 const QStringList &Errors() {
562 return Data.errors();
563 }
564
565 bool HandleEvent(not_null<QShortcutEvent*> event) {
566 return Launch(Data.lookup(event->shortcutId()));
567 }
568
569 void ToggleMediaShortcuts(bool toggled) {
570 Data.toggleMedia(toggled);
571 Platform::SetWatchingMediaKeys(toggled);
572 }
573
574 void ToggleSupportShortcuts(bool toggled) {
575 Data.toggleSupport(toggled);
576 }
577
578 void Finish() {
579 Data.clear();
580 }
581
582 } // namespace Shortcuts