Files
tubestation/widget/gtk/nsAppShell.cpp
Norisz Fay add86802b2 Backed out 16 changesets (bug 1934497) for causing Wayland related mochitest failures and bustage on WaylandSurface.cpp CLOSED TREE
Backed out changeset b2f82cada9b0 (bug 1934497)
Backed out changeset e6b221a0e987 (bug 1934497)
Backed out changeset e535888994ed (bug 1934497)
Backed out changeset 31f0a398817f (bug 1934497)
Backed out changeset 6225ae4714a9 (bug 1934497)
Backed out changeset 2f0971ca8b41 (bug 1934497)
Backed out changeset ed34f4ff5a2b (bug 1934497)
Backed out changeset 739018e9ae4a (bug 1934497)
Backed out changeset 4f50bd47febd (bug 1934497)
Backed out changeset bbe16df11895 (bug 1934497)
Backed out changeset d6e7e274dda3 (bug 1934497)
Backed out changeset a1cf316b3e7a (bug 1934497)
Backed out changeset 2fbec9faf49c (bug 1934497)
Backed out changeset f6ea1323c158 (bug 1934497)
Backed out changeset fe1e2b1b4f8b (bug 1934497)
Backed out changeset 59b2a1f052e4 (bug 1934497)
2024-12-10 11:58:37 +02:00

494 lines
16 KiB
C++

/* -*- 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");
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;
}