Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use GtkFileChooserNative to support the XDG Desktop Portal specification #19159

Merged
merged 50 commits into from
Apr 1, 2021
Merged

Conversation

tristan957
Copy link
Contributor

@tristan957 tristan957 commented Jul 8, 2019

Description of Change

With this commit, users on KDE/plasma will finally have support in Electron for their native file chooser dialogs. Fixes #2911.

Checklist

Release Notes

Notes: Added support for alternative file choosers on Linux through the usage of the XDG Desktop Portal in GtkFileChooserNative.

This PR is backwards compatible to support platforms like Ubuntu 16.04/14.04 whose GTK versions are less than 3.20.

@electron-cation electron-cation bot added the new-pr 🌱 PR opened in the last 24 hours label Jul 8, 2019
@welcome
Copy link

welcome bot commented Jul 8, 2019

💖 Thanks for opening this pull request! 💖

We use semantic commit messages to streamline the release process. Before your pull request can be merged, you should update your pull request title to start with a semantic prefix.

Examples of commit messages with semantic prefixes:

  • fix: don't overwrite prevent_default if default wasn't prevented
  • feat: add app.isPackaged() method
  • docs: app.isDefaultProtocolClient is now available on Linux

Things that will help get your PR across the finish line:

  • Follow the JavaScript, C++, and Python coding style.
  • Run npm run lint locally to catch formatting errors earlier.
  • Document any user-facing changes you've made following the documentation styleguide.
  • Include tests when adding/changing behavior.
  • Include screenshots and animated GIFs whenever possible.

We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 8, 2019

I would appreciate guidance in getting this PR to completion with regard to CI builds, styling, implementation, and testing.

Off the bat, there are a few issues with this PR:

  • I currently am not sure how to cast a GtkFileChooserNative to a GtkNativeDialog
  • Does the include of dlfcn.h need to be guarded in anyway?

I am going to take a look at the Chromium source code, and see what I find.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 8, 2019

Please note that this was not on my dev machine, so I could not compile to check my work.

@tristan957
Copy link
Contributor Author

Lint should be fixed. Apologies.

@tristan957
Copy link
Contributor Author

@TingPing @ckerr since you guys were a part of the last PR, can you provide assistance on this one?

@TingPing specifically if you see anything that can be done about casting a GtkFileChooserNative to GtkNativeDialog without the macro GTK_NATIVE_DIALOG that would be extremely helpful. This is dependent on whether or not GTK stays dynamically loaded or not.

@TingPing
Copy link
Contributor

TingPing commented Jul 9, 2019

Thanks a lot for picking this up!

@TingPing specifically if you see anything that can be done about casting a GtkFileChooserNative to GtkNativeDialog without the macro GTK_NATIVE_DIALOG that would be extremely helpful.

Honestly I think its acceptable to just ignore in this specific case and cast away to void*. (EDIT: Oh sorry, C++ is more strict about that right. I'm not sure off-hand then)

Hardcoding -ldl is not ideal as that isn't used on all unix-like systems.

Since this already depends on GLib I suggest using GModule which avoids the linking and header portability concerns.

You'd then use g_module_open(), g_module_symbol(), etc: https://developer.gnome.org/glib/stable/glib-Dynamic-Loading-of-Modules.html

@tristan957
Copy link
Contributor Author

Thanks for the link @TingPing. I will take a look at GModule later today and update the PR accordingly.

@tristan957
Copy link
Contributor Author

Call for action: I do not currently run a KDE Plasma desktop. If someone who does could inevitably test this PR, that would be great.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 9, 2019

@TingPing I was more wondering if the GtkFileChooserNative object is located in memory at the same spot as the GtkNativeDialog or does GTK_NATIVE_DIALOG move the pointer. I don't have a complete grasp of the GObject type system just yet. I can write it, but I don't know technicalities like this. If they are at the same spot, I can avoid the cast.

@tristan957
Copy link
Contributor Author

This is going to require a little more effort than previously thought. Still doable I think, but I am under the impression _dialog is going to have to change its type to a GtkFileChooser. I am also going to have to dip into libgtkui::SetGtkTransientForAura to account for this.

@electron-cation electron-cation bot removed the new-pr 🌱 PR opened in the last 24 hours label Jul 9, 2019
@tristan957
Copy link
Contributor Author

tristan957 commented Jul 9, 2019

The behavior of FileDialog::RunAsynchronous needs to be re-evaluated as "delete-event" does not exist for GtkNativeDialog & GtkFileChooserNative. Also there is no "present with time" api, so that will need to be reevaluated.

Both CHROME_CALLBACKS need to be adjusted.

I am going to need some guidance on libgtkui::SetGtkTransientForAura. I cannot find the definition. I just need to overload the function to have a SetGtkTransientForAura(void* dialog, aura::Window* parent). I need use the underlying GtkNativeDialog if GTK_IS_WIDGET(dialog) fails because that API provides gtk_native_dialog_set_transient_for.

Any skilled Electron contributor would be a huge help at this point.

@tristan957
Copy link
Contributor Author

I have also learned that preview widgets are ignored in the Native API. What should be the best course of action regarding that? Leaving the preview widget code in regardless of what dialog is actually being used seems best to me instead of making conditional checks around the code.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 10, 2019

I have gotten the build where it only fails on the libgtkui::SetGtkTransientForAura call. Looking at the codebase, this seems like something in the chromium code base. Relevant: https://github.com/chromium/chromium/blob/5250f0fc05c7af7cf4512714ac832749547b1bd0/chrome/browser/ui/libgtkui/gtk_util.cc#L137.

So...I think this leaves a couple of options. Ask the Chromium developers for an overloaded function to be added, or mirror the function in Electron. I am thinking mirroring the function in Electron will work best.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 10, 2019

I have come up with the following patch

diff --git a/shell/browser/ui/file_dialog_gtk.cc b/shell/browser/ui/file_dialog_gtk.cc
index ef6d3a545..50e536ad5 100644
--- a/shell/browser/ui/file_dialog_gtk.cc
+++ b/shell/browser/ui/file_dialog_gtk.cc
@@ -77,7 +77,10 @@ class FileChooserDialog {
 
     if (parent_) {
       parent_->SetEnabled(false);
-      libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
+      if (GTK_IS_WIDGET(dialog_))
+        libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
+      else
+        SetGtkTransientForAura(parent_->GetNativeWindow());
       if (GTK_IS_WINDOW(dialog_)) {
         gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
       } else {
@@ -225,6 +228,19 @@ class FileChooserDialog {
   CHROMEG_CALLBACK_0(FileChooserDialog, void, OnUpdatePreview, GtkFileChooser*);
 
   DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
+
+  // Sets |dialog| as transient for |parent|, which will keep it on top and
+  // center it above |parent|. Do nothing if |parent| is nullptr.
+  void SetGtkTransientForAura(aura::Window* parent) {
+    if (!parent || !parent->GetHost())
+      return;
+
+    void (*dl_gtk_native_dialog_set_transient_for)(void*, GtkWindow*) = nullptr;
+    g_module_symbol(
+        gtk_module_, "gtk_native_dialog_set_transient_for",
+        reinterpret_cast<void**>(&dl_gtk_native_dialog_set_transient_for);
+    dl_gtk_native_dialog_set_transient_for(dialog_, reinterpret_cast<GtkWindow*>(parent));
+  }
 };
 
 void FileChooserDialog::OnFileDialogResponse(GtkFileChooser* widget, int response) {

I have decided to merge this patch for the time being. It can be reverted though.

@TingPing
Copy link
Contributor

@TingPing I was more wondering if the GtkFileChooserNative object is located in memory at the same spot as the GtkNativeDialog or does GTK_NATIVE_DIALOG move the pointer. I don't have a complete grasp of the GObject type system just yet. I can write it, but I don't know technicalities like this. If they are at the same spot, I can avoid the cast.

The type casting macros in GLib do two things: They do a type check and then a regular C style cast. They do not change the pointer in any way.

@TingPing
Copy link
Contributor

TingPing commented Jul 10, 2019

The behavior of FileDialog::RunAsynchronous needs to be re-evaluated as "delete-event" does not exist for GtkNativeDialog & GtkFileChooserNative

It always should have been watching the response signal.

EDIT: I see it just hides the dialog on delete. This is fine I suppose but this doesn't need to have that signal to do it, it can be done from the response.

@TingPing
Copy link
Contributor

I have also learned that preview widgets are ignored in the Native API. What should be the best course of action regarding that?

Nothing can be done about that. Native dialogs cannot be modified.

@tristan957
Copy link
Contributor Author

Company and mclasen were helpful on #gtk yesterday. @TingPing when you close out of a GtkDialog, what is the response code? Is it the same as hitting cancel?

@TingPing
Copy link
Contributor

@TingPing when you close out of a GtkDialog, what is the response code? Is it the same as hitting cancel?

GTK_RESPONSE_DELETE_EVENT

@tristan957
Copy link
Contributor Author

I can probably include a workaround for that event then.

Is this even still an issue on KDE systems. I made a post on /r/kde, and people seem to get the plasma file chooser in electron applications.

@polarathene
Copy link

Is this even still an issue on KDE systems. I made a post on /r/kde, and people seem to get the plasma file chooser in electron applications.

Which electron application was that? My electron app does not have a native KDE dialog. If you package with Flatpak it will be a native dialog due to portals used there afaik.

@tristan957
Copy link
Contributor Author

tristan957 commented Jul 11, 2019

Does anyone know why Electron has a separate file chooser implementation than Chromium? @polarathene I have responded on Reddit so I'll let you know

@zcbenz
Copy link
Contributor

zcbenz commented Jul 15, 2019

Does anyone know why Electron has a separate file chooser implementation than Chromium?

The Chromium implementation of file chooser was limited of features, and could not meet Electron's feature requests.

@TingPing
Copy link
Contributor

TingPing commented Jul 15, 2019

As I understand it Chromium might be moving in a direction where they always use the ChromiumOS file chooser and not native toolkits also. So Electron probably wants to retain that native integration. (I pointed them in the direction of the file chooser portal but I don't really follow Chromium much)

@tristan957
Copy link
Contributor Author

Does anyone know how to pull an electron package out of circle CI so I could use it in a node project, or does the CI not produce those?

ckerr
ckerr previously requested changes Aug 9, 2019
Copy link
Member

@ckerr ckerr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! I think there are still some things that need cleanup, but 👍 on using native when available

void* (*dl_gtk_file_chooser_native_new)(const char*, GtkWindow*,
GtkFileChooserAction, const char*,
const char*) = nullptr;
bool found = g_module_symbol(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't call g_module_symbol() if gtk_module_ is false -- maybe it should be

Suggested change
bool found = g_module_symbol(
bool found = (gtk_module_ != nullptr) && g_module_symbol(

Copy link
Contributor

@TingPing TingPing Aug 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gtk_module_ should never be NULL, good idea to handle that ofc but it should be a loud warning when your app using GTK is loading a non-existing so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking we could make gtk_symbol_ a static class var, that way the only time we go searching for the lib is on the first invocation thoughts? Then it could be (gtk_module_ != NULL) || g_module_symbol...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question for that: when do we release the module?

But if we go the static field route, we could also cache the results of the lookups by having them all be static fields as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, to be clearer about my gtk_module_ != nullptr suggestion: the version I was reviewing had the test later in the code logic:

if (gtk_module_ != nullptr && found ...

My suggestion was to move this test to before the g_module_symbol() call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckerr good point on the release. Static fields for the functions might be a better solution. If none of the fields are NULL, don't do a module search. Let's say I go through the process of dynamically calling all the functions, while saving them as static class functions at the same time, do the dynamically loaded functions reside in memory regardless if we create a GModule object or not? @TingPing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter if its never released, the module is loaded as long as the process is a live anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckerr I will defer to your opinion on any static class variables (function pointers/module).

shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
shell/browser/ui/file_dialog_gtk.cc Outdated Show resolved Hide resolved
@tristan957
Copy link
Contributor Author

@ckerr thanks for taking a look at this. I will review and update the PR according to your suggestions hopefully later this weekend.

@squalou
Copy link

squalou commented Sep 1, 2021

Hi,
I'm wondering : has it landed in 14.0.0 released yesterday ?
(I've beeing trying to use it without a great success, with portal-kde installed, GTK_USE_PORTAL set to 1. )

[Edit] : no consistent result depending on distributions. Cannot have ubuntu family work. Did have some success on Arch. Weird.

@vchernin
Copy link

vchernin commented Oct 2, 2021

No one’s been able to figure out the cause of this feature seemingly not working on Ubuntu (or in Flatpak) so I’ve opened a follow up: #31258

If anyone has ideas on what might be the problem please share them.

csett86 added a commit to jitsi/jitsi-meet-electron that referenced this pull request Dec 1, 2021
Contains:

- Chromium 91 -> 96, including multiple webrtc and security fixes
- Linux file chooser portal fixes (electron/electron#19159)

Closes: #629
d3473r pushed a commit to d3473r/jitsi-meet-electron that referenced this pull request Sep 1, 2023
Contains:

- Chromium 91 -> 96, including multiple webrtc and security fixes
- Linux file chooser portal fixes (electron/electron#19159)

Closes: jitsi#629
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-review/requested 🗳 semver/minor backwards-compatible functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use desktop environment-aware file picker