Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / gtk / WidgetUtilsGtk.cpp
blob2ce81dedb75fb12341efc4785f173143fa24ef18
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"
12 #include "nsString.h"
13 #include "nsStringFwd.h"
14 #include "nsWindow.h"
15 #include "nsIGfxInfo.h"
16 #include "mozilla/Components.h"
17 #include "nsCOMPtr.h"
18 #include "nsIProperties.h"
19 #include "nsIFile.h"
20 #include "nsXULAppAPI.h"
21 #include "nsXPCOMCID.h"
22 #include "nsDirectoryServiceDefs.h"
23 #include "nsString.h"
24 #include "nsGtkKeyUtils.h"
25 #include "nsGtkUtils.h"
27 #include <gtk/gtk.h>
28 #include <dlfcn.h>
29 #include <glib.h>
31 #undef LOGW
32 #ifdef MOZ_LOGGING
33 # include "mozilla/Logging.h"
34 extern mozilla::LazyLogModule gWidgetLog;
35 # define LOGW(...) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
36 #else
37 # define LOGW(...)
38 #endif /* MOZ_LOGGING */
40 namespace mozilla::widget {
42 int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent() {
43 int32_t result = 0;
44 GdkDisplay* display = gdk_display_get_default();
45 if (!display) {
46 return 0;
49 GdkDeviceManager* manager = gdk_display_get_device_manager(display);
50 if (!manager) {
51 return 0;
54 GList* devices =
55 gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_SLAVE);
56 GList* list = devices;
58 while (devices) {
59 GdkDevice* device = static_cast<GdkDevice*>(devices->data);
60 if (gdk_device_get_source(device) == GDK_SOURCE_TOUCHSCREEN) {
61 result = 1;
62 break;
64 devices = devices->next;
67 if (list) {
68 g_list_free(list);
71 return result;
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");
100 }();
101 return isXwayland;
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())
113 : false;
114 return isX11Display;
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;
131 if (!aEvent) {
132 return;
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);
144 }();
145 return sRunning;
148 bool IsPackagedAppFileExists() {
149 static bool sRunning = [] {
150 nsresult rv;
151 nsCString path;
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);
169 }();
170 return sRunning;
173 const char* GetSnapInstanceName() {
174 static const char* sInstanceName = []() -> const char* {
175 const char* snapName = g_getenv("SNAP_NAME");
176 if (!snapName) {
177 return nullptr;
179 if (g_strcmp0(snapName, MOZ_APP_NAME)) {
180 return nullptr;
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);
189 }();
190 return sInstanceName;
193 bool ShouldUsePortal(PortalKind aPortalKind) {
194 static bool sPortalEnv = [] {
195 if (IsRunningUnderFlatpakOrSnap()) {
196 return true;
198 const char* portalEnvString = g_getenv("GTK_USE_PORTAL");
199 return portalEnvString && atoi(portalEnvString) != 0;
200 }();
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:
212 autoBehavior = true;
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();
219 return 2;
220 }();
222 switch (pref) {
223 case 0:
224 return false;
225 case 1:
226 return true;
227 default:
228 return autoBehavior;
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]));
241 g_strfreev(uris);
242 return result;
245 #ifdef MOZ_WAYLAND
246 static gboolean token_failed(gpointer aData);
248 class XDGTokenRequest {
249 public:
250 void SetTokenID(const char* aTokenID) {
251 LOGW("RequestWaylandFocusPromise() SetTokenID %s", aTokenID);
252 mTransferPromise->Resolve(aTokenID, __func__);
254 void Cancel() {
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);
266 ~XDGTokenRequest() {
267 MozClearPointer(mXdgToken, xdg_activation_token_v1_destroy);
268 if (mActivationTimeoutID) {
269 g_source_remove(mActivationTimeoutID);
273 private:
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));
284 request->Cancel();
285 return false;
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 = {
296 token_done,
298 #endif
300 RefPtr<FocusRequestPromise> RequestWaylandFocusPromise() {
301 #ifdef MOZ_WAYLAND
302 if (!GdkIsWaylandDisplay() || !KeymapWrapper::GetSeat()) {
303 LOGW("RequestWaylandFocusPromise() failed.");
304 return nullptr;
307 RefPtr<nsWindow> sourceWindow = nsWindow::GetFocusedWindow();
308 if (!sourceWindow || sourceWindow->IsDestroyed()) {
309 LOGW("RequestWaylandFocusPromise() missing source window");
310 return nullptr;
313 xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
314 if (!xdg_activation) {
315 LOGW("RequestWaylandFocusPromise() missing xdg_activation");
316 return nullptr;
319 wl_surface* focusSurface;
320 uint32_t focusSerial;
321 KeymapWrapper::GetFocusInfo(&focusSurface, &focusSerial);
322 if (!focusSurface) {
323 LOGW("RequestWaylandFocusPromise() missing focusSurface");
324 return nullptr;
327 GdkWindow* gdkWindow = sourceWindow->GetToplevelGdkWindow();
328 if (!gdkWindow) {
329 return nullptr;
331 wl_surface* surface = gdk_wayland_window_get_wl_surface(gdkWindow);
332 if (focusSurface != surface) {
333 LOGW("RequestWaylandFocusPromise() missing wl_surface");
334 return nullptr;
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)
354 return nullptr;
355 #endif
358 // https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html
359 static nsCString GetWindowManagerName() {
360 if (!GdkIsX11Display()) {
361 return {};
364 #ifdef MOZ_X11
365 Display* xdisplay = gdk_x11_get_default_xdisplay();
366 Window root_win =
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([&] {
375 if (prop_return) {
376 XFree(prop_return);
380 Atom property = XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", true);
381 Atom req_type = XInternAtom(xdisplay, "WINDOW", true);
382 if (!property || !req_type) {
383 return {};
385 int result =
386 XGetWindowProperty(xdisplay, root_win, property,
387 0L, // offset
388 sizeof(Window) / 4, // length
389 false, // delete
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) {
394 return {};
397 Window wmWindow = reinterpret_cast<Window*>(prop_return)[0];
398 if (!wmWindow) {
399 return {};
402 XFree(prop_return);
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) {
408 return {};
410 result =
411 XGetWindowProperty(xdisplay, wmWindow, property,
412 0L, // offset
413 INT32_MAX, // length
414 false, // delete
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) {
418 return {};
421 return nsCString(reinterpret_cast<const char*>(prop_return));
422 #else
423 return {};
424 #endif
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()) {
447 return wm;
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);
474 return nsCString();
475 }();
476 ToLowerCase(ident);
477 // Intentionally put into a ToNewCString copy, rather than just making a
478 // static nsCString to avoid leakchecking errors, since we really want to
479 // leak this string.
480 return nsDependentCString(ToNewCString(ident), ident.Length());
481 }();
482 return sIdentifier;
485 bool IsGnomeDesktopEnvironment() {
486 static bool sIsGnome =
487 !!FindInReadable("gnome"_ns, GetDesktopEnvironmentIdentifier());
488 return sIsGnome;
491 bool IsKdeDesktopEnvironment() {
492 static bool sIsKde = GetDesktopEnvironmentIdentifier().EqualsLiteral("kde");
493 return sIsKde;
496 bool IsCancelledGError(GError* aGError) {
497 return g_error_matches(aGError, G_IO_ERROR, G_IO_ERROR_CANCELLED);
500 } // namespace mozilla::widget