From 91a2ec225ada7d6c45d5e5c22dfef6c07d2eddc0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 8 Nov 2020 01:28:24 +0400 Subject: [PATCH] Add support for open with on linux --- .../platform/linux/file_utilities_linux.cpp | 96 ++++++++++++++++++- .../platform/linux/file_utilities_linux.h | 4 - .../SourceFiles/platform/linux/linux_libs.cpp | 9 ++ .../SourceFiles/platform/linux/linux_libs.h | 25 +++++ 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 3a74b6207..84fc2b205 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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 @@ -35,6 +37,58 @@ extern "C" { 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_type != nullptr) + && (Libs::gtk_widget_get_window != nullptr) + && (Libs::gtk_widget_realize != nullptr) + && (Libs::gtk_widget_show != nullptr) + && (Libs::gtk_widget_destroy != nullptr); +} + +void HandleAppChooserResponse( + GtkDialog *dialog, + int responseId, + GFile *file) { + GAppInfo *chosenAppInfo = nullptr; + + switch (responseId) { + case GTK_RESPONSE_OK: + chosenAppInfo = Libs::gtk_app_chooser_get_app_info( + Libs::gtk_app_chooser_cast(dialog)); + + if (chosenAppInfo) { + GList *uris = nullptr; + uris = g_list_prepend(uris, g_file_get_uri(file)); + g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr); + g_list_free(uris); + g_object_unref(chosenAppInfo); + } + + g_object_unref(file); + Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog)); + break; + + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + g_object_unref(file); + Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog)); + break; + + default: + break; + } +} +#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION + +} // namespace + namespace internal { QByteArray EscapeShell(const QByteArray &content) { @@ -78,6 +132,44 @@ void UnsafeOpenEmailLink(const QString &email) { UnsafeOpenUrl(qstr("mailto:") + email); } +bool UnsafeShowOpenWith(const QString &filepath) { +#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION + if (InFlatpak() + || InSnap() + || !ShowOpenWithSupported()) { + return false; + } + + const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); + auto gfileInstance = g_file_new_for_path(absolutePath.toUtf8()); + + auto appChooserDialog = Libs::gtk_app_chooser_dialog_new( + nullptr, + GTK_DIALOG_MODAL, + gfileInstance); + + g_signal_connect( + appChooserDialog, + "response", + G_CALLBACK(HandleAppChooserResponse), + gfileInstance); + + Libs::gtk_widget_realize(appChooserDialog); + + if (const auto activeWindow = Core::App().activeWindow()) { + Platform::internal::XSetTransientForHint( + Libs::gtk_widget_get_window(appChooserDialog), + activeWindow->widget().get()->windowHandle()->winId()); + } + + Libs::gtk_widget_show(appChooserDialog); + + return true; +#else // !TDESKTOP_DISABLE_GTK_INTEGRATION + return false; +#endif // TDESKTOP_DISABLE_GTK_INTEGRATION +} + void UnsafeLaunch(const QString &filepath) { const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); @@ -85,7 +177,9 @@ void UnsafeLaunch(const QString &filepath) { g_filename_to_uri(absolutePath.toUtf8(), nullptr, nullptr), nullptr, nullptr)) { - QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); + if (!UnsafeShowOpenWith(filepath)) { + QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); + } } } diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h index 4b57d656e..a0dc9c186 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h @@ -34,10 +34,6 @@ inline bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosit return false; } -inline bool UnsafeShowOpenWith(const QString &filepath) { - return false; -} - inline void PostprocessDownloaded(const QString &filepath) { } diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index 50f53f37a..793b811d7 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -70,6 +70,7 @@ bool setupGtkBase(QLibrary &lib_gtk) { 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_widget_get_type", gtk_widget_get_type)) 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; @@ -211,6 +212,7 @@ 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_widget_get_type gtk_widget_get_type = 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; @@ -246,6 +248,9 @@ 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; @@ -304,6 +309,10 @@ void start() { 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(); const auto settings = gtk_settings_get_default(); diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index a1f383534..cf207d88b 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -18,6 +18,9 @@ extern "C" { #define signals public } // extern "C" +// present start with gtk 3.0, we're building with gtk 2.0 headers +typedef struct _GtkAppChooser GtkAppChooser; + #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION #if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY @@ -172,6 +175,12 @@ 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; @@ -226,6 +235,22 @@ inline GtkWindow *gtk_window_cast(Object *obj) { return g_type_cic_helper(obj, gtk_window_get_type()); } +typedef GType (*f_gtk_widget_get_type)(void) G_GNUC_CONST; +extern f_gtk_widget_get_type gtk_widget_get_type; + +template +inline GtkWidget *gtk_widget_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_widget_get_type()); +} + +typedef GType (*f_gtk_app_chooser_get_type)(void) G_GNUC_CONST; +extern f_gtk_app_chooser_get_type gtk_app_chooser_get_type; + +template +inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_app_chooser_get_type()); +} + template inline bool g_type_cit_helper(Object *instance, GType iface_type) { if (!instance) return false;