Implement global menu on Linux

This commit is contained in:
Ilya Fedin 2020-03-04 09:45:44 +04:00 committed by John Preston
parent 5f33abd391
commit 46445e0542
3 changed files with 452 additions and 0 deletions

View File

@ -2239,6 +2239,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_linux_menu_undo" = "Undo";
"lng_linux_menu_redo" = "Redo";
"lng_linux_menu_tools" = "Tools";
"lng_linux_menu_help" = "Help";
"lng_linux_no_audio_prefs" = "You don't have any audio configuration applications installed.";

View File

@ -13,15 +13,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/linux_desktop_environment.h"
#include "platform/platform_notifications_manager.h"
#include "history/history.h"
#include "history/history_widget.h"
#include "history/history_inner_widget.h"
#include "main/main_account.h" // Account::sessionChanges.
#include "mainwindow.h"
#include "core/application.h"
#include "core/sandbox.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/about_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "window/window_session_controller.h"
#include "ui/widgets/input_fields.h"
#include "facades.h"
#include "app.h"
#include <QtCore/QSize>
#include <QtGui/QWindow>
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
#include <QtDBus/QDBusInterface>
@ -46,6 +54,10 @@ constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs;
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
constexpr auto kTrayIconFilename = "tdesktop-trayicon-XXXXXX.png"_cs;
constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs;
constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs;
constexpr auto kAppMenuInterface = kAppMenuService;
bool TrayIconMuted = true;
int32 TrayIconCount = 0;
base::flat_map<int, QImage> TrayIconImageBack;
@ -307,6 +319,71 @@ quint32 djbStringHash(QString string) {
return hash;
}
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
bool AppMenuSupported() {
static const auto Available = QDBusInterface(
kAppMenuService.utf16(),
kAppMenuObjectPath.utf16(),
kAppMenuInterface.utf16()).isValid();
return Available;
}
void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
auto message = QDBusMessage::createMethodCall(
kAppMenuService.utf16(),
kAppMenuObjectPath.utf16(),
kAppMenuInterface.utf16(),
qsl("RegisterWindow"));
message.setArguments({
winId,
QVariant::fromValue(menuPath)
});
QDBusConnection::sessionBus().send(message);
}
void UnregisterAppMenu(uint winId) {
auto message = QDBusMessage::createMethodCall(
kAppMenuService.utf16(),
kAppMenuObjectPath.utf16(),
kAppMenuInterface.utf16(),
qsl("UnregisterWindow"));
message.setArguments({
winId
});
QDBusConnection::sessionBus().send(message);
}
void SendKeySequence(
Qt::Key key,
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
const auto focused = QApplication::focusWidget();
if (qobject_cast<QLineEdit*>(focused)
|| qobject_cast<QTextEdit*>(focused)
|| qobject_cast<HistoryInner*>(focused)) {
QApplication::postEvent(
focused,
new QKeyEvent(QEvent::KeyPress, key, modifiers));
QApplication::postEvent(
focused,
new QKeyEvent(QEvent::KeyRelease, key, modifiers));
}
}
void ForceDisabled(QAction *action, bool disabled) {
if (action->isEnabled()) {
if (disabled) action->setDisabled(true);
} else if (!disabled) {
action->setDisabled(false);
}
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
} // namespace
MainWindow::MainWindow(not_null<Window::Controller*> controller)
@ -341,6 +418,12 @@ void MainWindow::initHook() {
} else {
LOG(("Not using Unity launcher counter."));
}
connect(
windowHandle(),
&QWindow::visibleChanged,
this,
&MainWindow::onVisibleChanged);
}
bool MainWindow::hasTrayIcon() const {
@ -575,9 +658,331 @@ void MainWindow::initTrayMenuHook() {
_trayIconMenuXEmbed->deleteOnHide(false);
}
void MainWindow::createGlobalMenu() {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (!AppMenuSupported()) return;
psMainMenu = new QMenu(this);
auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now));
psLogout = file->addAction(
tr::lng_mac_menu_logout(tr::now),
App::wnd(),
SLOT(onLogout()));
auto quit = file->addAction(
tr::lng_mac_menu_quit_telegram(tr::now, lt_telegram, qsl("Telegram")),
App::wnd(),
SLOT(quitFromTray()),
QKeySequence::Quit);
quit->setMenuRole(QAction::QuitRole);
auto edit = psMainMenu->addMenu(tr::lng_mac_menu_edit(tr::now));
psUndo = edit->addAction(
tr::lng_linux_menu_undo(tr::now),
this,
SLOT(psLinuxUndo()),
QKeySequence::Undo);
psRedo = edit->addAction(
tr::lng_linux_menu_redo(tr::now),
this,
SLOT(psLinuxRedo()),
QKeySequence::Redo);
edit->addSeparator();
psCut = edit->addAction(
tr::lng_mac_menu_cut(tr::now),
this,
SLOT(psLinuxCut()),
QKeySequence::Cut);
psCopy = edit->addAction(
tr::lng_mac_menu_copy(tr::now),
this,
SLOT(psLinuxCopy()),
QKeySequence::Copy);
psPaste = edit->addAction(
tr::lng_mac_menu_paste(tr::now),
this,
SLOT(psLinuxPaste()),
QKeySequence::Paste);
psDelete = edit->addAction(
tr::lng_mac_menu_delete(tr::now),
this,
SLOT(psLinuxDelete()),
QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
edit->addSeparator();
psBold = edit->addAction(
tr::lng_menu_formatting_bold(tr::now),
this,
SLOT(psLinuxBold()),
QKeySequence::Bold);
psItalic = edit->addAction(
tr::lng_menu_formatting_italic(tr::now),
this,
SLOT(psLinuxItalic()),
QKeySequence::Italic);
psUnderline = edit->addAction(
tr::lng_menu_formatting_underline(tr::now),
this,
SLOT(psLinuxUnderline()),
QKeySequence::Underline);
psStrikeOut = edit->addAction(
tr::lng_menu_formatting_strike_out(tr::now),
this,
SLOT(psLinuxStrikeOut()),
Ui::kStrikeOutSequence);
psMonospace = edit->addAction(
tr::lng_menu_formatting_monospace(tr::now),
this,
SLOT(psLinuxMonospace()),
Ui::kMonospaceSequence);
psClearFormat = edit->addAction(
tr::lng_menu_formatting_clear(tr::now),
this,
SLOT(psLinuxClearFormat()),
Ui::kClearFormatSequence);
edit->addSeparator();
psSelectAll = edit->addAction(
tr::lng_mac_menu_select_all(tr::now),
this, SLOT(psLinuxSelectAll()),
QKeySequence::SelectAll);
edit->addSeparator();
auto prefs = edit->addAction(
tr::lng_mac_menu_preferences(tr::now),
App::wnd(),
SLOT(showSettings()),
QKeySequence(Qt::ControlModifier | Qt::Key_Comma));
prefs->setMenuRole(QAction::PreferencesRole);
auto tools = psMainMenu->addMenu(tr::lng_linux_menu_tools(tr::now));
psContacts = tools->addAction(
tr::lng_mac_menu_contacts(tr::now),
crl::guard(this, [=] {
if (isHidden()) {
App::wnd()->showFromTray();
}
if (!account().sessionExists()) {
return;
}
Ui::show(
Box<PeerListBox>(std::make_unique<ContactsBoxController>(
sessionController()),
[](not_null<PeerListBox*> box) {
box->addButton(tr::lng_close(), [box] {
box->closeBox();
});
box->addLeftButton(tr::lng_profile_add_contact(), [] {
App::wnd()->onShowAddContact();
});
}));
}));
psAddContact = tools->addAction(
tr::lng_mac_menu_add_contact(tr::now),
App::wnd(),
SLOT(onShowAddContact()));
tools->addSeparator();
psNewGroup = tools->addAction(
tr::lng_mac_menu_new_group(tr::now),
App::wnd(),
SLOT(onShowNewGroup()));
psNewChannel = tools->addAction(
tr::lng_mac_menu_new_channel(tr::now),
App::wnd(),
SLOT(onShowNewChannel()));
auto help = psMainMenu->addMenu(tr::lng_linux_menu_help(tr::now));
auto about = help->addAction(
tr::lng_mac_menu_about_telegram(
tr::now,
lt_telegram,
qsl("Telegram")),
[] {
if (App::wnd() && App::wnd()->isHidden()) {
App::wnd()->showFromTray();
}
Ui::show(Box<AboutBox>());
});
about->setMenuRole(QAction::AboutQtRole);
_mainMenuPath.setPath(qsl("/MenuBar"));
_mainMenuExporter = new DBusMenuExporter(
_mainMenuPath.path(),
psMainMenu);
RegisterAppMenu(winId(), _mainMenuPath);
updateGlobalMenu();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
}
void MainWindow::psLinuxUndo() {
SendKeySequence(Qt::Key_Z, Qt::ControlModifier);
}
void MainWindow::psLinuxRedo() {
SendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier);
}
void MainWindow::psLinuxCut() {
SendKeySequence(Qt::Key_X, Qt::ControlModifier);
}
void MainWindow::psLinuxCopy() {
SendKeySequence(Qt::Key_C, Qt::ControlModifier);
}
void MainWindow::psLinuxPaste() {
SendKeySequence(Qt::Key_V, Qt::ControlModifier);
}
void MainWindow::psLinuxDelete() {
SendKeySequence(Qt::Key_Delete);
}
void MainWindow::psLinuxSelectAll() {
SendKeySequence(Qt::Key_A, Qt::ControlModifier);
}
void MainWindow::psLinuxBold() {
SendKeySequence(Qt::Key_B, Qt::ControlModifier);
}
void MainWindow::psLinuxItalic() {
SendKeySequence(Qt::Key_I, Qt::ControlModifier);
}
void MainWindow::psLinuxUnderline() {
SendKeySequence(Qt::Key_U, Qt::ControlModifier);
}
void MainWindow::psLinuxStrikeOut() {
SendKeySequence(Qt::Key_X, Qt::ControlModifier | Qt::ShiftModifier);
}
void MainWindow::psLinuxMonospace() {
SendKeySequence(Qt::Key_M, Qt::ControlModifier | Qt::ShiftModifier);
}
void MainWindow::psLinuxClearFormat() {
SendKeySequence(Qt::Key_N, Qt::ControlModifier | Qt::ShiftModifier);
}
void MainWindow::updateGlobalMenuHook() {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (!AppMenuSupported() || !App::wnd() || !positionInited()) return;
const auto focused = QApplication::focusWidget();
auto canUndo = false;
auto canRedo = false;
auto canCut = false;
auto canCopy = false;
auto canPaste = false;
auto canDelete = false;
auto canSelectAll = false;
const auto clipboardHasText = QGuiApplication::clipboard()
->ownsClipboard();
auto markdownEnabled = false;
if (const auto edit = qobject_cast<QLineEdit*>(focused)) {
canCut = canCopy = canDelete = edit->hasSelectedText();
canSelectAll = !edit->text().isEmpty();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = clipboardHasText;
} else if (const auto edit = qobject_cast<QTextEdit*>(focused)) {
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
canSelectAll = !edit->document()->isEmpty();
canUndo = edit->document()->isUndoAvailable();
canRedo = edit->document()->isRedoAvailable();
canPaste = clipboardHasText;
if (canCopy) {
if (const auto inputField = qobject_cast<Ui::InputField*>(
focused->parentWidget())) {
markdownEnabled = inputField->isMarkdownEnabled();
}
}
} else if (const auto list = qobject_cast<HistoryInner*>(focused)) {
canCopy = list->canCopySelected();
canDelete = list->canDeleteSelected();
}
App::wnd()->updateIsActive(0);
const auto logged = account().sessionExists();
const auto locked = Core::App().locked();
const auto inactive = !logged || locked;
const auto support = logged && account().session().supportMode();
ForceDisabled(psLogout, !logged && !locked);
ForceDisabled(psUndo, !canUndo);
ForceDisabled(psRedo, !canRedo);
ForceDisabled(psCut, !canCut);
ForceDisabled(psCopy, !canCopy);
ForceDisabled(psPaste, !canPaste);
ForceDisabled(psDelete, !canDelete);
ForceDisabled(psSelectAll, !canSelectAll);
ForceDisabled(psContacts, inactive || support);
ForceDisabled(psAddContact, inactive);
ForceDisabled(psNewGroup, inactive || support);
ForceDisabled(psNewChannel, inactive || support);
ForceDisabled(psBold, !markdownEnabled);
ForceDisabled(psItalic, !markdownEnabled);
ForceDisabled(psUnderline, !markdownEnabled);
ForceDisabled(psStrikeOut, !markdownEnabled);
ForceDisabled(psMonospace, !markdownEnabled);
ForceDisabled(psClearFormat, !markdownEnabled);
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
}
void MainWindow::onVisibleChanged(bool visible) {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (AppMenuSupported() && !_mainMenuPath.path().isEmpty()) {
if (visible) {
RegisterAppMenu(winId(), _mainMenuPath);
} else {
UnregisterAppMenu(winId());
}
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
}
MainWindow::~MainWindow() {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
delete _sniTrayIcon;
if (AppMenuSupported()) {
UnregisterAppMenu(winId());
delete _mainMenuExporter;
delete psMainMenu;
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
delete _trayIconMenuXEmbed;

View File

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
#include "statusnotifieritem.h"
#include <QtCore/QTemporaryFile>
#include <QtDBus/QDBusObjectPath>
#include <dbusmenuexporter.h>
#endif
namespace Platform {
@ -45,14 +47,33 @@ public slots:
const QString &newOwner);
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
void psLinuxUndo();
void psLinuxRedo();
void psLinuxCut();
void psLinuxCopy();
void psLinuxPaste();
void psLinuxDelete();
void psLinuxSelectAll();
void psLinuxBold();
void psLinuxItalic();
void psLinuxUnderline();
void psLinuxStrikeOut();
void psLinuxMonospace();
void psLinuxClearFormat();
void onVisibleChanged(bool visible);
protected:
void initHook() override;
void unreadCounterChangedHook() override;
void updateGlobalMenuHook() override;
void initTrayMenuHook() override;
bool hasTrayIcon() const override;
void workmodeUpdated(DBIWorkMode mode) override;
void createGlobalMenu() override;
QSystemTrayIcon *trayIcon = nullptr;
QMenu *trayIconMenu = nullptr;
@ -77,6 +98,30 @@ private:
StatusNotifierItem *_sniTrayIcon = nullptr;
std::unique_ptr<QTemporaryFile> _trayIconFile = nullptr;
DBusMenuExporter *_mainMenuExporter = nullptr;
QDBusObjectPath _mainMenuPath;
QMenu *psMainMenu = nullptr;
QAction *psLogout = nullptr;
QAction *psUndo = nullptr;
QAction *psRedo = nullptr;
QAction *psCut = nullptr;
QAction *psCopy = nullptr;
QAction *psPaste = nullptr;
QAction *psDelete = nullptr;
QAction *psSelectAll = nullptr;
QAction *psContacts = nullptr;
QAction *psAddContact = nullptr;
QAction *psNewGroup = nullptr;
QAction *psNewChannel = nullptr;
QAction *psBold = nullptr;
QAction *psItalic = nullptr;
QAction *psUnderline = nullptr;
QAction *psStrikeOut = nullptr;
QAction *psMonospace = nullptr;
QAction *psClearFormat = nullptr;
void setSNITrayIcon(int counter, bool muted, bool firstShow = false);
void attachToSNITrayIcon();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION