/***************************************************************************
 *   Copyright (C) 2006-2007 by Danny Kukawka                              *
 *                           <dkukawka@suse.de>, <danny.kukawka@web.de>    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License     *
 *   as published by the Free Software Foundation.                         *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/

/*! \file 	inactivity.cpp
 * \brief 	In this file can be found the inactivity related code.
 * \author 	Danny Kukawka, <dkukawka@suse.de>, <danny.kukawka@web.de>
 * \date    	2006-2007
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// own header
#include "inactivity.h"

/* needed for lXext C library linkage */
extern "C" {
	#include <X11/Xproto.h>
	#include <X11/extensions/dpms.h>
	#include <X11/extensions/scrnsaver.h>
}

// TDE Headers
#include <tdelocale.h>

/*! The default constructor of the class autosuspend */
inactivity::inactivity(screen *disp) : display(disp),
  prev_screensaver_enabled(false), prev_idle_time(0), correction_value(0) {
	kdDebugFuncIn(trace);

	proc = NULL;

	timeToInactivity = 0;
	blacklisted_running_last = 0;
	
	pidof_call_failed = false;
	pidof_call_started = false;
	pidof_call_returned = false;
	blacklisted_running = false;
	
	int dummy = 0;
	has_XSC_Extension = XScreenSaverQueryExtension( tqt_xdisplay(), &dummy, &dummy );
	
	checkInactivity =  new TQTimer( this );
	connect( checkInactivity, TQ_SIGNAL(timeout()), this, TQ_SLOT(check()));

	kdDebugFuncOut(trace);
}

/*! The default destructor of the class autosuspend */
inactivity::~inactivity() {
	kdDebugFuncIn(trace);

	delete proc;
	proc = NULL;

	kdDebugFuncOut(trace);
}

/*!
 * This function start the monitoring of inactivity of user on the X-Server.
 * Here we set the time for the signal \ref inactivityTimeExpired() and start
 * the needed TQTimer.
 * \param timeToExpire  Integer value representing the time of inactivity which need
 *			to elapse befor send signal. The time is in seconds.
 * \param blacked	TQStringList with blacklisted programs which if detected with
 *			pidof() as running prevent the autosuspend.
 */
void inactivity::start( int timeToExpire, TQStringList blacked ) {
	kdDebugFuncIn(trace);

	blacklist = blacked;
 
	if(timeToExpire > 0 && has_XSC_Extension){
		stop();
		timeToInactivity = (unsigned long) (timeToExpire * 1000);
		prev_screensaver_enabled = false;
		prev_idle_time = 0;
		correction_value = 0;
		checkInactivity->start(CHECK_for_INACTIVITY, true);
	}

	kdDebugFuncOut(trace);
}

/*!
 * \b Slot to call check as recheck inactivity if before a running PID
 * request was detected.
 */
void inactivity::recheck() {
	kdDebugFuncIn(trace);

	check(true);
	
	kdDebugFuncOut(trace);
}

/*!
 * \b Slot to call check without a recheck.
 */
void inactivity::check() {
	check(false);
}

/*!
 * \b Slot to check the current idle-time of the X-Server and if there
 * are blacklisted programs are running. If the through \ref timeToInactivity 
 * defined time is expired, this function emit signal \ref inactivityTimeExpired() .
 * \param recheck   boolean which define if this is a recheck or not. 
 *                  \li true, if this is a recheck. In this case we didn't call
 *                      \ref checkBlacklisted() again
 *                  \li false, if this is normal check
 */
void inactivity::check( bool recheck ) {
	kdDebugFuncIn(trace);

	if (timeToInactivity > 0) {	
		checkXInactivity();
		if (!pidof_call_started && !recheck) checkBlacklisted();
		
		if( idleTime < blacklisted_running_last ) {
			blacklisted_running_last = idleTime;
		}
	
		if((idleTime - blacklisted_running_last ) >= timeToInactivity) {
			if (!pidof_call_started) {
				if (( pidof_call_returned && !blacklisted_running ) ||
				( pidof_call_returned && pidof_call_failed )) {
					emit inactivityTimeExpired();
				}
				else {
					checkInactivity->start(CHECK_for_INACTIVITY, true);
				}
			}
			else {
				//called if there is a getPIDs() is running
				TQTimer::singleShot(500, this, TQ_SLOT(recheck()));
			}
		}
		else checkInactivity->start(CHECK_for_INACTIVITY, true);
	} else {
		kdWarning() <<  "timeToInactivity <= 0, stoped autosuspend checks!" << endl;
	}

	kdDebugFuncOut(trace);
}

/*!
 * This function stop the monitoring and reset all variables and Timer.
 */
void inactivity::stop() {
	kdDebugFuncIn(trace);

	if (checkInactivity->isActive()) checkInactivity->stop();
	timeToInactivity = 0;
	idleTime = 0;
	blacklisted_running_last = 0;
	
	pidof_call_failed = false;
	pidof_call_started = false;
	pidof_call_returned = false;
	blacklisted_running = false;

	kdDebugFuncOut(trace);
}

/*!
 * This function query the idle-time of user-imput from the X-Server and set
 * the return value to \ref idleTime.
 */
void inactivity::checkXInactivity(){
	kdDebugFuncIn(trace);

	idleTime = getXInactivity();
	kdDebug() << "autosuspend::checkXInactivity - idleTime: " << idleTime << endl;

	kdDebugFuncOut(trace);
}

/*!
 * This function query the idle-time of user-imput from the X-Server and
 * return the current idle-time. 
 */
unsigned long inactivity::getXInactivity(){
	kdDebugFuncIn(trace);

	if(has_XSC_Extension) {
		static XScreenSaverInfo* mitInfo = 0;
		if (!mitInfo) mitInfo = XScreenSaverAllocInfo ();
		XScreenSaverQueryInfo (tqt_xdisplay(), DefaultRootWindow (tqt_xdisplay()), mitInfo);
		kdDebugFuncOut(trace);
		return workaroundCreepyXServer(mitInfo);
	}
	else {
		kdDebugFuncOut(trace);
		return 0;
	}
	
}

/*!
 * This function workaround an XServer idleTime bug in the 
 * XScreenSaverExtension, if dpms is running. In this case always the
 * current dpms-state time is extracted from the current idletime.
 * This mean: XScreenSaverInfo->idle is not the time since the last
 * user activity, as descriped in the header file of the extension.
 * This result in SUSE bug # and sf.net bug #
 *
 * Workaround: check if if XServer is in a dpms state, check the 
 *             current timeout for this state and add this value to 
 * 	       the current idle time and return.
 *
 * \param _mitInfo a pointer to a structure containing the current XScreenSaver state
 * \return a unsigned long with the corrected idletime
 */
unsigned long inactivity::workaroundCreepyXServer(XScreenSaverInfo *_mitInfo){
	kdDebugFuncOut(trace);
	unsigned long _idleTime = _mitInfo->idle;

	// Detect whether the screensaver has become active since the last time,
	// because the idle time returned by XScreenSaverQueryInfo() is resetted
	// to 0 when the screensaver kicks in. Correct the idle time value if the
	// screensaver is active
	bool screensaver_enabled = false;
	if (display->checkScreenSaverActive())
	{
	  screensaver_enabled = true;
	}
	if (!prev_screensaver_enabled && screensaver_enabled)
	{
		correction_value = prev_idle_time + CHECK_for_INACTIVITY - _idleTime;
	}
	prev_idle_time = _idleTime;
	if (screensaver_enabled)
	{
	  _idleTime += correction_value;
	}
	else
	{
		correction_value = 0;
	}
	prev_screensaver_enabled = screensaver_enabled;

	int dummy;
	CARD16 standby, suspend, off;
	CARD16 state;
	BOOL onoff;

	Display *dpy = tqt_xdisplay();

	kdDebug() << "Current idleTime: " << _idleTime << endl;

	// Idle time is reset when the screensaver kicks in. Need to correct with the right offset

	if (DPMSQueryExtension(dpy, &dummy, &dummy)) {
		if (DPMSCapable(dpy)) {
			DPMSGetTimeouts(dpy, &standby, &suspend, &off);
			DPMSInfo(dpy, &state, &onoff);

			if (onoff) {
				switch (state) {
					case DPMSModeStandby:
						kdDebug() << "DPMS enabled. Monitor in Standby. Standby: "
							  << standby << " sec" << endl;
						// this check is a little bit paranoid, but be sure
						if (_idleTime < (unsigned) (standby * 1000))
							_idleTime += (standby * 1000);
						break;
					case DPMSModeSuspend:
						kdDebug() << "DPMS enabled. Monitor in Suspend. Suspend: "
							  << suspend << " sec" << endl;
						if (_idleTime < (unsigned) ((suspend + standby) * 1000))
							_idleTime += ((suspend + standby) * 1000);
						break;
					case DPMSModeOff:
						kdDebug() << "DPMS enabled. Monitor is Off. Off: "
							  << off << " sec" << endl;
						if (_idleTime < (unsigned) ((off + suspend + standby) * 1000))
							_idleTime += ((off + suspend + standby) * 1000);
						break;
					case DPMSModeOn:
					default:
						break;
				}
			}
		}
	}

	kdDebug() << "Corrected idleTime: " << _idleTime << endl;
	kdDebugFuncOut(trace);
	return _idleTime;
}

/*!
 * This funtion starts the monitoring of blacklisted processes.
 */
void inactivity::checkBlacklisted(){
	kdDebugFuncIn(trace);

	if (proc != NULL) {
		delete proc;
		proc = NULL;
	}

	proc = new TDEProcess;
	*proc << "pidof" << blacklist;

	connect( proc, TQ_SIGNAL(receivedStdout(TDEProcess *, char *, int)),this,
		 TQ_SLOT(getPIDs(TDEProcess *, char *, int)));
	connect( proc, TQ_SIGNAL(processExited(TDEProcess *)),
		 TQ_SLOT(getPIDsExited(TDEProcess *)));
	
	if (!proc->start(TDEProcess::NotifyOnExit, TDEProcess::AllOutput))
	{
		emit displayErrorMsg(i18n("Could not start 'pidof'. "
					  "Could not autosuspend the machine.\n"
					  "Please check your installation."));
	}

	pidof_call_started = true;
	pidof_call_returned = false;
	pidof_call_failed = false;

	kdDebugFuncOut(trace);
}


/*!
 * \b Slot to get the return of the command pidof and parse this to set
 * \ref blacklisted_running .
 * \param *proc     pointer to the sending TDEProcess
 * \param *buffer   the char pointer to the output of the process to stdout
 * \param *length   the length of the buffer
 */
void inactivity::getPIDs(TDEProcess */*proc*/, char *buffer, int /*length*/) {
	kdDebugFuncIn(trace);

	TQString pids(buffer);
	pids.remove(" ");
	if(pids.isEmpty() || pids == "\n" ) {
		kdDebug() << "NO! BLACKLISTED IS RUNNING" << endl;
		blacklisted_running = false;
	}
	else {
		if (pids.contains(TQRegExp("[0-9]"))) {
			kdDebug() << "BLACKLISTED IS RUNNING" << endl;
			blacklisted_running = true;
			blacklisted_running_last = idleTime;
		}
		else {
			kdError() << "GET BLACKLISTED FAILED - WRONG RETURN" << endl;
			blacklisted_running = false;
			pidof_call_failed = true;
		}
	}

	kdDebugFuncOut(trace);
}


/*!
 * \b Slot which called if the call of pidof is exited
 * \param proc the KProcess which called this slot
 */
void inactivity::getPIDsExited(TDEProcess *proc){
	kdDebugFuncIn(trace);

	pidof_call_returned = true;
	pidof_call_started = false;
	
	
	if (proc->normalExit()){
		// if returned some pids or if pid returned nothing
		if (proc->exitStatus() == 1 || proc->exitStatus() == 0){
			pidof_call_failed = false;
			kdDebugFuncOut(trace);
			return;
		}
	}
	// if something crashed/failed
	pidof_call_failed = true;
	kdDebugFuncOut(trace);
}

#include "inactivity.moc"
