bug 544240 - Idle service does not work on Qt on mobile platforms r=sdwilsh

--HG--
extra : rebase_source : aae22f47a0f0d3dd19690bf2dbc7a144bb255d14
This commit is contained in:
MikeK 2010-04-16 10:37:16 -07:00
parent c61e5af484
commit e684f2edfc
18 changed files with 640 additions and 273 deletions

View File

@ -44,7 +44,10 @@ class nsIdleServiceX : public nsIdleService
public:
NS_DECL_ISUPPORTS
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
bool PollIdleTime(PRUint32* aIdleTime);
protected:
bool UsePollMode();
};
#endif // nsIdleServiceX_h_

View File

@ -39,19 +39,19 @@
#include "nsIServiceManager.h"
#import <Foundation/Foundation.h>
NS_IMPL_ISUPPORTS1(nsIdleServiceX, nsIIdleService)
NS_IMPL_ISUPPORTS2(nsIdleServiceX, nsIIdleService, nsIdleService)
NS_IMETHODIMP
nsIdleServiceX::GetIdleTime(PRUint32 *aTimeDiff)
bool
nsIdleServiceX::PollIdleTime(PRUint32 *aIdleTime)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
kern_return_t rval;
mach_port_t masterPort;
rval = IOMasterPort(kIOMasterPortDefault, &masterPort);
if (rval != KERN_SUCCESS)
return NS_ERROR_FAILURE;
return false;
io_iterator_t hidItr;
rval = IOServiceGetMatchingServices(masterPort,
@ -59,7 +59,7 @@ nsIdleServiceX::GetIdleTime(PRUint32 *aTimeDiff)
&hidItr);
if (rval != KERN_SUCCESS)
return NS_ERROR_FAILURE;
return false;
NS_ASSERTION(hidItr, "Our iterator is null, but it ought not to be!");
io_registry_entry_t entry = IOIteratorNext(hidItr);
@ -72,7 +72,7 @@ nsIdleServiceX::GetIdleTime(PRUint32 *aTimeDiff)
(CFMutableDictionaryRef*)&hidProps,
kCFAllocatorDefault, 0);
if (rval != KERN_SUCCESS)
return NS_ERROR_FAILURE;
return false;
NS_ASSERTION(hidProps, "HIDProperties is null, but no error was returned.");
[hidProps autorelease];
@ -92,11 +92,18 @@ nsIdleServiceX::GetIdleTime(PRUint32 *aTimeDiff)
// convert to ms from ns
time /= 1000000;
if (time > PR_UINT32_MAX) // Overflow will occur
return NS_ERROR_CANNOT_CONVERT_DATA;
return false;
*aTimeDiff = static_cast<PRUint32>(time);
*aIdleTime = static_cast<PRUint32>(time);
return NS_OK;
return true;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
}
bool
nsIdleServiceX::UsePollMode()
{
return true;
}

View File

@ -62,8 +62,7 @@ static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nsnull;
static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nsnull;
static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nsnull;
NS_IMPL_ISUPPORTS1(nsIdleServiceGTK, nsIIdleService)
NS_IMPL_ISUPPORTS2(nsIdleServiceGTK, nsIdleService, nsIIdleService)
static void Initialize()
{
@ -122,28 +121,18 @@ nsIdleServiceGTK::~nsIdleServiceGTK()
#endif
}
NS_IMETHODIMP
nsIdleServiceGTK::GetIdleTime(PRUint32 *aTimeDiff)
bool
nsIdleServiceGTK::PollIdleTime(PRUint32 *aIdleTime)
{
if (!sInitialized) {
// For some reason, we could not find xscreensaver. This this might be because
// we are on a mobile platforms (e.g. Maemo/OSSO). In this case, fall back to
// using gLastInputEventTime which is
// The last user input event time in microseconds. If there are any pending
// native toolkit input events it returns the current time. The value is
// compatible with PR_IntervalToMicroseconds(PR_IntervalNow()).
// DEFINED IN widget/src/gtk2/nsWindow.cpp
extern PRUint32 gLastInputEventTime;
PRUint32 nowTime = PR_IntervalToMicroseconds(PR_IntervalNow());
*aTimeDiff = (nowTime - gLastInputEventTime) / 1000;
return NS_OK;
// For some reason, we could not find xscreensaver. This this might be
// because we are on a mobile platforms (e.g. Maemo/OSSO). In this
// case, let the base class handle it
return false;
}
// Ask xscreensaver about idle time:
*aTimeDiff = 0;
*aIdleTime = 0;
// We might not have a display (cf. in xpcshell)
Display *dplay = GDK_DISPLAY();
@ -151,11 +140,11 @@ nsIdleServiceGTK::GetIdleTime(PRUint32 *aTimeDiff)
#ifdef PR_LOGGING
PR_LOG(sIdleLog, PR_LOG_WARNING, ("No display found!\n"));
#endif
return NS_ERROR_FAILURE;
return false;
}
if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) {
return NS_ERROR_FAILURE;
return false;
}
int event_base, error_base;
@ -164,15 +153,21 @@ nsIdleServiceGTK::GetIdleTime(PRUint32 *aTimeDiff)
if (!mXssInfo)
mXssInfo = _XSSAllocInfo();
if (!mXssInfo)
return NS_ERROR_OUT_OF_MEMORY;
return false;
_XSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
*aTimeDiff = mXssInfo->idle;
return NS_OK;
*aIdleTime = mXssInfo->idle;
return true;
}
// If we get here, we couldn't get to XScreenSaver:
#ifdef PR_LOGGING
PR_LOG(sIdleLog, PR_LOG_WARNING, ("XSSQueryExtension returned false!\n"));
#endif
return NS_ERROR_FAILURE;
return false;
}
bool
nsIdleServiceGTK::UsePollMode()
{
return sInitialized;
}

View File

@ -61,10 +61,14 @@ public:
NS_DECL_ISUPPORTS
nsIdleServiceGTK();
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
bool PollIdleTime(PRUint32* aIdleTime);
private:
~nsIdleServiceGTK();
XScreenSaverInfo* mXssInfo;
protected:
bool UsePollMode();
};
#endif // nsIdleServiceGTK_h__

View File

@ -269,14 +269,13 @@ static PRBool gdk_keyboard_get_modmap_masks(Display* aDisplay,
/* initialization static functions */
static nsresult initialize_prefs (void);
PRUint32 gLastInputEventTime = 0;
static void UpdateLastInputEventTime() {
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
nsCOMPtr<nsIIdleService> idleService = do_GetService("@mozilla.org/widget/idleservice;1");
nsIdleService* is = static_cast<nsIdleService*>(idleService.get());
if (is)
is->IdleTimeWasModified();
static void
UpdateLastInputEventTime()
{
nsCOMPtr<nsIdleService> idleService = do_GetService("@mozilla.org/widget/idleservice;1");
if (idleService) {
idleService->ResetIdleTimeOut();
}
}
// this is the last window that had a drag event happen on it.
@ -464,9 +463,6 @@ nsWindow::nsWindow()
gBufferPixmapUsageCount++;
}
// Set gLastInputEventTime to some valid number
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
}
nsWindow::~nsWindow()

View File

@ -42,7 +42,7 @@
static int (*_System DSSaver_GetInactivityTime)(ULONG *, ULONG *);
#define SSCORE_NOERROR 0 // as in the DSSaver header files
NS_IMPL_ISUPPORTS1(nsIdleServiceOS2, nsIIdleService)
NS_IMPL_ISUPPORTS2(nsIdleServiceOS2, nsIIdleService, nsIdleService)
nsIdleServiceOS2::nsIdleServiceOS2()
: mHMod(NULLHANDLE), mInitialized(PR_FALSE)
@ -63,19 +63,26 @@ nsIdleServiceOS2::~nsIdleServiceOS2()
}
}
NS_IMETHODIMP
nsIdleServiceOS2::GetIdleTime(PRUint32 *aIdleTime)
bool
nsIdleServiceOS2::PollIdleTime(PRUint32 *aIdleTime)
{
if (!mInitialized)
return NS_ERROR_NOT_INITIALIZED;
return false;
ULONG mouse, keyboard;
if (DSSaver_GetInactivityTime(&mouse, &keyboard) != SSCORE_NOERROR) {
return NS_ERROR_FAILURE;
return false;
}
// we are only interested in activity in general, so take the minimum
// of both timers
*aIdleTime = PR_MIN(mouse, keyboard);
return NS_OK;
return true;
}
bool
nsIdleServiceOS2::UsePollMode()
{
return mInitialized;
}

View File

@ -53,11 +53,14 @@ public:
~nsIdleServiceOS2();
// ask the DSSaver DLL (sscore.dll) for the time of the last activity
NS_IMETHOD GetIdleTime(PRUint32 *aIdleTime);
bool PollIdleTime(PRUint32 *aIdleTime);
private:
HMODULE mHMod; // module handle for screensaver DLL
PRBool mInitialized; // fully initialized (function found in screensaver DLL?)
protected:
bool UsePollMode();
};
#endif // nsIdleServiceOS2_h__

View File

@ -58,7 +58,7 @@ static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nsnull;
static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nsnull;
NS_IMPL_ISUPPORTS1(nsIdleServiceQt, nsIIdleService)
NS_IMPL_ISUPPORTS2(nsIdleServiceQt, nsIIdleService, nsIdleService)
nsIdleServiceQt::nsIdleServiceQt()
: mXssInfo(nsnull)
@ -99,23 +99,23 @@ nsIdleServiceQt::~nsIdleServiceQt()
#endif
}
NS_IMETHODIMP
nsIdleServiceQt::GetIdleTime(PRUint32 *aTimeDiff)
bool
nsIdleServiceQt::PollIdleTime(PRUint32 *aIdleTime)
{
// Ask xscreensaver about idle time:
*aTimeDiff = 0;
*aIdleTime = 0;
// We might not have a display (cf. in xpcshell)
Display *dplay = QX11Info::display();
if (!dplay) {
return NS_ERROR_FAILURE;
return false;
}
if (!sInitialized) {
Initialize();
}
if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) {
return NS_ERROR_FAILURE;
return false;
}
int event_base, error_base;
@ -123,12 +123,19 @@ nsIdleServiceQt::GetIdleTime(PRUint32 *aTimeDiff)
if (!mXssInfo)
mXssInfo = _XSSAllocInfo();
if (!mXssInfo)
return NS_ERROR_OUT_OF_MEMORY;
return false;
_XSSQueryInfo(dplay, QX11Info::appRootWindow(), mXssInfo);
*aTimeDiff = mXssInfo->idle;
return NS_OK;
*aIdleTime = mXssInfo->idle;
return true;
}
return NS_ERROR_FAILURE;
return false;
}
bool
nsIdleServiceQt::UsePollMode()
{
return true;
}

View File

@ -59,10 +59,14 @@ public:
NS_DECL_ISUPPORTS
nsIdleServiceQt();
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
bool PollIdleTime(PRUint32* aIdleTime);
private:
~nsIdleServiceQt();
XScreenSaverInfo* mXssInfo;
protected:
bool UsePollMode();
};
#endif // nsIdleServiceQt_h__

View File

@ -72,6 +72,7 @@
#include "nsToolkit.h"
#include "nsIDeviceContext.h"
#include "nsIdleService.h"
#include "nsIRenderingContext.h"
#include "nsIRegion.h"
#include "nsIRollupListener.h"
@ -1248,6 +1249,9 @@ nsWindow::InitButtonEvent(nsMouseEvent &aMoveEvent,
nsEventStatus
nsWindow::OnButtonPressEvent(QGraphicsSceneMouseEvent *aEvent)
{
// The user has done something.
UserActivity();
QPointF pos = aEvent->pos();
// we check against the widgets geometry, so use parent coordinates
@ -1295,6 +1299,9 @@ nsWindow::OnButtonPressEvent(QGraphicsSceneMouseEvent *aEvent)
nsEventStatus
nsWindow::OnButtonReleaseEvent(QGraphicsSceneMouseEvent *aEvent)
{
// The user has done something.
UserActivity();
PRUint16 domButton;
switch (aEvent->button()) {
@ -1392,6 +1399,9 @@ nsWindow::OnKeyPressEvent(QKeyEvent *aEvent)
{
LOGFOCUS(("OnKeyPressEvent [%p]\n", (void *)this));
// The user has done something.
UserActivity();
PRBool setNoDefault = PR_FALSE;
// before we dispatch a key, check if it's the context menu key.
@ -1447,6 +1457,9 @@ nsWindow::OnKeyReleaseEvent(QKeyEvent *aEvent)
{
LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
// The user has done something.
UserActivity();
if (isContextMenuKeyEvent(aEvent)) {
// er, what do we do here? DoDefault or NoDefault?
return nsEventStatus_eConsumeDoDefault;
@ -2490,3 +2503,15 @@ nsWindow::GetIMEEnabled(PRUint32* aState)
return NS_OK;
}
void
nsWindow::UserActivity()
{
if (!mIdleService) {
mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
}
if (mIdleService) {
mIdleService->ResetIdleTimeOut();
}
}

View File

@ -100,6 +100,8 @@ class QEvent;
class MozQWidget;
class nsIdleService;
class nsWindow : public nsBaseWidget,
public nsSupportsWeakReference
{
@ -322,6 +324,7 @@ private:
PluginType mPluginType;
nsRefPtr<gfxASurface> mThebesSurface;
nsCOMPtr<nsIdleService> mIdleService;
PRBool mIsTransparent;
@ -363,6 +366,10 @@ private:
}
PRInt32 mQCursor;
// Call this function when the users activity is the direct cause of an
// event (like a keypress or mouse click).
void UserActivity();
// Remember dirty area caused by ::Scroll
QRegion mDirtyScrollArea;

View File

@ -41,35 +41,33 @@
#include "nsIdleServiceWin.h"
#include <windows.h>
NS_IMPL_ISUPPORTS2(nsIdleServiceWin, nsIIdleService, nsIdleService)
#ifdef WINCE
// The last user input event time in microseconds. If there are any pending
// native toolkit input events it returns the current time. The value is
// compatible with PR_IntervalToMicroseconds(PR_IntervalNow()).
// DEFINED IN widget/src/windows/nsWindow.cpp
extern PRUint32 gLastInputEventTime;
#endif
NS_IMPL_ISUPPORTS1(nsIdleServiceWin, nsIIdleService)
NS_IMETHODIMP
nsIdleServiceWin::GetIdleTime(PRUint32 *aTimeDiff)
bool
nsIdleServiceWin::PollIdleTime(PRUint32 *aIdleTime)
{
#ifndef WINCE
LASTINPUTINFO inputInfo;
inputInfo.cbSize = sizeof(inputInfo);
if (!::GetLastInputInfo(&inputInfo))
return NS_ERROR_FAILURE;
return false;
*aTimeDiff = SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime);
*aIdleTime = SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime);
return true;
#else
// NOTE: nowTime is not necessarily equivalent to GetTickCount() return value
// we need to compare apples to apples - hence the nowTime variable
PRUint32 nowTime = PR_IntervalToMicroseconds(PR_IntervalNow());
*aTimeDiff = SAFE_COMPARE_EVEN_WITH_WRAPPING(nowTime, gLastInputEventTime) / 1000;
// On WinCE we don't pull the idle time from the system.
return false;
#endif
}
bool
nsIdleServiceWin::UsePollMode()
{
#ifndef WINCE
return true;
#else
// On WinCE we don't pull the idle time from the system.
return false;
#endif
return NS_OK;
}

View File

@ -57,7 +57,10 @@ class nsIdleServiceWin : public nsIdleService
public:
NS_DECL_ISUPPORTS
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
bool PollIdleTime(PRUint32* aIdleTime);
protected:
bool UsePollMode();
};
#endif // nsIdleServiceWin_h__

View File

@ -140,7 +140,6 @@
#include "nsIFontMetrics.h"
#include "nsIFontEnumerator.h"
#include "nsIDeviceContext.h"
#include "nsIdleService.h"
#include "nsGUIEvent.h"
#include "nsFont.h"
#include "nsRect.h"
@ -316,24 +315,6 @@ HTCApiNavSetMode gHTCApiNavSetMode = nsnull;
static PRBool gCheckForHTCApi = PR_FALSE;
#endif
// The last user input event time in microseconds. If
// there are any pending native toolkit input events
// it returns the current time. The value is compatible
// with PR_IntervalToMicroseconds(PR_IntervalNow()).
#if !defined(WINCE)
static PRUint32 gLastInputEventTime = 0;
#else
PRUint32 gLastInputEventTime = 0;
#endif
static void UpdateLastInputEventTime() {
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
nsCOMPtr<nsIIdleService> idleService = do_GetService("@mozilla.org/widget/idleservice;1");
nsIdleService* is = static_cast<nsIdleService*>(idleService.get());
if (is)
is->IdleTimeWasModified();
}
// Global user preference for disabling native theme. Used
// in NativeWindowTheme.
PRBool gDisableNativeTheme = PR_FALSE;
@ -441,8 +422,7 @@ nsWindow::nsWindow() : nsBaseWidget()
#endif
} // !sInstanceCount
// Set gLastInputEventTime to some valid number
gLastInputEventTime = PR_IntervalToMicroseconds(PR_IntervalNow());
mIdleService = nsnull;
sInstanceCount++;
}
@ -3226,6 +3206,8 @@ PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode,
const nsModifierKeyState &aModKeyState,
PRUint32 aFlags)
{
UserActivity();
nsKeyEvent event(PR_TRUE, aEventType, this);
nsIntPoint point(0, 0);
@ -3339,8 +3321,6 @@ void nsWindow::DispatchPendingEvents()
return;
}
UpdateLastInputEventTime();
// We need to ensure that reflow events do not get starved.
// At the same time, we don't want to recurse through here
// as that would prevent us from dispatching starved paints.
@ -3399,6 +3379,8 @@ PRBool nsWindow::DispatchMouseEvent(PRUint32 aEventType, WPARAM wParam,
{
PRBool result = PR_FALSE;
UserActivity();
if (!mEventCallback) {
return result;
}
@ -5418,6 +5400,19 @@ void nsWindow::OnWindowPosChanging(LPWINDOWPOS& info)
}
#endif
void nsWindow::UserActivity()
{
// Check if we have the idle service, if not we try to get it.
if (!mIdleService) {
mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
}
// Check that we now have the idle service.
if (mIdleService) {
mIdleService->ResetIdleTimeOut();
}
}
// Gesture event processing. Handles WM_GESTURE events.
#if !defined(WINCE)
PRBool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam)

View File

@ -51,6 +51,7 @@
#include "nsBaseWidget.h"
#include "nsdefs.h"
#include "nsIdleService.h"
#include "nsToolkit.h"
#include "nsString.h"
#include "nsTArray.h"
@ -358,6 +359,12 @@ protected:
void OnWindowPosChanging(LPWINDOWPOS& info);
#endif // !defined(WINCE)
/**
* Function that registers when the user has been active (used for detecting
* when the user is idle).
*/
void UserActivity();
/**
* Methods for derived classes
*/
@ -462,6 +469,8 @@ protected:
static PRUint32 sOOPPPluginFocusEvent;
#endif
nsCOMPtr<nsIdleService> mIdleService;
// Hook Data Memebers for Dropdowns. sProcessHook Tells the
// hook methods whether they should be processing the hook
// messages.

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
@ -24,6 +24,7 @@
* Contributor(s):
* Gijs Kruitbosch <gijskruitbosch@gmail.com>
* Edward Lee <edward.lee@engineering.uiuc.edu>
* Mike Kristoffersen <moz@mikek.dk>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -47,201 +48,367 @@
#include "nsIServiceManager.h"
#include "nsDebug.h"
#include "nsCOMArray.h"
#include "prinrval.h"
// observer topics used:
#define OBSERVER_TOPIC_IDLE "idle"
#define OBSERVER_TOPIC_BACK "back"
#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
// interval in milliseconds between internal idle time requests
#define MIN_IDLE_POLL_INTERVAL 5000
#define MAX_IDLE_POLL_INTERVAL 300000
// Pref for last time (seconds since epoch) daily notification was sent
// interval in seconds between internal idle time requests.
#define MIN_IDLE_POLL_INTERVAL 5
#define MAX_IDLE_POLL_INTERVAL 300 /* 5 min */
// Pref for last time (seconds since epoch) daily notification was sent.
#define PREF_LAST_DAILY "idle.lastDailyNotification"
// Use this to find previously added observers in our array:
class IdleListenerComparator
{
public:
PRBool Equals(IdleListener a, IdleListener b) const
{
return (a.observer == b.observer) &&
(a.reqIdleTime == b.reqIdleTime);
}
PRBool Equals(IdleListener a, IdleListener b) const
{
return (a.observer == b.observer) &&
(a.reqIdleTime == b.reqIdleTime);
}
};
nsIdleService::nsIdleService()
NS_IMPL_ISUPPORTS1(nsIdleServiceDaily, nsIObserver)
NS_IMETHODIMP
nsIdleServiceDaily::Observe(nsISupports *,
const char *,
const PRUnichar *)
{
// Immediately create a timer to handle the daily notification
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
StartTimer(MAX_IDLE_POLL_INTERVAL);
// Notify anyone who cares.
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1");
observerService->NotifyObservers(nsnull,
OBSERVER_TOPIC_IDLE_DAILY,
nsnull);
// Remove from idle timeout.
mIdleService->RemoveIdleObserver(this, MAX_IDLE_POLL_INTERVAL*1000);
// Start timer for next search in 1 day.
if (mTimer) {
mTimer->InitWithFuncCallback(DailyCallback, this, 24*60*60*1000,
nsITimer::TYPE_ONE_SHOT);
}
return NS_OK;
}
void
nsIdleServiceDaily::Init(nsIdleService *aIdleService)
{
nsresult rv;
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
mIdleService = aIdleService;
// Wait for the user to become idle, so we can do todays idle tasks.
DailyCallback(0, this);
}
void
nsIdleServiceDaily::Shutdown()
{
if (mTimer) {
mTimer->Cancel();
mTimer = nsnull;
}
if (mIdleService) {
mIdleService->RemoveIdleObserver(this, MAX_IDLE_POLL_INTERVAL*1000);
mIdleService = nsnull;
}
}
void
nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure)
{
nsIdleServiceDaily* me = static_cast<nsIdleServiceDaily*>(aClosure);
// The one thing we do every day is to start waiting for the user to "have
// a significant idle time".
me->mIdleService->AddIdleObserver(me, MAX_IDLE_POLL_INTERVAL*1000);
}
nsIdleService::nsIdleService() : mLastIdleReset(0), mLastHandledActivity(0)
{
mDailyIdle = new nsIdleServiceDaily;
if (mDailyIdle) {
mDailyIdle->Init(this);
}
}
nsIdleService::~nsIdleService()
{
StopTimer();
StopTimer();
mDailyIdle->Shutdown();
}
NS_IMETHODIMP
nsIdleService::AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime)
{
NS_ENSURE_ARG_POINTER(aObserver);
NS_ENSURE_ARG(aIdleTime);
NS_ENSURE_ARG_POINTER(aObserver);
NS_ENSURE_ARG(aIdleTime);
// Put the time + observer in a struct we can keep:
IdleListener listener(aObserver, aIdleTime * 1000);
// Put the time + observer in a struct we can keep:
IdleListener listener(aObserver, aIdleTime);
if (!mArrayListeners.AppendElement(listener))
return NS_ERROR_OUT_OF_MEMORY;
if (!mArrayListeners.AppendElement(listener)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Create our timer callback if it's not there already
if (!mTimer) {
nsresult rv;
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// Create our timer callback if it's not there already.
if (!mTimer) {
nsresult rv;
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// Make sure our observer goes into 'idle' immediately if applicable.
CheckAwayState();
// Make sure our observer goes into 'idle' immediately if applicable.
CheckAwayState(false);
return NS_OK;
return NS_OK;
}
NS_IMETHODIMP
nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aTime)
{
NS_ENSURE_ARG_POINTER(aObserver);
NS_ENSURE_ARG(aTime);
IdleListener listener(aObserver, aTime * 1000);
NS_ENSURE_ARG_POINTER(aObserver);
NS_ENSURE_ARG(aTime);
IdleListener listener(aObserver, aTime);
// Find the entry and remove it:
IdleListenerComparator c;
if (mArrayListeners.RemoveElement(listener, c)) {
if (mArrayListeners.IsEmpty()) {
StopTimer();
}
return NS_OK;
// Find the entry and remove it:
IdleListenerComparator c;
if (mArrayListeners.RemoveElement(listener, c)) {
if (mArrayListeners.IsEmpty()) {
StopTimer();
}
return NS_OK;
}
// If we get here, we haven't removed anything:
return NS_ERROR_FAILURE;
// If we get here, we haven't removed anything:
return NS_ERROR_FAILURE;
}
void
nsIdleService::ResetIdleTimeOut()
{
mLastIdleReset = PR_IntervalToSeconds(PR_IntervalNow());
// A zero in mLastIdleReset indicates that this function has never been
// called.
if (!mLastIdleReset) mLastIdleReset = 1;
// Now check if this changes anything
CheckAwayState(true);
}
NS_IMETHODIMP
nsIdleService::GetIdleTime(PRUint32* idleTime)
{
// Check sanity of in parameter.
if (!idleTime) {
return NS_ERROR_NULL_POINTER;
}
// Polled idle time in ms
PRUint32 polledIdleTimeMS;
bool polledIdleTimeIsValid;
polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
// If we don't have any valid data, then we are not in idle - pr. definition.
if (!polledIdleTimeIsValid && 0 == mLastIdleReset) {
*idleTime = 0;
return NS_OK;
}
// If we never got a reset, just return the pulled time.
if (0 == mLastIdleReset) {
*idleTime = polledIdleTimeMS;
return NS_OK;
}
// timeSinceReset is in seconds.
PRUint32 timeSinceReset =
PR_IntervalToSeconds(PR_IntervalNow()) - mLastIdleReset;
// If we did't get pulled data, return the time since last idle reset.
if (!polledIdleTimeIsValid) {
// We need to convert to ms before returning the time.
*idleTime = timeSinceReset * 1000;
return NS_OK;
}
// Otherwise return the shortest time detected (in ms).
*idleTime = NS_MIN(timeSinceReset * 1000, polledIdleTimeMS);
return NS_OK;
}
bool
nsIdleService::PollIdleTime(PRUint32* /*aIdleTime*/)
{
// Default behavior is not to have the ability to poll an idle time.
return false;
}
bool
nsIdleService::UsePollMode()
{
PRUint32 dummy;
return PollIdleTime(&dummy);
}
void
nsIdleService::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
{
static_cast<nsIdleService*>(aClosure)->CheckAwayState();
static_cast<nsIdleService*>(aClosure)->CheckAwayState(false);
}
void
nsIdleService::CheckAwayState()
nsIdleService::CheckAwayState(bool aNoTimeReset)
{
// Get the idle time.
PRUint32 idleTime;
if (NS_FAILED(GetIdleTime(&idleTime)))
return;
/**
* Find our last detected idle time (it's important this happens before the
* call below to GetIdleTime, as we use the two values to detect if there
* has been user activity since the last time we were here).
*/
PRUint32 curTime = PR_Now() / PR_USEC_PER_SEC;
PRUint32 lastTime = curTime - mLastHandledActivity;
// Dynamically figure out what's the best time to poll again
PRUint32 nextPoll = MAX_IDLE_POLL_INTERVAL;
// Get the idle time (in seconds).
PRUint32 idleTime;
if (NS_FAILED(GetIdleTime(&idleTime))) {
return;
}
nsAutoString timeStr;
timeStr.AppendInt(idleTime);
// GetIdleTime returns the time in ms, internally we only calculate in s.
idleTime /= 1000;
// Change state first, and save observers that need notification, so
// removing things will always work without upsetting notifications.
nsCOMArray<nsIObserver> idleListeners;
nsCOMArray<nsIObserver> hereListeners;
// We need a text string to send with any state change events.
nsAutoString timeStr;
timeStr.AppendInt(idleTime);
// Set the time for last user activity.
mLastHandledActivity = curTime - idleTime;
/**
* Now, if the idle time, is less than what we expect, it means the
* user was active since last time that we checked.
*/
nsCOMArray<nsIObserver> notifyList;
if (lastTime > idleTime) {
// Loop trough all listeners, and find any that have detected idle.
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
IdleListener& curListener = mArrayListeners.ElementAt(i);
IdleListener& curListener = mArrayListeners.ElementAt(i);
// Assume the next best poll time is the time left before idle
PRUint32 curPoll = curListener.reqIdleTime - idleTime;
// For listeners that haven't gone idle yet:
if (!curListener.isIdle) {
// User has been idle longer than the listener expects
if (idleTime >= curListener.reqIdleTime) {
curListener.isIdle = PR_TRUE;
idleListeners.AppendObject(curListener.observer);
// We'll need to poll frequently to notice if the user is back
curPoll = MIN_IDLE_POLL_INTERVAL;
}
}
// For listeners that are waiting for the user to come back:
else {
// Short idle time means the user is back
if (idleTime < curListener.reqIdleTime) {
curListener.isIdle = PR_FALSE;
hereListeners.AppendObject(curListener.observer);
}
// Keep polling frequently to detect if the user comes back
else {
curPoll = MIN_IDLE_POLL_INTERVAL;
}
}
// Take the shortest poll time needed for each listener
nextPoll = PR_MIN(nextPoll, curPoll);
if (curListener.isIdle) {
notifyList.AppendObject(curListener.observer);
curListener.isIdle = false;
}
}
// Notify listeners gone idle:
for (PRInt32 i = 0; i < idleListeners.Count(); i++) {
idleListeners[i]->Observe(this, OBSERVER_TOPIC_IDLE, timeStr.get());
// Send the "non-idle" events.
for (PRInt32 i = 0; i < notifyList.Count(); i++) {
notifyList[i]->Observe(this, OBSERVER_TOPIC_BACK, timeStr.get());
}
}
/**
* Now we need to check for listeners that have expired, and while we are
* looping through all the elements, we will also calculate when, if ever
* the next one will need to be notified.
*/
// Clean up the list, so it's ready for the next iteration.
notifyList.Clear();
// Bail out if we don't need to calculate new times.
if (aNoTimeReset) {
return;
}
/**
* Placet to store the wait time to the next notification, note that
* PR_UINT32_MAX means no-one are listening (or that they have such a big
* delay that it doesn't matter).
*/
PRUint32 nextWaitTime = PR_UINT32_MAX;
/**
* Place to remember if there are any listeners that are in the idle state,
* if there are, we need to poll frequently in a polling environment to detect
* when the user becomes active again.
*/
bool anyOneIdle = false;
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
IdleListener& curListener = mArrayListeners.ElementAt(i);
// We are only interested in items, that are not in the idle state.
if (!curListener.isIdle) {
// If they have an idle time smaller than the actual idle time.
if (curListener.reqIdleTime <= idleTime) {
// then add the listener to the list of listeners that should be
// notified.
notifyList.AppendObject(curListener.observer);
// This listener is now idle.
curListener.isIdle = true;
} else {
// If it hasn't expired yet, then we should note the time when it should
// expire.
nextWaitTime = PR_MIN(nextWaitTime, curListener.reqIdleTime);
}
}
// Notify listeners that came back:
for (PRInt32 i = 0; i < hereListeners.Count(); i++) {
hereListeners[i]->Observe(this, OBSERVER_TOPIC_BACK, timeStr.get());
}
// Remember if anyone becomes idle (it's safe to do this as a binary compare
// as we are or'ing).
anyOneIdle |= curListener.isIdle;
}
// The user has been idle for a while, so try sending the daily idle
if (idleTime >= MAX_IDLE_POLL_INTERVAL) {
nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (pref) {
// Get the current number of seconds since epoch
PRUint32 nowSec = PR_Now() / PR_USEC_PER_SEC;
// In order to find when the next idle event should time out, we need to
// subtract the time we should wait, from the time that has already passed.
nextWaitTime -= idleTime;
// Get the last notification time; default to 0 for the first time
PRInt32 lastDaily = 0;
pref->GetIntPref(PREF_LAST_DAILY, &lastDaily);
// Notify all listeners that just timed out.
for (PRInt32 i = 0; i < notifyList.Count(); i++) {
notifyList[i]->Observe(this, OBSERVER_TOPIC_IDLE, timeStr.get());
}
// Has it been a day (24*60*60 seconds) since the last notification
if (nowSec - lastDaily > 86400) {
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1");
observerService->NotifyObservers(nsnull,
OBSERVER_TOPIC_IDLE_DAILY,
nsnull);
// If we are in poll mode, we need to poll for activity if anyone are idle,
// otherwise we can wait polling until they would expire.
if (UsePollMode() &&
anyOneIdle &&
nextWaitTime > MIN_IDLE_POLL_INTERVAL) {
nextWaitTime = MIN_IDLE_POLL_INTERVAL;
}
pref->SetIntPref(PREF_LAST_DAILY, nowSec);
}
}
}
// Restart the timer with the dynamically optimized poll time
StartTimer(nextPoll);
// Start the timer if there is anything to wait for.
if (PR_UINT32_MAX != nextWaitTime) {
StartTimer(nextWaitTime);
}
}
void
nsIdleService::StartTimer(PRUint32 aDelay)
{
if (mTimer) {
StopTimer();
mTimer->InitWithFuncCallback(IdleTimerCallback, this, aDelay,
nsITimer::TYPE_ONE_SHOT);
if (mTimer) {
StopTimer();
if (aDelay) {
mTimer->InitWithFuncCallback(IdleTimerCallback, this, aDelay*1000,
nsITimer::TYPE_ONE_SHOT);
}
}
}
void
nsIdleService::StopTimer()
{
if (mTimer) {
mTimer->Cancel();
}
if (mTimer) {
mTimer->Cancel();
}
}
void
nsIdleService::IdleTimeWasModified()
{
StartTimer(0);
}

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
@ -22,7 +22,8 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Gijs Kruitbosch <gijskruitbosch@gmail.com>
* Gijs Kruitbosch <gijskruitbosch@gmail.com>
* Mike Kristoffersen <moz@mikek.dk>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -46,43 +47,170 @@
#include "nsITimer.h"
#include "nsTArray.h"
#include "nsIObserver.h"
#include "nsIIdleService.h"
// Class we can use to store an observer with its associated idle time
// requirement and whether or not the observer thinks it's "idle".
/**
* Class we can use to store an observer with its associated idle time
* requirement and whether or not the observer thinks it's "idle".
*/
class IdleListener {
public:
nsCOMPtr<nsIObserver> observer;
PRUint32 reqIdleTime;
PRBool isIdle;
nsCOMPtr<nsIObserver> observer;
PRUint32 reqIdleTime;
bool isIdle;
IdleListener(nsIObserver* obs, PRUint32 reqIT, PRBool aIsIdle = PR_FALSE) :
observer(obs), reqIdleTime(reqIT), isIdle(aIsIdle) {}
~IdleListener() {}
IdleListener(nsIObserver* obs, PRUint32 reqIT, bool aIsIdle = false) :
observer(obs), reqIdleTime(reqIT), isIdle(aIsIdle) {}
~IdleListener() {}
};
// This one will be declared later.
class nsIdleService;
/**
* Class to handle the daily idle timer.
*/
class nsIdleServiceDaily : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
/**
* Function to call to tell the daily idle service that the idle service
* is ready.
*
* @param aIdleService
* Pointer to the idle service.
*/
void Init(nsIdleService *aIdleService);
/**
* This function will make this class release its allocated resources (its
* idle timer and/or its normal timer).
*/
void Shutdown();
private:
/**
* @note This is a normal pointer, or the idle service could keep it self
* alive.
*/
nsIdleService *mIdleService;
/**
* Place to hold the timer used by this class to determine when a day has
* passed, after that it will wait for idle time to be detected.
*/
nsCOMPtr<nsITimer> mTimer;
/**
* Function that is called back once a day.
*/
static void DailyCallback(nsITimer* aTimer, void* aClosure);
};
class nsIdleService : public nsIIdleService
{
public:
nsIdleService();
nsIdleService();
// Implement nsIIdleService methods, but not the idleTime getter,
// which is platform-dependent.
NS_IMETHOD AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime);
NS_IMETHOD RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime);
// Implement nsIIdleService methods.
NS_IMETHOD AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime);
NS_IMETHOD RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime);
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
void IdleTimeWasModified();
void ResetIdleTimeOut();
protected:
void CheckAwayState();
~nsIdleService();
~nsIdleService();
/**
* If there is a platform specific function to poll the system idel time
* then that must be returned in this function, and the function MUST return
* true, otherwise then the function should be left unimplemented or made
* to return false (this can also be used for systems where it depends on
* the configuration of the system if the idle time can be determined)
*
* @param aIdleTime
* The idle time in ms.
*
* @return true if the idle time could be polled, false otherwise.
*
* @note The time returned by this function can be different than the one
* returned by GetIdleTime, as that is corrected by any calls to
* ResetIdleTimeOut(), unless you overwrite that function too...
*/
virtual bool PollIdleTime(PRUint32* aIdleTime);
/**
* Function that determines if we are in poll mode or not.
*
* @return true if polling is supported, false otherwise.
*/
virtual bool UsePollMode();
/**
* Send expired events and start timers.
*
* @param aNoTimeReset
* If true new times will not be calculated.
*/
void CheckAwayState(bool aNoTimeReset);
private:
void StartTimer(PRUint32 aDelay);
void StopTimer();
nsCOMPtr<nsITimer> mTimer;
nsTArray<IdleListener> mArrayListeners;
/**
* Start the internal timer, restart it if it is allready running.
*
* @param aDelay
* The time in seconds that should pass before the next timeout.
*/
void StartTimer(PRUint32 aDelay);
/**
* Stop the internal timer, it is safe to call this function, even when
* there are no timers running.
*/
void StopTimer();
/**
* mTimer holds the internal timer used by this class to detect when to poll
* for idle time, when to check if idle timers should expire etc.
*/
nsCOMPtr<nsITimer> mTimer;
/**
* Array of listeners that wants to be notified about idle time.
*/
nsTArray<IdleListener> mArrayListeners;
/**
* Object keeping track of the daily idle thingy.
*/
nsCOMPtr<nsIdleServiceDaily> mDailyIdle;
/**
* Contains the time of the last idle reset or 0 if there haven't been a
* reset.
* <p>
* Time is kept in seconds since the epoch at midnight, January 1, 1970 UTC.
*/
PRUint32 mLastIdleReset;
/**
* The time since the last handled activity (which might be different than
* mLastIdleReset, since the activity that reset the idle timer could just
* have happend, and not handled yet).
* <p>
* Time is kept in seconds since the epoch at midnight, January 1, 1970 UTC.
*/
PRUint32 mLastHandledActivity;
/**
* Callback function that is called when the internal timer expires.
*/
static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
};
#endif // nsIdleService_h__

View File

@ -98,15 +98,17 @@ catch (ex)
ok(idleService, "nsIIdleService should exist and be implemented on all tier 1 platforms.");
var idleTime = null;
var gotIdleTime = false;
try
{
idleTime = idleService.idleTime;
gotIdleTime = true;
}
catch (ex)
{}
ok (idleTime, "Getting the idle time should not fail " +
"in normal circumstances on any tier 1 platform.");
ok (gotIdleTime, "Getting the idle time should not fail " +
"in normal circumstances on any tier 1 platform.");
// Now we set up a timeout to sanity-test the idleTime after 5 seconds
setTimeout(testIdleTime, 5000);
@ -149,21 +151,28 @@ ok(removedObserver, "The nsIIdleService should allow us to remove the observer j
function testIdleTime()
{
var gotIdleTime = false
try
{
var newIdleTime = idleService.idleTime;
gotIdleTime = true
}
catch (ex)
{}
ok(newIdleTime, "Getting the idle time should not fail " +
ok(gotIdleTime, "Getting the idle time should not fail " +
"in normal circumstances on any tier 1 platform.");
// Get the time difference, remove the approx. 5 seconds that we've waited,
// should be very close to 0 left.
var timeDiff = Math.abs((newIdleTime - idleTime) -
(Date.now() - startTimeStamp));
// 0.5 second leniency.
ok(timeDiff < 500, "The idle time should have increased by roughly the " +
"amount of time it took for the timeout to fire.");
var timePassed = Date.now() - startTimeStamp;
var idleTimeDiff = newIdleTime - idleTime;
// 1 second leniency.
ok(timeDiff < 1000, "The idle time should have increased by roughly the " +
"amount of time it took for the timeout to fire. " +
"You didn't touch the mouse or keyboard during the" +
"test did you?");
finishedTimeoutOK = true;
}