mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-05-01 21:44:03 +02:00
The Qt documentation says: This is the file name, without the full path or the trailing ".desktop" extension of the desktop entry that represents this application according to the freedesktop desktop entry specification. Qt 6.5.2 also automatically fixes it breaking all the current tdesktop and desktop-app usage expecting the file extension.
565 lines
15 KiB
C++
565 lines
15 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "platform/linux/main_window_linux.h"
|
|
|
|
#include "styles/style_window.h"
|
|
#include "platform/linux/specific_linux.h"
|
|
#include "platform/linux/linux_wayland_integration.h"
|
|
#include "history/history.h"
|
|
#include "history/history_widget.h"
|
|
#include "history/history_inner_widget.h"
|
|
#include "main/main_account.h" // Account::sessionChanges.
|
|
#include "main/main_session.h"
|
|
#include "mainwindow.h"
|
|
#include "core/application.h"
|
|
#include "core/core_settings.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_controller.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
|
#include "base/event_filter.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "ui/widgets/input_fields.h"
|
|
#include "ui/ui_utility.h"
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
#include "base/platform/linux/base_linux_xcb_utilities.h"
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
|
|
#include <QtCore/QSize>
|
|
#include <QtCore/QMimeData>
|
|
#include <QtGui/QWindow>
|
|
#include <QtWidgets/QMenuBar>
|
|
|
|
#include <glibmm.h>
|
|
#include <giomm.h>
|
|
|
|
namespace Platform {
|
|
namespace {
|
|
|
|
using internal::WaylandIntegration;
|
|
using WorkMode = Core::Settings::WorkMode;
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
void XCBSkipTaskbar(QWindow *window, bool skip) {
|
|
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
|
|
const auto root = base::Platform::XCB::GetRootWindow(connection);
|
|
if (!root.has_value()) {
|
|
return;
|
|
}
|
|
|
|
const auto stateAtom = base::Platform::XCB::GetAtom(
|
|
connection,
|
|
"_NET_WM_STATE");
|
|
|
|
if (!stateAtom.has_value()) {
|
|
return;
|
|
}
|
|
|
|
const auto skipTaskbarAtom = base::Platform::XCB::GetAtom(
|
|
connection,
|
|
"_NET_WM_STATE_SKIP_TASKBAR");
|
|
|
|
if (!skipTaskbarAtom.has_value()) {
|
|
return;
|
|
}
|
|
|
|
xcb_client_message_event_t xev;
|
|
xev.response_type = XCB_CLIENT_MESSAGE;
|
|
xev.type = *stateAtom;
|
|
xev.sequence = 0;
|
|
xev.window = window->winId();
|
|
xev.format = 32;
|
|
xev.data.data32[0] = skip ? 1 : 0;
|
|
xev.data.data32[1] = *skipTaskbarAtom;
|
|
xev.data.data32[2] = 0;
|
|
xev.data.data32[3] = 0;
|
|
xev.data.data32[4] = 0;
|
|
|
|
xcb_send_event(
|
|
connection,
|
|
false,
|
|
*root,
|
|
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
|
|
| XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
|
reinterpret_cast<const char*>(&xev));
|
|
}
|
|
|
|
void XCBSetDesktopFileName(QWindow *window) {
|
|
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
|
|
const auto utf8Atom = base::Platform::XCB::GetAtom(
|
|
connection,
|
|
"UTF8_STRING");
|
|
|
|
if (!utf8Atom.has_value()) {
|
|
return;
|
|
}
|
|
|
|
const auto filenameAtoms = {
|
|
base::Platform::XCB::GetAtom(connection, "_GTK_APPLICATION_ID"),
|
|
base::Platform::XCB::GetAtom(connection, "_KDE_NET_WM_DESKTOP_FILE"),
|
|
};
|
|
|
|
const auto filename = QGuiApplication::desktopFileName().toUtf8();
|
|
|
|
for (const auto atom : filenameAtoms) {
|
|
if (atom.has_value()) {
|
|
xcb_change_property(
|
|
connection,
|
|
XCB_PROP_MODE_REPLACE,
|
|
window->winId(),
|
|
*atom,
|
|
*utf8Atom,
|
|
8,
|
|
filename.size(),
|
|
filename.data());
|
|
}
|
|
}
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
|
|
void SkipTaskbar(QWindow *window, bool skip) {
|
|
if (const auto integration = WaylandIntegration::Instance()) {
|
|
integration->skipTaskbar(window, skip);
|
|
}
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
if (IsX11()) {
|
|
XCBSkipTaskbar(window, skip);
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
}
|
|
|
|
void SendKeySequence(
|
|
Qt::Key key,
|
|
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
|
|
const auto focused = static_cast<QObject*>(QApplication::focusWidget());
|
|
if (qobject_cast<QLineEdit*>(focused)
|
|
|| qobject_cast<QTextEdit*>(focused)
|
|
|| dynamic_cast<HistoryInner*>(focused)) {
|
|
QKeyEvent pressEvent(QEvent::KeyPress, key, modifiers);
|
|
focused->event(&pressEvent);
|
|
QKeyEvent releaseEvent(QEvent::KeyRelease, key, modifiers);
|
|
focused->event(&releaseEvent);
|
|
}
|
|
}
|
|
|
|
void ForceDisabled(QAction *action, bool disabled) {
|
|
if (action->isEnabled()) {
|
|
if (disabled) action->setDisabled(true);
|
|
} else if (!disabled) {
|
|
action->setDisabled(false);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
|
: Window::MainWindow(controller) {
|
|
}
|
|
|
|
void MainWindow::initHook() {
|
|
events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
|
if (e->type() == QEvent::ThemeChange) {
|
|
updateWindowIcon();
|
|
}
|
|
}, lifetime());
|
|
|
|
base::install_event_filter(windowHandle(), [=](not_null<QEvent*> e) {
|
|
if (e->type() == QEvent::Expose) {
|
|
auto ee = static_cast<QExposeEvent*>(e.get());
|
|
if (ee->region().isNull()) {
|
|
return base::EventFilterResult::Continue;
|
|
}
|
|
if (!windowHandle()
|
|
|| windowHandle()->parent()
|
|
|| !windowHandle()->isVisible()) {
|
|
return base::EventFilterResult::Continue;
|
|
}
|
|
handleNativeSurfaceChanged(true);
|
|
} else if (e->type() == QEvent::Hide) {
|
|
if (!windowHandle() || windowHandle()->parent()) {
|
|
return base::EventFilterResult::Continue;
|
|
}
|
|
handleNativeSurfaceChanged(false);
|
|
}
|
|
return base::EventFilterResult::Continue;
|
|
});
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
XCBSetDesktopFileName(windowHandle());
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
}
|
|
|
|
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
|
if (!TrayIconSupported()) {
|
|
return;
|
|
}
|
|
|
|
SkipTaskbar(windowHandle(), mode == WorkMode::TrayOnly);
|
|
}
|
|
|
|
void MainWindow::unreadCounterChangedHook() {
|
|
updateUnityCounter();
|
|
}
|
|
|
|
void MainWindow::updateWindowIcon() {
|
|
const auto session = sessionController()
|
|
? &sessionController()->session()
|
|
: nullptr;
|
|
setWindowIcon(Window::CreateIcon(session));
|
|
}
|
|
|
|
void MainWindow::updateUnityCounter() {
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
qApp->setBadgeNumber(Core::App().unreadBadge());
|
|
#else // Qt >= 6.6.0
|
|
static const auto djbStringHash = [](const std::string &string) {
|
|
uint hash = 5381;
|
|
for (const auto &curChar : string) {
|
|
hash = (hash << 5) + hash + curChar;
|
|
}
|
|
return hash;
|
|
};
|
|
|
|
const auto launcherUrl = Glib::ustring(
|
|
"application://"
|
|
+ QGuiApplication::desktopFileName().toStdString()
|
|
+ ".desktop");
|
|
const auto counterSlice = std::min(Core::App().unreadBadge(), 9999);
|
|
std::map<Glib::ustring, Glib::VariantBase> dbusUnityProperties;
|
|
|
|
if (counterSlice > 0) {
|
|
// According to the spec, it should be of 'x' D-Bus signature,
|
|
// which corresponds to signed 64-bit integer
|
|
// https://wiki.ubuntu.com/Unity/LauncherAPI#Low_level_DBus_API:_com.canonical.Unity.LauncherEntry
|
|
dbusUnityProperties["count"] = Glib::Variant<int64>::create(
|
|
counterSlice);
|
|
dbusUnityProperties["count-visible"] =
|
|
Glib::Variant<bool>::create(true);
|
|
} else {
|
|
dbusUnityProperties["count-visible"] =
|
|
Glib::Variant<bool>::create(false);
|
|
}
|
|
|
|
try {
|
|
const auto connection = Gio::DBus::Connection::get_sync(
|
|
Gio::DBus::BusType::SESSION);
|
|
|
|
connection->emit_signal(
|
|
"/com/canonical/unity/launcherentry/"
|
|
+ std::to_string(djbStringHash(launcherUrl)),
|
|
"com.canonical.Unity.LauncherEntry",
|
|
"Update",
|
|
{},
|
|
base::Platform::MakeGlibVariant(std::tuple{
|
|
launcherUrl,
|
|
dbusUnityProperties,
|
|
}));
|
|
} catch (...) {
|
|
}
|
|
#endif // Qt < 6.6.0
|
|
}
|
|
|
|
void MainWindow::createGlobalMenu() {
|
|
const auto ensureWindowShown = [=] {
|
|
if (isHidden()) {
|
|
showFromTray();
|
|
}
|
|
};
|
|
|
|
psMainMenu = new QMenuBar(this);
|
|
psMainMenu->hide();
|
|
|
|
auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now));
|
|
|
|
psLogout = file->addAction(
|
|
tr::lng_mac_menu_logout(tr::now),
|
|
this,
|
|
[=] {
|
|
ensureWindowShown();
|
|
controller().showLogoutConfirmation();
|
|
});
|
|
|
|
auto quit = file->addAction(
|
|
tr::lng_mac_menu_quit_telegram(tr::now, lt_telegram, u"Telegram"_q),
|
|
this,
|
|
[=] { 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),
|
|
[] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); },
|
|
QKeySequence::Undo);
|
|
|
|
psRedo = edit->addAction(
|
|
tr::lng_linux_menu_redo(tr::now),
|
|
[] {
|
|
SendKeySequence(
|
|
Qt::Key_Z,
|
|
Qt::ControlModifier | Qt::ShiftModifier);
|
|
},
|
|
QKeySequence::Redo);
|
|
|
|
edit->addSeparator();
|
|
|
|
psCut = edit->addAction(
|
|
tr::lng_mac_menu_cut(tr::now),
|
|
[] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); },
|
|
QKeySequence::Cut);
|
|
|
|
psCopy = edit->addAction(
|
|
tr::lng_mac_menu_copy(tr::now),
|
|
[] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); },
|
|
QKeySequence::Copy);
|
|
|
|
psPaste = edit->addAction(
|
|
tr::lng_mac_menu_paste(tr::now),
|
|
[] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); },
|
|
QKeySequence::Paste);
|
|
|
|
psDelete = edit->addAction(
|
|
tr::lng_mac_menu_delete(tr::now),
|
|
[] { SendKeySequence(Qt::Key_Delete); },
|
|
QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
|
|
|
|
edit->addSeparator();
|
|
|
|
psBold = edit->addAction(
|
|
tr::lng_menu_formatting_bold(tr::now),
|
|
[] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); },
|
|
QKeySequence::Bold);
|
|
|
|
psItalic = edit->addAction(
|
|
tr::lng_menu_formatting_italic(tr::now),
|
|
[] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); },
|
|
QKeySequence::Italic);
|
|
|
|
psUnderline = edit->addAction(
|
|
tr::lng_menu_formatting_underline(tr::now),
|
|
[] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); },
|
|
QKeySequence::Underline);
|
|
|
|
psStrikeOut = edit->addAction(
|
|
tr::lng_menu_formatting_strike_out(tr::now),
|
|
[] {
|
|
SendKeySequence(
|
|
Qt::Key_X,
|
|
Qt::ControlModifier | Qt::ShiftModifier);
|
|
},
|
|
Ui::kStrikeOutSequence);
|
|
|
|
psMonospace = edit->addAction(
|
|
tr::lng_menu_formatting_monospace(tr::now),
|
|
[] {
|
|
SendKeySequence(
|
|
Qt::Key_M,
|
|
Qt::ControlModifier | Qt::ShiftModifier);
|
|
},
|
|
Ui::kMonospaceSequence);
|
|
|
|
psClearFormat = edit->addAction(
|
|
tr::lng_menu_formatting_clear(tr::now),
|
|
[] {
|
|
SendKeySequence(
|
|
Qt::Key_N,
|
|
Qt::ControlModifier | Qt::ShiftModifier);
|
|
},
|
|
Ui::kClearFormatSequence);
|
|
|
|
edit->addSeparator();
|
|
|
|
psSelectAll = edit->addAction(
|
|
tr::lng_mac_menu_select_all(tr::now),
|
|
[] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); },
|
|
QKeySequence::SelectAll);
|
|
|
|
edit->addSeparator();
|
|
|
|
auto prefs = edit->addAction(
|
|
tr::lng_mac_menu_preferences(tr::now),
|
|
this,
|
|
[=] {
|
|
ensureWindowShown();
|
|
controller().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()) {
|
|
showFromTray();
|
|
}
|
|
|
|
if (!sessionController()) {
|
|
return;
|
|
}
|
|
|
|
sessionController()->show(
|
|
PrepareContactsBox(sessionController()));
|
|
}));
|
|
|
|
psAddContact = tools->addAction(
|
|
tr::lng_mac_menu_add_contact(tr::now),
|
|
this,
|
|
[=] {
|
|
Expects(sessionController() != nullptr);
|
|
ensureWindowShown();
|
|
sessionController()->showAddContact();
|
|
});
|
|
|
|
tools->addSeparator();
|
|
|
|
psNewGroup = tools->addAction(
|
|
tr::lng_mac_menu_new_group(tr::now),
|
|
this,
|
|
[=] {
|
|
Expects(sessionController() != nullptr);
|
|
ensureWindowShown();
|
|
sessionController()->showNewGroup();
|
|
});
|
|
|
|
psNewChannel = tools->addAction(
|
|
tr::lng_mac_menu_new_channel(tr::now),
|
|
this,
|
|
[=] {
|
|
Expects(sessionController() != nullptr);
|
|
ensureWindowShown();
|
|
sessionController()->showNewChannel();
|
|
});
|
|
|
|
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,
|
|
u"Telegram"_q),
|
|
[=] {
|
|
ensureWindowShown();
|
|
controller().show(Box<AboutBox>());
|
|
});
|
|
|
|
about->setMenuRole(QAction::AboutQtRole);
|
|
|
|
updateGlobalMenu();
|
|
}
|
|
|
|
void MainWindow::updateGlobalMenuHook() {
|
|
if (!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 mimeData = QGuiApplication::clipboard()->mimeData();
|
|
const auto clipboardHasText = mimeData ? mimeData->hasText() : false;
|
|
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 = dynamic_cast<Ui::InputField*>(
|
|
focused->parentWidget())) {
|
|
markdownEnabled = inputField->isMarkdownEnabled();
|
|
}
|
|
}
|
|
} else if (const auto list = dynamic_cast<HistoryInner*>(focused)) {
|
|
canCopy = list->canCopySelected();
|
|
canDelete = list->canDeleteSelected();
|
|
}
|
|
updateIsActive();
|
|
const auto logged = (sessionController() != nullptr);
|
|
const auto inactive = !logged || controller().locked();
|
|
const auto support = logged
|
|
&& sessionController()->session().supportMode();
|
|
ForceDisabled(psLogout, !logged && !Core::App().passcodeLocked());
|
|
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);
|
|
}
|
|
|
|
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
|
|
const auto t = evt->type();
|
|
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
|
|
if (qobject_cast<QLineEdit*>(obj)
|
|
|| qobject_cast<QTextEdit*>(obj)
|
|
|| dynamic_cast<HistoryInner*>(obj)) {
|
|
if (QApplication::focusWidget()) {
|
|
updateGlobalMenu();
|
|
}
|
|
}
|
|
}
|
|
return Window::MainWindow::eventFilter(obj, evt);
|
|
}
|
|
|
|
void MainWindow::handleNativeSurfaceChanged(bool exist) {
|
|
if (exist) {
|
|
SkipTaskbar(
|
|
windowHandle(),
|
|
(Core::App().settings().workMode() == WorkMode::TrayOnly)
|
|
&& TrayIconSupported());
|
|
}
|
|
}
|
|
|
|
MainWindow::~MainWindow() {
|
|
}
|
|
|
|
} // namespace Platform
|