1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "mozilla/Types.h"
13 #include "AsyncDBus.h"
14 #include "nsGtkUtils.h"
15 #include "nsIFileURL.h"
16 #include "nsIGIOService.h"
18 #include "nsIWidget.h"
20 #include "nsIStringBundle.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/dom/Promise.h"
25 #include "nsArrayEnumerator.h"
26 #include "nsEnumeratorUtils.h"
27 #include "nsNetUtil.h"
28 #include "nsReadableUtils.h"
29 #include "MozContainer.h"
30 #include "WidgetUtilsGtk.h"
32 #include "nsFilePicker.h"
36 # include "mozilla/Logging.h"
37 # include "nsTArray.h"
39 extern mozilla::LazyLogModule gWidgetLog
;
40 # define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
43 #endif /* MOZ_LOGGING */
45 using namespace mozilla
;
46 using mozilla::dom::Promise
;
48 #define MAX_PREVIEW_SIZE 180
50 #define MAX_PREVIEW_SOURCE_SIZE 4096
52 nsIFile
* nsFilePicker::mPrevDisplayDirectory
= nullptr;
54 void nsFilePicker::Shutdown() { NS_IF_RELEASE(mPrevDisplayDirectory
); }
56 static GtkFileChooserAction
GetGtkFileChooserAction(nsIFilePicker::Mode aMode
) {
57 GtkFileChooserAction action
;
60 case nsIFilePicker::modeSave
:
61 action
= GTK_FILE_CHOOSER_ACTION_SAVE
;
64 case nsIFilePicker::modeGetFolder
:
65 action
= GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
;
68 case nsIFilePicker::modeOpen
:
69 case nsIFilePicker::modeOpenMultiple
:
70 action
= GTK_FILE_CHOOSER_ACTION_OPEN
;
74 NS_WARNING("Unknown nsIFilePicker mode");
75 action
= GTK_FILE_CHOOSER_ACTION_OPEN
;
82 static void UpdateFilePreviewWidget(GtkFileChooser
* file_chooser
,
83 gpointer preview_widget_voidptr
) {
84 GtkImage
* preview_widget
= GTK_IMAGE(preview_widget_voidptr
);
85 char* image_filename
= gtk_file_chooser_get_preview_filename(file_chooser
);
88 if (!image_filename
) {
89 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
93 gint preview_width
= 0;
94 gint preview_height
= 0;
96 * if file is named pipe, Open is blocking which may lead to UI
97 * nonresponsiveness; if file is directory/socket, it also isn't
98 * likely to get preview */
99 if (stat(image_filename
, &st_buf
) || (!S_ISREG(st_buf
.st_mode
))) {
100 g_free(image_filename
);
101 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
102 return; /* stat failed or file is not regular */
105 GdkPixbufFormat
* preview_format
=
106 gdk_pixbuf_get_file_info(image_filename
, &preview_width
, &preview_height
);
107 if (!preview_format
|| preview_width
<= 0 || preview_height
<= 0 ||
108 preview_width
> MAX_PREVIEW_SOURCE_SIZE
||
109 preview_height
> MAX_PREVIEW_SOURCE_SIZE
) {
110 g_free(image_filename
);
111 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
115 GdkPixbuf
* preview_pixbuf
= nullptr;
116 // Only scale down images that are too big
117 if (preview_width
> MAX_PREVIEW_SIZE
|| preview_height
> MAX_PREVIEW_SIZE
) {
118 preview_pixbuf
= gdk_pixbuf_new_from_file_at_size(
119 image_filename
, MAX_PREVIEW_SIZE
, MAX_PREVIEW_SIZE
, nullptr);
121 preview_pixbuf
= gdk_pixbuf_new_from_file(image_filename
, nullptr);
124 g_free(image_filename
);
126 if (!preview_pixbuf
) {
127 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
131 GdkPixbuf
* preview_pixbuf_temp
= preview_pixbuf
;
132 preview_pixbuf
= gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp
);
133 g_object_unref(preview_pixbuf_temp
);
135 // This is the easiest way to do center alignment without worrying about
136 // containers Minimum 3px padding each side (hence the 6) just to make things
139 (MAX_PREVIEW_SIZE
+ 6 - gdk_pixbuf_get_width(preview_pixbuf
)) / 2;
140 gtk_misc_set_padding(GTK_MISC(preview_widget
), x_padding
, 0);
142 gtk_image_set_from_pixbuf(preview_widget
, preview_pixbuf
);
143 g_object_unref(preview_pixbuf
);
144 gtk_file_chooser_set_preview_widget_active(file_chooser
, TRUE
);
147 static nsAutoCString
MakeCaseInsensitiveShellGlob(const char* aPattern
) {
149 nsAutoCString result
;
150 unsigned int len
= strlen(aPattern
);
152 for (unsigned int i
= 0; i
< len
; i
++) {
153 if (!g_ascii_isalpha(aPattern
[i
])) {
154 // non-ASCII characters will also trigger this path, so unicode
155 // is safely handled albeit case-sensitively
156 result
.Append(aPattern
[i
]);
160 // add the lowercase and uppercase version of a character to a bracket
161 // match, so it matches either the lowercase or uppercase char.
163 result
.Append(g_ascii_tolower(aPattern
[i
]));
164 result
.Append(g_ascii_toupper(aPattern
[i
]));
171 NS_IMPL_ISUPPORTS(nsFilePicker
, nsIFilePicker
)
173 nsFilePicker::nsFilePicker()
177 mFileChooserDelegate(nullptr) {
178 mUseNativeFileChooser
=
179 widget::ShouldUsePortal(widget::PortalKind::FilePicker
);
182 nsFilePicker::~nsFilePicker() = default;
184 void ReadMultipleFiles(gpointer filename
, gpointer array
) {
185 nsCOMPtr
<nsIFile
> localfile
;
187 NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename
)),
188 false, getter_AddRefs(localfile
));
189 if (NS_SUCCEEDED(rv
)) {
190 nsCOMArray
<nsIFile
>& files
= *static_cast<nsCOMArray
<nsIFile
>*>(array
);
191 files
.AppendObject(localfile
);
197 void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser
) {
200 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
204 gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser
));
205 g_slist_foreach(list
, ReadMultipleFiles
, static_cast<gpointer
>(&mFiles
));
208 gchar
* filename
= gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser
));
209 mFileURL
.Assign(filename
);
213 GtkFileFilter
* filter
=
214 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser
));
215 GSList
* filter_list
=
216 gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser
));
218 mSelectedType
= static_cast<int16_t>(g_slist_index(filter_list
, filter
));
219 g_slist_free(filter_list
);
221 // Remember last used directory.
222 nsCOMPtr
<nsIFile
> file
;
223 GetFile(getter_AddRefs(file
));
225 nsCOMPtr
<nsIFile
> dir
;
226 file
->GetParent(getter_AddRefs(dir
));
228 dir
.swap(mPrevDisplayDirectory
);
233 void nsFilePicker::InitNative(nsIWidget
* aParent
, const nsAString
& aTitle
) {
234 mParentWidget
= aParent
;
235 mTitle
.Assign(aTitle
);
239 nsFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
240 Promise
** aRetPromise
) {
241 #ifdef MOZ_ENABLE_DBUS
242 if (!widget::ShouldUsePortal(widget::PortalKind::FilePicker
) ||
243 aMode
!= nsIFilePicker::modeGetFolder
) {
244 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
247 const char kFreedesktopPortalName
[] = "org.freedesktop.portal.Desktop";
248 const char kFreedesktopPortalPath
[] = "/org/freedesktop/portal/desktop";
249 const char kFreedesktopPortalFileChooser
[] =
250 "org.freedesktop.portal.FileChooser";
253 MOZ_ASSERT(aRetPromise
);
255 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
256 if (NS_WARN_IF(!globalObject
)) {
257 return NS_ERROR_FAILURE
;
261 RefPtr
<Promise
> retPromise
= Promise::Create(globalObject
, result
);
262 if (NS_WARN_IF(result
.Failed())) {
263 return result
.StealNSResult();
266 widget::CreateDBusProxyForBus(
268 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
),
269 /* aInterfaceInfo = */ nullptr, kFreedesktopPortalName
,
270 kFreedesktopPortalPath
, kFreedesktopPortalFileChooser
)
272 GetCurrentSerialEventTarget(), __func__
,
273 [retPromise
](RefPtr
<GDBusProxy
>&& aProxy
) {
274 const char kFreedesktopPortalVersionProperty
[] = "version";
275 // Folder selection was added in version 3 of xdg-desktop-portal
276 const uint32_t kFreedesktopPortalMinimumVersion
= 3;
277 uint32_t foundVersion
= 0;
279 RefPtr
<GVariant
> property
=
280 dont_AddRef(g_dbus_proxy_get_cached_property(
281 aProxy
, kFreedesktopPortalVersionProperty
));
284 foundVersion
= g_variant_get_uint32(property
);
285 LOG(("Found portal version: %u", foundVersion
));
288 retPromise
->MaybeResolve(foundVersion
>=
289 kFreedesktopPortalMinimumVersion
);
291 [retPromise
](GUniquePtr
<GError
>&& aError
) {
292 g_printerr("Failed to create DBUS proxy: %s\n", aError
->message
);
293 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
296 retPromise
.forget(aRetPromise
);
299 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
304 nsFilePicker::AppendFilters(int32_t aFilterMask
) {
305 mAllowURLs
= !!(aFilterMask
& filterAllowURLs
);
306 return nsBaseFilePicker::AppendFilters(aFilterMask
);
310 nsFilePicker::AppendFilter(const nsAString
& aTitle
, const nsAString
& aFilter
) {
311 if (aFilter
.EqualsLiteral("..apps")) {
312 // No platform specific thing we can do here, really....
316 nsAutoCString filter
, name
;
317 CopyUTF16toUTF8(aFilter
, filter
);
318 CopyUTF16toUTF8(aTitle
, name
);
320 mFilters
.AppendElement(filter
);
321 mFilterNames
.AppendElement(name
);
327 nsFilePicker::SetDefaultString(const nsAString
& aString
) {
334 nsFilePicker::GetDefaultString(nsAString
& aString
) {
336 return NS_ERROR_FAILURE
;
340 nsFilePicker::SetDefaultExtension(const nsAString
& aExtension
) {
341 mDefaultExtension
= aExtension
;
347 nsFilePicker::GetDefaultExtension(nsAString
& aExtension
) {
348 aExtension
= mDefaultExtension
;
354 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
355 *aFilterIndex
= mSelectedType
;
361 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
362 mSelectedType
= aFilterIndex
;
368 nsFilePicker::GetFile(nsIFile
** aFile
) {
369 NS_ENSURE_ARG_POINTER(aFile
);
372 nsCOMPtr
<nsIURI
> uri
;
373 nsresult rv
= GetFileURL(getter_AddRefs(uri
));
376 nsCOMPtr
<nsIFileURL
> fileURL(do_QueryInterface(uri
, &rv
));
377 NS_ENSURE_SUCCESS(rv
, rv
);
379 nsCOMPtr
<nsIFile
> file
;
380 rv
= fileURL
->GetFile(getter_AddRefs(file
));
381 NS_ENSURE_SUCCESS(rv
, rv
);
388 nsFilePicker::GetFileURL(nsIURI
** aFileURL
) {
390 return NS_NewURI(aFileURL
, mFileURL
);
394 nsFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
395 NS_ENSURE_ARG_POINTER(aFiles
);
397 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
398 return NS_NewArrayEnumerator(aFiles
, mFiles
, NS_GET_IID(nsIFile
));
401 return NS_ERROR_FAILURE
;
404 nsresult
nsFilePicker::Show(nsIFilePicker::ResultCode
* aReturn
) {
405 NS_ENSURE_ARG_POINTER(aReturn
);
407 nsresult rv
= Open(nullptr);
408 if (NS_FAILED(rv
)) return rv
;
411 g_main_context_iteration(nullptr, TRUE
);
419 nsFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
420 // Can't show two dialogs concurrently with the same filepicker
421 if (mRunning
) return NS_ERROR_NOT_AVAILABLE
;
423 NS_ConvertUTF16toUTF8
title(mTitle
);
425 GtkWindow
* parent_widget
=
426 GTK_WINDOW(mParentWidget
->GetNativeData(NS_NATIVE_SHELLWIDGET
));
428 GtkFileChooserAction action
= GetGtkFileChooserAction(mMode
);
430 const gchar
* accept_button
;
431 NS_ConvertUTF16toUTF8
buttonLabel(mOkButtonLabel
);
432 if (!mOkButtonLabel
.IsEmpty()) {
433 accept_button
= buttonLabel
.get();
435 accept_button
= nullptr;
439 GtkFileChooserNew(title
.get(), parent_widget
, action
, accept_button
);
441 // If we have --enable-proxy-bypass-protection, then don't allow
442 // remote URLs to be used.
443 #ifndef MOZ_PROXY_BYPASS_PROTECTION
445 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser
), FALSE
);
449 if (action
== GTK_FILE_CHOOSER_ACTION_OPEN
||
450 action
== GTK_FILE_CHOOSER_ACTION_SAVE
) {
451 GtkWidget
* img_preview
= gtk_image_new();
452 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser
),
454 g_signal_connect(file_chooser
, "update-preview",
455 G_CALLBACK(UpdateFilePreviewWidget
), img_preview
);
458 GtkFileChooserSetModal(file_chooser
, parent_widget
, TRUE
);
460 NS_ConvertUTF16toUTF8
defaultName(mDefault
);
462 case nsIFilePicker::modeOpenMultiple
:
463 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser
),
466 case nsIFilePicker::modeSave
:
467 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser
),
472 /* no additional setup needed */
476 nsCOMPtr
<nsIFile
> defaultPath
;
477 if (mDisplayDirectory
) {
478 mDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
479 } else if (mPrevDisplayDirectory
) {
480 mPrevDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
484 if (!defaultName
.IsEmpty() && mMode
!= nsIFilePicker::modeSave
) {
485 // Try to select the intended file. Even if it doesn't exist, GTK still
486 // switches directories.
487 defaultPath
->AppendNative(defaultName
);
489 defaultPath
->GetNativePath(path
);
490 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser
), path
.get());
492 nsAutoCString directory
;
493 defaultPath
->GetNativePath(directory
);
495 // Workaround for problematic refcounting in GTK3 before 3.16.
496 // We need to keep a reference to the dialog's internal delegate.
497 // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
498 // delegate by the time this gets processed in the event loop.
499 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
500 if (GTK_IS_DIALOG(file_chooser
)) {
501 GtkDialog
* dialog
= GTK_DIALOG(file_chooser
);
502 GtkContainer
* area
= GTK_CONTAINER(gtk_dialog_get_content_area(dialog
));
503 gtk_container_forall(
505 [](GtkWidget
* widget
, gpointer data
) {
506 if (GTK_IS_FILE_CHOOSER_WIDGET(widget
)) {
507 auto result
= static_cast<GtkFileChooserWidget
**>(data
);
508 *result
= GTK_FILE_CHOOSER_WIDGET(widget
);
511 &mFileChooserDelegate
);
513 if (mFileChooserDelegate
!= nullptr) {
514 g_object_ref(mFileChooserDelegate
);
517 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser
),
522 if (GTK_IS_DIALOG(file_chooser
)) {
523 gtk_dialog_set_default_response(GTK_DIALOG(file_chooser
),
524 GTK_RESPONSE_ACCEPT
);
527 int32_t count
= mFilters
.Length();
528 for (int32_t i
= 0; i
< count
; ++i
) {
529 // This is fun... the GTK file picker does not accept a list of filters
530 // so we need to split out each string, and add it manually.
532 char** patterns
= g_strsplit(mFilters
[i
].get(), ";", -1);
534 return NS_ERROR_OUT_OF_MEMORY
;
537 GtkFileFilter
* filter
= gtk_file_filter_new();
538 for (int j
= 0; patterns
[j
] != nullptr; ++j
) {
539 nsAutoCString caseInsensitiveFilter
=
540 MakeCaseInsensitiveShellGlob(g_strstrip(patterns
[j
]));
541 gtk_file_filter_add_pattern(filter
, caseInsensitiveFilter
.get());
544 g_strfreev(patterns
);
546 if (!mFilterNames
[i
].IsEmpty()) {
547 // If we have a name for our filter, let's use that.
548 const char* filter_name
= mFilterNames
[i
].get();
549 gtk_file_filter_set_name(filter
, filter_name
);
551 // If we don't have a name, let's just use the filter pattern.
552 const char* filter_pattern
= mFilters
[i
].get();
553 gtk_file_filter_set_name(filter
, filter_pattern
);
556 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
558 // Set the initially selected filter
559 if (mSelectedType
== i
) {
560 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
564 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser
),
568 mCallback
= aCallback
;
570 g_signal_connect(file_chooser
, "response", G_CALLBACK(OnResponse
), this);
571 GtkFileChooserShow(file_chooser
);
577 void nsFilePicker::OnResponse(void* file_chooser
, gint response_id
,
578 gpointer user_data
) {
579 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
, response_id
);
583 void nsFilePicker::OnDestroy(GtkWidget
* file_chooser
, gpointer user_data
) {
584 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
,
585 GTK_RESPONSE_CANCEL
);
588 bool nsFilePicker::WarnForNonReadableFile(void* file_chooser
) {
589 nsCOMPtr
<nsIFile
> file
;
590 GetFile(getter_AddRefs(file
));
595 bool isReadable
= false;
596 file
->IsReadable(&isReadable
);
601 nsCOMPtr
<nsIStringBundleService
> stringService
=
602 mozilla::components::StringBundle::Service();
603 if (!stringService
) {
607 nsCOMPtr
<nsIStringBundle
> filepickerBundle
;
608 nsresult rv
= stringService
->CreateBundle(
609 "chrome://global/locale/filepicker.properties",
610 getter_AddRefs(filepickerBundle
));
615 nsAutoString errorMessage
;
616 rv
= filepickerBundle
->GetStringFromName("selectedFileNotReadableError",
622 GtkDialogFlags flags
= GTK_DIALOG_DESTROY_WITH_PARENT
;
623 auto* cancel_dialog
= gtk_message_dialog_new(
624 GTK_WINDOW(file_chooser
), flags
, GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
625 "%s", NS_ConvertUTF16toUTF8(errorMessage
).get());
626 gtk_dialog_run(GTK_DIALOG(cancel_dialog
));
627 gtk_widget_destroy(cancel_dialog
);
632 void nsFilePicker::Done(void* file_chooser
, gint response
) {
635 nsIFilePicker::ResultCode result
;
637 case GTK_RESPONSE_OK
:
638 case GTK_RESPONSE_ACCEPT
:
639 ReadValuesFromFileChooser(file_chooser
);
640 result
= nsIFilePicker::returnOK
;
641 if (mMode
== nsIFilePicker::modeSave
) {
642 nsCOMPtr
<nsIFile
> file
;
643 GetFile(getter_AddRefs(file
));
646 file
->Exists(&exists
);
647 if (exists
) result
= nsIFilePicker::returnReplace
;
649 } else if (mMode
== nsIFilePicker::modeOpen
) {
650 if (WarnForNonReadableFile(file_chooser
)) {
651 result
= nsIFilePicker::returnCancel
;
656 case GTK_RESPONSE_CANCEL
:
657 case GTK_RESPONSE_CLOSE
:
658 case GTK_RESPONSE_DELETE_EVENT
:
659 result
= nsIFilePicker::returnCancel
;
663 NS_WARNING("Unexpected response");
664 result
= nsIFilePicker::returnCancel
;
668 // A "response" signal won't be sent again but "destroy" will be.
669 g_signal_handlers_disconnect_by_func(file_chooser
, FuncToGpointer(OnDestroy
),
672 // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
673 // OnDestroy, the widget would be destroyed anyway but it is fine if
674 // gtk_widget_destroy is called more than once. gtk_widget_destroy has
675 // requests that any remaining references be released, but the reference
676 // count will not be decremented again if GtkWindow's reference has already
678 GtkFileChooserDestroy(file_chooser
);
680 if (mFileChooserDelegate
) {
681 // Properly deref our acquired reference. We call this after
682 // gtk_widget_destroy() to try and ensure that pending file info
683 // queries caused by updating the current folder have been cancelled.
684 // However, we do not know for certain when the callback will run after
687 [](gpointer data
) -> gboolean
{
688 g_object_unref(data
);
689 return G_SOURCE_REMOVE
;
691 mFileChooserDelegate
);
692 mFileChooserDelegate
= nullptr;
696 mCallback
->Done(result
);
704 // All below functions available as of GTK 3.20+
705 void* nsFilePicker::GtkFileChooserNew(const gchar
* title
, GtkWindow
* parent
,
706 GtkFileChooserAction action
,
707 const gchar
* accept_label
) {
708 static auto sGtkFileChooserNativeNewPtr
=
709 (void* (*)(const gchar
*, GtkWindow
*, GtkFileChooserAction
, const gchar
*,
710 const gchar
*))dlsym(RTLD_DEFAULT
,
711 "gtk_file_chooser_native_new");
712 if (mUseNativeFileChooser
&& sGtkFileChooserNativeNewPtr
!= nullptr) {
713 return (*sGtkFileChooserNativeNewPtr
)(title
, parent
, action
, accept_label
,
716 if (accept_label
== nullptr) {
717 accept_label
= (action
== GTK_FILE_CHOOSER_ACTION_SAVE
) ? GTK_STOCK_SAVE
720 GtkWidget
* file_chooser
= gtk_file_chooser_dialog_new(
721 title
, parent
, action
, GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
722 accept_label
, GTK_RESPONSE_ACCEPT
, nullptr);
723 gtk_dialog_set_alternative_button_order(
724 GTK_DIALOG(file_chooser
), GTK_RESPONSE_ACCEPT
, GTK_RESPONSE_CANCEL
, -1);
728 void nsFilePicker::GtkFileChooserShow(void* file_chooser
) {
729 static auto sGtkNativeDialogShowPtr
=
730 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_show");
731 if (mUseNativeFileChooser
&& sGtkNativeDialogShowPtr
!= nullptr) {
732 const char* portalEnvString
= g_getenv("GTK_USE_PORTAL");
734 (portalEnvString
&& *portalEnvString
== '0') || !portalEnvString
;
736 setenv("GTK_USE_PORTAL", "1", true);
738 (*sGtkNativeDialogShowPtr
)(file_chooser
);
740 unsetenv("GTK_USE_PORTAL");
743 g_signal_connect(file_chooser
, "destroy", G_CALLBACK(OnDestroy
), this);
744 gtk_widget_show(GTK_WIDGET(file_chooser
));
748 void nsFilePicker::GtkFileChooserDestroy(void* file_chooser
) {
749 static auto sGtkNativeDialogDestroyPtr
=
750 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_destroy");
751 if (mUseNativeFileChooser
&& sGtkNativeDialogDestroyPtr
!= nullptr) {
752 (*sGtkNativeDialogDestroyPtr
)(file_chooser
);
754 gtk_widget_destroy(GTK_WIDGET(file_chooser
));
758 void nsFilePicker::GtkFileChooserSetModal(void* file_chooser
,
759 GtkWindow
* parent_widget
,
761 static auto sGtkNativeDialogSetModalPtr
= (void (*)(void*, gboolean
))dlsym(
762 RTLD_DEFAULT
, "gtk_native_dialog_set_modal");
763 if (mUseNativeFileChooser
&& sGtkNativeDialogSetModalPtr
!= nullptr) {
764 (*sGtkNativeDialogSetModalPtr
)(file_chooser
, modal
);
766 GtkWindow
* window
= GTK_WINDOW(file_chooser
);
767 gtk_window_set_modal(window
, modal
);
768 if (parent_widget
!= nullptr) {
769 gtk_window_set_destroy_with_parent(window
, modal
);