From ada22ee6cc86f3158d600b83783601f117edbb9d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 10 Jan 2021 07:51:38 +0400 Subject: [PATCH] Split GTK integration into a singleton --- Telegram/CMakeLists.txt | 29 +- .../platform/linux/file_utilities_linux.cpp | 733 +----------------- .../platform/linux/file_utilities_linux.h | 118 --- .../platform/linux/linux_gdk_helper.cpp | 26 +- .../platform/linux/linux_gdk_helper.h | 4 - .../platform/linux/linux_gtk_file_dialog.cpp | 714 +++++++++++++++++ .../platform/linux/linux_gtk_file_dialog.h | 31 + .../platform/linux/linux_gtk_integration.cpp | 491 ++++++++++++ .../platform/linux/linux_gtk_integration.h | 59 ++ .../linux/linux_gtk_integration_dummy.cpp | 74 ++ .../platform/linux/linux_gtk_integration_p.h | 158 ++++ .../SourceFiles/platform/linux/linux_libs.cpp | 360 --------- .../SourceFiles/platform/linux/linux_libs.h | 303 -------- .../platform/linux/linux_open_with_dialog.cpp | 132 ++++ .../platform/linux/linux_open_with_dialog.h | 18 + .../platform/linux/linux_xlib_helper.cpp | 2 - .../platform/linux/linux_xlib_helper.h | 2 - .../platform/linux/main_window_linux.cpp | 13 +- .../platform/linux/main_window_linux.h | 2 - .../platform/linux/specific_linux.cpp | 159 ++-- .../platform/linux/specific_linux.h | 1 - 21 files changed, 1800 insertions(+), 1629 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_integration.h create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h delete mode 100644 Telegram/SourceFiles/platform/linux/linux_libs.cpp delete mode 100644 Telegram/SourceFiles/platform/linux/linux_libs.h create mode 100644 Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f5f02c7b9..81df3d5aa 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -821,10 +821,15 @@ PRIVATE platform/linux/linux_gdk_helper.h platform/linux/linux_gsd_media_keys.cpp platform/linux/linux_gsd_media_keys.h - platform/linux/linux_libs.cpp - platform/linux/linux_libs.h + platform/linux/linux_gtk_file_dialog.cpp + platform/linux/linux_gtk_file_dialog.h + platform/linux/linux_gtk_integration_p.h + platform/linux/linux_gtk_integration.cpp + platform/linux/linux_gtk_integration.h platform/linux/linux_notification_service_watcher.cpp platform/linux/linux_notification_service_watcher.h + platform/linux/linux_open_with_dialog.cpp + platform/linux/linux_open_with_dialog.h platform/linux/linux_wayland_integration.cpp platform/linux/linux_wayland_integration.h platform/linux/linux_xlib_helper.cpp @@ -1134,6 +1139,26 @@ if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp) endif() +if (LINUX AND TDESKTOP_DISABLE_GTK_INTEGRATION) + remove_target_sources(Telegram ${src_loc} + platform/linux/linux_gdk_helper.cpp + platform/linux/linux_gdk_helper.h + platform/linux/linux_gtk_file_dialog.cpp + platform/linux/linux_gtk_file_dialog.h + platform/linux/linux_gtk_integration_p.h + platform/linux/linux_gtk_integration.cpp + platform/linux/linux_open_with_dialog.cpp + platform/linux/linux_open_with_dialog.h + platform/linux/linux_xlib_helper.cpp + platform/linux/linux_xlib_helper.h + ) + + nice_target_sources(Telegram ${src_loc} + PRIVATE + platform/linux/linux_gtk_integration_dummy.cpp + ) +endif() + if (NOT DESKTOP_APP_USE_PACKAGED) nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c) endif() diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 1c09e2edd..fd1bae21d 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -7,14 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/file_utilities_linux.h" -#include "platform/linux/linux_libs.h" -#include "platform/linux/linux_gdk_helper.h" -#include "platform/linux/linux_desktop_environment.h" +#include "platform/linux/linux_gtk_integration.h" #include "platform/linux/specific_linux.h" -#include "storage/localstorage.h" -#include "base/qt_adapters.h" -#include "window/window_controller.h" -#include "core/application.h" #include @@ -24,121 +18,10 @@ extern "C" { #define signals public } // extern "C" -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -#include - -extern "C" { -#undef signals -#include -#include -#define signals public -} // extern "C" -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +using Platform::internal::GtkIntegration; namespace Platform { namespace File { -namespace { - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -bool ShowOpenWithSupported() { - return Platform::internal::GdkHelperLoaded() - && (Libs::gtk_app_chooser_dialog_new != nullptr) - && (Libs::gtk_app_chooser_get_app_info != nullptr) - && (Libs::gtk_app_chooser_get_type != nullptr) - && (Libs::gtk_widget_get_window != nullptr) - && (Libs::gtk_widget_realize != nullptr) - && (Libs::gtk_widget_show != nullptr) - && (Libs::gtk_widget_destroy != nullptr); -} - -class OpenWithDialog : public QWindow { -public: - OpenWithDialog(const QString &filepath); - ~OpenWithDialog(); - - bool exec(); - -private: - static void handleResponse(OpenWithDialog *dialog, int responseId); - - GFile *_gfileInstance = nullptr; - GtkWidget *_gtkWidget = nullptr; - QEventLoop _loop; - std::optional _result = std::nullopt; -}; - -OpenWithDialog::OpenWithDialog(const QString &filepath) -: _gfileInstance(g_file_new_for_path(filepath.toUtf8())) -, _gtkWidget(Libs::gtk_app_chooser_dialog_new( - nullptr, - GTK_DIALOG_MODAL, - _gfileInstance)) { - g_signal_connect_swapped( - _gtkWidget, - "response", - G_CALLBACK(handleResponse), - this); -} - -OpenWithDialog::~OpenWithDialog() { - Libs::gtk_widget_destroy(_gtkWidget); - g_object_unref(_gfileInstance); -} - -bool OpenWithDialog::exec() { - Libs::gtk_widget_realize(_gtkWidget); - - if (const auto activeWindow = Core::App().activeWindow()) { - Platform::internal::XSetTransientForHint( - Libs::gtk_widget_get_window(_gtkWidget), - activeWindow->widget().get()->windowHandle()->winId()); - } - - QGuiApplicationPrivate::showModalWindow(this); - Libs::gtk_widget_show(_gtkWidget); - - if (!_result.has_value()) { - _loop.exec(); - } - - QGuiApplicationPrivate::hideModalWindow(this); - return *_result; -} - -void OpenWithDialog::handleResponse(OpenWithDialog *dialog, int responseId) { - GAppInfo *chosenAppInfo = nullptr; - dialog->_result = true; - - switch (responseId) { - case GTK_RESPONSE_OK: - chosenAppInfo = Libs::gtk_app_chooser_get_app_info( - Libs::gtk_app_chooser_cast(dialog->_gtkWidget)); - - if (chosenAppInfo) { - GList *uris = nullptr; - uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance)); - g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr); - g_list_free(uris); - g_object_unref(chosenAppInfo); - } - - break; - - case GTK_RESPONSE_CANCEL: - case GTK_RESPONSE_DELETE_EVENT: - break; - - default: - dialog->_result = false; - break; - } - - dialog->_loop.quit(); -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace - namespace internal { QByteArray EscapeShell(const QByteArray &content) { @@ -183,18 +66,16 @@ void UnsafeOpenEmailLink(const QString &email) { } bool UnsafeShowOpenWith(const QString &filepath) { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (InFlatpak() - || InSnap() - || !ShowOpenWithSupported()) { + if (InFlatpak() || InSnap()) { return false; } - const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); - return OpenWithDialog(absolutePath).exec(); -#else // !TDESKTOP_DISABLE_GTK_INTEGRATION + if (const auto integration = GtkIntegration::Instance()) { + const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); + return integration->showOpenWithDialog(absolutePath); + } + return false; -#endif // TDESKTOP_DISABLE_GTK_INTEGRATION } void UnsafeLaunch(const QString &filepath) { @@ -213,136 +94,6 @@ void UnsafeLaunch(const QString &filepath) { } // namespace File namespace FileDialog { -namespace { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - -// GTK file chooser image preview: thanks to Chromium - -// The size of the preview we display for selected image files. We set height -// larger than width because generally there is more free space vertically -// than horiztonally (setting the preview image will alway expand the width of -// the dialog, but usually not the height). The image's aspect ratio will always -// be preserved. -constexpr auto kPreviewWidth = 256; -constexpr auto kPreviewHeight = 512; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -using Type = ::FileDialog::internal::Type; - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -bool UseNative(Type type = Type::ReadFile) { - // use gtk file dialog on gtk-based desktop environments - // or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3) - // or if portals are used and operation is to open folder - // and portal doesn't support folder choosing - const auto sandboxedOrCustomPortal = InFlatpak() - || InSnap() - || UseXDGDesktopPortal(); - - const auto neededForPortal = (type == Type::ReadFolder) - && !CanOpenDirectoryWithPortal(); - - const auto neededNonForced = DesktopEnvironment::IsGtkBased() - || (sandboxedOrCustomPortal && neededForPortal); - - const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal; - - return IsGtkIntegrationForced() - || (neededNonForced && !excludeNonForced); -} - -bool NativeSupported() { - return Platform::internal::GdkHelperLoaded() - && (Libs::gtk_widget_hide_on_delete != nullptr) - && (Libs::gtk_clipboard_store != nullptr) - && (Libs::gtk_clipboard_get != nullptr) - && (Libs::gtk_widget_destroy != nullptr) - && (Libs::gtk_dialog_get_type != nullptr) - && (Libs::gtk_dialog_run != nullptr) - && (Libs::gtk_widget_realize != nullptr) - && (Libs::gdk_window_set_modal_hint != nullptr) - && (Libs::gtk_widget_show != nullptr) - && (Libs::gdk_window_focus != nullptr) - && (Libs::gtk_widget_hide != nullptr) - && (Libs::gtk_widget_hide_on_delete != nullptr) - && (Libs::gtk_file_chooser_dialog_new != nullptr) - && (Libs::gtk_file_chooser_get_type != nullptr) - && (Libs::gtk_file_chooser_set_current_folder != nullptr) - && (Libs::gtk_file_chooser_get_current_folder != nullptr) - && (Libs::gtk_file_chooser_set_current_name != nullptr) - && (Libs::gtk_file_chooser_select_filename != nullptr) - && (Libs::gtk_file_chooser_get_filenames != nullptr) - && (Libs::gtk_file_chooser_set_filter != nullptr) - && (Libs::gtk_file_chooser_get_filter != nullptr) - && (Libs::gtk_window_get_type != nullptr) - && (Libs::gtk_window_set_title != nullptr) - && (Libs::gtk_file_chooser_set_local_only != nullptr) - && (Libs::gtk_file_chooser_set_action != nullptr) - && (Libs::gtk_file_chooser_set_select_multiple != nullptr) - && (Libs::gtk_file_chooser_set_do_overwrite_confirmation != nullptr) - && (Libs::gtk_file_chooser_remove_filter != nullptr) - && (Libs::gtk_file_filter_set_name != nullptr) - && (Libs::gtk_file_filter_add_pattern != nullptr) - && (Libs::gtk_file_chooser_add_filter != nullptr) - && (Libs::gtk_file_filter_new != nullptr); -} - -bool PreviewSupported() { - return NativeSupported() - && (Libs::gdk_pixbuf_new_from_file_at_size != nullptr); -} - -bool GetNative( - QPointer parent, - QStringList &files, - QByteArray &remoteContent, - const QString &caption, - const QString &filter, - Type type, - QString startFile) { - internal::GtkFileDialog dialog(parent, caption, QString(), filter); - - dialog.setModal(true); - if (type == Type::ReadFile || type == Type::ReadFiles) { - dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); - dialog.setAcceptMode(QFileDialog::AcceptOpen); - } else if (type == Type::ReadFolder) { - dialog.setAcceptMode(QFileDialog::AcceptOpen); - dialog.setFileMode(QFileDialog::Directory); - dialog.setOption(QFileDialog::ShowDirsOnly); - } else { - dialog.setFileMode(QFileDialog::AnyFile); - dialog.setAcceptMode(QFileDialog::AcceptSave); - } - if (startFile.isEmpty() || startFile.at(0) != '/') { - startFile = cDialogLastPath() + '/' + startFile; - } - dialog.selectFile(startFile); - - int res = dialog.exec(); - - QString path = dialog.directory().absolutePath(); - if (path != cDialogLastPath()) { - cSetDialogLastPath(path); - Local::writeSettings(); - } - - if (res == QDialog::Accepted) { - if (type == Type::ReadFiles) { - files = dialog.selectedFiles(); - } else { - files = dialog.selectedFiles().mid(0, 1); - } - return true; - } - - files = QStringList(); - remoteContent = QByteArray(); - return false; -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace bool Get( QPointer parent, @@ -350,23 +101,24 @@ bool Get( QByteArray &remoteContent, const QString &caption, const QString &filter, - Type type, + ::FileDialog::internal::Type type, QString startFile) { if (parent) { parent = parent->window(); } -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (UseNative(type) && NativeSupported()) { - return GetNative( - parent, - files, - remoteContent, - caption, - filter, - type, - startFile); + if (const auto integration = GtkIntegration::Instance()) { + if (integration->fileDialogSupported() + && integration->useFileDialog(type)) { + return integration->getFileDialog( + parent, + files, + remoteContent, + caption, + filter, + type, + startFile); + } } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION return ::FileDialog::internal::GetDefault( parent, files, @@ -377,448 +129,5 @@ bool Get( startFile); } -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -namespace internal { - -QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { - g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this); - g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(Libs::gtk_widget_hide_on_delete), nullptr); - if (PreviewSupported()) { - _preview = Libs::gtk_image_new(); - g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this); - Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview); - } -} - -QGtkDialog::~QGtkDialog() { - Libs::gtk_clipboard_store(Libs::gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); - Libs::gtk_widget_destroy(gtkWidget); -} - -GtkDialog *QGtkDialog::gtkDialog() const { - return Libs::gtk_dialog_cast(gtkWidget); -} - -void QGtkDialog::exec() { - if (modality() == Qt::ApplicationModal) { - // block input to the whole app, including other GTK dialogs - Libs::gtk_dialog_run(gtkDialog()); - } else { - // block input to the window, allow input to other GTK dialogs - QEventLoop loop; - connect(this, SIGNAL(accept()), &loop, SLOT(quit())); - connect(this, SIGNAL(reject()), &loop, SLOT(quit())); - loop.exec(); - } -} - -void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { - connect(parent, &QWindow::destroyed, this, &QGtkDialog::onParentWindowDestroyed, - Qt::UniqueConnection); - setParent(parent); - setFlags(flags); - setModality(modality); - - Libs::gtk_widget_realize(gtkWidget); // creates X window - - if (parent) { - Platform::internal::XSetTransientForHint(Libs::gtk_widget_get_window(gtkWidget), parent->winId()); - } - - if (modality != Qt::NonModal) { - Libs::gdk_window_set_modal_hint(Libs::gtk_widget_get_window(gtkWidget), true); - QGuiApplicationPrivate::showModalWindow(this); - } - - Libs::gtk_widget_show(gtkWidget); - Libs::gdk_window_focus(Libs::gtk_widget_get_window(gtkWidget), 0); -} - -void QGtkDialog::hide() { - QGuiApplicationPrivate::hideModalWindow(this); - Libs::gtk_widget_hide(gtkWidget); -} - -void QGtkDialog::onResponse(QGtkDialog *dialog, int response) { - if (response == GTK_RESPONSE_OK) - emit dialog->accept(); - else - emit dialog->reject(); -} - -void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) { - auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget)); - if (!filename) { - Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); - return; - } - - // Don't attempt to open anything which isn't a regular file. If a named pipe, - // this may hang. See https://crbug.com/534754. - struct stat stat_buf; - if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { - g_free(filename); - Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); - return; - } - - // This will preserve the image's aspect ratio. - auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr); - g_free(filename); - if (pixbuf) { - Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf); - g_object_unref(pixbuf); - } - Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false); -} - -void QGtkDialog::onParentWindowDestroyed() { - // The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it. - setParent(nullptr); -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -namespace { - -const char *filterRegExp = -"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; - -QStringList makeFilterList(const QString &filter) { - QString f(filter); - - if (f.isEmpty()) - return QStringList(); - - QString sep(QLatin1String(";;")); - int i = f.indexOf(sep, 0); - if (i == -1) { - if (f.indexOf(QLatin1Char('\n'), 0) != -1) { - sep = QLatin1Char('\n'); - i = f.indexOf(sep, 0); - } - } - - return f.split(sep); -} - -// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)" -QStringList cleanFilterList(const QString &filter) { - QRegExp regexp(QString::fromLatin1(filterRegExp)); - Q_ASSERT(regexp.isValid()); - QString f = filter; - int i = regexp.indexIn(f); - if (i >= 0) - f = regexp.cap(2); - return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts); -} - -} // namespace - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent) -, _windowTitle(caption) -, _initialDirectory(directory) { - auto filters = makeFilterList(filter); - const int numFilters = filters.count(); - _nameFilters.reserve(numFilters); - for (int i = 0; i < numFilters; ++i) { - _nameFilters << filters[i].simplified(); - } - - d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr, - GTK_FILE_CHOOSER_ACTION_OPEN, - // https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new - // first_button_text doesn't need explicit conversion to char*, while all others are vardict - tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL, - tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr))); - connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); - connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected())); - - g_signal_connect(Libs::gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this); - g_signal_connect_swapped(Libs::gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this); -} - -GtkFileDialog::~GtkFileDialog() { -} - -void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { - _dir.clear(); - _selection.clear(); - - applyOptions(); - return d->show(flags, modality, parent); -} - -void GtkFileDialog::setVisible(bool visible) { - if (visible) { - if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) { - return; - } - } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) { - return; - } - - if (visible) { - showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr); - } else { - hideHelper(); - } - - // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below - // updates the state correctly, but skips showing the non-native version: - setAttribute(Qt::WA_DontShowOnScreen); - - QDialog::setVisible(visible); -} - -int GtkFileDialog::exec() { - d->setModality(windowModality()); - - bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); - setAttribute(Qt::WA_DeleteOnClose, false); - - bool wasShowModal = testAttribute(Qt::WA_ShowModal); - setAttribute(Qt::WA_ShowModal, true); - setResult(0); - - show(); - - QPointer guard = this; - d->exec(); - if (guard.isNull()) - return QDialog::Rejected; - - setAttribute(Qt::WA_ShowModal, wasShowModal); - - return result(); -} - -void GtkFileDialog::hideHelper() { - // After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder() - // & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual - // values before hiding the dialog - _dir = directory().absolutePath(); - _selection = selectedFiles(); - - d->hide(); -} - -bool GtkFileDialog::defaultNameFilterDisables() const { - return false; -} - -void GtkFileDialog::setDirectory(const QString &directory) { - GtkDialog *gtkDialog = d->gtkDialog(); - Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), directory.toUtf8()); -} - -QDir GtkFileDialog::directory() const { - // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder() - // returns a bogus value -> return the cached value before hiding - if (!_dir.isEmpty()) - return _dir; - - QString ret; - GtkDialog *gtkDialog = d->gtkDialog(); - gchar *folder = Libs::gtk_file_chooser_get_current_folder(Libs::gtk_file_chooser_cast(gtkDialog)); - if (folder) { - ret = QString::fromUtf8(folder); - g_free(folder); - } - return QDir(ret); -} - -void GtkFileDialog::selectFile(const QString &filename) { - _initialFiles.clear(); - _initialFiles.append(filename); -} - -QStringList GtkFileDialog::selectedFiles() const { - // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames() - // returns a bogus value -> return the cached value before hiding - if (!_selection.isEmpty()) - return _selection; - - QStringList selection; - GtkDialog *gtkDialog = d->gtkDialog(); - GSList *filenames = Libs::gtk_file_chooser_get_filenames(Libs::gtk_file_chooser_cast(gtkDialog)); - for (GSList *it = filenames; it; it = it->next) - selection += QString::fromUtf8((const char*)it->data); - g_slist_free(filenames); - return selection; -} - -void GtkFileDialog::setFilter() { - applyOptions(); -} - -void GtkFileDialog::selectNameFilter(const QString &filter) { - GtkFileFilter *gtkFilter = _filters.value(filter); - if (gtkFilter) { - GtkDialog *gtkDialog = d->gtkDialog(); - Libs::gtk_file_chooser_set_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter); - } -} - -QString GtkFileDialog::selectedNameFilter() const { - GtkDialog *gtkDialog = d->gtkDialog(); - GtkFileFilter *gtkFilter = Libs::gtk_file_chooser_get_filter(Libs::gtk_file_chooser_cast(gtkDialog)); - return _filterNames.value(gtkFilter); -} - -void GtkFileDialog::onAccepted() { - emit accept(); - -// QString filter = selectedNameFilter(); -// if (filter.isEmpty()) -// emit filterSelected(filter); - -// QList files = selectedFiles(); -// emit filesSelected(files); -// if (files.count() == 1) -// emit fileSelected(files.first()); -} - -void GtkFileDialog::onRejected() { - emit reject(); - -// -} - -void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) { -// QString selection; -// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog)); -// if (filename) { -// selection = QString::fromUtf8(filename); -// g_free(filename); -// } -// emit helper->currentChanged(QUrl::fromLocalFile(selection)); -} - -void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) { -// emit dialog->directoryEntered(dialog->directory()); -} - -GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) { - switch (fileMode) { - case QFileDialog::AnyFile: - case QFileDialog::ExistingFile: - case QFileDialog::ExistingFiles: - if (acceptMode == QFileDialog::AcceptOpen) - return GTK_FILE_CHOOSER_ACTION_OPEN; - else - return GTK_FILE_CHOOSER_ACTION_SAVE; - case QFileDialog::Directory: - default: - if (acceptMode == QFileDialog::AcceptOpen) - return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - else - return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; - } -} - -bool CustomButtonsSupported() { - return (Libs::gtk_dialog_get_widget_for_response != nullptr) - && (Libs::gtk_button_set_label != nullptr) - && (Libs::gtk_button_get_type != nullptr); -} - -void GtkFileDialog::applyOptions() { - GtkDialog *gtkDialog = d->gtkDialog(); - - Libs::gtk_window_set_title(Libs::gtk_window_cast(gtkDialog), _windowTitle.toUtf8()); - Libs::gtk_file_chooser_set_local_only(Libs::gtk_file_chooser_cast(gtkDialog), true); - - const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode); - Libs::gtk_file_chooser_set_action(Libs::gtk_file_chooser_cast(gtkDialog), action); - - const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles); - Libs::gtk_file_chooser_set_select_multiple(Libs::gtk_file_chooser_cast(gtkDialog), selectMultiple); - - const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite); - Libs::gtk_file_chooser_set_do_overwrite_confirmation(Libs::gtk_file_chooser_cast(gtkDialog), confirmOverwrite); - - if (!_nameFilters.isEmpty()) - setNameFilters(_nameFilters); - - if (!_initialDirectory.isEmpty()) - setDirectory(_initialDirectory); - - for_const (const auto &filename, _initialFiles) { - if (_acceptMode == QFileDialog::AcceptSave) { - QFileInfo fi(filename); - Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8()); - Libs::gtk_file_chooser_set_current_name(Libs::gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8()); - } else if (filename.endsWith('/')) { - Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); - } else { - Libs::gtk_file_chooser_select_filename(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); - } - } - - const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front(); - if (!initialNameFilter.isEmpty()) - selectNameFilter(initialNameFilter); - - if (CustomButtonsSupported()) { - GtkWidget *acceptButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK); - if (acceptButton) { - /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) - Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8()); - else*/ if (_acceptMode == QFileDialog::AcceptOpen) - Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8()); - else - Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8()); - } - - GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL); - if (rejectButton) { - /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject)) - Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8()); - else*/ - Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8()); - } - } -} - -void GtkFileDialog::setNameFilters(const QStringList &filters) { - GtkDialog *gtkDialog = d->gtkDialog(); - foreach (GtkFileFilter *filter, _filters) - Libs::gtk_file_chooser_remove_filter(Libs::gtk_file_chooser_cast(gtkDialog), filter); - - _filters.clear(); - _filterNames.clear(); - - for_const (auto &filter, filters) { - GtkFileFilter *gtkFilter = Libs::gtk_file_filter_new(); - auto name = filter;//.left(filter.indexOf(QLatin1Char('('))); - auto extensions = cleanFilterList(filter); - - Libs::gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8()); - for_const (auto &ext, extensions) { - auto caseInsensitiveExt = QString(); - caseInsensitiveExt.reserve(4 * ext.size()); - for_const (auto ch, ext) { - auto chLower = ch.toLower(); - auto chUpper = ch.toUpper(); - if (chLower != chUpper) { - caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']'); - } else { - caseInsensitiveExt.append(ch); - } - } - - Libs::gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8()); - } - - Libs::gtk_file_chooser_add_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter); - - _filters.insert(filter, gtkFilter); - _filterNames.insert(gtkFilter, filter); - } -} - -} // namespace internal -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace FileDialog } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h index a0dc9c186..50c6d6a6d 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h @@ -9,15 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_file_utilities.h" -#include -#include - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -typedef struct _GtkWidget GtkWidget; -typedef struct _GtkDialog GtkDialog; -typedef struct _GtkFileFilter GtkFileFilter; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - namespace Platform { namespace File { namespace internal { @@ -45,114 +36,5 @@ inline void InitLastPath() { ::FileDialog::internal::InitLastPathDefault(); } -namespace internal { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - -// This is a patched copy of qgtk2 theme plugin. -// We need to use our own gtk file dialog instead of -// styling Qt file dialog, because Qt only works with gtk2. -// We need to be able to work with gtk2 and gtk3, because -// we use gtk3 to work with appindicator3. -class QGtkDialog : public QWindow { - Q_OBJECT - -public: - QGtkDialog(GtkWidget *gtkWidget); - ~QGtkDialog(); - - GtkDialog *gtkDialog() const; - - void exec(); - void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); - void hide(); - -signals: - void accept(); - void reject(); - -protected: - static void onResponse(QGtkDialog *dialog, int response); - static void onUpdatePreview(QGtkDialog *dialog); - -private slots: - void onParentWindowDestroyed(); - -private: - GtkWidget *gtkWidget; - GtkWidget *_preview = nullptr; - -}; - -class GtkFileDialog : public QDialog { - Q_OBJECT - -public: - GtkFileDialog(QWidget *parent = Q_NULLPTR, - const QString &caption = QString(), - const QString &directory = QString(), - const QString &filter = QString()); - ~GtkFileDialog(); - - void setVisible(bool visible) override; - - void setWindowTitle(const QString &windowTitle) { - _windowTitle = windowTitle; - } - void setAcceptMode(QFileDialog::AcceptMode acceptMode) { - _acceptMode = acceptMode; - } - void setFileMode(QFileDialog::FileMode fileMode) { - _fileMode = fileMode; - } - void setOption(QFileDialog::Option option, bool on = true) { - if (on) { - _options |= option; - } else { - _options &= ~option; - } - } - - int exec() override; - - bool defaultNameFilterDisables() const; - void setDirectory(const QString &directory); - QDir directory() const; - void selectFile(const QString &filename); - QStringList selectedFiles() const; - void setFilter(); - void selectNameFilter(const QString &filter); - QString selectedNameFilter() const; - -private slots: - void onAccepted(); - void onRejected(); - -private: - static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper); - static void onCurrentFolderChanged(GtkFileDialog *helper); - void applyOptions(); - void setNameFilters(const QStringList &filters); - - void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); - void hideHelper(); - - // Options - QFileDialog::Options _options; - QString _windowTitle = "Choose file"; - QString _initialDirectory; - QStringList _initialFiles; - QStringList _nameFilters; - QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; - QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; - - QString _dir; - QStringList _selection; - QHash _filters; - QHash _filterNames; - QScopedPointer d; -}; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace internal } // namespace FileDialog } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp index 35f3c0c1c..08b1fad2f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp @@ -5,10 +5,9 @@ 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 */ -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION #include "platform/linux/linux_gdk_helper.h" -#include "platform/linux/linux_libs.h" +#include "platform/linux/linux_gtk_integration_p.h" extern "C" { #undef signals @@ -19,6 +18,8 @@ extern "C" { namespace Platform { namespace internal { +using namespace Platform::Gtk; + enum class GtkLoaded { GtkNone, Gtk2, @@ -44,7 +45,7 @@ f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr; // To be able to compile with gtk-2.0 headers as well template inline bool gdk_is_x11_window_check(Object *obj) { - return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type()); + return g_type_cit_helper(obj, gdk_x11_window_get_type()); } using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window); @@ -57,20 +58,20 @@ using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window); f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr; bool GdkHelperLoadGtk2(QLibrary &lib) { -#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY +#ifdef LINK_TO_GTK return false; -#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY - if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false; - if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false; +#else // LINK_TO_GTK + if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false; + if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false; return true; -#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY +#endif // !LINK_TO_GTK } bool GdkHelperLoadGtk3(QLibrary &lib) { - if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false; - if (!LOAD_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false; - if (!LOAD_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false; - if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false; + if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false; + if (!LOAD_GTK_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false; + if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false; + if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false; return true; } @@ -103,4 +104,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId) { } // namespace internal } // namespace Platform -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h index a7efb5685..6ddee649b 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h @@ -7,11 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include - class QLibrary; -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION extern "C" { #undef signals #include @@ -28,4 +25,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId); } // namespace internal } // namespace Platform -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp new file mode 100644 index 000000000..d9adb924d --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp @@ -0,0 +1,714 @@ +/* +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/linux_gtk_file_dialog.h" + +#include "platform/linux/linux_gtk_integration_p.h" +#include "platform/linux/linux_gdk_helper.h" +#include "platform/linux/linux_desktop_environment.h" +#include "platform/linux/specific_linux.h" +#include "lang/lang_keys.h" +#include "storage/localstorage.h" +#include "base/qt_adapters.h" + +#include +#include +#include + +namespace Platform { +namespace FileDialog { +namespace Gtk { + +using namespace Platform::Gtk; + +namespace { + +// GTK file chooser image preview: thanks to Chromium + +// The size of the preview we display for selected image files. We set height +// larger than width because generally there is more free space vertically +// than horiztonally (setting the preview image will alway expand the width of +// the dialog, but usually not the height). The image's aspect ratio will always +// be preserved. +constexpr auto kPreviewWidth = 256; +constexpr auto kPreviewHeight = 512; + +const char *filterRegExp = +"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; + +QStringList makeFilterList(const QString &filter) { + QString f(filter); + + if (f.isEmpty()) + return QStringList(); + + QString sep(QLatin1String(";;")); + int i = f.indexOf(sep, 0); + if (i == -1) { + if (f.indexOf(QLatin1Char('\n'), 0) != -1) { + sep = QLatin1Char('\n'); + i = f.indexOf(sep, 0); + } + } + + return f.split(sep); +} + +// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)" +QStringList cleanFilterList(const QString &filter) { + QRegExp regexp(QString::fromLatin1(filterRegExp)); + Assert(regexp.isValid()); + QString f = filter; + int i = regexp.indexIn(f); + if (i >= 0) + f = regexp.cap(2); + return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts); +} + +bool PreviewSupported() { + return (gdk_pixbuf_new_from_file_at_size != nullptr); +} + +bool CustomButtonsSupported() { + return (gtk_dialog_get_widget_for_response != nullptr) + && (gtk_button_set_label != nullptr) + && (gtk_button_get_type != nullptr); +} + +// This is a patched copy of qgtk2 theme plugin. +// We need to use our own gtk file dialog instead of +// styling Qt file dialog, because Qt only works with gtk2. +// We need to be able to work with gtk2 and gtk3, because +// we use gtk3 to work with appindicator3. +class QGtkDialog : public QWindow { +public: + QGtkDialog(GtkWidget *gtkWidget); + ~QGtkDialog(); + + GtkDialog *gtkDialog() const; + + void exec(); + void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); + void hide(); + + rpl::producer<> accept(); + rpl::producer<> reject(); + +protected: + static void onResponse(QGtkDialog *dialog, int response); + static void onUpdatePreview(QGtkDialog *dialog); + +private: + void onParentWindowDestroyed(); + + GtkWidget *gtkWidget = nullptr; + GtkWidget *_preview = nullptr; + + rpl::event_stream<> _accept; + rpl::event_stream<> _reject; + rpl::lifetime _lifetime; + +}; + +class GtkFileDialog : public QDialog { +public: + GtkFileDialog(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &filter = QString()); + ~GtkFileDialog(); + + void setVisible(bool visible) override; + + void setWindowTitle(const QString &windowTitle) { + _windowTitle = windowTitle; + } + void setAcceptMode(QFileDialog::AcceptMode acceptMode) { + _acceptMode = acceptMode; + } + void setFileMode(QFileDialog::FileMode fileMode) { + _fileMode = fileMode; + } + void setOption(QFileDialog::Option option, bool on = true) { + if (on) { + _options |= option; + } else { + _options &= ~option; + } + } + + int exec() override; + + bool defaultNameFilterDisables() const; + void setDirectory(const QString &directory); + QDir directory() const; + void selectFile(const QString &filename); + QStringList selectedFiles() const; + void setFilter(); + void selectNameFilter(const QString &filter); + QString selectedNameFilter() const; + +private: + static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper); + static void onCurrentFolderChanged(GtkFileDialog *helper); + void applyOptions(); + void setNameFilters(const QStringList &filters); + + void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); + void hideHelper(); + + void onAccepted(); + void onRejected(); + + // Options + QFileDialog::Options _options; + QString _windowTitle = "Choose file"; + QString _initialDirectory; + QStringList _initialFiles; + QStringList _nameFilters; + QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; + QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; + + QString _dir; + QStringList _selection; + QHash _filters; + QHash _filterNames; + QScopedPointer d; + + rpl::lifetime _lifetime; +}; + +QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { + g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this); + g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr); + if (PreviewSupported()) { + _preview = gtk_image_new(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this); + gtk_file_chooser_set_preview_widget(gtk_file_chooser_cast(gtkWidget), _preview); + } +} + +QGtkDialog::~QGtkDialog() { + gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); + gtk_widget_destroy(gtkWidget); +} + +GtkDialog *QGtkDialog::gtkDialog() const { + return gtk_dialog_cast(gtkWidget); +} + +void QGtkDialog::exec() { + if (modality() == Qt::ApplicationModal) { + // block input to the whole app, including other GTK dialogs + gtk_dialog_run(gtkDialog()); + } else { + // block input to the window, allow input to other GTK dialogs + QEventLoop loop; + + accept( + ) | rpl::start_with_next([=, &loop] { + loop.quit(); + }, _lifetime); + + reject( + ) | rpl::start_with_next([=, &loop] { + loop.quit(); + }, _lifetime); + + loop.exec(); + } +} + +void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { + connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); }, + Qt::UniqueConnection); + setParent(parent); + setFlags(flags); + setModality(modality); + + gtk_widget_realize(gtkWidget); // creates X window + + if (parent) { + internal::XSetTransientForHint(gtk_widget_get_window(gtkWidget), parent->winId()); + } + + if (modality != Qt::NonModal) { + gdk_window_set_modal_hint(gtk_widget_get_window(gtkWidget), true); + QGuiApplicationPrivate::showModalWindow(this); + } + + gtk_widget_show(gtkWidget); + gdk_window_focus(gtk_widget_get_window(gtkWidget), 0); +} + +void QGtkDialog::hide() { + QGuiApplicationPrivate::hideModalWindow(this); + gtk_widget_hide(gtkWidget); +} + +rpl::producer<> QGtkDialog::accept() { + return _accept.events(); +} + +rpl::producer<> QGtkDialog::reject() { + return _reject.events(); +} + +void QGtkDialog::onResponse(QGtkDialog *dialog, int response) { + if (response == GTK_RESPONSE_OK) + dialog->_accept.fire({}); + else + dialog->_reject.fire({}); +} + +void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) { + auto filename = gtk_file_chooser_get_preview_filename(gtk_file_chooser_cast(dialog->gtkWidget)); + if (!filename) { + gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), false); + return; + } + + // Don't attempt to open anything which isn't a regular file. If a named pipe, + // this may hang. See https://crbug.com/534754. + struct stat stat_buf; + if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { + g_free(filename); + gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), false); + return; + } + + // This will preserve the image's aspect ratio. + auto pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr); + g_free(filename); + if (pixbuf) { + gtk_image_set_from_pixbuf(gtk_image_cast(dialog->_preview), pixbuf); + g_object_unref(pixbuf); + } + gtk_file_chooser_set_preview_widget_active(gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false); +} + +void QGtkDialog::onParentWindowDestroyed() { + // The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it. + setParent(nullptr); +} + +GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent) +, _windowTitle(caption) +, _initialDirectory(directory) { + auto filters = makeFilterList(filter); + const int numFilters = filters.count(); + _nameFilters.reserve(numFilters); + for (int i = 0; i < numFilters; ++i) { + _nameFilters << filters[i].simplified(); + } + + d.reset(new QGtkDialog(gtk_file_chooser_dialog_new("", nullptr, + GTK_FILE_CHOOSER_ACTION_OPEN, + // https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new + // first_button_text doesn't need explicit conversion to char*, while all others are vardict + tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL, + tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr))); + + d.data()->accept( + ) | rpl::start_with_next([=] { + onAccepted(); + }, _lifetime); + + d.data()->reject( + ) | rpl::start_with_next([=] { + onRejected(); + }, _lifetime); + + g_signal_connect(gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this); + g_signal_connect_swapped(gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this); +} + +GtkFileDialog::~GtkFileDialog() { +} + +void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { + _dir.clear(); + _selection.clear(); + + applyOptions(); + return d->show(flags, modality, parent); +} + +void GtkFileDialog::setVisible(bool visible) { + if (visible) { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) { + return; + } + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) { + return; + } + + if (visible) { + showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr); + } else { + hideHelper(); + } + + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); + + QDialog::setVisible(visible); +} + +int GtkFileDialog::exec() { + d->setModality(windowModality()); + + bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, false); + + bool wasShowModal = testAttribute(Qt::WA_ShowModal); + setAttribute(Qt::WA_ShowModal, true); + setResult(0); + + show(); + + QPointer guard = this; + d->exec(); + if (guard.isNull()) + return QDialog::Rejected; + + setAttribute(Qt::WA_ShowModal, wasShowModal); + + return result(); +} + +void GtkFileDialog::hideHelper() { + // After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder() + // & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual + // values before hiding the dialog + _dir = directory().absolutePath(); + _selection = selectedFiles(); + + d->hide(); +} + +bool GtkFileDialog::defaultNameFilterDisables() const { + return false; +} + +void GtkFileDialog::setDirectory(const QString &directory) { + GtkDialog *gtkDialog = d->gtkDialog(); + gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), directory.toUtf8()); +} + +QDir GtkFileDialog::directory() const { + // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder() + // returns a bogus value -> return the cached value before hiding + if (!_dir.isEmpty()) + return _dir; + + QString ret; + GtkDialog *gtkDialog = d->gtkDialog(); + gchar *folder = gtk_file_chooser_get_current_folder(gtk_file_chooser_cast(gtkDialog)); + if (folder) { + ret = QString::fromUtf8(folder); + g_free(folder); + } + return QDir(ret); +} + +void GtkFileDialog::selectFile(const QString &filename) { + _initialFiles.clear(); + _initialFiles.append(filename); +} + +QStringList GtkFileDialog::selectedFiles() const { + // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames() + // returns a bogus value -> return the cached value before hiding + if (!_selection.isEmpty()) + return _selection; + + QStringList selection; + GtkDialog *gtkDialog = d->gtkDialog(); + GSList *filenames = gtk_file_chooser_get_filenames(gtk_file_chooser_cast(gtkDialog)); + for (GSList *it = filenames; it; it = it->next) + selection += QString::fromUtf8((const char*)it->data); + g_slist_free(filenames); + return selection; +} + +void GtkFileDialog::setFilter() { + applyOptions(); +} + +void GtkFileDialog::selectNameFilter(const QString &filter) { + GtkFileFilter *gtkFilter = _filters.value(filter); + if (gtkFilter) { + GtkDialog *gtkDialog = d->gtkDialog(); + gtk_file_chooser_set_filter(gtk_file_chooser_cast(gtkDialog), gtkFilter); + } +} + +QString GtkFileDialog::selectedNameFilter() const { + GtkDialog *gtkDialog = d->gtkDialog(); + GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(gtk_file_chooser_cast(gtkDialog)); + return _filterNames.value(gtkFilter); +} + +void GtkFileDialog::onAccepted() { + emit accept(); + +// QString filter = selectedNameFilter(); +// if (filter.isEmpty()) +// emit filterSelected(filter); + +// QList files = selectedFiles(); +// emit filesSelected(files); +// if (files.count() == 1) +// emit fileSelected(files.first()); +} + +void GtkFileDialog::onRejected() { + emit reject(); + +// +} + +void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) { +// QString selection; +// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog)); +// if (filename) { +// selection = QString::fromUtf8(filename); +// g_free(filename); +// } +// emit helper->currentChanged(QUrl::fromLocalFile(selection)); +} + +void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) { +// emit dialog->directoryEntered(dialog->directory()); +} + +GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) { + switch (fileMode) { + case QFileDialog::AnyFile: + case QFileDialog::ExistingFile: + case QFileDialog::ExistingFiles: + if (acceptMode == QFileDialog::AcceptOpen) + return GTK_FILE_CHOOSER_ACTION_OPEN; + else + return GTK_FILE_CHOOSER_ACTION_SAVE; + case QFileDialog::Directory: + default: + if (acceptMode == QFileDialog::AcceptOpen) + return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + else + return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; + } +} + +void GtkFileDialog::applyOptions() { + GtkDialog *gtkDialog = d->gtkDialog(); + + gtk_window_set_title(gtk_window_cast(gtkDialog), _windowTitle.toUtf8()); + gtk_file_chooser_set_local_only(gtk_file_chooser_cast(gtkDialog), true); + + const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode); + gtk_file_chooser_set_action(gtk_file_chooser_cast(gtkDialog), action); + + const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles); + gtk_file_chooser_set_select_multiple(gtk_file_chooser_cast(gtkDialog), selectMultiple); + + const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite); + gtk_file_chooser_set_do_overwrite_confirmation(gtk_file_chooser_cast(gtkDialog), confirmOverwrite); + + if (!_nameFilters.isEmpty()) + setNameFilters(_nameFilters); + + if (!_initialDirectory.isEmpty()) + setDirectory(_initialDirectory); + + for_const (const auto &filename, _initialFiles) { + if (_acceptMode == QFileDialog::AcceptSave) { + QFileInfo fi(filename); + gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8()); + gtk_file_chooser_set_current_name(gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8()); + } else if (filename.endsWith('/')) { + gtk_file_chooser_set_current_folder(gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); + } else { + gtk_file_chooser_select_filename(gtk_file_chooser_cast(gtkDialog), filename.toUtf8()); + } + } + + const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front(); + if (!initialNameFilter.isEmpty()) + selectNameFilter(initialNameFilter); + + if (CustomButtonsSupported()) { + GtkWidget *acceptButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK); + if (acceptButton) { + /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) + gtk_button_set_label(gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8()); + else*/ if (_acceptMode == QFileDialog::AcceptOpen) + gtk_button_set_label(gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8()); + else + gtk_button_set_label(gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8()); + } + + GtkWidget *rejectButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL); + if (rejectButton) { + /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject)) + gtk_button_set_label(gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8()); + else*/ + gtk_button_set_label(gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8()); + } + } +} + +void GtkFileDialog::setNameFilters(const QStringList &filters) { + GtkDialog *gtkDialog = d->gtkDialog(); + foreach (GtkFileFilter *filter, _filters) + gtk_file_chooser_remove_filter(gtk_file_chooser_cast(gtkDialog), filter); + + _filters.clear(); + _filterNames.clear(); + + for_const (auto &filter, filters) { + GtkFileFilter *gtkFilter = gtk_file_filter_new(); + auto name = filter;//.left(filter.indexOf(QLatin1Char('('))); + auto extensions = cleanFilterList(filter); + + gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8()); + for_const (auto &ext, extensions) { + auto caseInsensitiveExt = QString(); + caseInsensitiveExt.reserve(4 * ext.size()); + for_const (auto ch, ext) { + auto chLower = ch.toLower(); + auto chUpper = ch.toUpper(); + if (chLower != chUpper) { + caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']'); + } else { + caseInsensitiveExt.append(ch); + } + } + + gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8()); + } + + gtk_file_chooser_add_filter(gtk_file_chooser_cast(gtkDialog), gtkFilter); + + _filters.insert(filter, gtkFilter); + _filterNames.insert(gtkFilter, filter); + } +} + +} // namespace + +bool Supported() { + return internal::GdkHelperLoaded() + && (gtk_widget_hide_on_delete != nullptr) + && (gtk_clipboard_store != nullptr) + && (gtk_clipboard_get != nullptr) + && (gtk_widget_destroy != nullptr) + && (gtk_dialog_get_type != nullptr) + && (gtk_dialog_run != nullptr) + && (gtk_widget_realize != nullptr) + && (gdk_window_set_modal_hint != nullptr) + && (gtk_widget_show != nullptr) + && (gdk_window_focus != nullptr) + && (gtk_widget_hide != nullptr) + && (gtk_widget_hide_on_delete != nullptr) + && (gtk_file_chooser_dialog_new != nullptr) + && (gtk_file_chooser_get_type != nullptr) + && (gtk_file_chooser_set_current_folder != nullptr) + && (gtk_file_chooser_get_current_folder != nullptr) + && (gtk_file_chooser_set_current_name != nullptr) + && (gtk_file_chooser_select_filename != nullptr) + && (gtk_file_chooser_get_filenames != nullptr) + && (gtk_file_chooser_set_filter != nullptr) + && (gtk_file_chooser_get_filter != nullptr) + && (gtk_window_get_type != nullptr) + && (gtk_window_set_title != nullptr) + && (gtk_file_chooser_set_local_only != nullptr) + && (gtk_file_chooser_set_action != nullptr) + && (gtk_file_chooser_set_select_multiple != nullptr) + && (gtk_file_chooser_set_do_overwrite_confirmation != nullptr) + && (gtk_file_chooser_remove_filter != nullptr) + && (gtk_file_filter_set_name != nullptr) + && (gtk_file_filter_add_pattern != nullptr) + && (gtk_file_chooser_add_filter != nullptr) + && (gtk_file_filter_new != nullptr); +} + +bool Use(Type type) { + // use gtk file dialog on gtk-based desktop environments + // or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3) + // or if portals are used and operation is to open folder + // and portal doesn't support folder choosing + const auto sandboxedOrCustomPortal = InFlatpak() + || InSnap() + || UseXDGDesktopPortal(); + + const auto neededForPortal = (type == Type::ReadFolder) + && !CanOpenDirectoryWithPortal(); + + const auto neededNonForced = DesktopEnvironment::IsGtkBased() + || (sandboxedOrCustomPortal && neededForPortal); + + const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal; + + return IsGtkIntegrationForced() + || (neededNonForced && !excludeNonForced); +} + +bool Get( + QPointer parent, + QStringList &files, + QByteArray &remoteContent, + const QString &caption, + const QString &filter, + Type type, + QString startFile) { + GtkFileDialog dialog(parent, caption, QString(), filter); + + dialog.setModal(true); + if (type == Type::ReadFile || type == Type::ReadFiles) { + dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + } else if (type == Type::ReadFolder) { + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::Directory); + dialog.setOption(QFileDialog::ShowDirsOnly); + } else { + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setAcceptMode(QFileDialog::AcceptSave); + } + if (startFile.isEmpty() || startFile.at(0) != '/') { + startFile = cDialogLastPath() + '/' + startFile; + } + dialog.selectFile(startFile); + + int res = dialog.exec(); + + QString path = dialog.directory().absolutePath(); + if (path != cDialogLastPath()) { + cSetDialogLastPath(path); + Local::writeSettings(); + } + + if (res == QDialog::Accepted) { + if (type == Type::ReadFiles) { + files = dialog.selectedFiles(); + } else { + files = dialog.selectedFiles().mid(0, 1); + } + return true; + } + + files = QStringList(); + remoteContent = QByteArray(); + return false; +} + +} // namespace Gtk +} // namespace FileDialog +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h new file mode 100644 index 000000000..0891a0e10 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.h @@ -0,0 +1,31 @@ +/* +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 +*/ +#pragma once + +#include "core/file_utilities.h" + +namespace Platform { +namespace FileDialog { +namespace Gtk { + +using Type = ::FileDialog::internal::Type; + +bool Supported(); +bool Use(Type type = Type::ReadFile); +bool Get( + QPointer parent, + QStringList &files, + QByteArray &remoteContent, + const QString &caption, + const QString &filter, + Type type, + QString startFile); + +} // namespace Gtk +} // namespace FileDialog +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp new file mode 100644 index 000000000..a5ae6dac0 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp @@ -0,0 +1,491 @@ +/* +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/linux_gtk_integration.h" + +#include "platform/linux/linux_gtk_integration_p.h" +#include "base/platform/base_platform_info.h" +#include "platform/linux/linux_xlib_helper.h" +#include "platform/linux/linux_gdk_helper.h" +#include "platform/linux/linux_gtk_file_dialog.h" +#include "platform/linux/linux_open_with_dialog.h" +#include "platform/linux/specific_linux.h" +#include "core/sandbox.h" +#include "core/core_settings.h" +#include "core/application.h" +#include "main/main_domain.h" +#include "mainwindow.h" + +namespace Platform { +namespace internal { + +using namespace Platform::Gtk; + +namespace { + +bool GtkTriedToInit = false; +bool GtkLoaded = false; + +bool LoadLibrary(QLibrary &lib, const char *name, int version) { +#ifdef LINK_TO_GTK + return true; +#else // LINK_TO_GTK + DEBUG_LOG(("Loading '%1' with version %2...").arg( + QLatin1String(name)).arg(version)); + lib.setFileNameAndVersion(QLatin1String(name), version); + if (lib.load()) { + DEBUG_LOG(("Loaded '%1' with version %2!").arg( + QLatin1String(name)).arg(version)); + return true; + } + lib.setFileNameAndVersion(QLatin1String(name), QString()); + if (lib.load()) { + DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name))); + return true; + } + LOG(("Could not load '%1' with version %2 :(").arg( + QLatin1String(name)).arg(version)); + return false; +#endif // !LINK_TO_GTK +} + +void GtkMessageHandler( + const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) { + // Silence false-positive Gtk warnings (we are using Xlib to set + // the WM_TRANSIENT_FOR hint). + if (message != qstr("GtkDialog mapped without a transient parent. " + "This is discouraged.")) { + // For other messages, call the default handler. + g_log_default_handler(log_domain, log_level, message, unused_data); + } +} + +bool SetupGtkBase(QLibrary &lib_gtk) { + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_init_check", gtk_init_check)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_check_version", gtk_check_version)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_settings_get_default", gtk_settings_get_default)) return false; + + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_hide", gtk_widget_hide)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_get_window", gtk_widget_get_window)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_image", gtk_clipboard_wait_for_image)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_selection_data_targets_include_image", gtk_selection_data_targets_include_image)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_selection_data_free", gtk_selection_data_free)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_select_filename", gtk_file_chooser_select_filename)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_filenames", gtk_file_chooser_get_filenames)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_filter", gtk_file_chooser_set_filter)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_filter", gtk_file_chooser_get_filter)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_window_get_type", gtk_window_get_type)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_window_set_title", gtk_window_set_title)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_local_only", gtk_file_chooser_set_local_only)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_action", gtk_file_chooser_set_action)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_select_multiple", gtk_file_chooser_set_select_multiple)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_do_overwrite_confirmation", gtk_file_chooser_set_do_overwrite_confirmation)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_remove_filter", gtk_file_chooser_remove_filter)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_new", gtk_image_new)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false; + + if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_get_type", gtk_dialog_get_type)) return false; + if (!LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_run", gtk_dialog_run)) return false; + + if (!LOAD_GTK_SYMBOL(lib_gtk, "gdk_atom_intern", gdk_atom_intern)) return false; + + if (LOAD_GTK_SYMBOL(lib_gtk, "gdk_set_allowed_backends", gdk_set_allowed_backends)) { + // We work only with Wayland and X11 GDK backends. + // Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call. + // See https://github.com/telegramdesktop/tdesktop/issues/3176 + // See https://github.com/telegramdesktop/tdesktop/issues/3162 + if(IsWayland()) { + DEBUG_LOG(("Limit allowed GDK backends to wayland,x11")); + gdk_set_allowed_backends("wayland,x11"); + } else { + DEBUG_LOG(("Limit allowed GDK backends to x11,wayland")); + gdk_set_allowed_backends("x11,wayland"); + } + } + + // gtk_init will reset the Xlib error handler, + // and that causes Qt applications to quit on X errors. + // Therefore, we need to manually restore it. + XErrorHandlerRestorer handlerRestorer; + + DEBUG_LOG(("Library gtk functions loaded!")); + GtkTriedToInit = true; + if (!gtk_init_check(0, 0)) { + gtk_init_check = nullptr; + DEBUG_LOG(("Failed to gtk_init_check(0, 0)!")); + return false; + } + DEBUG_LOG(("Checked gtk with gtk_init_check!")); + + // Use our custom log handler. + g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, GtkMessageHandler, nullptr); + + return true; +} + +bool GetImageFromClipboardSupported() { + return (gtk_clipboard_get != nullptr) + && (gtk_clipboard_wait_for_contents != nullptr) + && (gtk_clipboard_wait_for_image != nullptr) + && (gtk_selection_data_targets_include_image != nullptr) + && (gtk_selection_data_free != nullptr) + && (gdk_pixbuf_get_pixels != nullptr) + && (gdk_pixbuf_get_width != nullptr) + && (gdk_pixbuf_get_height != nullptr) + && (gdk_pixbuf_get_rowstride != nullptr) + && (gdk_pixbuf_get_has_alpha != nullptr) + && (gdk_atom_intern != nullptr); +} + +template +std::optional GtkSetting(const QString &propertyName) { + const auto integration = GtkIntegration::Instance(); + if (!integration + || !integration->loaded() + || gtk_settings_get_default == nullptr) { + return std::nullopt; + } + auto settings = gtk_settings_get_default(); + T value; + g_object_get(settings, propertyName.toUtf8(), &value, nullptr); + return value; +} + +bool IconThemeShouldBeSet() { + // change the icon theme only if + // it isn't already set by a platformtheme plugin + // if QT_QPA_PLATFORMTHEME=(gtk2|gtk3), then force-apply the icon theme + static const auto Result = + // QGenericUnixTheme + (QIcon::themeName() == qstr("hicolor") + && QIcon::fallbackThemeName() == qstr("hicolor")) + // QGnomeTheme + || (QIcon::themeName() == qstr("Adwaita") + && QIcon::fallbackThemeName() == qstr("gnome")) + // qt5ct + || (QIcon::themeName().isEmpty() + && QIcon::fallbackThemeName().isEmpty()) + || IsGtkIntegrationForced(); + + return Result; +} + +bool CursorSizeShouldBeSet() { + // change the cursor size only on Wayland and if it wasn't already set + static const auto Result = IsWayland() + && qEnvironmentVariableIsEmpty("XCURSOR_SIZE"); + + return Result; +} + +void SetIconTheme() { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + const auto integration = GtkIntegration::Instance(); + + if (!integration + || !IconThemeShouldBeSet()) { + return; + } + + const auto themeName = integration->getStringSetting( + qsl("gtk-icon-theme-name")); + + const auto fallbackThemeName = integration->getStringSetting( + qsl("gtk-fallback-icon-theme")); + + if (!themeName.has_value() || !fallbackThemeName.has_value()) { + return; + } + + DEBUG_LOG(("Setting GTK icon theme")); + + QIcon::setThemeName(*themeName); + QIcon::setFallbackThemeName(*fallbackThemeName); + + DEBUG_LOG(("New icon theme: %1").arg(QIcon::themeName())); + DEBUG_LOG(("New fallback icon theme: %1").arg( + QIcon::fallbackThemeName())); + + SetApplicationIcon(Window::CreateIcon()); + if (App::wnd()) { + App::wnd()->setWindowIcon(Window::CreateIcon()); + } + + Core::App().domain().notifyUnreadBadgeChanged(); + }); +} + +void SetCursorSize() { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + const auto integration = GtkIntegration::Instance(); + + if (!integration + || !CursorSizeShouldBeSet()) { + return; + } + + const auto newCursorSize = integration->getIntSetting( + qsl("gtk-cursor-theme-size")); + + if (!newCursorSize.has_value()) { + return; + } + + DEBUG_LOG(("Setting GTK cursor size")); + qputenv("XCURSOR_SIZE", QByteArray::number(*newCursorSize)); + DEBUG_LOG(("New cursor size: %1").arg(*newCursorSize)); + }); +} + +void DarkModeChanged() { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + Core::App().settings().setSystemDarkMode(IsDarkMode()); + }); +} + +void DecorationLayoutChanged() { + Core::Sandbox::Instance().customEnterFromEventLoop([] { + Core::App().settings().setWindowControlsLayout( + WindowControlsLayout()); + }); +} + +} // namespace + +GtkIntegration::GtkIntegration() { +} + +GtkIntegration *GtkIntegration::Instance() { + static const auto useGtkIntegration = !qEnvironmentVariableIsSet( + kDisableGtkIntegration.utf8()); + + if (!useGtkIntegration) { + return nullptr; + } + + static GtkIntegration instance; + return &instance; +} + +void GtkIntegration::load() { + DEBUG_LOG(("Loading GTK")); + + QLibrary lib_gtk; + lib_gtk.setLoadHints(QLibrary::DeepBindHint); + + if (LoadLibrary(lib_gtk, "gtk-3", 0)) { + GtkLoaded = SetupGtkBase(lib_gtk); + } + if (!GtkLoaded + && !GtkTriedToInit + && LoadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { + GtkLoaded = SetupGtkBase(lib_gtk); + } + + if (GtkLoaded) { + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size); + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_has_alpha", gdk_pixbuf_get_has_alpha); + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_pixels", gdk_pixbuf_get_pixels); + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_width", gdk_pixbuf_get_width); + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_height", gdk_pixbuf_get_height); + LOAD_GTK_SYMBOL(lib_gtk, "gdk_pixbuf_get_rowstride", gdk_pixbuf_get_rowstride); + + GdkHelperLoad(lib_gtk); + + LOAD_GTK_SYMBOL(lib_gtk, "gtk_dialog_get_widget_for_response", gtk_dialog_get_widget_for_response); + LOAD_GTK_SYMBOL(lib_gtk, "gtk_button_set_label", gtk_button_set_label); + LOAD_GTK_SYMBOL(lib_gtk, "gtk_button_get_type", gtk_button_get_type); + + LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_dialog_new", gtk_app_chooser_dialog_new); + LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_get_app_info", gtk_app_chooser_get_app_info); + LOAD_GTK_SYMBOL(lib_gtk, "gtk_app_chooser_get_type", gtk_app_chooser_get_type); + + SetIconTheme(); + SetCursorSize(); + + const auto settings = gtk_settings_get_default(); + + g_signal_connect( + settings, + "notify::gtk-icon-theme-name", + G_CALLBACK(SetIconTheme), + nullptr); + + g_signal_connect( + settings, + "notify::gtk-theme-name", + G_CALLBACK(DarkModeChanged), + nullptr); + + g_signal_connect( + settings, + "notify::gtk-cursor-theme-size", + G_CALLBACK(SetCursorSize), + nullptr); + + if (checkVersion(3, 0, 0)) { + g_signal_connect( + settings, + "notify::gtk-application-prefer-dark-theme", + G_CALLBACK(DarkModeChanged), + nullptr); + } + + if (checkVersion(3, 12, 0)) { + g_signal_connect( + settings, + "notify::gtk-decoration-layout", + G_CALLBACK(DecorationLayoutChanged), + nullptr); + } + } else { + LOG(("Could not load gtk-3 or gtk-x11-2.0!")); + } +} + +bool GtkIntegration::loaded() const { + return GtkLoaded; +} + +bool GtkIntegration::checkVersion(uint major, uint minor, uint micro) const { + return (loaded() && gtk_check_version != nullptr) + ? !gtk_check_version(major, minor, micro) + : false; +} + +std::optional GtkIntegration::getBoolSetting( + const QString &propertyName) const { + const auto value = GtkSetting(propertyName); + if (!value.has_value()) { + return std::nullopt; + } + DEBUG_LOG(("Getting GTK setting, %1: %2") + .arg(propertyName) + .arg(Logs::b(*value))); + return *value; +} + +std::optional GtkIntegration::getIntSetting( + const QString &propertyName) const { + const auto value = GtkSetting(propertyName); + if (value.has_value()) { + DEBUG_LOG(("Getting GTK setting, %1: %2") + .arg(propertyName) + .arg(*value)); + } + return value; +} + +std::optional GtkIntegration::getStringSetting( + const QString &propertyName) const { + auto value = GtkSetting(propertyName); + if (!value.has_value()) { + return std::nullopt; + } + const auto str = QString::fromUtf8(*value); + g_free(*value); + DEBUG_LOG(("Getting GTK setting, %1: '%2'").arg(propertyName).arg(str)); + return str; +} + +bool GtkIntegration::fileDialogSupported() const { + return FileDialog::Gtk::Supported(); +} + +bool GtkIntegration::useFileDialog(FileDialogType type) const { + return FileDialog::Gtk::Use(type); +} + +bool GtkIntegration::getFileDialog( + QPointer parent, + QStringList &files, + QByteArray &remoteContent, + const QString &caption, + const QString &filter, + FileDialogType type, + QString startFile) const { + return FileDialog::Gtk::Get( + parent, + files, + remoteContent, + caption, + filter, + type, + startFile); +} + +bool GtkIntegration::showOpenWithDialog(const QString &filepath) const { + return File::internal::ShowOpenWithDialog(filepath); +} + +QImage GtkIntegration::getImageFromClipboard() const { + QImage data; + + if (!GetImageFromClipboardSupported()) { + return data; + } + + const auto clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + if (!clipboard) { + return data; + } + + auto gsel = gtk_clipboard_wait_for_contents( + clipboard, + gdk_atom_intern("TARGETS", true)); + + if (gsel) { + if (gtk_selection_data_targets_include_image(gsel, false)) { + auto img = gtk_clipboard_wait_for_image(clipboard); + + if (img) { + data = QImage( + gdk_pixbuf_get_pixels(img), + gdk_pixbuf_get_width(img), + gdk_pixbuf_get_height(img), + gdk_pixbuf_get_rowstride(img), + gdk_pixbuf_get_has_alpha(img) + ? QImage::Format_RGBA8888 + : QImage::Format_RGB888).copy(); + + g_object_unref(img); + } + } + + gtk_selection_data_free(gsel); + } + + return data; +} + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h new file mode 100644 index 000000000..df393befa --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h @@ -0,0 +1,59 @@ +/* +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 +*/ +#pragma once + +#include "core/file_utilities.h" + +namespace Platform { +namespace internal { + +inline constexpr auto kDisableGtkIntegration = "TDESKTOP_DISABLE_GTK_INTEGRATION"_cs; + +class GtkIntegration { +public: + static GtkIntegration *Instance(); + + void load(); + [[nodiscard]] bool loaded() const; + [[nodiscard]] bool checkVersion( + uint major, + uint minor, + uint micro) const; + + [[nodiscard]] std::optional getBoolSetting( + const QString &propertyName) const; + + [[nodiscard]] std::optional getIntSetting( + const QString &propertyName) const; + + [[nodiscard]] std::optional getStringSetting( + const QString &propertyName) const; + + using FileDialogType = ::FileDialog::internal::Type; + [[nodiscard]] bool fileDialogSupported() const; + [[nodiscard]] bool useFileDialog( + FileDialogType type = FileDialogType::ReadFile) const; + [[nodiscard]] bool getFileDialog( + QPointer parent, + QStringList &files, + QByteArray &remoteContent, + const QString &caption, + const QString &filter, + FileDialogType type, + QString startFile) const; + + [[nodiscard]] bool showOpenWithDialog(const QString &filepath) const; + + [[nodiscard]] QImage getImageFromClipboard() const; + +private: + GtkIntegration(); +}; + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp new file mode 100644 index 000000000..00df2f046 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp @@ -0,0 +1,74 @@ +/* +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/linux_gtk_integration.h" + +namespace Platform { +namespace internal { + +GtkIntegration::GtkIntegration() { +} + +GtkIntegration *GtkIntegration::Instance() { + return nullptr; +} + +void GtkIntegration::load() { +} + +bool GtkIntegration::loaded() const { + return false; +} + +bool GtkIntegration::checkVersion(uint major, uint minor, uint micro) const { + return false; +} + +std::optional GtkIntegration::getBoolSetting( + const QString &propertyName) const { + return std::nullopt; +} + +std::optional GtkIntegration::getIntSetting( + const QString &propertyName) const { + return std::nullopt; +} + +std::optional GtkIntegration::getStringSetting( + const QString &propertyName) const { + return std::nullopt; +} + +bool GtkIntegration::fileDialogSupported() const { + return false; +} + +bool GtkIntegration::useFileDialog(FileDialogType type) const { + return false; +} + +bool GtkIntegration::getFileDialog( + QPointer parent, + QStringList &files, + QByteArray &remoteContent, + const QString &caption, + const QString &filter, + FileDialogType type, + QString startFile) const { + return false; +} + +bool GtkIntegration::showOpenWithDialog(const QString &filepath) const { + return false; +} + +QImage GtkIntegration::getImageFromClipboard() const { + return {}; +} + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h new file mode 100644 index 000000000..301a828e5 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_p.h @@ -0,0 +1,158 @@ +/* +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 +*/ +#pragma once + +#include + +extern "C" { +#undef signals +#include +#include +#define signals public +} // extern "C" + +#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY +#define LINK_TO_GTK +#endif // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY + +#ifdef LINK_TO_GTK +#define LOAD_GTK_SYMBOL(lib, name, func) (func = ::func) +#else // LINK_TO_GTK +#define LOAD_GTK_SYMBOL Platform::Gtk::LoadSymbol +#endif // !LINK_TO_GTK + +// To be able to compile with gtk-2.0 headers as well +typedef struct _GtkAppChooser GtkAppChooser; + +namespace Platform { +namespace Gtk { + +template +bool LoadSymbol(QLibrary &lib, const char *name, Function &func) { + func = nullptr; + if (!lib.isLoaded()) { + return false; + } + + func = reinterpret_cast(lib.resolve(name)); + if (func) { + return true; + } + LOG(("Error: failed to load '%1' function!").arg(name)); + return false; +} + +inline gboolean (*gtk_init_check)(int *argc, char ***argv) = nullptr; +inline const gchar* (*gtk_check_version)(guint required_major, guint required_minor, guint required_micro) = nullptr; +inline GtkSettings* (*gtk_settings_get_default)(void) = nullptr; +inline void (*gtk_widget_show)(GtkWidget *widget) = nullptr; +inline void (*gtk_widget_hide)(GtkWidget *widget) = nullptr; +inline GdkWindow* (*gtk_widget_get_window)(GtkWidget *widget) = nullptr; +inline void (*gtk_widget_realize)(GtkWidget *widget) = nullptr; +inline gboolean (*gtk_widget_hide_on_delete)(GtkWidget *widget) = nullptr; +inline void (*gtk_widget_destroy)(GtkWidget *widget) = nullptr; +inline GtkClipboard* (*gtk_clipboard_get)(GdkAtom selection) = nullptr; +inline void (*gtk_clipboard_store)(GtkClipboard *clipboard) = nullptr; +inline GtkSelectionData* (*gtk_clipboard_wait_for_contents)(GtkClipboard *clipboard, GdkAtom target) = nullptr; +inline GdkPixbuf* (*gtk_clipboard_wait_for_image)(GtkClipboard *clipboard) = nullptr; +inline gboolean (*gtk_selection_data_targets_include_image)(const GtkSelectionData *selection_data, gboolean writable) = nullptr; +inline void (*gtk_selection_data_free)(GtkSelectionData *data) = nullptr; +inline GtkWidget* (*gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED = nullptr; +inline gboolean (*gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename) = nullptr; +inline gchar* (*gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser) = nullptr; +inline void (*gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name) = nullptr; +inline gboolean (*gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename) = nullptr; +inline GSList* (*gtk_file_chooser_get_filenames)(GtkFileChooser *chooser) = nullptr; +inline void (*gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr; +inline GtkFileFilter* (*gtk_file_chooser_get_filter)(GtkFileChooser *chooser) = nullptr; +inline void (*gtk_window_set_title)(GtkWindow *window, const gchar *title) = nullptr; +inline void (*gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only) = nullptr; +inline void (*gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action) = nullptr; +inline void (*gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple) = nullptr; +inline void (*gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation) = nullptr; +inline GtkWidget* (*gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id) = nullptr; +inline void (*gtk_button_set_label)(GtkButton *button, const gchar *label) = nullptr; +inline void (*gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr; +inline void (*gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name) = nullptr; +inline void (*gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern) = nullptr; +inline void (*gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr; +inline void (*gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget) = nullptr; +inline gchar* (*gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser) = nullptr; +inline void (*gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active) = nullptr; +inline GtkFileFilter* (*gtk_file_filter_new)(void) = nullptr; +inline GtkWidget* (*gtk_image_new)(void) = nullptr; +inline void (*gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf) = nullptr; +inline GtkWidget* (*gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file) = nullptr; +inline GAppInfo* (*gtk_app_chooser_get_app_info)(GtkAppChooser *self) = nullptr; +inline void (*gdk_set_allowed_backends)(const gchar *backends) = nullptr; +inline void (*gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal) = nullptr; +inline void (*gdk_window_focus)(GdkWindow *window, guint32 timestamp) = nullptr; + +template +inline Result *g_type_cic_helper(Object *instance, GType iface_type) { + return reinterpret_cast(g_type_check_instance_cast(reinterpret_cast(instance), iface_type)); +} + +inline GType (*gtk_dialog_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkDialog *gtk_dialog_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_dialog_get_type()); +} + +inline GType (*gtk_file_chooser_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_file_chooser_get_type()); +} + +inline GType (*gtk_image_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkImage *gtk_image_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_image_get_type()); +} + +inline GType (*gtk_button_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkButton *gtk_button_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_button_get_type()); +} + +inline GType (*gtk_window_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkWindow *gtk_window_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_window_get_type()); +} + +inline GType (*gtk_app_chooser_get_type)(void) G_GNUC_CONST = nullptr; +template +inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_app_chooser_get_type()); +} + +template +inline bool g_type_cit_helper(Object *instance, GType iface_type) { + if (!instance) return false; + + auto ginstance = reinterpret_cast(instance); + if (ginstance->g_class && ginstance->g_class->g_type == iface_type) { + return true; + } + return g_type_check_instance_is_a(ginstance, iface_type); +} + +inline gint (*gtk_dialog_run)(GtkDialog *dialog) = nullptr; +inline GdkAtom (*gdk_atom_intern)(const gchar *atom_name, gboolean only_if_exists) = nullptr; +inline GdkPixbuf* (*gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error) = nullptr; +inline gboolean (*gdk_pixbuf_get_has_alpha)(const GdkPixbuf *pixbuf) = nullptr; +inline guchar* (*gdk_pixbuf_get_pixels)(const GdkPixbuf *pixbuf) = nullptr; +inline int (*gdk_pixbuf_get_width)(const GdkPixbuf *pixbuf) = nullptr; +inline int (*gdk_pixbuf_get_height)(const GdkPixbuf *pixbuf) = nullptr; +inline int (*gdk_pixbuf_get_rowstride)(const GdkPixbuf *pixbuf) = nullptr; + +} // namespace Gtk +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp deleted file mode 100644 index 2dbddebb8..000000000 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/* -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/linux_libs.h" - -#include "base/platform/base_platform_info.h" -#include "platform/linux/linux_xlib_helper.h" -#include "platform/linux/linux_gdk_helper.h" -#include "platform/linux/specific_linux.h" -#include "core/sandbox.h" -#include "core/core_settings.h" -#include "core/application.h" -#include "main/main_domain.h" -#include "mainwindow.h" - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -using Platform::internal::XErrorHandlerRestorer; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -namespace Platform { -namespace Libs { -namespace { - -bool gtkTriedToInit = false; -bool gtkLoaded = false; - -bool loadLibrary(QLibrary &lib, const char *name, int version) { -#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY - return true; -#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY - DEBUG_LOG(("Loading '%1' with version %2...").arg(QLatin1String(name)).arg(version)); - lib.setFileNameAndVersion(QLatin1String(name), version); - if (lib.load()) { - DEBUG_LOG(("Loaded '%1' with version %2!").arg(QLatin1String(name)).arg(version)); - return true; - } - lib.setFileNameAndVersion(QLatin1String(name), QString()); - if (lib.load()) { - DEBUG_LOG(("Loaded '%1' without version!").arg(QLatin1String(name))); - return true; - } - LOG(("Could not load '%1' with version %2 :(").arg(QLatin1String(name)).arg(version)); - return false; -#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY -} - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -void gtkMessageHandler( - const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, - gpointer unused_data) { - // Silence false-positive Gtk warnings (we are using Xlib to set - // the WM_TRANSIENT_FOR hint). - if (message != qstr("GtkDialog mapped without a transient parent. " - "This is discouraged.")) { - // For other messages, call the default handler. - g_log_default_handler(log_domain, log_level, message, unused_data); - } -} - -bool setupGtkBase(QLibrary &lib_gtk) { - if (!LOAD_SYMBOL(lib_gtk, "gtk_init_check", gtk_init_check)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_check_version", gtk_check_version)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_settings_get_default", gtk_settings_get_default)) return false; - - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide", gtk_widget_hide)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_get_window", gtk_widget_get_window)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_image", gtk_clipboard_wait_for_image)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_selection_data_targets_include_image", gtk_selection_data_targets_include_image)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_selection_data_free", gtk_selection_data_free)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_select_filename", gtk_file_chooser_select_filename)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_filenames", gtk_file_chooser_get_filenames)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_filter", gtk_file_chooser_set_filter)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_filter", gtk_file_chooser_get_filter)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_window_get_type", gtk_window_get_type)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_window_set_title", gtk_window_set_title)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_local_only", gtk_file_chooser_set_local_only)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_action", gtk_file_chooser_set_action)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_select_multiple", gtk_file_chooser_set_select_multiple)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_do_overwrite_confirmation", gtk_file_chooser_set_do_overwrite_confirmation)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_remove_filter", gtk_file_chooser_remove_filter)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_image_new", gtk_image_new)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false; - - if (!LOAD_SYMBOL(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_dialog_get_type", gtk_dialog_get_type)) return false; - if (!LOAD_SYMBOL(lib_gtk, "gtk_dialog_run", gtk_dialog_run)) return false; - - if (!LOAD_SYMBOL(lib_gtk, "gdk_atom_intern", gdk_atom_intern)) return false; - - if (LOAD_SYMBOL(lib_gtk, "gdk_set_allowed_backends", gdk_set_allowed_backends)) { - // We work only with Wayland and X11 GDK backends. - // Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call. - // See https://github.com/telegramdesktop/tdesktop/issues/3176 - // See https://github.com/telegramdesktop/tdesktop/issues/3162 - if(IsWayland()) { - DEBUG_LOG(("Limit allowed GDK backends to wayland,x11")); - gdk_set_allowed_backends("wayland,x11"); - } else { - DEBUG_LOG(("Limit allowed GDK backends to x11,wayland")); - gdk_set_allowed_backends("x11,wayland"); - } - } - - // gtk_init will reset the Xlib error handler, and that causes - // Qt applications to quit on X errors. Therefore, we need to manually restore it. - XErrorHandlerRestorer handlerRestorer; - - DEBUG_LOG(("Library gtk functions loaded!")); - gtkTriedToInit = true; - if (!gtk_init_check(0, 0)) { - gtk_init_check = nullptr; - DEBUG_LOG(("Failed to gtk_init_check(0, 0)!")); - return false; - } - DEBUG_LOG(("Checked gtk with gtk_init_check!")); - - // Use our custom log handler. - g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, gtkMessageHandler, nullptr); - - return true; -} - -bool IconThemeShouldBeSet() { - // change the icon theme only if it isn't already set by a platformtheme plugin - // if QT_QPA_PLATFORMTHEME=(gtk2|gtk3), then force-apply the icon theme - static const auto Result = - // QGenericUnixTheme - (QIcon::themeName() == qstr("hicolor") - && QIcon::fallbackThemeName() == qstr("hicolor")) - // QGnomeTheme - || (QIcon::themeName() == qstr("Adwaita") - && QIcon::fallbackThemeName() == qstr("gnome")) - // qt5ct - || (QIcon::themeName().isEmpty() - && QIcon::fallbackThemeName().isEmpty()) - || IsGtkIntegrationForced(); - - return Result; -} - -bool CursorSizeShouldBeSet() { - // change the cursor size only on Wayland and if it wasn't already set - static const auto Result = IsWayland() - && qEnvironmentVariableIsEmpty("XCURSOR_SIZE"); - - return Result; -} - -void SetIconTheme() { - Core::Sandbox::Instance().customEnterFromEventLoop([] { - if (GtkSettingSupported() - && GtkLoaded() - && IconThemeShouldBeSet()) { - DEBUG_LOG(("Setting GTK icon theme")); - QIcon::setThemeName(GtkSetting("gtk-icon-theme-name")); - QIcon::setFallbackThemeName(GtkSetting("gtk-fallback-icon-theme")); - - DEBUG_LOG(("New icon theme: %1").arg(QIcon::themeName())); - DEBUG_LOG(("New fallback icon theme: %1").arg(QIcon::fallbackThemeName())); - - SetApplicationIcon(Window::CreateIcon()); - if (App::wnd()) { - App::wnd()->setWindowIcon(Window::CreateIcon()); - } - - Core::App().domain().notifyUnreadBadgeChanged(); - } - }); -} - -void SetCursorSize() { - Core::Sandbox::Instance().customEnterFromEventLoop([] { - if (GtkSettingSupported() - && GtkLoaded() - && CursorSizeShouldBeSet()) { - DEBUG_LOG(("Setting GTK cursor size")); - - const auto newCursorSize = GtkSetting("gtk-cursor-theme-size"); - qputenv("XCURSOR_SIZE", QByteArray::number(newCursorSize)); - - DEBUG_LOG(("New cursor size: %1").arg(newCursorSize)); - } - }); -} - -void DarkModeChanged() { - Core::Sandbox::Instance().customEnterFromEventLoop([] { - Core::App().settings().setSystemDarkMode(IsDarkMode()); - }); -} - -void DecorationLayoutChanged() { - Core::Sandbox::Instance().customEnterFromEventLoop([] { - Core::App().settings().setWindowControlsLayout(WindowControlsLayout()); - }); -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -f_gtk_init_check gtk_init_check = nullptr; -f_gtk_check_version gtk_check_version = nullptr; -f_gtk_settings_get_default gtk_settings_get_default = nullptr; -f_gtk_widget_show gtk_widget_show = nullptr; -f_gtk_widget_hide gtk_widget_hide = nullptr; -f_gtk_widget_get_window gtk_widget_get_window = nullptr; -f_gtk_widget_realize gtk_widget_realize = nullptr; -f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete = nullptr; -f_gtk_widget_destroy gtk_widget_destroy = nullptr; -f_gtk_clipboard_get gtk_clipboard_get = nullptr; -f_gtk_clipboard_store gtk_clipboard_store = nullptr; -f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents = nullptr; -f_gtk_clipboard_wait_for_image gtk_clipboard_wait_for_image = nullptr; -f_gtk_selection_data_targets_include_image gtk_selection_data_targets_include_image = nullptr; -f_gtk_selection_data_free gtk_selection_data_free = nullptr; -f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new = nullptr; -f_gtk_file_chooser_get_type gtk_file_chooser_get_type = nullptr; -f_gtk_image_get_type gtk_image_get_type = nullptr; -f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder = nullptr; -f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder = nullptr; -f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name = nullptr; -f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename = nullptr; -f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames = nullptr; -f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter = nullptr; -f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter = nullptr; -f_gtk_window_get_type gtk_window_get_type = nullptr; -f_gtk_window_set_title gtk_window_set_title = nullptr; -f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only = nullptr; -f_gtk_file_chooser_set_action gtk_file_chooser_set_action = nullptr; -f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple = nullptr; -f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation = nullptr; -f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter = nullptr; -f_gtk_file_filter_set_name gtk_file_filter_set_name = nullptr; -f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern = nullptr; -f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter = nullptr; -f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget = nullptr; -f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename = nullptr; -f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active = nullptr; -f_gtk_file_filter_new gtk_file_filter_new = nullptr; -f_gtk_image_new gtk_image_new = nullptr; -f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf = nullptr; -f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response = nullptr; -f_gtk_button_set_label gtk_button_set_label = nullptr; -f_gtk_button_get_type gtk_button_get_type = nullptr; -f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new = nullptr; -f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info = nullptr; -f_gtk_app_chooser_get_type gtk_app_chooser_get_type = nullptr; -f_gdk_set_allowed_backends gdk_set_allowed_backends = nullptr; -f_gdk_window_set_modal_hint gdk_window_set_modal_hint = nullptr; -f_gdk_window_focus gdk_window_focus = nullptr; -f_gtk_dialog_get_type gtk_dialog_get_type = nullptr; -f_gtk_dialog_run gtk_dialog_run = nullptr; -f_gdk_atom_intern gdk_atom_intern = nullptr; -f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size = nullptr; -f_gdk_pixbuf_get_has_alpha gdk_pixbuf_get_has_alpha = nullptr; -f_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels = nullptr; -f_gdk_pixbuf_get_width gdk_pixbuf_get_width = nullptr; -f_gdk_pixbuf_get_height gdk_pixbuf_get_height = nullptr; -f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride = nullptr; - -bool GtkLoaded() { - return gtkLoaded; -} - -::GtkClipboard *GtkClipboard() { - if (gtk_clipboard_get != nullptr) { - return gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); - } - - return nullptr; -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -void start() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (!UseGtkIntegration()) { - return; - } - - DEBUG_LOG(("Loading libraries")); - - QLibrary lib_gtk; - lib_gtk.setLoadHints(QLibrary::DeepBindHint); - - if (loadLibrary(lib_gtk, "gtk-3", 0)) { - gtkLoaded = setupGtkBase(lib_gtk); - } - if (!gtkLoaded && !gtkTriedToInit && loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { - gtkLoaded = setupGtkBase(lib_gtk); - } - - if (gtkLoaded) { - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size); - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_has_alpha", gdk_pixbuf_get_has_alpha); - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_pixels", gdk_pixbuf_get_pixels); - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_width", gdk_pixbuf_get_width); - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_height", gdk_pixbuf_get_height); - LOAD_SYMBOL(lib_gtk, "gdk_pixbuf_get_rowstride", gdk_pixbuf_get_rowstride); - - internal::GdkHelperLoad(lib_gtk); - - LOAD_SYMBOL(lib_gtk, "gtk_dialog_get_widget_for_response", gtk_dialog_get_widget_for_response); - LOAD_SYMBOL(lib_gtk, "gtk_button_set_label", gtk_button_set_label); - LOAD_SYMBOL(lib_gtk, "gtk_button_get_type", gtk_button_get_type); - - LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_dialog_new", gtk_app_chooser_dialog_new); - LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_app_info", gtk_app_chooser_get_app_info); - LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_type", gtk_app_chooser_get_type); - - SetIconTheme(); - SetCursorSize(); - - const auto settings = gtk_settings_get_default(); - g_signal_connect(settings, "notify::gtk-icon-theme-name", G_CALLBACK(SetIconTheme), nullptr); - g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(DarkModeChanged), nullptr); - g_signal_connect(settings, "notify::gtk-cursor-theme-size", G_CALLBACK(SetCursorSize), nullptr); - - if (!gtk_check_version(3, 0, 0)) { - g_signal_connect(settings, "notify::gtk-application-prefer-dark-theme", G_CALLBACK(DarkModeChanged), nullptr); - } - - if (!gtk_check_version(3, 12, 0)) { - g_signal_connect(settings, "notify::gtk-decoration-layout", G_CALLBACK(DecorationLayoutChanged), nullptr); - } - } else { - LOG(("Could not load gtk-3 or gtk-x11-2.0!")); - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -} - -} // namespace Libs -} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h deleted file mode 100644 index b9661512b..000000000 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ /dev/null @@ -1,303 +0,0 @@ -/* -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 -*/ -#pragma once - -#include - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - -extern "C" { -#undef signals -#include -#include -#define signals public -} // extern "C" - -// present starting with gtk 3.0, we can build with gtk2 headers -typedef struct _GtkAppChooser GtkAppChooser; - -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY -#define LOAD_SYMBOL(lib, name, func) (func = ::func) -#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY -#define LOAD_SYMBOL Platform::Libs::load -#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY - -namespace Platform { -namespace Libs { - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -bool GtkLoaded(); -::GtkClipboard *GtkClipboard(); -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -void start(); - -template -bool load(QLibrary &lib, const char *name, Function &func) { - func = nullptr; - if (!lib.isLoaded()) { - return false; - } - - func = reinterpret_cast(lib.resolve(name)); - if (func) { - return true; - } - LOG(("Error: failed to load '%1' function!").arg(name)); - return false; -} - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -typedef gboolean (*f_gtk_init_check)(int *argc, char ***argv); -extern f_gtk_init_check gtk_init_check; - -typedef const gchar* (*f_gtk_check_version)(guint required_major, guint required_minor, guint required_micro); -extern f_gtk_check_version gtk_check_version; - -typedef GtkSettings* (*f_gtk_settings_get_default)(void); -extern f_gtk_settings_get_default gtk_settings_get_default; - -typedef void (*f_gtk_widget_show)(GtkWidget *widget); -extern f_gtk_widget_show gtk_widget_show; - -typedef void (*f_gtk_widget_hide)(GtkWidget *widget); -extern f_gtk_widget_hide gtk_widget_hide; - -typedef GdkWindow* (*f_gtk_widget_get_window)(GtkWidget *widget); -extern f_gtk_widget_get_window gtk_widget_get_window; - -typedef void (*f_gtk_widget_realize)(GtkWidget *widget); -extern f_gtk_widget_realize gtk_widget_realize; - -typedef gboolean (*f_gtk_widget_hide_on_delete)(GtkWidget *widget); -extern f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete; - -typedef void (*f_gtk_widget_destroy)(GtkWidget *widget); -extern f_gtk_widget_destroy gtk_widget_destroy; - -typedef ::GtkClipboard* (*f_gtk_clipboard_get)(GdkAtom selection); -extern f_gtk_clipboard_get gtk_clipboard_get; - -typedef void (*f_gtk_clipboard_store)(::GtkClipboard *clipboard); -extern f_gtk_clipboard_store gtk_clipboard_store; - -typedef GtkSelectionData* (*f_gtk_clipboard_wait_for_contents)(::GtkClipboard *clipboard, GdkAtom target); -extern f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents; - -typedef GdkPixbuf* (*f_gtk_clipboard_wait_for_image)(::GtkClipboard *clipboard); -extern f_gtk_clipboard_wait_for_image gtk_clipboard_wait_for_image; - -typedef gboolean (*f_gtk_selection_data_targets_include_image)(const GtkSelectionData *selection_data, gboolean writable); -extern f_gtk_selection_data_targets_include_image gtk_selection_data_targets_include_image; - -typedef void (*f_gtk_selection_data_free)(GtkSelectionData *data); -extern f_gtk_selection_data_free gtk_selection_data_free; - -typedef GtkWidget* (*f_gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED; -extern f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new; - -typedef gboolean (*f_gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename); -extern f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder; - -typedef gchar* (*f_gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser); -extern f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder; - -typedef void (*f_gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name); -extern f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name; - -typedef gboolean (*f_gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename); -extern f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename; - -typedef GSList* (*f_gtk_file_chooser_get_filenames)(GtkFileChooser *chooser); -extern f_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames; - -typedef void (*f_gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); -extern f_gtk_file_chooser_set_filter gtk_file_chooser_set_filter; - -typedef GtkFileFilter* (*f_gtk_file_chooser_get_filter)(GtkFileChooser *chooser); -extern f_gtk_file_chooser_get_filter gtk_file_chooser_get_filter; - -typedef void (*f_gtk_window_set_title)(GtkWindow *window, const gchar *title); -extern f_gtk_window_set_title gtk_window_set_title; - -typedef void (*f_gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only); -extern f_gtk_file_chooser_set_local_only gtk_file_chooser_set_local_only; - -typedef void (*f_gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action); -extern f_gtk_file_chooser_set_action gtk_file_chooser_set_action; - -typedef void (*f_gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple); -extern f_gtk_file_chooser_set_select_multiple gtk_file_chooser_set_select_multiple; - -typedef void (*f_gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation); -extern f_gtk_file_chooser_set_do_overwrite_confirmation gtk_file_chooser_set_do_overwrite_confirmation; - -typedef GtkWidget* (*f_gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id); -extern f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response; - -typedef void (*f_gtk_button_set_label)(GtkButton *button, const gchar *label); -extern f_gtk_button_set_label gtk_button_set_label; - -typedef void (*f_gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); -extern f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter; - -typedef void (*f_gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name); -extern f_gtk_file_filter_set_name gtk_file_filter_set_name; - -typedef void (*f_gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern); -extern f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern; - -typedef void (*f_gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); -extern f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter; - -typedef void (*f_gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget); -extern f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget; - -typedef gchar* (*f_gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser); -extern f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename; - -typedef void (*f_gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active); -extern f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active; - -typedef GtkFileFilter* (*f_gtk_file_filter_new)(void); -extern f_gtk_file_filter_new gtk_file_filter_new; - -typedef GtkWidget* (*f_gtk_image_new)(void); -extern f_gtk_image_new gtk_image_new; - -typedef void (*f_gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf); -extern f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf; - -typedef GtkWidget* (*f_gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file); -extern f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new; - -typedef GAppInfo* (*f_gtk_app_chooser_get_app_info)(GtkAppChooser *self); -extern f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info; - -typedef void (*f_gdk_set_allowed_backends)(const gchar *backends); -extern f_gdk_set_allowed_backends gdk_set_allowed_backends; - -typedef void (*f_gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal); -extern f_gdk_window_set_modal_hint gdk_window_set_modal_hint; - -typedef void (*f_gdk_window_focus)(GdkWindow *window, guint32 timestamp); -extern f_gdk_window_focus gdk_window_focus; - -template -inline Result *g_type_cic_helper(Object *instance, GType iface_type) { - return reinterpret_cast(g_type_check_instance_cast(reinterpret_cast(instance), iface_type)); -} - -typedef GType (*f_gtk_dialog_get_type)(void) G_GNUC_CONST; -extern f_gtk_dialog_get_type gtk_dialog_get_type; - -template -inline GtkDialog *gtk_dialog_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_dialog_get_type()); -} - -typedef GType (*f_gtk_file_chooser_get_type)(void) G_GNUC_CONST; -extern f_gtk_file_chooser_get_type gtk_file_chooser_get_type; - -template -inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_file_chooser_get_type()); -} - -typedef GType (*f_gtk_image_get_type)(void) G_GNUC_CONST; -extern f_gtk_image_get_type gtk_image_get_type; - -template -inline GtkImage *gtk_image_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_image_get_type()); -} - -typedef GType (*f_gtk_button_get_type)(void) G_GNUC_CONST; -extern f_gtk_button_get_type gtk_button_get_type; - -template -inline GtkButton *gtk_button_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_button_get_type()); -} - -typedef GType (*f_gtk_window_get_type)(void) G_GNUC_CONST; -extern f_gtk_window_get_type gtk_window_get_type; - -template -inline GtkWindow *gtk_window_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_window_get_type()); -} - -typedef GType (*f_gtk_app_chooser_get_type)(void) G_GNUC_CONST; -extern f_gtk_app_chooser_get_type gtk_app_chooser_get_type; - -template -inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) { - return g_type_cic_helper(obj, gtk_app_chooser_get_type()); -} - -template -inline bool g_type_cit_helper(Object *instance, GType iface_type) { - if (!instance) return false; - - auto ginstance = reinterpret_cast(instance); - if (ginstance->g_class && ginstance->g_class->g_type == iface_type) { - return true; - } - return g_type_check_instance_is_a(ginstance, iface_type); -} - -typedef gint (*f_gtk_dialog_run)(GtkDialog *dialog); -extern f_gtk_dialog_run gtk_dialog_run; - -typedef GdkAtom (*f_gdk_atom_intern)(const gchar *atom_name, gboolean only_if_exists); -extern f_gdk_atom_intern gdk_atom_intern; - -typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error); -extern f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size; - -typedef gboolean (*f_gdk_pixbuf_get_has_alpha)(const GdkPixbuf *pixbuf); -extern f_gdk_pixbuf_get_has_alpha gdk_pixbuf_get_has_alpha; - -typedef guchar* (*f_gdk_pixbuf_get_pixels)(const GdkPixbuf *pixbuf); -extern f_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels; - -typedef int (*f_gdk_pixbuf_get_width)(const GdkPixbuf *pixbuf); -extern f_gdk_pixbuf_get_width gdk_pixbuf_get_width; - -typedef int (*f_gdk_pixbuf_get_height)(const GdkPixbuf *pixbuf); -extern f_gdk_pixbuf_get_height gdk_pixbuf_get_height; - -typedef int (*f_gdk_pixbuf_get_rowstride)(const GdkPixbuf *pixbuf); -extern f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride; - -inline bool GtkSettingSupported() { - return gtk_settings_get_default != nullptr; -} - -template -inline T GtkSetting(const gchar *propertyName) { - GtkSettings *settings = gtk_settings_get_default(); - T value; - g_object_get(settings, propertyName, &value, nullptr); - return value; -} - -inline QString GtkSetting(const gchar *propertyName) { - gchararray value = GtkSetting(propertyName); - QString str = QString::fromUtf8(value); - g_free(value); - DEBUG_LOG(("Getting GTK setting, %1: '%2'").arg(propertyName).arg(str)); - return str; -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - -} // namespace Libs -} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp new file mode 100644 index 000000000..63ffba923 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.cpp @@ -0,0 +1,132 @@ +/* +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/linux_open_with_dialog.h" + +#include "platform/linux/linux_gtk_integration_p.h" +#include "platform/linux/linux_gdk_helper.h" +#include "window/window_controller.h" +#include "core/application.h" + +#include + +namespace Platform { +namespace File { +namespace internal { +namespace { + +using namespace Platform::Gtk; + +bool OpenWithDialogSupported() { + return Platform::internal::GdkHelperLoaded() + && (gtk_app_chooser_dialog_new != nullptr) + && (gtk_app_chooser_get_app_info != nullptr) + && (gtk_app_chooser_get_type != nullptr) + && (gtk_widget_get_window != nullptr) + && (gtk_widget_realize != nullptr) + && (gtk_widget_show != nullptr) + && (gtk_widget_destroy != nullptr); +} + +class OpenWithDialog : public QWindow { +public: + OpenWithDialog(const QString &filepath); + ~OpenWithDialog(); + + bool exec(); + +private: + static void handleResponse(OpenWithDialog *dialog, int responseId); + + GFile *_gfileInstance = nullptr; + GtkWidget *_gtkWidget = nullptr; + QEventLoop _loop; + std::optional _result; +}; + +OpenWithDialog::OpenWithDialog(const QString &filepath) +: _gfileInstance(g_file_new_for_path(filepath.toUtf8())) +, _gtkWidget(gtk_app_chooser_dialog_new( + nullptr, + GTK_DIALOG_MODAL, + _gfileInstance)) { + g_signal_connect_swapped( + _gtkWidget, + "response", + G_CALLBACK(handleResponse), + this); +} + +OpenWithDialog::~OpenWithDialog() { + gtk_widget_destroy(_gtkWidget); + g_object_unref(_gfileInstance); +} + +bool OpenWithDialog::exec() { + gtk_widget_realize(_gtkWidget); + + if (const auto activeWindow = Core::App().activeWindow()) { + Platform::internal::XSetTransientForHint( + gtk_widget_get_window(_gtkWidget), + activeWindow->widget().get()->windowHandle()->winId()); + } + + QGuiApplicationPrivate::showModalWindow(this); + gtk_widget_show(_gtkWidget); + + if (!_result.has_value()) { + _loop.exec(); + } + + QGuiApplicationPrivate::hideModalWindow(this); + return *_result; +} + +void OpenWithDialog::handleResponse(OpenWithDialog *dialog, int responseId) { + GAppInfo *chosenAppInfo = nullptr; + dialog->_result = true; + + switch (responseId) { + case GTK_RESPONSE_OK: + chosenAppInfo = gtk_app_chooser_get_app_info( + gtk_app_chooser_cast(dialog->_gtkWidget)); + + if (chosenAppInfo) { + GList *uris = nullptr; + uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance)); + g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr); + g_list_free(uris); + g_object_unref(chosenAppInfo); + } + + break; + + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + break; + + default: + dialog->_result = false; + break; + } + + dialog->_loop.quit(); +} + +} // namespace + +bool ShowOpenWithDialog(const QString &filepath) { + if (!OpenWithDialogSupported()) { + return false; + } + + return OpenWithDialog(filepath).exec(); +} + +} // namespace internal +} // namespace File +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h b/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h new file mode 100644 index 000000000..3a29f6b09 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_open_with_dialog.h @@ -0,0 +1,18 @@ +/* +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 +*/ +#pragma once + +namespace Platform { +namespace File { +namespace internal { + +bool ShowOpenWithDialog(const QString &filepath); + +} // namespace internal +} // namespace File +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_xlib_helper.cpp b/Telegram/SourceFiles/platform/linux/linux_xlib_helper.cpp index 3396f1763..270f0ae8f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xlib_helper.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xlib_helper.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_xlib_helper.h" -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION extern "C" { #include } @@ -37,4 +36,3 @@ XErrorHandlerRestorer::~XErrorHandlerRestorer() = default; } // namespace internal } // namespace Platform -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/linux_xlib_helper.h b/Telegram/SourceFiles/platform/linux/linux_xlib_helper.h index def4c9bba..abd172066 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xlib_helper.h +++ b/Telegram/SourceFiles/platform/linux/linux_xlib_helper.h @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION namespace Platform { namespace internal { @@ -23,4 +22,3 @@ private: } // namespace internal } // namespace Platform -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index e25200c24..80820c163 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -522,6 +522,11 @@ void ForceDisabled(QAction *action, bool disabled) { MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) { +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } void MainWindow::initHook() { @@ -911,14 +916,6 @@ void MainWindow::updateWaylandDecorationColors() { windowHandle()->resize(windowHandle()->size()); } -void MainWindow::LibsLoaded() { -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION -} - void MainWindow::initTrayMenuHook() { _trayIconMenuXEmbed.emplace(nullptr, trayIconMenu); _trayIconMenuXEmbed->deleteOnHide(false); diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 62ac1e2fc..ee7ecbee5 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -46,8 +46,6 @@ public: bool isActiveForTrayMenu() override; - static void LibsLoaded(); - ~MainWindow(); protected: diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 3ba1f7994..578dbba84 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/specific_linux.h" -#include "platform/linux/linux_libs.h" +#include "platform/linux/linux_gtk_integration.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_xcb_utilities_linux.h" #include "base/qt_adapters.h" @@ -68,11 +68,11 @@ extern "C" { using namespace Platform; using Platform::File::internal::EscapeShell; using Platform::internal::WaylandIntegration; +using Platform::internal::GtkIntegration; namespace Platform { namespace { -constexpr auto kDisableGtkIntegration = "TDESKTOP_DISABLE_GTK_INTEGRATION"_cs; constexpr auto kIgnoreGtkIncompatibility = "TDESKTOP_I_KNOW_ABOUT_GTK_INCOMPATIBILITY"_cs; constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs; @@ -332,21 +332,6 @@ bool GenerateDesktopFile( } } -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -bool GetImageFromClipboardSupported() { - return (Libs::gtk_clipboard_wait_for_contents != nullptr) - && (Libs::gtk_clipboard_wait_for_image != nullptr) - && (Libs::gtk_selection_data_targets_include_image != nullptr) - && (Libs::gtk_selection_data_free != nullptr) - && (Libs::gdk_pixbuf_get_pixels != nullptr) - && (Libs::gdk_pixbuf_get_width != nullptr) - && (Libs::gdk_pixbuf_get_height != nullptr) - && (Libs::gdk_pixbuf_get_rowstride != nullptr) - && (Libs::gdk_pixbuf_get_has_alpha != nullptr) - && (Libs::gdk_atom_intern != nullptr); -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - uint XCBMoveResizeFromEdges(Qt::Edges edges) { if (edges == (Qt::TopEdge | Qt::LeftEdge)) return 0; @@ -564,28 +549,17 @@ bool IsStaticBinary() { #endif // !DESKTOP_APP_USE_PACKAGED } -bool UseGtkIntegration() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - static const auto Result = !qEnvironmentVariableIsSet( - kDisableGtkIntegration.utf8()); - - return Result; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - - return false; -} - bool IsGtkIntegrationForced() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION static const auto Result = [&] { + if (!GtkIntegration::Instance()) { + return false; + } + return PlatformThemes.contains(qstr("gtk3"), Qt::CaseInsensitive) || PlatformThemes.contains(qstr("gtk2"), Qt::CaseInsensitive); }(); return Result; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - - return false; } bool AreQtPluginsBundled() { @@ -723,64 +697,38 @@ QString GetIconName() { } QImage GetImageFromClipboard() { - QImage data; - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (!GetImageFromClipboardSupported() || !Libs::GtkClipboard()) { - return data; + if (const auto integration = GtkIntegration::Instance()) { + return integration->getImageFromClipboard(); } - auto gsel = Libs::gtk_clipboard_wait_for_contents( - Libs::GtkClipboard(), - Libs::gdk_atom_intern("TARGETS", true)); - - if (gsel) { - if (Libs::gtk_selection_data_targets_include_image(gsel, false)) { - auto img = Libs::gtk_clipboard_wait_for_image( - Libs::GtkClipboard()); - - if (img) { - data = QImage( - Libs::gdk_pixbuf_get_pixels(img), - Libs::gdk_pixbuf_get_width(img), - Libs::gdk_pixbuf_get_height(img), - Libs::gdk_pixbuf_get_rowstride(img), - Libs::gdk_pixbuf_get_has_alpha(img) - ? QImage::Format_RGBA8888 - : QImage::Format_RGB888).copy(); - - g_object_unref(img); - } - } - - Libs::gtk_selection_data_free(gsel); - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - - return data; + return {}; } std::optional IsDarkMode() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (Libs::GtkSettingSupported() && Libs::GtkLoaded()) { - if (Libs::gtk_check_version != nullptr - && !Libs::gtk_check_version(3, 0, 0) - && Libs::GtkSetting( - "gtk-application-prefer-dark-theme")) { - return true; - } - - const auto themeName = Libs::GtkSetting("gtk-theme-name").toLower(); - - if (themeName.contains(qsl("-dark"))) { - return true; - } - - return false; + const auto integration = GtkIntegration::Instance(); + if (!integration) { + return std::nullopt; } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - return std::nullopt; + if (integration->checkVersion(3, 0, 0)) { + const auto preferDarkTheme = integration->getBoolSetting( + qsl("gtk-application-prefer-dark-theme")); + + if (!preferDarkTheme.has_value()) { + return std::nullopt; + } else if (*preferDarkTheme) { + return true; + } + } + + const auto themeName = integration->getStringSetting(qsl("gtk-theme-name")); + if (!themeName.has_value()) { + return std::nullopt; + } else if (themeName->toLower().contains(qsl("-dark"))) { + return true; + } + + return false; } bool AutostartSupported() { @@ -848,38 +796,44 @@ bool WindowsNeedShadow() { } Window::ControlsLayout WindowControlsLayout() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (Libs::GtkSettingSupported() - && Libs::GtkLoaded() - && Libs::gtk_check_version != nullptr - && !Libs::gtk_check_version(3, 12, 0)) { - const auto decorationLayout = Libs::GtkSetting( - "gtk-decoration-layout").split(':'); + const auto gtkResult = []() -> std::optional { + const auto integration = GtkIntegration::Instance(); + if (!integration || !integration->checkVersion(3, 12, 0)) { + return std::nullopt; + } + + const auto decorationLayoutSetting = integration->getStringSetting( + qsl("gtk-decoration-layout")); + + if (!decorationLayoutSetting.has_value()) { + return std::nullopt; + } + + const auto decorationLayout = decorationLayoutSetting->split(':'); std::vector controlsLeft; ranges::transform( decorationLayout[0].split(','), ranges::back_inserter(controlsLeft), - GtkKeywordToWindowControl - ); + GtkKeywordToWindowControl); std::vector controlsRight; if (decorationLayout.size() > 1) { ranges::transform( decorationLayout[1].split(','), ranges::back_inserter(controlsRight), - GtkKeywordToWindowControl - ); + GtkKeywordToWindowControl); } return Window::ControlsLayout{ .left = controlsLeft, .right = controlsRight }; - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION + }(); - if (DesktopEnvironment::IsUnity()) { + if (gtkResult.has_value()) { + return *gtkResult; + } else if (DesktopEnvironment::IsUnity()) { return Window::ControlsLayout{ .left = { Window::Control::Close, @@ -983,7 +937,7 @@ void start() { // if gtk integration and qgtk3/qgtk2 platformtheme (or qgtk2 style) // is used at the same time, the app will crash - if (UseGtkIntegration() + if (GtkIntegration::Instance() && !IsStaticBinary() && !qEnvironmentVariableIsSet( kIgnoreGtkIncompatibility.utf8())) { @@ -1004,7 +958,7 @@ void start() { "Keep in mind that this will lead to clipboard issues " "and tdesktop will be unable to get settings from GTK " "(such as decoration layout, dark mode & more).", - kDisableGtkIntegration.utf8().constData()); + internal::kDisableGtkIntegration.utf8().constData()); qunsetenv("QT_QPA_PLATFORMTHEME"); qunsetenv("QT_STYLE_OVERRIDE"); @@ -1015,7 +969,7 @@ void start() { } } - if (!UseGtkIntegration()) { + if (!GtkIntegration::Instance()) { g_warning( "GTK integration was disabled on build or in runtime. " "This will lead to clipboard issues and a lack of some features " @@ -1240,8 +1194,9 @@ void start() { DEBUG_LOG(("Icon theme: %1").arg(QIcon::themeName())); DEBUG_LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName())); - Libs::start(); - MainWindow::LibsLoaded(); + if (const auto integration = GtkIntegration::Instance()) { + return integration->load(); + } // wait for interface announce to know if native window frame is supported if (const auto waylandIntegration = WaylandIntegration::Instance()) { diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 2f279c5d4..39b91baef 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -21,7 +21,6 @@ bool InFlatpak(); bool InSnap(); bool IsStaticBinary(); bool AreQtPluginsBundled(); -bool UseGtkIntegration(); bool IsGtkIntegrationForced(); bool UseXDGDesktopPortal(); bool CanOpenDirectoryWithPortal();