 /**************************************************************************
 *   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 	dbusInterface.cpp
 * \brief 	In this file can be found the functionality to connect to
 *		the D-Bus, to handle D-Bus session management
 * \author 	Danny Kukawka, <dkukawka@suse.de>, <danny.kukawka@web.de>
 * \date    	2006-2007
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
 
// TQt - Header
#include <tqtimer.h>

// KDE Header
#include <tdelocale.h>

// DBUS - Header
#include "dbus/dbus-shared.h"
#include "dbusInterface.h"
#include <tqdbusdatalist.h>
#include <tqdbusdatamap.h>
#include <tqdbuserror.h>
#include <tqdbusmessage.h>
#include <tqdbusvariant.h>

// system headers
#include <unistd.h>

#define DBUS_CONN_NAME "TDEPowersave"

static void* myInstance = 0;

/*! The default constructor of the class dbusInterface. */
dbusInterface::dbusInterface():
    dBusConn(),
    dBusWatch(0),
    dBusLocal(0),
    systemdSession(),
    systemdSeat(0),
    systemdInhibit(-1),
    consolekitSession(),
    consolekitSeat(0)
    {
	kdDebugFuncIn(trace);

	// add pointer to this for filter_function()
	myInstance=this;

	// init connection to dbus
	initDBUS();

	kdDebugFuncOut(trace);
}

/*! This is the default destructor of class dbusPowersaveConnection. */
dbusInterface::~dbusInterface(){
	kdDebugFuncIn(trace);

	close();
	myInstance = NULL;

	kdDebugFuncOut(trace);
}

/*! 
 * This function return information about connection status to the DBUS daemon.
 * \return boolean with the state of the connection to D-Bus
 * \retval true if connected
 * \retval false if disconnected
 */
bool dbusInterface::isConnectedToDBUS() {
	return dBusConn.isConnected();
}

/*! 
 * This function try a reconnect to D-Bus.
 * \return boolean with the result of the operation
 * \retval true if successful reconnected to D-Bus
 * \retval false if unsuccessful
 */
bool dbusInterface::reconnect() {
	// close D-Bus connection
	close();
	// init D-Bus conntection
	return (initDBUS());
}

/*! 
 * This function close the connection to powersave over the D-Bus daemon.
 * \return boolean with the result of the operation
 * \retval true if successful closed the connection
 * \retval false if any problems
 */
bool dbusInterface::close() {
	if( dBusConn.isConnected() ) {
		if( dBusWatch ) {
			delete dBusWatch;
		}
		if( dBusLocal ) {
			delete dBusLocal;
		}
		if( systemdSeat ) {
			delete systemdSeat;
		}
		if( consolekitSeat ) {
			delete consolekitSeat;
		}
	}
	dBusConn.closeConnection(DBUS_CONN_NAME);
	return true;
}

/*! 
 * This function initialise the connection to the D-Bus daemon.
 * \return boolean with the result of the operation
 * \retval true if successful initialised D-Bus connection
 * \retval false if unsuccessful
 */
bool dbusInterface::initDBUS(){
	kdDebugFuncIn(trace);

	dBusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus, DBUS_CONN_NAME);

	if( !dBusConn.isConnected() ) {
		kdError() << "Failed to open connection to system message bus: " << dBusConn.lastError().message() << endl;
		TQTimer::singleShot(4000, this, TQ_SLOT(reconnect()));
		return false;
	}

	// watcher for NameOwnerChanged signals
	dBusWatch = new TQT_DBusProxy(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, dBusConn);
	TQObject::connect(dBusWatch, TQ_SIGNAL(dbusSignal(const TQT_DBusMessage&)),
			  this, TQ_SLOT(handleDBusSignal(const TQT_DBusMessage&)));

	// watcher for Disconnect signal
	dBusLocal = new TQT_DBusProxy(DBUS_SERVICE_DBUS, DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, dBusConn);
	TQObject::connect(dBusLocal, TQ_SIGNAL(dbusSignal(const TQT_DBusMessage&)),
			  this, TQ_SLOT(handleDBusSignal(const TQT_DBusMessage&)));

	// find already running SystemD
	TQT_DBusProxy checkSystemD(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, dBusConn);
	if( checkSystemD.canSend() ) {
		TQValueList<TQT_DBusData> params;
		params << TQT_DBusData::fromString(SYSTEMD_LOGIN1_SERVICE);
		TQT_DBusMessage reply = checkSystemD.sendWithReply("NameHasOwner", params);
		if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 && reply[0].toBool() ) {
			onServiceRegistered(SYSTEMD_LOGIN1_SERVICE);
		}
	}

	// find already running ConsoleKit
	TQT_DBusProxy checkConsoleKit(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, dBusConn);
	if( checkConsoleKit.canSend() ) {
		TQValueList<TQT_DBusData> params;
		params << TQT_DBusData::fromString(CK_SERVICE);
		TQT_DBusMessage reply = checkConsoleKit.sendWithReply("NameHasOwner", params);
		if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 && reply[0].toBool() ) {
			onServiceRegistered(CK_SERVICE);
		}
	}

	kdDebugFuncOut(trace);
	return true;
}

/*! 
 * This function handles signals from the D-Bus daemon.
 */
void dbusInterface::handleDBusSignal(const TQT_DBusMessage& msg) {
	// dbus terminated
	if( msg.path() == DBUS_PATH_LOCAL
	 && msg.interface() == DBUS_INTERFACE_LOCAL
	 && msg.member() == "Disconnected" ) {
		close();
		TQTimer::singleShot(1000, this, TQ_SLOT(reconnect()));
		return;
	}

	// service registered / unregistered
	if( msg.path() == DBUS_PATH_DBUS
	 && msg.interface() == DBUS_INTERFACE_DBUS
	 && msg.member() == "NameOwnerChanged" ) {
		if( msg[1].toString().isEmpty() ) {
			// old-owner is empty
			onServiceRegistered(msg[0].toString());
		}
		if( msg[2].toString().isEmpty() ) {
			// new-owner is empty
			onServiceUnregistered(msg[0].toString());
		}
		return;
	}

	// systemd session changed
	if( systemdSeat && systemdSeat->canSend()
	 && msg.path() == systemdSeat->path()
	 && msg.interface() == DBUS_INTERFACE_PROPERTIES
	 && msg.member() == "PropertiesChanged"
	 && msg[0].toString() == SYSTEMD_LOGIN1_SEAT_IFACE) {
		bool activeSessionProperty = false;
		TQT_DBusDataMap<TQString> map = msg[1].toStringKeyMap();
		TQT_DBusDataMap<TQString>::const_iterator it = map.begin();
		for (; !activeSessionProperty && it != map.end(); ++it) {
			if( it.key() != "ActiveSession" ) {
				activeSessionProperty = true;
			}
		}
		TQValueList<TQString> list = msg[2].toList().toStringList();
		TQValueList<TQString>::const_iterator it1 = list.begin();
		for (; !activeSessionProperty && it1 != list.end(); ++it1) {
			if( (*it1) == "ActiveSession" ) {
				activeSessionProperty = true;
			}
		}
		if( activeSessionProperty ) {
			emit activeSessionChanged(checkActiveSession());
		}
		return;
	}

	// consolekit session changed
	if( consolekitSeat && consolekitSeat->canSend()
	 && msg.path() == consolekitSeat->path()
	 && msg.interface() == CK_SEAT_IFACE
	 && msg.member() == "ActiveSessionChanged") {
		emit activeSessionChanged(msg[0].toString() == TQString(consolekitSession));
		return;
	}
}

/*!
 * This function handles dBus service registering
 */
void dbusInterface::onServiceRegistered(const TQString& service) {
	if( service == SYSTEMD_LOGIN1_SERVICE ) {
		// get current session
		TQT_DBusProxy managerIface(SYSTEMD_LOGIN1_SERVICE, SYSTEMD_LOGIN1_PATH, SYSTEMD_LOGIN1_MANAGER_IFACE, dBusConn);
		systemdSession = TQT_DBusObjectPath();
		if( managerIface.canSend() ) {
			TQValueList<TQT_DBusData> params;
			params << TQT_DBusData::fromUInt32( getpid() );
			TQT_DBusMessage reply = managerIface.sendWithReply("GetSessionByPID", params);
			if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
				systemdSession = reply[0].toObjectPath();
			}
		}
		if( !systemdSession.isValid() ) {
			kdWarning() << "The session is not registered with systemd" << endl;
			return;
		}

		// get session seat
		TQT_DBusProxy sessionProperties(SYSTEMD_LOGIN1_SERVICE, systemdSession, DBUS_INTERFACE_PROPERTIES, dBusConn);
		TQT_DBusObjectPath seat;
		if( sessionProperties.canSend() ) {
			TQValueList<TQT_DBusData> params;
			params
			 << TQT_DBusData::fromString( SYSTEMD_LOGIN1_SESSION_IFACE )
			 << TQT_DBusData::fromString( "Seat" );
			TQT_DBusMessage reply = sessionProperties.sendWithReply("Get", params);
			if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
				seat = reply[0].toVariant().value.toStruct()[1].toObjectPath();
			}
		}
		if( !seat.isValid() ) {
			kdWarning() << "Unable to associate systemd session with a seat" << endl;
			return;
		}

		// watch session changes
		systemdSeat = new TQT_DBusProxy(SYSTEMD_LOGIN1_SERVICE, seat, DBUS_INTERFACE_PROPERTIES, dBusConn);
		TQObject::connect(systemdSeat, TQ_SIGNAL(dbusSignal(const TQT_DBusMessage&)),
				  this, TQ_SLOT(handleDBusSignal(const TQT_DBusMessage&)));

		// inhibit systemd handling of power/sleep/hibernate/lid buttons
		// http://www.freedesktop.org/wiki/Software/systemd/inhibit
		if( !systemdInhibit.isValid() && managerIface.canSend() ) {
			TQValueList<TQT_DBusData> params;
			params
			 << TQT_DBusData::fromString("handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch") // what
			 << TQT_DBusData::fromString("TDEPowersave") // who
			 << TQT_DBusData::fromString("TDE handles power events") // why
			 << TQT_DBusData::fromString("block"); // mode
			TQT_DBusMessage reply = managerIface.sendWithReply("Inhibit", params);
			if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
				systemdInhibit = reply[0].toUnixFd();
			}
		}
		return;
	}
	if( service == CK_SERVICE ) {
		// get current session
		TQT_DBusProxy managerIface(CK_SERVICE, CK_MANAGER_OBJECT, CK_MANAGER_IFACE, dBusConn);
		consolekitSession = TQT_DBusObjectPath();
		if( managerIface.canSend() ) {
			TQValueList<TQT_DBusData> params;
			params << TQT_DBusData::fromUInt32( getpid() );
			TQT_DBusMessage reply = managerIface.sendWithReply("GetSessionForUnixProcess", params);
			if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
				consolekitSession = reply[0].toObjectPath();
			}
		}
		if( !consolekitSession.isValid() ) {
			kdWarning() << "The session is not registered with consolekit" << endl;
			return;
		}

		// get session seat
		TQT_DBusObjectPath seat;
		if( dBusConn.isConnected() ) {
			TQT_DBusMessage msg = TQT_DBusMessage::methodCall(CK_SERVICE, consolekitSession, CK_SESSION_IFACE, "GetSeatId");
			TQT_DBusMessage reply = dBusConn.sendWithReply(msg);
			if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
				seat = reply[0].toObjectPath();
			}
		}
		if( !seat.isValid() ) {
			kdWarning() << "Unable to associate consolekit session with a seat" << endl;
			return;
		}

		// watch session changes
		consolekitSeat = new TQT_DBusProxy(CK_SERVICE, seat, CK_SEAT_IFACE, dBusConn);
		TQObject::connect(consolekitSeat, TQ_SIGNAL(dbusSignal(const TQT_DBusMessage&)),
				  this, TQ_SLOT(handleDBusSignal(const TQT_DBusMessage&)));
		return;
	}
}

/*!
 * This function handles dBus service unregistering
 */
void dbusInterface::onServiceUnregistered(const TQString& service) {
	if( service == SYSTEMD_LOGIN1_SERVICE ) {
		systemdSession = TQT_DBusObjectPath();
		if( systemdSeat ) {
			delete systemdSeat;
		}
		return;
	}
	if( service == CK_SERVICE ) {
		consolekitSession = TQT_DBusObjectPath();
		if( consolekitSeat ) {
			delete consolekitSeat;
		}
		return;
	}
}

/*!
 * This functions is used to check if session is active
 */
bool dbusInterface::checkActiveSession() {
	if( systemdSeat && systemdSeat->canSend() ) {
		TQT_DBusObjectPath activeSession;
		TQValueList<TQT_DBusData> params;
		params
		 << TQT_DBusData::fromString( SYSTEMD_LOGIN1_SEAT_IFACE )
		 << TQT_DBusData::fromString( "ActiveSession" );
		TQT_DBusMessage reply = systemdSeat->sendWithReply("Get", params);
		if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
			activeSession = reply[0].toVariant().value.toStruct()[1].toObjectPath();
			return (activeSession == systemdSession);
		}
	}
	if( consolekitSeat && consolekitSeat->canSend() ) {
		TQT_DBusObjectPath activeSession;
		TQValueList<TQT_DBusData> params;
		TQT_DBusMessage reply = consolekitSeat->sendWithReply("GetActiveSession", params);
		if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1 ) {
			activeSession = reply[0].toObjectPath();
			return (activeSession == consolekitSession);
		}
	}
	return false;
}

#include "dbusInterface.moc"
