Split GTK integration into a singleton

This commit is contained in:
Ilya Fedin 2021-01-10 07:51:38 +04:00 committed by John Preston
parent bb016e1489
commit ada22ee6cc
21 changed files with 1800 additions and 1629 deletions

View File

@ -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()

View File

@ -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 <QtGui/QDesktopServices>
@ -24,121 +18,10 @@ extern "C" {
#define signals public
} // extern "C"
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
#include <private/qguiapplication_p.h>
extern "C" {
#undef signals
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#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<bool> _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<QWidget> 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<QWidget> 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<QDialog> 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<QUrl> 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

View File

@ -9,15 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_file_utilities.h"
#include <QtGui/QWindow>
#include <QtWidgets/QFileDialog>
#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<QString, GtkFileFilter*> _filters;
QHash<GtkFileFilter*, QString> _filterNames;
QScopedPointer<QGtkDialog> d;
};
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace internal
} // namespace FileDialog
} // namespace Platform

View File

@ -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 <typename Object>
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

View File

@ -7,11 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <QtCore/QObject>
class QLibrary;
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
extern "C" {
#undef signals
#include <gtk/gtk.h>
@ -28,4 +25,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId);
} // namespace internal
} // namespace Platform
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION

View File

@ -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 <QtGui/QWindow>
#include <QtWidgets/QFileDialog>
#include <private/qguiapplication_p.h>
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<QString, GtkFileFilter*> _filters;
QHash<GtkFileFilter*, QString> _filterNames;
QScopedPointer<QGtkDialog> 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<QDialog> 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<QUrl> 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<QWidget> 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

View File

@ -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<QWidget> parent,
QStringList &files,
QByteArray &remoteContent,
const QString &caption,
const QString &filter,
Type type,
QString startFile);
} // namespace Gtk
} // namespace FileDialog
} // namespace Platform

View File

@ -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 <typename T>
std::optional<T> 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<bool> GtkIntegration::getBoolSetting(
const QString &propertyName) const {
const auto value = GtkSetting<gboolean>(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<int> GtkIntegration::getIntSetting(
const QString &propertyName) const {
const auto value = GtkSetting<gint>(propertyName);
if (value.has_value()) {
DEBUG_LOG(("Getting GTK setting, %1: %2")
.arg(propertyName)
.arg(*value));
}
return value;
}
std::optional<QString> GtkIntegration::getStringSetting(
const QString &propertyName) const {
auto value = GtkSetting<gchararray>(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<QWidget> 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

View File

@ -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<bool> getBoolSetting(
const QString &propertyName) const;
[[nodiscard]] std::optional<int> getIntSetting(
const QString &propertyName) const;
[[nodiscard]] std::optional<QString> 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<QWidget> 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

View File

@ -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<bool> GtkIntegration::getBoolSetting(
const QString &propertyName) const {
return std::nullopt;
}
std::optional<int> GtkIntegration::getIntSetting(
const QString &propertyName) const {
return std::nullopt;
}
std::optional<QString> 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<QWidget> 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

View File

@ -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 <QtCore/QLibrary>
extern "C" {
#undef signals
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#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 <typename Function>
bool LoadSymbol(QLibrary &lib, const char *name, Function &func) {
func = nullptr;
if (!lib.isLoaded()) {
return false;
}
func = reinterpret_cast<Function>(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 <typename Result, typename Object>
inline Result *g_type_cic_helper(Object *instance, GType iface_type) {
return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(instance), iface_type));
}
inline GType (*gtk_dialog_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkDialog *gtk_dialog_cast(Object *obj) {
return g_type_cic_helper<GtkDialog, Object>(obj, gtk_dialog_get_type());
}
inline GType (*gtk_file_chooser_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) {
return g_type_cic_helper<GtkFileChooser, Object>(obj, gtk_file_chooser_get_type());
}
inline GType (*gtk_image_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkImage *gtk_image_cast(Object *obj) {
return g_type_cic_helper<GtkImage, Object>(obj, gtk_image_get_type());
}
inline GType (*gtk_button_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkButton *gtk_button_cast(Object *obj) {
return g_type_cic_helper<GtkButton, Object>(obj, gtk_button_get_type());
}
inline GType (*gtk_window_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkWindow *gtk_window_cast(Object *obj) {
return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type());
}
inline GType (*gtk_app_chooser_get_type)(void) G_GNUC_CONST = nullptr;
template <typename Object>
inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) {
return g_type_cic_helper<GtkAppChooser, Object>(obj, gtk_app_chooser_get_type());
}
template <typename Object>
inline bool g_type_cit_helper(Object *instance, GType iface_type) {
if (!instance) return false;
auto ginstance = reinterpret_cast<GTypeInstance*>(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

View File

@ -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<gint>("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

View File

@ -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 <QtCore/QLibrary>
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
extern "C" {
#undef signals
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#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 <typename Function>
bool load(QLibrary &lib, const char *name, Function &func) {
func = nullptr;
if (!lib.isLoaded()) {
return false;
}
func = reinterpret_cast<Function>(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 <typename Result, typename Object>
inline Result *g_type_cic_helper(Object *instance, GType iface_type) {
return reinterpret_cast<Result*>(g_type_check_instance_cast(reinterpret_cast<GTypeInstance*>(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 <typename Object>
inline GtkDialog *gtk_dialog_cast(Object *obj) {
return g_type_cic_helper<GtkDialog, Object>(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 <typename Object>
inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) {
return g_type_cic_helper<GtkFileChooser, Object>(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 <typename Object>
inline GtkImage *gtk_image_cast(Object *obj) {
return g_type_cic_helper<GtkImage, Object>(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 <typename Object>
inline GtkButton *gtk_button_cast(Object *obj) {
return g_type_cic_helper<GtkButton, Object>(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 <typename Object>
inline GtkWindow *gtk_window_cast(Object *obj) {
return g_type_cic_helper<GtkWindow, Object>(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 <typename Object>
inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) {
return g_type_cic_helper<GtkAppChooser, Object>(obj, gtk_app_chooser_get_type());
}
template <typename Object>
inline bool g_type_cit_helper(Object *instance, GType iface_type) {
if (!instance) return false;
auto ginstance = reinterpret_cast<GTypeInstance*>(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 <typename T>
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<gchararray>(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

View File

@ -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 <private/qguiapplication_p.h>
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<bool> _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

View File

@ -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

View File

@ -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 <X11/Xlib.h>
}
@ -37,4 +36,3 @@ XErrorHandlerRestorer::~XErrorHandlerRestorer() = default;
} // namespace internal
} // namespace Platform
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION

View File

@ -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

View File

@ -522,6 +522,11 @@ void ForceDisabled(QAction *action, bool disabled) {
MainWindow::MainWindow(not_null<Window::Controller*> controller)
: Window::MainWindow(controller) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
qDBusRegisterMetaType<ToolTip>();
qDBusRegisterMetaType<IconPixmap>();
qDBusRegisterMetaType<IconPixmapList>();
#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<ToolTip>();
qDBusRegisterMetaType<IconPixmap>();
qDBusRegisterMetaType<IconPixmapList>();
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
void MainWindow::initTrayMenuHook() {
_trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
_trayIconMenuXEmbed->deleteOnHide(false);

View File

@ -46,8 +46,6 @@ public:
bool isActiveForTrayMenu() override;
static void LibsLoaded();
~MainWindow();
protected:

View File

@ -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<bool> 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<gboolean>(
"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<Window::ControlsLayout> {
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<Window::Control> controlsLeft;
ranges::transform(
decorationLayout[0].split(','),
ranges::back_inserter(controlsLeft),
GtkKeywordToWindowControl
);
GtkKeywordToWindowControl);
std::vector<Window::Control> 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()) {

View File

@ -21,7 +21,6 @@ bool InFlatpak();
bool InSnap();
bool IsStaticBinary();
bool AreQtPluginsBundled();
bool UseGtkIntegration();
bool IsGtkIntegrationForced();
bool UseXDGDesktopPortal();
bool CanOpenDirectoryWithPortal();