/* -*- 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 #include "nsUserIdleServiceGTK.h" #include "nsDebug.h" #include "prlink.h" #include "mozilla/Logging.h" #include "WidgetUtilsGtk.h" #ifdef MOZ_X11 # include # include # include #endif #ifdef MOZ_ENABLE_DBUS # include # 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&& 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 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::max()) { idleTime = std::numeric_limits::max(); } mLastIdleTime = idleTime; mPollInProgress = false; MOZ_LOG(sIdleLog, LogLevel::Info, ("Async handler got %d\n", mLastIdleTime)); }, [this](GUniquePtr&& 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&& aProxy) { mProxy = std::move(aProxy); mUserIdleServiceGTK->AcceptServiceCallback(); }, [this](GUniquePtr&& 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 mProxy; RefPtr 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(this); break; #endif #ifdef MOZ_X11 case IDLE_SERVICE_XSCREENSAVER: mIdleService = MakeUnique(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); }