/***************************************************************************
                          xautolock.cpp  - user inactivity timer
                             -------------------
    begin                : Mon 01 Dec 2003
    copyright            : (C) 2002 by Michael Curtis
    email                : michael@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/


#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>

#include "config-kmess.h"
#include "kmessdebug.h"
#include "xautolock.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#ifdef Q_WS_X11
#include <QX11Info>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
/* The following include is to make --enable-final work */
#include <X11/Xutil.h>

#endif

#ifdef HAVE_XSCREENSAVER
#include <X11/extensions/scrnsaver.h>
#else
// A special warning for all Gentoo users out there.. :-/
#warning You are compiling KMess without support for the X11 screensaver extension. The auto-away feature will not work.
#endif


#ifdef KMESSDEBUG_AUTOLOCK
//   #define KMESSDEBUG_AUTOLOCK_DBUS_QUERYING
#endif



// Construct
XAutoLock::XAutoLock()
  : active_( false ),
    idle_( false ),
    idleTime_( 0 ),
    lastCheck_( (uint)time( 0 ) ),
    mitAvailable_( false ),
    mitInfo_( 0 ),
    triggerTime_( 0 )
{
  timer_ = new QTimer( this );
  timer_->stop();

  connect( timer_, SIGNAL( timeout()   ),
           this,     SLOT( checkIdle() ) );

  #ifdef HAVE_XSCREENSAVER
    int dummy;
    mitAvailable_ = ( XScreenSaverQueryExtension( QX11Info::display(), &dummy, &dummy ) != 0 );

    // when the screensaver (de)activates, update idle as appropriate.
    QDBusConnection::sessionBus().connect( "org.freedesktop.ScreenSaver", "/ScreenSaver",
                                           "org.freedesktop.ScreenSaver", "ActiveChanged",
                                          this, SLOT( checkIdle() ) );
  #endif
}


// Destroy
XAutoLock::~XAutoLock()
{
  emit activity();
  timer_->stop();

#ifdef HAVE_XSCREENSAVER
  if( mitInfo_ != 0 )
  {
    XFree( mitInfo_ );
  }
#endif

  delete timer_;
}


// The main function which checks for idle status
void XAutoLock::checkIdle()
{
  unsigned int now;
  unsigned long timeIdle;

  now = (uint)time( 0 );

  if( abs( lastCheck_ - now ) > 120 )
  {
    // Whoah, two minutes since we were last called?  Something strange is happening...
    resetTimer();
  }

  lastCheck_ = now;

  // Check for screen saver or locker presence. If it's on, we're idle
  if( isScreenSaverActive() )
  {
    // Just a bit more, so idle status will be reported
    timeIdle = triggerTime_ + 1;
  }
  // If the MIT X extension is on, check with this method
  else if( mitAvailable_ )
  {
    timeIdle = getMitIdle();
  }
  // Otherwise, find out when was the last mouse movement
  else
  {
    timeIdle = getMouseIdle();
  }

  if ( timeIdle > triggerTime_ && active_ )
  {
    emit timeout();
    idle_ = true;
  }
  else if( idle_ )
  {
    resetTimer();
  }
}


// Get idle time by asking the MIT-SCREEN-SAVER extension (if available)
unsigned long XAutoLock::getMitIdle()
{
  #ifdef HAVE_XSCREENSAVER
    if ( !mitInfo_ ) mitInfo_ = XScreenSaverAllocInfo();
    XScreenSaverQueryInfo (QX11Info::display(), DefaultRootWindow( QX11Info::display() ), mitInfo_ );

    return mitInfo_->idle / 1000;
  #else
    return 0;
  #endif
}


// Get idle time by detecting time since last mouse movement
unsigned int XAutoLock::getMouseIdle()
{
  // NYI
  return 0;
}


// Get screen saver status by asking the MIT-SCREEN-SAVER extension (if available)
bool XAutoLock::isMitScreenSaverActive()
{
  #ifdef HAVE_XSCREENSAVER
    if( ! mitAvailable_ )
      return false;

    XScreenSaverInfo *info = XScreenSaverAllocInfo();
    XScreenSaverQueryInfo( QX11Info::display(), DefaultRootWindow( QX11Info::display() ), info );

    bool state = ( info->state == ScreenSaverOn );

    XFree( info );
    return state;
  #else
    return false;
  #endif
}


// Get screen saver status by asking the KDE Screen Saver or X extensions
bool XAutoLock::isScreenSaverActive()
{
#ifdef KMESSDEBUG_AUTOLOCK_DBUS_QUERYING
  kmDebug() << "Calling D-Bus for screensaver status...";
#endif

  // Get on the bus
  const QDBusConnection dbus( QDBusConnection::sessionBus() );

  if( ! dbus.isConnected() )
  {
#ifdef KMESSDEBUG_AUTOLOCK_DBUS_QUERYING
    kmDebug() << "D-Bus is not connected! Falling back to the X screensaver extension.";
#endif
    return isMitScreenSaverActive();
  }

  // Generate the message we'll send to the active Screen Saver service
  const QDBusMessage message( QDBusMessage::createMethodCall( "org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "GetActive" ) );

  // Call the service and wait for it to answer
  const QDBusReply<bool>& reply( dbus.call( message ) );

  // Check if the answer is valid, and if so return the obtained value
  if( reply.isValid() )
  {
#ifdef KMESSDEBUG_AUTOLOCK_DBUS_QUERYING
    kmDebug() << "Screensaver is" << ( reply ? "" : "not" ) << "active";
#endif
    return reply;
  }

#ifdef KMESSDEBUG_AUTOLOCK_DBUS_QUERYING
  kmDebug() << "Got error screensaver response:" << reply.error() << ". Falling back to the X screensaver extension.";
#endif

  // As a fallback, when the DCOP call fails, ask the MIT-SCREEN-SAVER extension.
  return isMitScreenSaverActive();
}


// Reset the timer (after we've just become active again, for example)
void XAutoLock::resetTimer()
{
  idle_ = false;
  idleTime_ = (uint)time( 0 );

  emit activity();
}


// Set the timeout
void XAutoLock::setTimeOut( unsigned int timeOut )
{
  active_ = true;

  triggerTime_ = timeOut;
}


// Start the idle timer
void XAutoLock::startTimer()
{
  timer_->start( 5000 );

  resetTimer();
}


// Stop the idle timer
void XAutoLock::stopTimer()
{
  timer_->stop();
  active_ = false;
}


#include "xautolock.moc"
