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/. */
6 #include "WidgetUtilsGtk.h"
8 #include "MainThreadUtils.h"
9 #include "mozilla/StaticPrefs_widget.h"
10 #include "mozilla/UniquePtr.h"
11 #include "nsReadableUtils.h"
13 #include "nsStringFwd.h"
15 #include "nsIGfxInfo.h"
16 #include "mozilla/Components.h"
18 #include "nsIProperties.h"
20 #include "nsXULAppAPI.h"
21 #include "nsXPCOMCID.h"
22 #include "nsDirectoryServiceDefs.h"
24 #include "nsGtkKeyUtils.h"
25 #include "nsGtkUtils.h"
33 # include "mozilla/Logging.h"
34 extern mozilla::LazyLogModule gWidgetLog
;
35 # define LOGW(...) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
38 #endif /* MOZ_LOGGING */
40 namespace mozilla::widget
{
42 int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent() {
44 GdkDisplay
* display
= gdk_display_get_default();
49 GdkDeviceManager
* manager
= gdk_display_get_device_manager(display
);
55 gdk_device_manager_list_devices(manager
, GDK_DEVICE_TYPE_SLAVE
);
56 GList
* list
= devices
;
59 GdkDevice
* device
= static_cast<GdkDevice
*>(devices
->data
);
60 if (gdk_device_get_source(device
) == GDK_SOURCE_TOUCHSCREEN
) {
64 devices
= devices
->next
;
74 bool IsMainWindowTransparent() {
75 return nsWindow::IsToplevelWindowTransparent();
78 // We avoid linking gdk_*_display_get_type directly in order to avoid a runtime
79 // dependency on GTK built with both backends. Other X11- and Wayland-specific
80 // functions get stubbed out by libmozgtk and crash when called, but those
81 // should only be called when the matching backend is already in use.
83 bool GdkIsWaylandDisplay(GdkDisplay
* display
) {
84 static auto sGdkWaylandDisplayGetType
=
85 (GType(*)())dlsym(RTLD_DEFAULT
, "gdk_wayland_display_get_type");
86 return sGdkWaylandDisplayGetType
&&
87 G_TYPE_CHECK_INSTANCE_TYPE(display
, sGdkWaylandDisplayGetType());
90 bool GdkIsX11Display(GdkDisplay
* display
) {
91 static auto sGdkX11DisplayGetType
=
92 (GType(*)())dlsym(RTLD_DEFAULT
, "gdk_x11_display_get_type");
93 return sGdkX11DisplayGetType
&&
94 G_TYPE_CHECK_INSTANCE_TYPE(display
, sGdkX11DisplayGetType());
97 bool IsXWaylandProtocol() {
98 static bool isXwayland
= [] {
99 return !GdkIsWaylandDisplay() && !!getenv("WAYLAND_DISPLAY");
104 bool GdkIsWaylandDisplay() {
105 static bool isWaylandDisplay
= gdk_display_get_default() &&
106 GdkIsWaylandDisplay(gdk_display_get_default());
107 return isWaylandDisplay
;
110 bool GdkIsX11Display() {
111 static bool isX11Display
= gdk_display_get_default()
112 ? GdkIsX11Display(gdk_display_get_default())
117 GdkDevice
* GdkGetPointer() {
118 GdkDisplay
* display
= gdk_display_get_default();
119 GdkDeviceManager
* deviceManager
= gdk_display_get_device_manager(display
);
120 return gdk_device_manager_get_client_pointer(deviceManager
);
123 static GdkEvent
* sLastMousePressEvent
= nullptr;
124 GdkEvent
* GetLastMousePressEvent() { return sLastMousePressEvent
; }
126 void SetLastMousePressEvent(GdkEvent
* aEvent
) {
127 if (sLastMousePressEvent
) {
128 GUniquePtr
<GdkEvent
> event(sLastMousePressEvent
);
129 sLastMousePressEvent
= nullptr;
134 GUniquePtr
<GdkEvent
> event(gdk_event_copy(aEvent
));
135 sLastMousePressEvent
= event
.release();
138 bool IsRunningUnderSnap() { return !!GetSnapInstanceName(); }
140 bool IsRunningUnderFlatpak() {
141 // https://gitlab.gnome.org/GNOME/gtk/-/blob/4300a5c609306ce77cbc8a3580c19201dccd8d13/gdk/gdk.c#L472
142 static bool sRunning
= [] {
143 return g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS
);
148 bool IsPackagedAppFileExists() {
149 static bool sRunning
= [] {
152 nsCOMPtr
<nsIFile
> file
;
153 nsCOMPtr
<nsIProperties
> directoryService
;
155 directoryService
= do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
);
156 NS_ENSURE_TRUE(directoryService
, FALSE
);
158 rv
= directoryService
->Get(NS_GRE_DIR
, NS_GET_IID(nsIFile
),
159 getter_AddRefs(file
));
160 NS_ENSURE_SUCCESS(rv
, FALSE
);
162 rv
= file
->AppendNative("is-packaged-app"_ns
);
163 NS_ENSURE_SUCCESS(rv
, FALSE
);
165 rv
= file
->GetNativePath(path
);
166 NS_ENSURE_SUCCESS(rv
, FALSE
);
168 return g_file_test(path
.get(), G_FILE_TEST_EXISTS
);
173 const char* GetSnapInstanceName() {
174 static const char* sInstanceName
= []() -> const char* {
175 const char* snapName
= g_getenv("SNAP_NAME");
179 if (g_strcmp0(snapName
, MOZ_APP_NAME
)) {
182 // Intentionally leaked, as keeping a pointer to the environment forever
183 // is a bit suspicious.
184 if (const char* instanceName
= g_getenv("SNAP_INSTANCE_NAME")) {
185 return g_strdup(instanceName
);
187 // Instance name didn't exist for snapd <= 2.35:
188 return g_strdup(snapName
);
190 return sInstanceName
;
193 bool ShouldUsePortal(PortalKind aPortalKind
) {
194 static bool sPortalEnv
= [] {
195 if (IsRunningUnderFlatpakOrSnap()) {
198 const char* portalEnvString
= g_getenv("GTK_USE_PORTAL");
199 return portalEnvString
&& atoi(portalEnvString
) != 0;
202 bool autoBehavior
= sPortalEnv
;
203 const int32_t pref
= [&] {
204 switch (aPortalKind
) {
205 case PortalKind::FilePicker
:
206 return StaticPrefs::widget_use_xdg_desktop_portal_file_picker();
207 case PortalKind::MimeHandler
:
208 // Mime portal breaks default browser handling, see bug 1516290.
209 autoBehavior
= IsRunningUnderFlatpakOrSnap();
210 return StaticPrefs::widget_use_xdg_desktop_portal_mime_handler();
211 case PortalKind::Settings
:
213 return StaticPrefs::widget_use_xdg_desktop_portal_settings();
214 case PortalKind::Location
:
215 return StaticPrefs::widget_use_xdg_desktop_portal_location();
216 case PortalKind::OpenUri
:
217 return StaticPrefs::widget_use_xdg_desktop_portal_open_uri();
232 nsTArray
<nsCString
> ParseTextURIList(const nsACString
& aData
) {
233 UniquePtr
<char[]> data(ToNewCString(aData
));
234 gchar
** uris
= g_uri_list_extract_uris(data
.get());
236 nsTArray
<nsCString
> result
;
237 for (size_t i
= 0; i
< g_strv_length(uris
); i
++) {
238 result
.AppendElement(nsCString(uris
[i
]));
246 static gboolean
token_failed(gpointer aData
);
248 class XDGTokenRequest
{
250 void SetTokenID(const char* aTokenID
) {
251 LOGW("RequestWaylandFocusPromise() SetTokenID %s", aTokenID
);
252 mTransferPromise
->Resolve(aTokenID
, __func__
);
255 LOGW("RequestWaylandFocusPromise() canceled");
256 mTransferPromise
->Reject(false, __func__
);
257 mActivationTimeoutID
= 0;
260 XDGTokenRequest(xdg_activation_token_v1
* aXdgToken
,
261 RefPtr
<FocusRequestPromise::Private
> aTransferPromise
)
262 : mXdgToken(aXdgToken
), mTransferPromise(std::move(aTransferPromise
)) {
263 mActivationTimeoutID
=
264 g_timeout_add(sActivationTimeout
, token_failed
, this);
267 MozClearPointer(mXdgToken
, xdg_activation_token_v1_destroy
);
268 if (mActivationTimeoutID
) {
269 g_source_remove(mActivationTimeoutID
);
274 xdg_activation_token_v1
* mXdgToken
;
275 RefPtr
<FocusRequestPromise::Private
> mTransferPromise
;
276 guint mActivationTimeoutID
;
277 // Reject FocusRequestPromise if we don't get XDG token in 0.5 sec.
278 static constexpr int sActivationTimeout
= 500;
281 // Failed to get token in time
282 static gboolean
token_failed(gpointer data
) {
283 UniquePtr
<XDGTokenRequest
> request(static_cast<XDGTokenRequest
*>(data
));
288 // We've got activation token from Wayland compositor so it's time to use it.
289 static void token_done(gpointer data
, struct xdg_activation_token_v1
* provider
,
290 const char* tokenID
) {
291 UniquePtr
<XDGTokenRequest
> request(static_cast<XDGTokenRequest
*>(data
));
292 request
->SetTokenID(tokenID
);
295 static const struct xdg_activation_token_v1_listener token_listener
= {
300 RefPtr
<FocusRequestPromise
> RequestWaylandFocusPromise() {
302 if (!GdkIsWaylandDisplay() || !KeymapWrapper::GetSeat()) {
303 LOGW("RequestWaylandFocusPromise() failed.");
307 RefPtr
<nsWindow
> sourceWindow
= nsWindow::GetFocusedWindow();
308 if (!sourceWindow
|| sourceWindow
->IsDestroyed()) {
309 LOGW("RequestWaylandFocusPromise() missing source window");
313 xdg_activation_v1
* xdg_activation
= WaylandDisplayGet()->GetXdgActivation();
314 if (!xdg_activation
) {
315 LOGW("RequestWaylandFocusPromise() missing xdg_activation");
319 wl_surface
* focusSurface
;
320 uint32_t focusSerial
;
321 KeymapWrapper::GetFocusInfo(&focusSurface
, &focusSerial
);
323 LOGW("RequestWaylandFocusPromise() missing focusSurface");
327 GdkWindow
* gdkWindow
= sourceWindow
->GetToplevelGdkWindow();
331 wl_surface
* surface
= gdk_wayland_window_get_wl_surface(gdkWindow
);
332 if (focusSurface
!= surface
) {
333 LOGW("RequestWaylandFocusPromise() missing wl_surface");
337 RefPtr
<FocusRequestPromise::Private
> transferPromise
=
338 new FocusRequestPromise::Private(__func__
);
340 xdg_activation_token_v1
* aXdgToken
=
341 xdg_activation_v1_get_activation_token(xdg_activation
);
342 xdg_activation_token_v1_add_listener(
343 aXdgToken
, &token_listener
,
344 new XDGTokenRequest(aXdgToken
, transferPromise
));
345 xdg_activation_token_v1_set_serial(aXdgToken
, focusSerial
,
346 KeymapWrapper::GetSeat());
347 xdg_activation_token_v1_set_surface(aXdgToken
, focusSurface
);
348 xdg_activation_token_v1_commit(aXdgToken
);
350 LOGW("RequestWaylandFocusPromise() XDG Token sent");
352 return transferPromise
.forget();
353 #else // !defined(MOZ_WAYLAND)
358 // https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html
359 static nsCString
GetWindowManagerName() {
360 if (!GdkIsX11Display()) {
365 Display
* xdisplay
= gdk_x11_get_default_xdisplay();
367 GDK_WINDOW_XID(gdk_screen_get_root_window(gdk_screen_get_default()));
369 int actual_format_return
;
370 Atom actual_type_return
;
371 unsigned long nitems_return
;
372 unsigned long bytes_after_return
;
373 unsigned char* prop_return
= nullptr;
374 auto releaseXProperty
= MakeScopeExit([&] {
380 Atom property
= XInternAtom(xdisplay
, "_NET_SUPPORTING_WM_CHECK", true);
381 Atom req_type
= XInternAtom(xdisplay
, "WINDOW", true);
382 if (!property
|| !req_type
) {
386 XGetWindowProperty(xdisplay
, root_win
, property
,
388 sizeof(Window
) / 4, // length
390 req_type
, &actual_type_return
, &actual_format_return
,
391 &nitems_return
, &bytes_after_return
, &prop_return
);
393 if (result
!= Success
|| bytes_after_return
!= 0 || nitems_return
!= 1) {
397 Window wmWindow
= reinterpret_cast<Window
*>(prop_return
)[0];
403 prop_return
= nullptr;
405 property
= XInternAtom(xdisplay
, "_NET_WM_NAME", true);
406 req_type
= XInternAtom(xdisplay
, "UTF8_STRING", true);
407 if (!property
|| !req_type
) {
411 XGetWindowProperty(xdisplay
, wmWindow
, property
,
415 req_type
, &actual_type_return
, &actual_format_return
,
416 &nitems_return
, &bytes_after_return
, &prop_return
);
417 if (result
!= Success
|| bytes_after_return
!= 0) {
421 return nsCString(reinterpret_cast<const char*>(prop_return
));
427 // Getting a reliable identifier is quite tricky. We try to use the standard
428 // XDG_CURRENT_DESKTOP environment first, _NET_WM_NAME later, and a set of
429 // legacy / non-standard environment variables otherwise.
431 // Documentation for some of those can be found in:
433 // https://wiki.archlinux.org/title/Environment_variables#Examples
434 // https://wiki.archlinux.org/title/Xdg-utils#Environment_variables
435 const nsCString
& GetDesktopEnvironmentIdentifier() {
436 MOZ_ASSERT(NS_IsMainThread());
437 static const nsDependentCString sIdentifier
= [] {
438 nsCString ident
= [] {
439 auto Env
= [](const char* aKey
) -> const char* {
440 const char* v
= getenv(aKey
);
441 return v
&& *v
? v
: nullptr;
443 if (const char* currentDesktop
= Env("XDG_CURRENT_DESKTOP")) {
444 return nsCString(currentDesktop
);
446 if (auto wm
= GetWindowManagerName(); !wm
.IsEmpty()) {
449 if (const char* sessionDesktop
= Env("XDG_SESSION_DESKTOP")) {
450 // This is not really standardized in freedesktop.org, but it is
451 // documented here, and should be set in systemd systems.
452 // https://www.freedesktop.org/software/systemd/man/pam_systemd.html#%24XDG_SESSION_DESKTOP
453 return nsCString(sessionDesktop
);
455 // We try first the DE-specific variables, then SESSION_DESKTOP, to match
456 // the documented order in:
457 // https://wiki.archlinux.org/title/Xdg-utils#Environment_variables
458 if (getenv("GNOME_DESKTOP_SESSION_ID")) {
459 return nsCString("gnome"_ns
);
461 if (getenv("KDE_FULL_SESSION")) {
462 return nsCString("kde"_ns
);
464 if (getenv("MATE_DESKTOP_SESSION_ID")) {
465 return nsCString("mate"_ns
);
467 if (getenv("LXQT_SESSION_CONFIG")) {
468 return nsCString("lxqt"_ns
);
470 if (const char* desktopSession
= Env("DESKTOP_SESSION")) {
471 // Try the legacy DESKTOP_SESSION as a last resort.
472 return nsCString(desktopSession
);
477 // Intentionally put into a ToNewCString copy, rather than just making a
478 // static nsCString to avoid leakchecking errors, since we really want to
480 return nsDependentCString(ToNewCString(ident
), ident
.Length());
485 bool IsGnomeDesktopEnvironment() {
486 static bool sIsGnome
=
487 !!FindInReadable("gnome"_ns
, GetDesktopEnvironmentIdentifier());
491 bool IsKdeDesktopEnvironment() {
492 static bool sIsKde
= GetDesktopEnvironmentIdentifier().EqualsLiteral("kde");
496 bool IsCancelledGError(GError
* aGError
) {
497 return g_error_matches(aGError
, G_IO_ERROR
, G_IO_ERROR_CANCELLED
);
500 } // namespace mozilla::widget