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()
174 : mSelectedType(0), mAllowURLs(false), mFileChooserDelegate(nullptr) {
175 mUseNativeFileChooser
=
176 widget::ShouldUsePortal(widget::PortalKind::FilePicker
);
179 nsFilePicker::~nsFilePicker() = default;
181 void ReadMultipleFiles(gpointer filename
, gpointer array
) {
182 nsCOMPtr
<nsIFile
> localfile
;
184 NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename
)),
185 false, getter_AddRefs(localfile
));
186 if (NS_SUCCEEDED(rv
)) {
187 nsCOMArray
<nsIFile
>& files
= *static_cast<nsCOMArray
<nsIFile
>*>(array
);
188 files
.AppendObject(localfile
);
194 void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser
) {
197 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
201 gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser
));
202 g_slist_foreach(list
, ReadMultipleFiles
, static_cast<gpointer
>(&mFiles
));
205 gchar
* filename
= gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser
));
206 mFileURL
.Assign(filename
);
210 GtkFileFilter
* filter
=
211 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser
));
212 GSList
* filter_list
=
213 gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser
));
215 mSelectedType
= static_cast<int16_t>(g_slist_index(filter_list
, filter
));
216 g_slist_free(filter_list
);
218 // Remember last used directory.
219 nsCOMPtr
<nsIFile
> file
;
220 GetFile(getter_AddRefs(file
));
222 nsCOMPtr
<nsIFile
> dir
;
223 file
->GetParent(getter_AddRefs(dir
));
225 dir
.swap(mPrevDisplayDirectory
);
230 void nsFilePicker::InitNative(nsIWidget
* aParent
, const nsAString
& aTitle
) {
231 mParentWidget
= aParent
;
232 mTitle
.Assign(aTitle
);
236 nsFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
237 Promise
** aRetPromise
) {
238 #ifdef MOZ_ENABLE_DBUS
239 if (!widget::ShouldUsePortal(widget::PortalKind::FilePicker
) ||
240 aMode
!= nsIFilePicker::modeGetFolder
) {
241 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
244 const char kFreedesktopPortalName
[] = "org.freedesktop.portal.Desktop";
245 const char kFreedesktopPortalPath
[] = "/org/freedesktop/portal/desktop";
246 const char kFreedesktopPortalFileChooser
[] =
247 "org.freedesktop.portal.FileChooser";
250 MOZ_ASSERT(aRetPromise
);
252 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
253 if (NS_WARN_IF(!globalObject
)) {
254 return NS_ERROR_FAILURE
;
258 RefPtr
<Promise
> retPromise
= Promise::Create(globalObject
, result
);
259 if (NS_WARN_IF(result
.Failed())) {
260 return result
.StealNSResult();
263 widget::CreateDBusProxyForBus(
265 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
),
266 /* aInterfaceInfo = */ nullptr, kFreedesktopPortalName
,
267 kFreedesktopPortalPath
, kFreedesktopPortalFileChooser
)
269 GetCurrentSerialEventTarget(), __func__
,
270 [retPromise
](RefPtr
<GDBusProxy
>&& aProxy
) {
271 const char kFreedesktopPortalVersionProperty
[] = "version";
272 // Folder selection was added in version 3 of xdg-desktop-portal
273 const uint32_t kFreedesktopPortalMinimumVersion
= 3;
274 uint32_t foundVersion
= 0;
276 RefPtr
<GVariant
> property
=
277 dont_AddRef(g_dbus_proxy_get_cached_property(
278 aProxy
, kFreedesktopPortalVersionProperty
));
281 foundVersion
= g_variant_get_uint32(property
);
282 LOG(("Found portal version: %u", foundVersion
));
285 retPromise
->MaybeResolve(foundVersion
>=
286 kFreedesktopPortalMinimumVersion
);
288 [retPromise
](GUniquePtr
<GError
>&& aError
) {
289 g_printerr("Failed to create DBUS proxy: %s\n", aError
->message
);
290 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
293 retPromise
.forget(aRetPromise
);
296 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
301 nsFilePicker::AppendFilters(int32_t aFilterMask
) {
302 mAllowURLs
= !!(aFilterMask
& filterAllowURLs
);
303 return nsBaseFilePicker::AppendFilters(aFilterMask
);
307 nsFilePicker::AppendFilter(const nsAString
& aTitle
, const nsAString
& aFilter
) {
308 if (aFilter
.EqualsLiteral("..apps")) {
309 // No platform specific thing we can do here, really....
313 nsAutoCString filter
, name
;
314 CopyUTF16toUTF8(aFilter
, filter
);
315 CopyUTF16toUTF8(aTitle
, name
);
317 mFilters
.AppendElement(filter
);
318 mFilterNames
.AppendElement(name
);
324 nsFilePicker::SetDefaultString(const nsAString
& aString
) {
331 nsFilePicker::GetDefaultString(nsAString
& aString
) {
333 return NS_ERROR_FAILURE
;
337 nsFilePicker::SetDefaultExtension(const nsAString
& aExtension
) {
338 mDefaultExtension
= aExtension
;
344 nsFilePicker::GetDefaultExtension(nsAString
& aExtension
) {
345 aExtension
= mDefaultExtension
;
351 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
352 *aFilterIndex
= mSelectedType
;
358 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
359 mSelectedType
= aFilterIndex
;
365 nsFilePicker::GetFile(nsIFile
** aFile
) {
366 NS_ENSURE_ARG_POINTER(aFile
);
369 nsCOMPtr
<nsIURI
> uri
;
370 nsresult rv
= GetFileURL(getter_AddRefs(uri
));
373 nsCOMPtr
<nsIFileURL
> fileURL(do_QueryInterface(uri
, &rv
));
374 NS_ENSURE_SUCCESS(rv
, rv
);
376 nsCOMPtr
<nsIFile
> file
;
377 rv
= fileURL
->GetFile(getter_AddRefs(file
));
378 NS_ENSURE_SUCCESS(rv
, rv
);
385 nsFilePicker::GetFileURL(nsIURI
** aFileURL
) {
387 return NS_NewURI(aFileURL
, mFileURL
);
391 nsFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
392 NS_ENSURE_ARG_POINTER(aFiles
);
394 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
395 return NS_NewArrayEnumerator(aFiles
, mFiles
, NS_GET_IID(nsIFile
));
398 return NS_ERROR_FAILURE
;
401 nsresult
nsFilePicker::Show(nsIFilePicker::ResultCode
* aReturn
) {
402 NS_ENSURE_ARG_POINTER(aReturn
);
404 nsresult rv
= Open(nullptr);
405 if (NS_FAILED(rv
)) return rv
;
407 while (mFileChooser
) {
408 g_main_context_iteration(nullptr, TRUE
);
416 nsFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
417 // Can't show two dialogs concurrently with the same filepicker
418 if (mFileChooser
) return NS_ERROR_NOT_AVAILABLE
;
420 NS_ConvertUTF16toUTF8
title(mTitle
);
422 GtkWindow
* parent_widget
=
423 GTK_WINDOW(mParentWidget
->GetNativeData(NS_NATIVE_SHELLWIDGET
));
425 GtkFileChooserAction action
= GetGtkFileChooserAction(mMode
);
427 const gchar
* accept_button
;
428 NS_ConvertUTF16toUTF8
buttonLabel(mOkButtonLabel
);
429 if (!mOkButtonLabel
.IsEmpty()) {
430 accept_button
= buttonLabel
.get();
432 accept_button
= nullptr;
436 GtkFileChooserNew(title
.get(), parent_widget
, action
, accept_button
);
438 // If we have --enable-proxy-bypass-protection, then don't allow
439 // remote URLs to be used.
440 #ifndef MOZ_PROXY_BYPASS_PROTECTION
442 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser
), FALSE
);
446 if (action
== GTK_FILE_CHOOSER_ACTION_OPEN
||
447 action
== GTK_FILE_CHOOSER_ACTION_SAVE
) {
448 GtkWidget
* img_preview
= gtk_image_new();
449 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser
),
451 g_signal_connect(file_chooser
, "update-preview",
452 G_CALLBACK(UpdateFilePreviewWidget
), img_preview
);
455 GtkFileChooserSetModal(file_chooser
, parent_widget
, TRUE
);
457 NS_ConvertUTF16toUTF8
defaultName(mDefault
);
459 case nsIFilePicker::modeOpenMultiple
:
460 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser
),
463 case nsIFilePicker::modeSave
:
464 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser
),
469 /* no additional setup needed */
473 nsCOMPtr
<nsIFile
> defaultPath
;
474 if (mDisplayDirectory
) {
475 mDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
476 } else if (mPrevDisplayDirectory
) {
477 mPrevDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
481 if (!defaultName
.IsEmpty() && mMode
!= nsIFilePicker::modeSave
) {
482 // Try to select the intended file. Even if it doesn't exist, GTK still
483 // switches directories.
484 defaultPath
->AppendNative(defaultName
);
486 defaultPath
->GetNativePath(path
);
487 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser
), path
.get());
489 nsAutoCString directory
;
490 defaultPath
->GetNativePath(directory
);
492 // Workaround for problematic refcounting in GTK3 before 3.16.
493 // We need to keep a reference to the dialog's internal delegate.
494 // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
495 // delegate by the time this gets processed in the event loop.
496 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
497 if (GTK_IS_DIALOG(file_chooser
)) {
498 GtkDialog
* dialog
= GTK_DIALOG(file_chooser
);
499 GtkContainer
* area
= GTK_CONTAINER(gtk_dialog_get_content_area(dialog
));
500 gtk_container_forall(
502 [](GtkWidget
* widget
, gpointer data
) {
503 if (GTK_IS_FILE_CHOOSER_WIDGET(widget
)) {
504 auto result
= static_cast<GtkFileChooserWidget
**>(data
);
505 *result
= GTK_FILE_CHOOSER_WIDGET(widget
);
508 &mFileChooserDelegate
);
510 if (mFileChooserDelegate
!= nullptr) {
511 g_object_ref(mFileChooserDelegate
);
514 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser
),
519 if (GTK_IS_DIALOG(file_chooser
)) {
520 gtk_dialog_set_default_response(GTK_DIALOG(file_chooser
),
521 GTK_RESPONSE_ACCEPT
);
524 int32_t count
= mFilters
.Length();
525 for (int32_t i
= 0; i
< count
; ++i
) {
526 // This is fun... the GTK file picker does not accept a list of filters
527 // so we need to split out each string, and add it manually.
529 char** patterns
= g_strsplit(mFilters
[i
].get(), ";", -1);
531 return NS_ERROR_OUT_OF_MEMORY
;
534 GtkFileFilter
* filter
= gtk_file_filter_new();
535 for (int j
= 0; patterns
[j
] != nullptr; ++j
) {
536 nsAutoCString caseInsensitiveFilter
=
537 MakeCaseInsensitiveShellGlob(g_strstrip(patterns
[j
]));
538 gtk_file_filter_add_pattern(filter
, caseInsensitiveFilter
.get());
541 g_strfreev(patterns
);
543 if (!mFilterNames
[i
].IsEmpty()) {
544 // If we have a name for our filter, let's use that.
545 const char* filter_name
= mFilterNames
[i
].get();
546 gtk_file_filter_set_name(filter
, filter_name
);
548 // If we don't have a name, let's just use the filter pattern.
549 const char* filter_pattern
= mFilters
[i
].get();
550 gtk_file_filter_set_name(filter
, filter_pattern
);
553 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
555 // Set the initially selected filter
556 if (mSelectedType
== i
) {
557 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
561 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser
),
564 mFileChooser
= file_chooser
;
565 mCallback
= aCallback
;
567 g_signal_connect(file_chooser
, "response", G_CALLBACK(OnResponse
), this);
568 GtkFileChooserShow(file_chooser
);
574 nsFilePicker::Close() {
576 // Call ourself as done.
577 Done(mFileChooser
, GTK_RESPONSE_CLOSE
);
584 void nsFilePicker::OnResponse(void* file_chooser
, gint response_id
,
585 gpointer user_data
) {
586 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
, response_id
);
590 void nsFilePicker::OnDestroy(GtkWidget
* file_chooser
, gpointer user_data
) {
591 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
,
592 GTK_RESPONSE_CANCEL
);
595 bool nsFilePicker::WarnForNonReadableFile(void* file_chooser
) {
596 nsCOMPtr
<nsIFile
> file
;
597 GetFile(getter_AddRefs(file
));
602 bool isReadable
= false;
603 file
->IsReadable(&isReadable
);
608 nsCOMPtr
<nsIStringBundleService
> stringService
=
609 mozilla::components::StringBundle::Service();
610 if (!stringService
) {
614 nsCOMPtr
<nsIStringBundle
> filepickerBundle
;
615 nsresult rv
= stringService
->CreateBundle(
616 "chrome://global/locale/filepicker.properties",
617 getter_AddRefs(filepickerBundle
));
622 nsAutoString errorMessage
;
623 rv
= filepickerBundle
->GetStringFromName("selectedFileNotReadableError",
629 GtkDialogFlags flags
= GTK_DIALOG_DESTROY_WITH_PARENT
;
630 auto* cancel_dialog
= gtk_message_dialog_new(
631 GTK_WINDOW(file_chooser
), flags
, GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
632 "%s", NS_ConvertUTF16toUTF8(errorMessage
).get());
633 gtk_dialog_run(GTK_DIALOG(cancel_dialog
));
634 gtk_widget_destroy(cancel_dialog
);
639 void nsFilePicker::Done(void* file_chooser
, gint response
) {
640 mFileChooser
= nullptr;
642 nsIFilePicker::ResultCode result
;
644 case GTK_RESPONSE_OK
:
645 case GTK_RESPONSE_ACCEPT
:
646 ReadValuesFromFileChooser(file_chooser
);
647 result
= nsIFilePicker::returnOK
;
648 if (mMode
== nsIFilePicker::modeSave
) {
649 nsCOMPtr
<nsIFile
> file
;
650 GetFile(getter_AddRefs(file
));
653 file
->Exists(&exists
);
654 if (exists
) result
= nsIFilePicker::returnReplace
;
656 } else if (mMode
== nsIFilePicker::modeOpen
) {
657 if (WarnForNonReadableFile(file_chooser
)) {
658 result
= nsIFilePicker::returnCancel
;
663 case GTK_RESPONSE_CANCEL
:
664 case GTK_RESPONSE_CLOSE
:
665 case GTK_RESPONSE_DELETE_EVENT
:
666 result
= nsIFilePicker::returnCancel
;
670 NS_WARNING("Unexpected response");
671 result
= nsIFilePicker::returnCancel
;
675 // A "response" signal won't be sent again but "destroy" will be.
676 g_signal_handlers_disconnect_by_func(file_chooser
, FuncToGpointer(OnDestroy
),
679 // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
680 // OnDestroy, the widget would be destroyed anyway but it is fine if
681 // gtk_widget_destroy is called more than once. gtk_widget_destroy has
682 // requests that any remaining references be released, but the reference
683 // count will not be decremented again if GtkWindow's reference has already
685 GtkFileChooserDestroy(file_chooser
);
687 if (mFileChooserDelegate
) {
688 // Properly deref our acquired reference. We call this after
689 // gtk_widget_destroy() to try and ensure that pending file info
690 // queries caused by updating the current folder have been cancelled.
691 // However, we do not know for certain when the callback will run after
694 [](gpointer data
) -> gboolean
{
695 g_object_unref(data
);
696 return G_SOURCE_REMOVE
;
698 mFileChooserDelegate
);
699 mFileChooserDelegate
= nullptr;
703 mCallback
->Done(result
);
711 // All below functions available as of GTK 3.20+
712 void* nsFilePicker::GtkFileChooserNew(const gchar
* title
, GtkWindow
* parent
,
713 GtkFileChooserAction action
,
714 const gchar
* accept_label
) {
715 static auto sGtkFileChooserNativeNewPtr
=
716 (void* (*)(const gchar
*, GtkWindow
*, GtkFileChooserAction
, const gchar
*,
717 const gchar
*))dlsym(RTLD_DEFAULT
,
718 "gtk_file_chooser_native_new");
719 if (mUseNativeFileChooser
&& sGtkFileChooserNativeNewPtr
!= nullptr) {
720 return (*sGtkFileChooserNativeNewPtr
)(title
, parent
, action
, accept_label
,
723 if (accept_label
== nullptr) {
724 accept_label
= (action
== GTK_FILE_CHOOSER_ACTION_SAVE
) ? GTK_STOCK_SAVE
727 GtkWidget
* file_chooser
= gtk_file_chooser_dialog_new(
728 title
, parent
, action
, GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
729 accept_label
, GTK_RESPONSE_ACCEPT
, nullptr);
730 gtk_dialog_set_alternative_button_order(
731 GTK_DIALOG(file_chooser
), GTK_RESPONSE_ACCEPT
, GTK_RESPONSE_CANCEL
, -1);
735 void nsFilePicker::GtkFileChooserShow(void* file_chooser
) {
736 static auto sGtkNativeDialogShowPtr
=
737 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_show");
738 if (mUseNativeFileChooser
&& sGtkNativeDialogShowPtr
!= nullptr) {
739 const char* portalEnvString
= g_getenv("GTK_USE_PORTAL");
741 (portalEnvString
&& *portalEnvString
== '0') || !portalEnvString
;
743 setenv("GTK_USE_PORTAL", "1", true);
745 (*sGtkNativeDialogShowPtr
)(file_chooser
);
747 unsetenv("GTK_USE_PORTAL");
750 g_signal_connect(file_chooser
, "destroy", G_CALLBACK(OnDestroy
), this);
751 gtk_widget_show(GTK_WIDGET(file_chooser
));
755 void nsFilePicker::GtkFileChooserDestroy(void* file_chooser
) {
756 static auto sGtkNativeDialogDestroyPtr
=
757 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_destroy");
758 if (mUseNativeFileChooser
&& sGtkNativeDialogDestroyPtr
!= nullptr) {
759 (*sGtkNativeDialogDestroyPtr
)(file_chooser
);
761 gtk_widget_destroy(GTK_WIDGET(file_chooser
));
765 void nsFilePicker::GtkFileChooserSetModal(void* file_chooser
,
766 GtkWindow
* parent_widget
,
768 static auto sGtkNativeDialogSetModalPtr
= (void (*)(void*, gboolean
))dlsym(
769 RTLD_DEFAULT
, "gtk_native_dialog_set_modal");
770 if (mUseNativeFileChooser
&& sGtkNativeDialogSetModalPtr
!= nullptr) {
771 (*sGtkNativeDialogSetModalPtr
)(file_chooser
, modal
);
773 GtkWindow
* window
= GTK_WINDOW(file_chooser
);
774 gtk_window_set_modal(window
, modal
);
775 if (parent_widget
!= nullptr) {
776 gtk_window_set_destroy_with_parent(window
, modal
);