gecko-dev/widget/gtk/nsUserIdleServiceGTK.cpp

318 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
*/
/* 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 <gtk/gtk.h>
#include "nsUserIdleServiceGTK.h"
#include "nsDebug.h"
#include "prlink.h"
#include "mozilla/Logging.h"
#include "WidgetUtilsGtk.h"
#ifdef MOZ_X11
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <gdk/gdkx.h>
#endif
#ifdef MOZ_ENABLE_DBUS
# include <gio/gio.h>
# include "AsyncDBus.h"
# include "WakeLockListener.h"
# include "nsIObserverService.h"
# include "mozilla/UniquePtrExtensions.h"
#endif
using mozilla::LogLevel;
static mozilla::LazyLogModule sIdleLog("nsIUserIdleService");
using namespace mozilla;
using namespace mozilla::widget;
#ifdef MOZ_X11
typedef struct {
Window window; // Screen saver window
int state; // ScreenSaver(Off,On,Disabled)
int kind; // ScreenSaver(Blanked,Internal,External)
unsigned long til_or_since; // milliseconds since/til screensaver kicks in
unsigned long idle; // milliseconds idle
unsigned long event_mask; // event stuff
} XScreenSaverInfo;
typedef bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
int* error_base);
typedef XScreenSaverInfo* (*_XScreenSaverAllocInfo_fn)(void);
typedef void (*_XScreenSaverQueryInfo_fn)(Display* dpy, Drawable drw,
XScreenSaverInfo* info);
class UserIdleServiceX11 : public UserIdleServiceImpl {
public:
bool PollIdleTime(uint32_t* aIdleTime) override {
// Ask xscreensaver about idle time:
*aIdleTime = 0;
// We might not have a display (cf. in xpcshell)
Display* dplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
if (!dplay) {
MOZ_LOG(sIdleLog, LogLevel::Warning, ("No display found!\n"));
return false;
}
int event_base, error_base;
if (mXSSQueryExtension(dplay, &event_base, &error_base)) {
if (!mXssInfo) mXssInfo = mXSSAllocInfo();
if (!mXssInfo) return false;
mXSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
*aIdleTime = mXssInfo->idle;
MOZ_LOG(sIdleLog, LogLevel::Info,
("UserIdleServiceX11::PollIdleTime() %d\n", *aIdleTime));
return true;
}
// If we get here, we couldn't get to XScreenSaver:
MOZ_LOG(sIdleLog, LogLevel::Warning,
("XSSQueryExtension returned false!\n"));
return false;
}
bool ProbeImplementation() override {
MOZ_LOG(sIdleLog, LogLevel::Info,
("UserIdleServiceX11::UserIdleServiceX11()\n"));
if (!mozilla::widget::GdkIsX11Display()) {
return false;
}
// This will leak - See comments in ~UserIdleServiceX11().
PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1");
if (!xsslib) // ouch.
{
MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n"));
return false;
}
mXSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
xsslib, "XScreenSaverQueryExtension");
mXSSAllocInfo = (_XScreenSaverAllocInfo_fn)PR_FindFunctionSymbol(
xsslib, "XScreenSaverAllocInfo");
mXSSQueryInfo = (_XScreenSaverQueryInfo_fn)PR_FindFunctionSymbol(
xsslib, "XScreenSaverQueryInfo");
if (!mXSSQueryExtension) {
MOZ_LOG(sIdleLog, LogLevel::Warning,
("Failed to get XSSQueryExtension!\n"));
}
if (!mXSSAllocInfo) {
MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n"));
}
if (!mXSSQueryInfo) {
MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n"));
}
if (!mXSSQueryExtension || !mXSSAllocInfo || !mXSSQueryInfo) {
// We're missing X11 symbols
return false;
}
// UserIdleServiceX11 uses sync init, confirm it now.
mUserIdleServiceGTK->AcceptServiceCallback();
return true;
}
explicit UserIdleServiceX11(nsUserIdleServiceGTK* aUserIdleService)
: UserIdleServiceImpl(aUserIdleService) {};
~UserIdleServiceX11() {
# ifdef MOZ_X11
if (mXssInfo) {
XFree(mXssInfo);
}
# endif
// It is not safe to unload libXScrnSaver until each display is closed because
// the library registers callbacks through XESetCloseDisplay (Bug 397607).
// (Also the library and its functions are scoped for the file not the object.)
# if 0
if (xsslib) {
PR_UnloadLibrary(xsslib);
xsslib = nullptr;
}
# endif
}
private:
XScreenSaverInfo* mXssInfo = nullptr;
_XScreenSaverQueryExtension_fn mXSSQueryExtension = nullptr;
_XScreenSaverAllocInfo_fn mXSSAllocInfo = nullptr;
_XScreenSaverQueryInfo_fn mXSSQueryInfo = nullptr;
};
#endif
#ifdef MOZ_ENABLE_DBUS
class UserIdleServiceMutter : public UserIdleServiceImpl {
public:
bool PollIdleTime(uint32_t* aIdleTime) override {
MOZ_LOG(sIdleLog, LogLevel::Info, ("PollIdleTime() request\n"));
// We're not ready yet
if (!mProxy) {
return false;
}
if (!mPollInProgress) {
mPollInProgress = true;
DBusProxyCall(mProxy, "GetIdletime", nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
mCancellable)
->Then(
GetCurrentSerialEventTarget(), __func__,
// It's safe to capture this as we use mCancellable to stop
// listening.
[this](RefPtr<GVariant>&& aResult) {
if (!g_variant_is_of_type(aResult, G_VARIANT_TYPE_TUPLE) ||
g_variant_n_children(aResult) != 1) {
MOZ_LOG(sIdleLog, LogLevel::Info,
("PollIdleTime() Unexpected params type: %s\n",
g_variant_get_type_string(aResult)));
mLastIdleTime = 0;
return;
}
RefPtr<GVariant> iTime =
dont_AddRef(g_variant_get_child_value(aResult, 0));
if (!g_variant_is_of_type(iTime, G_VARIANT_TYPE_UINT64)) {
MOZ_LOG(sIdleLog, LogLevel::Info,
("PollIdleTime() Unexpected params type: %s\n",
g_variant_get_type_string(aResult)));
mLastIdleTime = 0;
return;
}
uint64_t idleTime = g_variant_get_uint64(iTime);
if (idleTime > std::numeric_limits<uint32_t>::max()) {
idleTime = std::numeric_limits<uint32_t>::max();
}
mLastIdleTime = idleTime;
mPollInProgress = false;
MOZ_LOG(sIdleLog, LogLevel::Info,
("Async handler got %d\n", mLastIdleTime));
},
[this](GUniquePtr<GError>&& aError) {
mPollInProgress = false;
if (!IsCancelledGError(aError.get())) {
MOZ_LOG(
sIdleLog, LogLevel::Warning,
("Failed to call GetIdletime(): %s\n", aError->message));
mUserIdleServiceGTK->RejectAndTryNextServiceCallback();
}
});
}
*aIdleTime = mLastIdleTime;
MOZ_LOG(sIdleLog, LogLevel::Info,
("PollIdleTime() returns %d\n", *aIdleTime));
return true;
}
bool ProbeImplementation() override {
MOZ_LOG(sIdleLog, LogLevel::Info,
("UserIdleServiceMutter::UserIdleServiceMutter()\n"));
mCancellable = dont_AddRef(g_cancellable_new());
CreateDBusProxyForBus(
G_BUS_TYPE_SESSION,
GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
nullptr, "org.gnome.Mutter.IdleMonitor",
"/org/gnome/Mutter/IdleMonitor/Core", "org.gnome.Mutter.IdleMonitor",
mCancellable)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this](RefPtr<GDBusProxy>&& aProxy) {
mProxy = std::move(aProxy);
mUserIdleServiceGTK->AcceptServiceCallback();
},
[this](GUniquePtr<GError>&& aError) {
if (!IsCancelledGError(aError.get())) {
mUserIdleServiceGTK->RejectAndTryNextServiceCallback();
}
});
return true;
}
explicit UserIdleServiceMutter(nsUserIdleServiceGTK* aUserIdleService)
: UserIdleServiceImpl(aUserIdleService) {};
~UserIdleServiceMutter() {
if (mCancellable) {
g_cancellable_cancel(mCancellable);
mCancellable = nullptr;
}
mProxy = nullptr;
}
private:
RefPtr<GDBusProxy> mProxy;
RefPtr<GCancellable> mCancellable;
uint32_t mLastIdleTime = 0;
bool mPollInProgress = false;
};
#endif
void nsUserIdleServiceGTK::ProbeService() {
MOZ_LOG(sIdleLog, LogLevel::Info,
("nsUserIdleServiceGTK::ProbeService() mIdleServiceType %d\n",
mIdleServiceType));
MOZ_ASSERT(!mIdleService);
switch (mIdleServiceType) {
#ifdef MOZ_ENABLE_DBUS
case IDLE_SERVICE_MUTTER:
mIdleService = MakeUnique<UserIdleServiceMutter>(this);
break;
#endif
#ifdef MOZ_X11
case IDLE_SERVICE_XSCREENSAVER:
mIdleService = MakeUnique<UserIdleServiceX11>(this);
break;
#endif
default:
return;
}
if (!mIdleService->ProbeImplementation()) {
RejectAndTryNextServiceCallback();
}
}
void nsUserIdleServiceGTK::AcceptServiceCallback() {
MOZ_LOG(sIdleLog, LogLevel::Info,
("nsUserIdleServiceGTK::AcceptServiceCallback() type %d\n",
mIdleServiceType));
mIdleServiceInitialized = true;
}
void nsUserIdleServiceGTK::RejectAndTryNextServiceCallback() {
MOZ_LOG(sIdleLog, LogLevel::Info,
("nsUserIdleServiceGTK::RejectAndTryNextServiceCallback() type %d\n",
mIdleServiceType));
// Delete recent non-working service
MOZ_ASSERT(mIdleService, "Nothing to reject?");
mIdleService = nullptr;
mIdleServiceInitialized = false;
mIdleServiceType++;
if (mIdleServiceType < IDLE_SERVICE_NONE) {
MOZ_LOG(sIdleLog, LogLevel::Info,
("nsUserIdleServiceGTK try next idle service\n"));
ProbeService();
} else {
MOZ_LOG(sIdleLog, LogLevel::Info, ("nsUserIdleServiceGTK failed\n"));
}
}
bool nsUserIdleServiceGTK::PollIdleTime(uint32_t* aIdleTime) {
if (!mIdleServiceInitialized) {
return false;
}
return mIdleService->PollIdleTime(aIdleTime);
}