mirror of
https://github.com/vale981/tdesktop
synced 2025-03-08 11:11:39 -05:00

Currently the build without implicitly included precompiled header is not supported anyway (because Qt MOC source files do not include stdafx.h, they include plain headers). So when we decide to support building without implicitly included precompiled headers we'll have to fix all the headers anyway.
475 lines
13 KiB
C++
475 lines
13 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
*/
|
|
#include "shortcuts.h"
|
|
|
|
#include "mainwindow.h"
|
|
#include "passcodewidget.h"
|
|
#include "mainwidget.h"
|
|
#include "media/player/media_player_instance.h"
|
|
#include "pspecific.h"
|
|
#include "core/parse_helper.h"
|
|
|
|
namespace ShortcutCommands {
|
|
|
|
using Handler = bool(*)();
|
|
|
|
bool lock_telegram() {
|
|
if (auto w = App::wnd()) {
|
|
if (App::passcoded()) {
|
|
w->passcodeWidget()->onSubmit();
|
|
return true;
|
|
} else if (Global::LocalPasscode()) {
|
|
w->setupPasscode();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool minimize_telegram() {
|
|
if (auto w = App::wnd()) {
|
|
if (cWorkMode() == dbiwmTrayOnly) {
|
|
w->minimizeToTray();
|
|
} else {
|
|
w->setWindowState(Qt::WindowMinimized);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool close_telegram() {
|
|
if (!Ui::hideWindowNoQuit()) {
|
|
if (auto w = App::wnd()) {
|
|
w->close();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool quit_telegram() {
|
|
App::quit();
|
|
return true;
|
|
}
|
|
|
|
//void start_stop_recording() {
|
|
|
|
//}
|
|
|
|
//void cancel_recording() {
|
|
|
|
//}
|
|
|
|
bool media_play() {
|
|
Media::Player::instance()->play();
|
|
return true;
|
|
}
|
|
|
|
bool media_pause() {
|
|
Media::Player::instance()->pause(AudioMsgId::Type::Song);
|
|
return true;
|
|
}
|
|
|
|
bool media_playpause() {
|
|
Media::Player::instance()->playPause();
|
|
return true;
|
|
}
|
|
|
|
bool media_stop() {
|
|
Media::Player::instance()->stop();
|
|
return true;
|
|
}
|
|
|
|
bool media_previous() {
|
|
Media::Player::instance()->previous();
|
|
return true;
|
|
}
|
|
|
|
bool media_next() {
|
|
Media::Player::instance()->next();
|
|
return true;
|
|
}
|
|
|
|
bool search() {
|
|
if (auto m = App::main()) {
|
|
return m->cmd_search();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool previous_chat() {
|
|
if (auto m = App::main()) {
|
|
return m->cmd_previous_chat();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool next_chat() {
|
|
if (auto m = App::main()) {
|
|
return m->cmd_next_chat();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// other commands here
|
|
|
|
} // namespace ShortcutCommands
|
|
|
|
inline bool qMapLessThanKey(const ShortcutCommands::Handler &a, const ShortcutCommands::Handler &b) {
|
|
return a < b;
|
|
}
|
|
|
|
namespace Shortcuts {
|
|
|
|
struct DataStruct;
|
|
DataStruct *DataPtr = nullptr;
|
|
|
|
namespace {
|
|
|
|
void createCommand(const QString &command, ShortcutCommands::Handler handler);
|
|
QKeySequence setShortcut(const QString &keys, const QString &command);
|
|
void destroyShortcut(QShortcut *shortcut);
|
|
|
|
} // namespace
|
|
|
|
struct DataStruct {
|
|
DataStruct() {
|
|
t_assert(DataPtr == nullptr);
|
|
DataPtr = this;
|
|
|
|
if (autoRepeatCommands.isEmpty()) {
|
|
autoRepeatCommands.insert(qsl("media_previous"));
|
|
autoRepeatCommands.insert(qsl("media_next"));
|
|
autoRepeatCommands.insert(qsl("next_chat"));
|
|
autoRepeatCommands.insert(qsl("previous_chat"));
|
|
}
|
|
|
|
if (mediaCommands.isEmpty()) {
|
|
mediaCommands.insert(qsl("media_play"));
|
|
mediaCommands.insert(qsl("media_playpause"));
|
|
mediaCommands.insert(qsl("media_play"));
|
|
mediaCommands.insert(qsl("media_stop"));
|
|
mediaCommands.insert(qsl("media_previous"));
|
|
mediaCommands.insert(qsl("media_next"));
|
|
}
|
|
|
|
#define DeclareAlias(keys, command) setShortcut(qsl(keys), qsl(#command))
|
|
#define DeclareCommand(keys, command) createCommand(qsl(#command), ShortcutCommands::command); DeclareAlias(keys, command)
|
|
|
|
DeclareCommand("ctrl+w", close_telegram);
|
|
DeclareAlias("ctrl+f4", close_telegram);
|
|
DeclareCommand("ctrl+l", lock_telegram);
|
|
DeclareCommand("ctrl+m", minimize_telegram);
|
|
DeclareCommand("ctrl+q", quit_telegram);
|
|
|
|
//DeclareCommand("ctrl+r", start_stop_recording);
|
|
//DeclareCommand("ctrl+shift+r", cancel_recording);
|
|
//DeclareCommand("media record", start_stop_recording);
|
|
|
|
DeclareCommand("media play", media_play);
|
|
DeclareCommand("media pause", media_pause);
|
|
DeclareCommand("toggle media play/pause", media_playpause);
|
|
DeclareCommand("media stop", media_stop);
|
|
DeclareCommand("media previous", media_previous);
|
|
DeclareCommand("media next", media_next);
|
|
|
|
DeclareCommand("ctrl+f", search);
|
|
DeclareAlias("search", search);
|
|
DeclareAlias("find", search);
|
|
|
|
DeclareCommand("ctrl+pgdown", next_chat);
|
|
DeclareAlias("alt+down", next_chat);
|
|
DeclareCommand("ctrl+pgup", previous_chat);
|
|
DeclareAlias("alt+up", previous_chat);
|
|
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
|
|
DeclareAlias("meta+tab", next_chat);
|
|
DeclareAlias("meta+shift+tab", previous_chat);
|
|
DeclareAlias("meta+backtab", previous_chat);
|
|
} else {
|
|
DeclareAlias("ctrl+tab", next_chat);
|
|
DeclareAlias("ctrl+shift+tab", previous_chat);
|
|
DeclareAlias("ctrl+backtab", previous_chat);
|
|
}
|
|
|
|
// other commands here
|
|
|
|
#undef DeclareCommand
|
|
#undef DeclareAlias
|
|
}
|
|
QStringList errors;
|
|
|
|
QMap<QString, ShortcutCommands::Handler> commands;
|
|
QMap<ShortcutCommands::Handler, QString> commandnames;
|
|
|
|
QMap<QKeySequence, QShortcut*> sequences;
|
|
QMap<int, ShortcutCommands::Handler> handlers;
|
|
|
|
QSet<QShortcut*> mediaShortcuts;
|
|
QSet<QString> autoRepeatCommands;
|
|
QSet<QString> mediaCommands;
|
|
|
|
};
|
|
|
|
namespace {
|
|
|
|
void createCommand(const QString &command, ShortcutCommands::Handler handler) {
|
|
t_assert(DataPtr != nullptr);
|
|
t_assert(!command.isEmpty());
|
|
|
|
DataPtr->commands.insert(command, handler);
|
|
DataPtr->commandnames.insert(handler, command);
|
|
}
|
|
|
|
QKeySequence setShortcut(const QString &keys, const QString &command) {
|
|
t_assert(DataPtr != nullptr);
|
|
t_assert(!command.isEmpty());
|
|
if (keys.isEmpty()) return QKeySequence();
|
|
|
|
QKeySequence seq(keys, QKeySequence::PortableText);
|
|
if (seq.isEmpty()) {
|
|
DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys));
|
|
} else {
|
|
auto it = DataPtr->commands.constFind(command);
|
|
if (it == DataPtr->commands.cend()) {
|
|
LOG(("Warning: could not find shortcut command handler '%1'").arg(command));
|
|
} else {
|
|
auto shortcut = std::make_unique<QShortcut>(seq, App::wnd(), nullptr, nullptr, Qt::ApplicationShortcut);
|
|
if (!DataPtr->autoRepeatCommands.contains(command)) {
|
|
shortcut->setAutoRepeat(false);
|
|
}
|
|
auto isMediaShortcut = DataPtr->mediaCommands.contains(command);
|
|
if (isMediaShortcut) {
|
|
shortcut->setEnabled(false);
|
|
}
|
|
int shortcutId = shortcut->id();
|
|
if (!shortcutId) {
|
|
DataPtr->errors.push_back(qsl("Could not create shortcut '%1'!").arg(keys));
|
|
} else {
|
|
auto seqIt = DataPtr->sequences.find(seq);
|
|
if (seqIt == DataPtr->sequences.cend()) {
|
|
seqIt = DataPtr->sequences.insert(seq, shortcut.release());
|
|
} else {
|
|
auto oldShortcut = seqIt.value();
|
|
seqIt.value() = shortcut.release();
|
|
destroyShortcut(oldShortcut);
|
|
}
|
|
DataPtr->handlers.insert(shortcutId, it.value());
|
|
if (isMediaShortcut) {
|
|
DataPtr->mediaShortcuts.insert(seqIt.value());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
QKeySequence removeShortcut(const QString &keys) {
|
|
t_assert(DataPtr != nullptr);
|
|
if (keys.isEmpty()) return QKeySequence();
|
|
|
|
QKeySequence seq(keys, QKeySequence::PortableText);
|
|
if (seq.isEmpty()) {
|
|
DataPtr->errors.push_back(qsl("Could not derive key sequence '%1'!").arg(keys));
|
|
} else {
|
|
auto seqIt = DataPtr->sequences.find(seq);
|
|
if (seqIt != DataPtr->sequences.cend()) {
|
|
auto shortcut = seqIt.value();
|
|
DataPtr->sequences.erase(seqIt);
|
|
destroyShortcut(shortcut);
|
|
}
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
void destroyShortcut(QShortcut *shortcut) {
|
|
t_assert(DataPtr != nullptr);
|
|
|
|
DataPtr->handlers.remove(shortcut->id());
|
|
DataPtr->mediaShortcuts.remove(shortcut);
|
|
delete shortcut;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void start() {
|
|
t_assert(Global::started());
|
|
|
|
new DataStruct();
|
|
|
|
// write default shortcuts to a file if they are not there already
|
|
bool defaultValid = false;
|
|
QFile defaultFile(cWorkingDir() + qsl("tdata/shortcuts-default.json"));
|
|
if (defaultFile.open(QIODevice::ReadOnly)) {
|
|
QJsonParseError error = { 0, QJsonParseError::NoError };
|
|
QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(defaultFile.readAll()), &error);
|
|
defaultFile.close();
|
|
|
|
if (error.error == QJsonParseError::NoError && doc.isArray()) {
|
|
QJsonArray shortcuts(doc.array());
|
|
if (!shortcuts.isEmpty() && (*shortcuts.constBegin()).isObject()) {
|
|
QJsonObject versionObject((*shortcuts.constBegin()).toObject());
|
|
QJsonObject::const_iterator version = versionObject.constFind(qsl("version"));
|
|
if (version != versionObject.constEnd() && (*version).isString() && (*version).toString() == QString::number(AppVersion)) {
|
|
defaultValid = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!defaultValid && defaultFile.open(QIODevice::WriteOnly)) {
|
|
const char *defaultHeader = "\
|
|
// This is a list of default shortcuts for Telegram Desktop\n\
|
|
// Please don't modify it, its content is not used in any way\n\
|
|
// You can place your own shortcuts in the 'shortcuts-custom.json' file\n\n";
|
|
defaultFile.write(defaultHeader);
|
|
|
|
QJsonArray shortcuts;
|
|
|
|
QJsonObject version;
|
|
version.insert(qsl("version"), QString::number(AppVersion));
|
|
shortcuts.push_back(version);
|
|
|
|
for (auto i = DataPtr->sequences.cbegin(), e = DataPtr->sequences.cend(); i != e; ++i) {
|
|
auto h = DataPtr->handlers.constFind(i.value()->id());
|
|
if (h != DataPtr->handlers.cend()) {
|
|
auto n = DataPtr->commandnames.constFind(h.value());
|
|
if (n != DataPtr->commandnames.cend()) {
|
|
QJsonObject entry;
|
|
entry.insert(qsl("keys"), i.key().toString().toLower());
|
|
entry.insert(qsl("command"), n.value());
|
|
shortcuts.append(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
QJsonDocument doc;
|
|
doc.setArray(shortcuts);
|
|
defaultFile.write(doc.toJson(QJsonDocument::Indented));
|
|
defaultFile.close();
|
|
}
|
|
|
|
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
|
|
QFile customFile(cWorkingDir() + qsl("tdata/shortcuts-custom.json"));
|
|
if (customFile.exists()) {
|
|
if (customFile.open(QIODevice::ReadOnly)) {
|
|
QJsonParseError error = { 0, QJsonParseError::NoError };
|
|
QJsonDocument doc = QJsonDocument::fromJson(base::parse::stripComments(customFile.readAll()), &error);
|
|
customFile.close();
|
|
|
|
if (error.error != QJsonParseError::NoError) {
|
|
DataPtr->errors.push_back(qsl("Failed to parse! Error: %2").arg(error.errorString()));
|
|
} else if (!doc.isArray()) {
|
|
DataPtr->errors.push_back(qsl("Failed to parse! Error: array expected"));
|
|
} else {
|
|
QJsonArray shortcuts = doc.array();
|
|
int limit = ShortcutsCountLimit;
|
|
for (QJsonArray::const_iterator i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) {
|
|
if (!(*i).isObject()) {
|
|
DataPtr->errors.push_back(qsl("Bad entry! Error: object expected"));
|
|
} else {
|
|
QKeySequence seq;
|
|
QJsonObject entry((*i).toObject());
|
|
QJsonObject::const_iterator keys = entry.constFind(qsl("keys")), command = entry.constFind(qsl("command"));
|
|
if (keys == entry.constEnd() || command == entry.constEnd() || !(*keys).isString() || (!(*command).isString() && !(*command).isNull())) {
|
|
DataPtr->errors.push_back(qsl("Bad entry! {\"keys\": \"...\", \"command\": [ \"...\" | null ]} expected"));
|
|
} else if ((*command).isNull()) {
|
|
seq = removeShortcut((*keys).toString());
|
|
} else {
|
|
seq = setShortcut((*keys).toString(), (*command).toString());
|
|
}
|
|
if (!--limit) {
|
|
DataPtr->errors.push_back(qsl("Too many entries! Limit is %1").arg(ShortcutsCountLimit));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
DataPtr->errors.push_back(qsl("Could not read the file!"));
|
|
}
|
|
if (!DataPtr->errors.isEmpty()) {
|
|
DataPtr->errors.push_front(qsl("While reading file '%1'...").arg(customFile.fileName()));
|
|
}
|
|
} else if (customFile.open(QIODevice::WriteOnly)) {
|
|
const char *customContent = "\
|
|
// This is a list of your own shortcuts for Telegram Desktop\n\
|
|
// You can see full list of commands in the 'shortcuts-default.json' file\n\
|
|
// Place a null value instead of a command string to switch the shortcut off\n\n\
|
|
[\n\
|
|
// {\n\
|
|
// \"command\": \"close_telegram\",\n\
|
|
// \"keys\": \"ctrl+f4\"\n\
|
|
// },\n\
|
|
// {\n\
|
|
// \"command\": \"quit_telegram\",\n\
|
|
// \"keys\": \"ctrl+q\"\n\
|
|
// }\n\
|
|
]\n";
|
|
customFile.write(customContent);
|
|
customFile.close();
|
|
}
|
|
}
|
|
|
|
const QStringList &errors() {
|
|
t_assert(DataPtr != nullptr);
|
|
return DataPtr->errors;
|
|
}
|
|
|
|
bool launch(int shortcutId) {
|
|
t_assert(DataPtr != nullptr);
|
|
|
|
auto it = DataPtr->handlers.constFind(shortcutId);
|
|
if (it == DataPtr->handlers.cend()) {
|
|
return false;
|
|
}
|
|
return (*it.value())();
|
|
}
|
|
|
|
bool launch(const QString &command) {
|
|
t_assert(DataPtr != nullptr);
|
|
|
|
auto it = DataPtr->commands.constFind(command);
|
|
if (it == DataPtr->commands.cend()) {
|
|
return false;
|
|
}
|
|
return (*it.value())();
|
|
}
|
|
|
|
void enableMediaShortcuts() {
|
|
if (!DataPtr) return;
|
|
for_const (auto shortcut, DataPtr->mediaShortcuts) {
|
|
shortcut->setEnabled(true);
|
|
}
|
|
Platform::SetWatchingMediaKeys(true);
|
|
}
|
|
|
|
void disableMediaShortcuts() {
|
|
if (!DataPtr) return;
|
|
for_const (auto shortcut, DataPtr->mediaShortcuts) {
|
|
shortcut->setEnabled(false);
|
|
}
|
|
Platform::SetWatchingMediaKeys(false);
|
|
}
|
|
|
|
void finish() {
|
|
delete DataPtr;
|
|
DataPtr = nullptr;
|
|
}
|
|
|
|
} // namespace Shortcuts
|