summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsAppShell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsAppShell.cpp')
-rw-r--r--widget/gtk/nsAppShell.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
new file mode 100644
index 0000000000..d26b43737d
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "nsAppShell.h"
+#include "nsBaseAppShell.h"
+#include "nsWindow.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/Unused.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+# include <gio/gio.h>
+# include "nsIObserverService.h"
+# include "WidgetUtilsGtk.h"
+#endif
+#include "WakeLockListener.h"
+#include "gfxPlatform.h"
+#include "nsAppRunner.h"
+#include "mozilla/XREAppData.h"
+#include "ScreenHelperGTK.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using mozilla::widget::HeadlessScreenHelper;
+using mozilla::widget::ScreenHelperGTK;
+using mozilla::widget::ScreenManager;
+
+#define NOTIFY_TOKEN 0xFA
+#define QUIT_TOKEN 0xFB
+
+LazyLogModule gWidgetLog("Widget");
+LazyLogModule gWidgetDragLog("WidgetDrag");
+LazyLogModule gWidgetWaylandLog("WidgetWayland");
+LazyLogModule gWidgetPopupLog("WidgetPopup");
+LazyLogModule gWidgetVsync("WidgetVsync");
+LazyLogModule gDmabufLog("Dmabuf");
+LazyLogModule gClipboardLog("WidgetClipboard");
+
+static GPollFunc sPollFunc;
+
+nsAppShell* sAppShell = nullptr;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint PollWrapper(GPollFD* aUfds, guint aNfsd, gint aTimeout) {
+ if (aTimeout == 0) {
+ // When the timeout is 0, there is no wait, so no point in notifying
+ // the BackgroundHangMonitor and the profiler.
+ return (*sPollFunc)(aUfds, aNfsd, aTimeout);
+ }
+
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ gint result;
+ {
+ gint timeout = aTimeout;
+ gint64 begin = 0;
+ if (aTimeout != -1) {
+ begin = g_get_monotonic_time();
+ }
+
+ AUTO_PROFILER_LABEL("PollWrapper", IDLE);
+ AUTO_PROFILER_THREAD_SLEEP;
+ do {
+ result = (*sPollFunc)(aUfds, aNfsd, timeout);
+
+ // The result will be -1 with the EINTR error if the poll was interrupted
+ // by a signal, typically the signal sent by the profiler to sample the
+ // process. We are only done waiting if we are not in that case.
+ if (result != -1 || errno != EINTR) {
+ break;
+ }
+
+ if (aTimeout != -1) {
+ // Adjust the timeout to account for the time already spent waiting.
+ gint elapsedSinceBegin = (g_get_monotonic_time() - begin) / 1000;
+ if (elapsedSinceBegin < aTimeout) {
+ timeout = aTimeout - elapsedSinceBegin;
+ } else {
+ // poll returns 0 to indicate the call timed out before any fd
+ // became ready.
+ result = 0;
+ break;
+ }
+ }
+ } while (true);
+ }
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+ return result;
+}
+
+// Emit resume-events on GdkFrameClock if flush-events has not been
+// balanced by resume-events at dispose.
+// For https://bugzilla.gnome.org/show_bug.cgi?id=742636
+static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
+static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
+static GQuark sPendingResumeQuark;
+
+static void OnFlushEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
+}
+
+static void OnResumeEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
+}
+
+static void WrapGdkFrameClockConstructed(GObject* object) {
+ sRealGdkFrameClockConstructed(object);
+
+ g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
+ g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
+ nullptr);
+}
+
+static void WrapGdkFrameClockDispose(GObject* object) {
+ if (g_object_get_qdata(object, sPendingResumeQuark)) {
+ g_signal_emit_by_name(object, "resume-events");
+ }
+
+ sRealGdkFrameClockDispose(object);
+}
+
+/*static*/
+gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ nsAppShell* self = static_cast<nsAppShell*>(data);
+
+ unsigned char c;
+ Unused << read(self->mPipeFDs[0], &c, 1);
+ switch (c) {
+ case NOTIFY_TOKEN:
+ self->NativeEventCallback();
+ break;
+ case QUIT_TOKEN:
+ self->Exit();
+ break;
+ default:
+ NS_ASSERTION(false, "wrong token");
+ break;
+ }
+ return TRUE;
+}
+
+nsAppShell::~nsAppShell() {
+ sAppShell = nullptr;
+
+#ifdef MOZ_ENABLE_DBUS
+ StopDBusListening();
+#endif
+ mozilla::hal::Shutdown();
+
+ if (mTag) g_source_remove(mTag);
+ if (mPipeFDs[0]) close(mPipeFDs[0]);
+ if (mPipeFDs[1]) close(mPipeFDs[1]);
+}
+
+mozilla::StaticRefPtr<WakeLockListener> sWakeLockListener;
+static void AddScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> powerManager =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (powerManager) {
+ sWakeLockListener = new WakeLockListener();
+ powerManager->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void RemoveScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> powerManager =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (powerManager) {
+ powerManager->RemoveWakeLockListener(sWakeLockListener);
+ sWakeLockListener = nullptr;
+ }
+}
+
+#ifdef MOZ_ENABLE_DBUS
+void nsAppShell::DBusSessionSleepCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData) {
+ if (g_strcmp0(aSignalName, "PrepareForSleep")) {
+ return;
+ }
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return;
+ }
+ if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE) ||
+ g_variant_n_children(aParameters) != 1) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected location updated signal params type: %s\n",
+ g_variant_get_type_string(aParameters))
+ .get());
+ return;
+ }
+
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_get_child_value(aParameters, 0));
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected location updated signal params type: %s\n",
+ g_variant_get_type_string(aParameters))
+ .get());
+ return;
+ }
+
+ gboolean suspend = g_variant_get_boolean(variant);
+ if (suspend) {
+ // Post sleep_notification
+ observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
+ nullptr);
+ } else {
+ // Post wake_notification
+ observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
+ nullptr);
+ }
+}
+
+void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData) {
+ if (g_strcmp0(aSignalName, "PropertiesChanged")) {
+ return;
+ }
+ nsBaseAppShell::OnSystemTimezoneChange();
+}
+
+void nsAppShell::DBusConnectClientResponse(GObject* aObject,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GDBusProxy> proxyClient =
+ dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error)));
+ if (!proxyClient) {
+ if (!IsCancelledGError(error.get())) {
+ NS_WARNING(
+ nsPrintfCString("Failed to connect to client: %s\n", error->message)
+ .get());
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<nsAppShell*>(aUserData);
+ if (!strcmp(g_dbus_proxy_get_name(proxyClient), "org.freedesktop.login1")) {
+ self->mLogin1Proxy = std::move(proxyClient);
+ g_signal_connect(self->mLogin1Proxy, "g-signal",
+ G_CALLBACK(DBusSessionSleepCallback), self);
+ } else {
+ self->mTimedate1Proxy = std::move(proxyClient);
+ g_signal_connect(self->mTimedate1Proxy, "g-signal",
+ G_CALLBACK(DBusTimedatePropertiesChangedCallback), self);
+ }
+}
+
+// Based on
+// https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
+// Use login1 to signal sleep and wake notifications.
+void nsAppShell::StartDBusListening() {
+ MOZ_DIAGNOSTIC_ASSERT(!mLogin1Proxy, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mTimedate1Proxy, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mLogin1ProxyCancellable, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mTimedate1ProxyCancellable, "Already configured?");
+
+ mLogin1ProxyCancellable = dont_AddRef(g_cancellable_new());
+ mTimedate1ProxyCancellable = dont_AddRef(g_cancellable_new());
+
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.login1", "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager", mLogin1ProxyCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
+
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
+ "org.freedesktop.DBus.Properties", mTimedate1ProxyCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
+}
+
+void nsAppShell::StopDBusListening() {
+ if (mLogin1Proxy) {
+ g_signal_handlers_disconnect_matched(mLogin1Proxy, G_SIGNAL_MATCH_DATA, 0,
+ 0, nullptr, nullptr, this);
+ }
+ if (mLogin1ProxyCancellable) {
+ g_cancellable_cancel(mLogin1ProxyCancellable);
+ mLogin1ProxyCancellable = nullptr;
+ }
+ mLogin1Proxy = nullptr;
+
+ if (mTimedate1Proxy) {
+ g_signal_handlers_disconnect_matched(mTimedate1Proxy, G_SIGNAL_MATCH_DATA,
+ 0, 0, nullptr, nullptr, this);
+ }
+ if (mTimedate1ProxyCancellable) {
+ g_cancellable_cancel(mTimedate1ProxyCancellable);
+ mTimedate1ProxyCancellable = nullptr;
+ }
+ mTimedate1Proxy = nullptr;
+}
+#endif
+
+void nsAppShell::TermSignalHandler(int signo) {
+ if (signo != SIGTERM) {
+ NS_WARNING("Wrong signal!");
+ return;
+ }
+ sAppShell->ScheduleQuitEvent();
+}
+
+void nsAppShell::InstallTermSignalHandler() {
+ if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
+ !sAppShell) {
+ return;
+ }
+
+ struct sigaction act = {}, oldact;
+ act.sa_handler = TermSignalHandler;
+ sigfillset(&act.sa_mask);
+
+ if (NS_WARN_IF(sigaction(SIGTERM, nullptr, &oldact) != 0)) {
+ return;
+ }
+ if (oldact.sa_handler != SIG_DFL) {
+ NS_WARNING("SIGTERM signal handler is already set?");
+ }
+
+ sigaction(SIGTERM, &act, nullptr);
+}
+
+nsresult nsAppShell::Init() {
+ mozilla::hal::Init();
+
+#ifdef MOZ_ENABLE_DBUS
+ if (XRE_IsParentProcess()) {
+ StartDBusListening();
+ }
+#endif
+
+ if (!sPollFunc) {
+ sPollFunc = g_main_context_get_poll_func(nullptr);
+ g_main_context_set_poll_func(nullptr, &PollWrapper);
+ }
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
+ }
+
+ if (gtk_check_version(3, 16, 3) == nullptr) {
+ // Before 3.16.3, GDK cannot override classname by --class command line
+ // option when program uses gdk_set_program_class().
+ //
+ // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
+ //
+ // Only bother doing this for the parent process, since it's the one
+ // creating top-level windows.
+ if (gAppData) {
+ gdk_set_program_class(gAppData->remotingName);
+ }
+ }
+ }
+
+ if (!sPendingResumeQuark &&
+ gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
+ // GTK 3.8 - 3.14 registered this type when creating the frame clock
+ // for the root window of the display when the display was opened.
+ GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
+ if (gdkFrameClockIdleType) { // not in versions prior to 3.8
+ sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
+ auto gdk_frame_clock_idle_class =
+ G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
+ auto constructed = &gdk_frame_clock_idle_class->constructed;
+ sRealGdkFrameClockConstructed = *constructed;
+ *constructed = WrapGdkFrameClockConstructed;
+ auto dispose = &gdk_frame_clock_idle_class->dispose;
+ sRealGdkFrameClockDispose = *dispose;
+ *dispose = WrapGdkFrameClockDispose;
+ }
+ }
+
+ // Workaround for bug 1209659 which is fixed by Gtk3.20
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ unsetenv("GTK_CSD");
+ }
+
+ // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
+ GSList* pixbufFormats = gdk_pixbuf_get_formats();
+ for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
+ GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
+ gchar* name = gdk_pixbuf_format_get_name(format);
+ if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
+ strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
+ strcmp(name, "svg") && strcmp(name, "webp") && strcmp(name, "avif")) {
+ gdk_pixbuf_format_set_disabled(format, TRUE);
+ }
+ g_free(name);
+ }
+ g_slist_free(pixbufFormats);
+
+ int err = pipe(mPipeFDs);
+ if (err) return NS_ERROR_OUT_OF_MEMORY;
+
+ GIOChannel* ioc;
+ GSource* source;
+
+ // make the pipe nonblocking
+
+ int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+ flags = fcntl(mPipeFDs[1], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+
+ ioc = g_io_channel_unix_new(mPipeFDs[0]);
+ source = g_io_create_watch(ioc, G_IO_IN);
+ g_io_channel_unref(ioc);
+ g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
+ nullptr);
+ g_source_set_can_recurse(source, TRUE);
+ mTag = g_source_attach(source, nullptr);
+ g_source_unref(source);
+
+ sAppShell = this;
+
+ return nsBaseAppShell::Init();
+failed:
+ close(mPipeFDs[0]);
+ close(mPipeFDs[1]);
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppShell::Run() {
+ if (XRE_IsParentProcess()) {
+ AddScreenWakeLockListener();
+ }
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ if (XRE_IsParentProcess()) {
+ RemoveScreenWakeLockListener();
+ }
+ return rv;
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ unsigned char buf[] = {NOTIFY_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+void nsAppShell::ScheduleQuitEvent() {
+ unsigned char buf[] = {QUIT_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ if (mSuspendNativeCount) {
+ return false;
+ }
+ bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
+ return didProcessEvent;
+}