/*

Greeter widget for kdm

Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org>
Copyright (C) 2000-2004 Oswald Buddenhagen <ossi@kde.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.

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.

*/

#include "kgreeter.h"
#include "kconsole.h"
#include "kdmconfig.h"
#include "kdmclock.h"
#include "kdm_greet.h"
#include "themer/kdmthemer.h"
#include "themer/kdmitem.h"
#include "themer/kdmlabel.h"

#include <KColorScheme>
#include <KConfigGroup>
#include <klocale.h>
#include <kseparator.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>

#include <QAction>
#include <QBuffer>
#include <QDir>
#include <QFile>
#include <QImageReader>
#include <QKeyEvent>
#include <QLabel>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMenu>
#include <QMovie>
#include <QPainter>
#include <QPushButton>
#include <QStyle>

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

#include <X11/Xlib.h>

class UserListView : public QListWidget {
  public:
	UserListView( QWidget *parent = 0 )
		: QListWidget( parent )
		, cachedSizeHint( -1, 0 )
	{
		setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Ignored );
		setUniformItemSizes( true );
		setVerticalScrollMode( ScrollPerPixel );
		setIconSize( QSize( 48, 48 ) );
		setAlternatingRowColors( true );
	}

	mutable QSize cachedSizeHint;

  protected:
	virtual QSize sizeHint() const
	{
		if (!cachedSizeHint.isValid()) {
			ensurePolished();
			QStyleOptionViewItem vo( viewOptions() );
			QAbstractListModel *md( static_cast<QAbstractListModel *>(model()) );
			uint maxw = 0, h = 0;
			for (int i = 0, rc = md->rowCount(); i < rc; i++) {
				QSize sh = itemDelegate()->sizeHint( vo, md->index( i ) );
				uint thisw = sh.width();
				if (thisw > maxw)
					maxw = thisw;
				h += sh.height();
			}
			cachedSizeHint.setWidth(
				style()->pixelMetric( QStyle::PM_ScrollBarExtent ) +
				(frameWidth() +
				 (style()->styleHint( QStyle::SH_ScrollView_FrameOnlyAroundContents ) ?
					style()->pixelMetric(QStyle::PM_DefaultFrameWidth) : 0)) * 2 +
				maxw );
			cachedSizeHint.setHeight( frameWidth() * 2 + h );
		}
		return cachedSizeHint;
	}

	virtual void keyPressEvent( QKeyEvent *event )
	{
		switch (event->key()) {
		case Qt::Key_Enter:
		case Qt::Key_Return:
			if (currentItem())
				emit itemDoubleClicked( currentItem() );
			event->accept();
			break;
		case Qt::Key_Space:
			if (currentItem())
				emit itemClicked( currentItem() );
			event->accept();
			break;
		default:
			QListWidget::keyPressEvent( event );
			break;
		}
	}

	virtual void mousePressEvent( QMouseEvent *event )
	{
		m_suppressClick = false;
		QListWidget::mousePressEvent(event);
	}

	virtual void mouseReleaseEvent( QMouseEvent *event )
	{
		if (m_suppressClick)
			m_suppressClick = false;
		else
			QListWidget::mouseReleaseEvent(event);
	}

	virtual void mouseDoubleClickEvent( QMouseEvent *event )
	{
		m_suppressClick = true;
		QListWidget::mouseDoubleClickEvent(event);
	}

  private:
	bool m_suppressClick;
};

class UserListViewItem : public QListWidgetItem {
  public:
	UserListViewItem( UserListView *parent, const QString &text,
	                  const QPixmap &pixmap, const QString &username )
		: QListWidgetItem( parent )
		, login( username )
	{
		setIcon( pixmap );
		setText( text );
		parent->cachedSizeHint.setWidth( -1 );
	}

	QString login;
};


int KGreeter::curPlugin = -1;
PluginList KGreeter::pluginList;

KGreeter::KGreeter( bool framed )
  : inherited( framed )
  , dName( dname )
  , userView( 0 )
  , userList( 0 )
  , nNormals( 0 )
  , nSpecials( 0 )
  , curPrev( 0 )
  , prevValid( true )
  , needLoad( false )
{
	stsGroup = new KConfigGroup( KSharedConfig::openConfig( _stsFile ),
	                             "PrevUser" );

	if (_userList) {
		userView = new UserListView( this );
		connect( userView, SIGNAL(itemClicked( QListWidgetItem * )),
		         SLOT(slotUserClicked( QListWidgetItem * )) );
		connect( userView, SIGNAL(itemDoubleClicked( QListWidgetItem * )),
		         SLOT(accept()) );
	}
	if (_userCompletion)
		userList = new QStringList;
	if (userView || userList)
		insertUsers();

	sessMenu = new QMenu( this );
	connect( sessMenu, SIGNAL(triggered( QAction * )),
	         SLOT(slotSessionSelected()) );

	sessGroup = new QActionGroup( this );
	insertSessions();

	if (curPlugin < 0) {
		curPlugin = 0;
		pluginList = KGVerify::init( _pluginsLogin );
	}
}

KGreeter::~KGreeter()
{
	hide();
	delete userList;
	delete verify;
	delete stsGroup;
}

#define FILE_LIMIT_ICON 20
#define FILE_LIMIT_IMAGE 200
#define PIXEL_LIMIT_ICON 100
#define PIXEL_LIMIT_IMAGE 300

// replace this with a simple !access(..., X_OK) once we run with non-root real uid
static bool
dirAccessible( const char *dir )
{
    struct stat st;

    if (stat( dir, &st ))
        return false;
    return (st.st_mode & S_IXOTH) != 0;
}

static bool
loadFace( QByteArray &fn, QImage &p, const QByteArray &pp, bool complain = false )
{
	int fd, ico;
	if ((fd = open( fn.data(), O_RDONLY | O_NONBLOCK )) < 0) {
		if (errno != ENOENT) {
			if (pp.isEmpty() || dirAccessible( pp.data() ))
				(complain ? logError : logInfo)
					( "Cannot load %s: %m\n", fn.data() );
			return false;
		}
		fn.chop( 5 );
		if ((fd = open( fn.data(), O_RDONLY | O_NONBLOCK )) < 0) {
			if ((complain || errno != ENOENT) &&
			    (pp.isEmpty() || dirAccessible( pp.data() )))
				(complain ? logError : logInfo)
					( "Cannot load %s: %m\n", fn.data() );
			return false;
		}
		ico = 0;
	} else
		ico = 1;
	QFile f;
	f.open( fd, QFile::ReadOnly );
	int fs = f.size();
	if (fs > (ico ? FILE_LIMIT_ICON : FILE_LIMIT_IMAGE) * 1000) {
		logWarn( "%s exceeds file size limit (%dkB)\n",
		         fn.data(), ico ? FILE_LIMIT_ICON : FILE_LIMIT_IMAGE );
		return false;
	}
	QByteArray fc = f.read( fs );
	::close( fd );
	QBuffer buf( &fc );
	buf.open( QBuffer::ReadOnly );
	QImageReader ir( &buf );
	QSize sz = ir.size();
	int lim = ico ? PIXEL_LIMIT_ICON : PIXEL_LIMIT_IMAGE;
	if (sz.width() > lim || sz.height() > lim) {
		logWarn( "%s exceeds image dimension limit (%dx%d)\n",
		         fn.data(), lim, lim );
		return false;
	}
	sz.scale( 48, 48, Qt::KeepAspectRatio );
	ir.setScaledSize( sz );
	p = ir.read();
	if (p.isNull()) {
		logInfo( "%s is no valid image\n", fn.data() );
		return false;
	}
	if (p.width() < 48) {
		QImage np( 48, p.height(), QImage::Format_ARGB32 );
		np.fill( 0 );
		QPainter pnt( &np );
		pnt.drawImage( (48 - p.width()) / 2, 0, p );
		p = np;
	}
	return true;
}

void
KGreeter::insertUser( const QImage &default_pix,
                      const QString &username, struct passwd *ps )
{
	if (userList)
		userList->append( username );
	if (!userView)
		return;

	int dp = 0, nd = 0;
	if (_faceSource == FACE_USER_ONLY ||
	    _faceSource == FACE_PREFER_USER)
		dp = 1;
	if (_faceSource != FACE_USER_ONLY &&
	    _faceSource != FACE_ADMIN_ONLY)
		nd = 1;
	QImage p;
	do {
		dp ^= 1;
		QByteArray pp, fn;
		if (!dp) {
			fn = pp = QByteArray( ps->pw_dir );
			fn += '/';
		} else {
			fn = QFile::encodeName( _faceDir );
			fn += '/';
			fn += ps->pw_name;
		}
		fn += ".face.icon";
		if (loadFace( fn, p, pp ))
			goto gotit;
	} while (--nd >= 0);
	p = default_pix;
  gotit:
	QString realname = KStringHandler::from8Bit( ps->pw_gecos );
	realname.truncate( realname.indexOf( ',' ) & (~0U >> 1) );
	if (realname.isEmpty() || realname == username)
		new UserListViewItem( userView, username, QPixmap::fromImage( p ), username );
	else {
		realname.append( "\n" ).append( username );
		new UserListViewItem( userView, realname, QPixmap::fromImage( p ), username );
	}
}

class UserList {
  public:
	UserList( char **in );
	bool hasUser( const char *str ) const { return users.contains( str ); }
	bool hasGroup( gid_t gid ) const { return groups.contains( gid ); }
	bool hasGroups() const { return !groups.isEmpty(); }

  private:
	QSet<gid_t> groups;
	QSet<QByteArray> users;
};

UserList::UserList( char **in )
{
	struct group *grp;

	for (; *in; in++)
		if (**in == '@') {
			if ((grp = getgrnam( *in + 1 ))) {
				for (; *grp->gr_mem; grp->gr_mem++)
					users.insert( *grp->gr_mem );
				groups.insert( grp->gr_gid );
			}
		} else
			users.insert( *in );
}

void
KGreeter::insertUsers()
{
	struct passwd *ps;

	// XXX remove seteuid-voodoo when we run as nobody
	if (!(ps = getpwnam( "nobody" )))
		return;
	if (setegid( ps->pw_gid ))
		return;
	if (seteuid( ps->pw_uid )) {
		setegid( 0 );
		return;
	}

	QImage default_pix;
	if (userView) {
		QByteArray fn = QFile::encodeName( _faceDir ) + "/.default.face.icon";
		if (!loadFace( fn, default_pix, QByteArray(), true )) {
			default_pix = QImage( 48, 48, QImage::Format_ARGB32 );
			default_pix.fill( 0 );
		}
	}
	if (_showUsers == SHOW_ALL) {
		UserList noUsers( _noUsers );
		QSet<QString> dupes;
		for (setpwent(); (ps = getpwent()) != 0;) {
			if (*ps->pw_dir && *ps->pw_shell &&
			    (ps->pw_uid >= (unsigned)_lowUserId ||
			     (!ps->pw_uid && _showRoot)) &&
			    ps->pw_uid <= (unsigned)_highUserId &&
			    !noUsers.hasUser( ps->pw_name ) &&
			    !noUsers.hasGroup( ps->pw_gid ))
			{
				QString username( QFile::decodeName( ps->pw_name ) );
				if (!dupes.contains( username )) {
					dupes.insert( username );
					insertUser( default_pix, username, ps );
				}
			}
		}
	} else {
		UserList users( _users );
		if (users.hasGroups()) {
			QSet<QString> dupes;
			for (setpwent(); (ps = getpwent()) != 0;) {
				if (*ps->pw_dir && *ps->pw_shell &&
				    (ps->pw_uid >= (unsigned)_lowUserId ||
				     (!ps->pw_uid && _showRoot)) &&
				    ps->pw_uid <= (unsigned)_highUserId &&
				    (users.hasUser( ps->pw_name ) ||
				     users.hasGroup( ps->pw_gid )))
				{
					QString username( QFile::decodeName( ps->pw_name ) );
					if (!dupes.contains( username )) {
						dupes.insert( username );
						insertUser( default_pix, username, ps );
					}
				}
			}
		} else {
			for (int i = 0; _users[i]; i++)
				if ((ps = getpwnam( _users[i] )) && (ps->pw_uid || _showRoot))
					insertUser( default_pix, QFile::decodeName( _users[i] ), ps );
		}
	}
	endpwent();
	endgrent();
	if (_sortUsers) {
		if (userView)
			userView->sortItems();
		if (userList)
			userList->sort();
	}

	// XXX remove seteuid-voodoo when we run as nobody
	seteuid( 0 );
	setegid( 0 );
}

void
KGreeter::putSession( const QString &type, const QString &name, bool hid, const char *exe )
{
	int prio = exe ? (!strcmp( exe, "default" ) ? 0 :
	                  !strcmp( exe, "custom" ) ? 1 :
	                  !strcmp( exe, "failsafe" ) ? 3 : 2) : 2;
	for (int i = 0; i < sessionTypes.size(); i++)
		if (sessionTypes[i].type == type) {
			sessionTypes[i].prio = prio;
			return;
		}
	sessionTypes.append( SessType( name, type, hid, prio ) );
}

void
KGreeter::insertSessions()
{
	for (char **dit = _sessionsDirs; *dit; ++dit)
		foreach (QString ent, QDir( *dit ).entryList())
			if (ent.endsWith( ".desktop" )) {
				KConfigGroup dsk(
				    KSharedConfig::openConfig(
				        QString( *dit ).append( '/' ).append( ent ) ),
				    "Desktop Entry" );
				putSession( ent.left( ent.length() - 8 ),
				            dsk.readEntry( "Name" ),
				            (dsk.readEntry( "Hidden", false ) ||
				             (dsk.hasKey( "TryExec" ) &&
				              KStandardDirs::findExe(
				                  dsk.readEntry( "TryExec" ) ).isEmpty())),
				            dsk.readEntry( "Exec" ).toLatin1() );
			}
	putSession( "default", i18nc("@item:inlistbox session type", "Default"), false, "default" );
	putSession( "custom", i18nc("@item:inlistbox session type", "Custom"), false, "custom" );
	putSession( "failsafe", i18nc("@item:inlistbox session type", "Failsafe"), false, "failsafe" );
	qSort( sessionTypes );
	for (int i = 0; i < sessionTypes.size() && !sessionTypes[i].hid; i++) {
		sessionTypes[i].action = sessGroup->addAction( sessionTypes[i].name );
		sessionTypes[i].action->setData( i );
		sessionTypes[i].action->setCheckable( true );
		switch (sessionTypes[i].prio) {
		case 0: case 1: nSpecials++; break;
		case 2: nNormals++; break;
		}
	}
	sessMenu->addActions( sessGroup->actions() );
}

void
KGreeter::slotUserEntered()
{
	struct passwd *pw;

	if (userView) {
		if ((pw = getpwnam( curUser.toLocal8Bit().data() ))) {
			QString theUser = QString::fromLocal8Bit( pw->pw_name );
			for (int i = 0, rc = userView->model()->rowCount(); i < rc; i++) {
				UserListViewItem *item =
					static_cast<UserListViewItem *>(userView->item( i ));
				if (item->login == theUser) {
					userView->setCurrentItem( item );
					goto oke;
				}
			}
		}
		userView->clearSelection();
	}
  oke:
	if (isVisible())
		slotLoadPrevWM();
	else
		QTimer::singleShot( 0, this, SLOT(slotLoadPrevWM()) );
}

void
KGreeter::slotUserClicked( QListWidgetItem *item )
{
	if (item) {
		curUser = ((UserListViewItem *)item)->login;
		verify->setUser( curUser );
		slotLoadPrevWM();
	}
}

void
KGreeter::slotSessionSelected()
{
	verify->gplugChanged();
}

void
KGreeter::reject()
{
	verify->reject();
}

void
KGreeter::accept()
{
	verify->accept();
}

void // private
KGreeter::setPrevWM( QAction *wm )
{
	if (curPrev != wm) {
		if (curPrev)
			curPrev->setText( sessionTypes[curPrev->data().toInt()].name );
		if (wm)
			wm->setText( i18nc( "@item:inmenu session type",
			                    "%1 (previous)",
			                    sessionTypes[wm->data().toInt()].name ) );
		curPrev = wm;
	}
}

void
KGreeter::slotLoadPrevWM()
{
	int len, i, b;
	unsigned long crc, by;
	QByteArray name;
	char *sess;

	// XXX this should actually check for !CoreBusy - would it be safe?
	if (verify->coreState != KGVerify::CoreIdle) {
		needLoad = true;
		return;
	}
	needLoad = false;

	prevValid = true;
	name = curUser.toLocal8Bit();
	gSendInt( G_ReadDmrc );
	gSendStr( name.data() );
	gRecvInt(); // ignore status code ...
	if ((len = name.length())) {
		gSendInt( G_GetDmrc );
		gSendStr( "Session" );
		sess = gRecvStr();
		if (!sess) { /* no such user */
			if (!userView && !userList) { // don't fake if user list shown
				prevValid = false;
				/* simple crc32 */
				for (crc = _forgingSeed, i = 0; i < len; i++) {
					by = (crc & 255) ^ name[i];
					for (b = 0; b < 8; b++)
						by = (by >> 1) ^ (-(by & 1) & 0xedb88320);
					crc = (crc >> 8) ^ by;
				}
				/* forge a session with this hash - default & custom more probable */
				/* XXX - this should do a statistical analysis of the real users */
#if 1
				setPrevWM( sessionTypes[crc % (nSpecials * 2 + nNormals) % (nSpecials + nNormals)].action );
#else
				i = crc % (nSpecials * 2 + nNormals);
				if (i < nNormals)
					setPrevWM( sessionTypes[i + nSpecials].action );
				else
					setPrevWM( sessionTypes[(i - nNormals) / 2].action );
#endif
				return;
			}
		} else {
			for (int i = 0; i < sessionTypes.count() && !sessionTypes[i].hid; i++)
				if (sessionTypes[i].type == sess) {
					free( sess );
					setPrevWM( sessionTypes[i].action );
					return;
				}
			if (!sessGroup->checkedAction())
				KFMsgBox::box( this, sorrybox,
				               i18n("Your saved session type '%1' is not valid any more.\n"
				                    "Please select a new one, otherwise 'default' will be used.", sess ) );
			free( sess );
			prevValid = false;
		}
	}
	setPrevWM( 0 );
}

void // protected
KGreeter::pluginSetup()
{
	int field = 0;
	QString ent, pn( verify->pluginName() ), dn( dName + '_' + pn );

	if (_preselUser != PRESEL_PREV)
		stsGroup->deleteEntry( verify->entitiesLocal() ? dName : dn, false );
	if (_preselUser != PRESEL_NONE && verify->entityPresettable()) {
		if (verify->entitiesLocal())
			ent = _preselUser == PRESEL_PREV ?
				stsGroup->readEntry( dName, QString() ) : _defaultUser;
		else
			ent = _preselUser == PRESEL_PREV ?
				stsGroup->readEntry( dn, QString() ) :
				verify->getConf( 0, (pn + ".DefaultEntity").toLatin1(), QVariant() ).toString();
		field = verify->entitiesFielded() ?
			verify->getConf( 0, (pn + ".FocusField").toLatin1(), QVariant( 0 ) ).toInt() :
			_focusPasswd;
	}
	verify->presetEntity( ent, field );
	if (userList)
		verify->loadUsers( *userList );
}

void
KGreeter::verifyPluginChanged( int id )
{
	curPlugin = id;
	pluginSetup();
}

void
KGreeter::verifyClear()
{
	curUser.clear();
	slotUserEntered();
	if (QAction *curSel = sessGroup->checkedAction())
		curSel->setChecked( false );
}

void
KGreeter::verifyOk()
{
	if (_preselUser == PRESEL_PREV && verify->entityPresettable())
		stsGroup->writeEntry( verify->entitiesLocal() ?
		                         dName :
		                         dName + '_' + verify->pluginName(),
		                      verify->getEntity() );
	if (QAction *curSel = sessGroup->checkedAction()) {
		gSendInt( G_PutDmrc );
		gSendStr( "Session" );
		gSendStr( sessionTypes[curSel->data().toInt()].type.toUtf8() );
	} else if (!prevValid) {
		gSendInt( G_PutDmrc );
		gSendStr( "Session" );
		gSendStr( "default" );
	}
	done( ex_login );
}

void
KGreeter::verifyFailed()
{
	if (userView)
		userView->setEnabled( false );
	if (needLoad)
		slotLoadPrevWM();
}

void
KGreeter::verifyRetry()
{
	if (userView)
		userView->setEnabled( true );
}

void
KGreeter::verifySetUser( const QString &user )
{
	curUser = user;
	slotUserEntered();
}


KStdGreeter::KStdGreeter()
  : KGreeter()
  , clock( 0 )
  , pixLabel( 0 )
{
	QBoxLayout *main_box;
#ifdef WITH_KDM_XCONSOLE
	if (consoleView) {
		QBoxLayout *ex_box = new QVBoxLayout( this );
		main_box = new QHBoxLayout();
		ex_box->addLayout( main_box );
		ex_box->addWidget( consoleView );
	} else
#endif
	{
		main_box = new QHBoxLayout( this );
	}
	int rs = layout()->spacing();
	main_box->setSpacing( layout()->margin() );

	if (userView)
		main_box->addWidget( userView );

	QBoxLayout *inner_box = new QVBoxLayout();
	main_box->addLayout( inner_box );
	inner_box->setSpacing( rs );

	if (!_authorized && _authComplain) {
		QLabel *complainLabel = new QLabel(
			i18n("Warning: this is an unsecured session"), this );
		complainLabel->setToolTip(
			i18n("This display requires no X authorization.\n"
			     "This means that anybody can connect to it,\n"
			     "open windows on it or intercept your input.") );
		complainLabel->setAlignment( Qt::AlignCenter );
		complainLabel->setFont( *_failFont );
		QPalette p;
		p.setBrush( QPalette::WindowText,
			KColorScheme( QPalette::Active, KColorScheme::Window )
				.foreground( KColorScheme::NegativeText ) );
		complainLabel->setPalette( p );
		inner_box->addWidget( complainLabel );
	}
	if (!_greetString.isEmpty()) {
		QLabel *welcomeLabel = new QLabel( _greetString, this );
		welcomeLabel->setAlignment( Qt::AlignCenter );
		welcomeLabel->setFont( *_greetFont );
		inner_box->addWidget( welcomeLabel );
	}

	switch (_logoArea) {
		case LOGO_CLOCK:
			clock = new KdmClock( this );
			break;
		case LOGO_LOGO:
			{
				QMovie *movie = new QMovie( this );
				movie->setFileName( _logo );
				if (movie->isValid()) {
					movie->start();
					pixLabel = new QLabel( this );
					pixLabel->setMovie( movie );
					if (!movie->currentImage().hasAlphaChannel())
						pixLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken );
					pixLabel->setIndent( 0 );
				} else
					delete movie;
			}
			break;
	}

	if (userView) {
		if (clock)
			inner_box->addWidget( clock, 0, Qt::AlignCenter );
		else if (pixLabel)
			inner_box->addWidget( pixLabel, 0, Qt::AlignCenter );
		inner_box->addSpacing( inner_box->spacing() );
	} else {
		if (clock)
			main_box->addWidget( clock, 0, Qt::AlignCenter );
		else if (pixLabel)
			main_box->addWidget( pixLabel, 0, Qt::AlignCenter );
	}

	goButton = new QPushButton( i18nc("@action:button", "L&ogin"), this );
	goButton->setDefault( true );
	connect( goButton, SIGNAL(clicked()), SLOT(accept()) );
	QPushButton *menuButton = new QPushButton( i18nc("@action:button", "&Menu"), this );
	//helpButton

	QWidget *prec;
	if (userView)
		prec = userView;
#ifdef WITH_KDM_XCONSOLE
	else if (consoleView)
		prec = consoleView;
#endif
	else
		prec = menuButton;
	KGStdVerify *sverify =
		new KGStdVerify( this, this, prec, QString(),
		                 pluginList, KGreeterPlugin::Authenticate,
		                 KGreeterPlugin::Login );
	inner_box->addLayout( sverify->getLayout() );
	QMenu *plugMenu = sverify->getPlugMenu();
	sverify->selectPlugin( curPlugin );
	verify = sverify;

	inner_box->addWidget( new KSeparator( Qt::Horizontal, this ) );

	QBoxLayout *hbox2 = new QHBoxLayout();
	inner_box->addLayout( hbox2 );
	hbox2->addWidget( goButton );
	hbox2->addStretch( 1 );
	hbox2->addWidget( menuButton );
	hbox2->addStretch( 1 );

	if (sessMenu->actions().count() > 1) {
		inserten( i18nc("@title:menu", "Session &Type"), Qt::ALT+Qt::Key_T, sessMenu );
		needSep = true;
	}

	if (plugMenu) {
		inserten( i18nc("@title:menu", "&Authentication Method"), Qt::ALT+Qt::Key_A, plugMenu );
		needSep = true;
	}

#ifdef XDMCP
	completeMenu( LOGIN_LOCAL_ONLY, ex_choose, i18nc("@action:inmenu", "&Remote Login"), Qt::ALT+Qt::Key_R );
#else
	completeMenu();
#endif

	if (optMenu)
		menuButton->setMenu( optMenu );
	else
		menuButton->hide();

	pluginSetup();

	verify->start();
}

void
KStdGreeter::pluginSetup()
{
	inherited::pluginSetup();
	if (userView) {
		if (verify->entitiesLocal() && verify->entityPresettable())
			userView->show();
		else
			userView->hide();
	}
	adjustGeometry();
	update();
}

void
KStdGreeter::verifyFailed()
{
	goButton->setEnabled( false );
	inherited::verifyFailed();
}

void
KStdGreeter::verifyRetry()
{
	goButton->setEnabled( true );
	inherited::verifyRetry();
}


KThemedGreeter::KThemedGreeter( KdmThemer *_themer )
	: KGreeter( true )
	, themer( _themer )
//	, clock( 0 )
{
	// We do all painting ourselves
	setAttribute( Qt::WA_NoSystemBackground, true );
	// Allow tracking the mouse position
	setMouseTracking( true );

	adjustGeometry();

	themer->setWidget( this );

	connect( themer, SIGNAL(activated( const QString & )),
	         SLOT(slotThemeActivated( const QString & )) );

	KdmItem *console_node = themer->findNode( "xconsole" ); // kdm ext
	KdmItem *console_rect = themer->findNode( "xconsole-rect" ); // kdm ext
	if (!console_rect)
		console_rect = console_node;
	userlist_node = themer->findNode( "userlist" );
	userlist_rect = themer->findNode( "userlist-rect" );
	if (!userlist_rect)
		userlist_rect = userlist_node;
	caps_warning = themer->findNode( "caps-lock-warning" );
	xauth_warning = themer->findNode( "xauth-warning" ); // kdm ext
	pam_error = themer->findNode( "pam-error" );
	KdmLabel *pam_error_label = qobject_cast<KdmLabel *>(pam_error);
	if (pam_error_label)
		pam_error_label->setText( i18n("Login failed") );
	timed_label = themer->findNode( "timed-label" );

	KdmItem *itm;
	if ((itm = themer->findNode( "pam-message" ))) // done via msgboxes
		itm->setVisible( false );
	if ((itm = themer->findNode( "language_button" ))) // not implemented yet
		itm->setVisible( false );

	if (console_node) {
#ifdef WITH_KDM_XCONSOLE
		if (consoleView)
			console_node->setWidget( consoleView );
		else
#endif
			console_rect->setVisible( false );
	}

	if (xauth_warning && (_authorized || !_authComplain))
		xauth_warning->setVisible( false );

//	if (!_greetString.isEmpty()) {
//	}
//	clock = new KdmClock( this, "clock" );

	QWidget *prec;
	if (userView)
		prec = userView;
#ifdef WITH_KDM_XCONSOLE
	else if (consoleView)
		prec = consoleView;
#endif
	else
		prec = 0;
	KGThemedVerify *tverify =
		new KGThemedVerify( this, themer, this, prec, QString(),
		                    pluginList, KGreeterPlugin::Authenticate,
		                    KGreeterPlugin::Login );
	QMenu *plugMenu = tverify->getPlugMenu();
	tverify->selectPlugin( curPlugin );
	verify = tverify;

	if ((session_button = themer->findNode( "session_button" ))) {
		if (sessMenu->actions().count() <= 1) {
			session_button->setVisible( false );
			session_button = 0;
		}
	} else {
		if (sessMenu->actions().count() > 1) {
			inserten( i18nc("@title:menu", "Session &Type"), Qt::ALT+Qt::Key_T, sessMenu );
			needSep = true;
		}
	}

	if (plugMenu) {
		inserten( i18nc("@title:menu", "&Authentication Method"), Qt::ALT+Qt::Key_A, plugMenu );
		needSep = true;
	}

#ifdef XDMCP
	completeMenu( LOGIN_LOCAL_ONLY, ex_choose, i18nc("@action:inmenu", "&Remote Login"), Qt::ALT+Qt::Key_R );
#else
	completeMenu();
#endif

	if ((system_button = themer->findNode( "system_button" ))) {
		if (optMenu)
			addAction( optMenu->menuAction() );
		else
			system_button->setVisible( false );
	}

	pluginSetup();

	verify->start();
}

KThemedGreeter::~KThemedGreeter()
{
	themer->setWidget( 0 );
}

bool
KThemedGreeter::event( QEvent *e )
{
	if (themer)
		themer->widgetEvent( e );
	return inherited::event( e );
}

void
KThemedGreeter::pluginSetup()
{
	inherited::pluginSetup();

	if (userView && verify->entitiesLocal() && verify->entityPresettable() && userlist_node) {
		userlist_node->setWidget( userView );
		userlist_rect->setVisible( true );
	} else {
		if (userView)
			userView->hide();
		if (userlist_rect)
			userlist_rect->setVisible( false );
	}
}

#if 0
void
KThemedGreeter::verifyFailed()
{
//	goButton->setEnabled( false );
	inherited::verifyFailed();
}

void
KThemedGreeter::verifyRetry()
{
//	goButton->setEnabled( true );
	inherited::verifyRetry();
}
#endif

void
KThemedGreeter::updateStatus( bool fail, bool caps, int timedleft )
{
	if (pam_error)
		pam_error->setVisible( fail );
	if (caps_warning)
		caps_warning->setVisible( caps );
	if (timed_label) {
		if (timedleft) {
			if (timedleft != KdmLabel::timedDelay) {
				KdmLabel::timedDelay = timedleft;
				KdmLabel::timedUser = curUser;
				timed_label->setVisible( true );
				timed_label->update();
			}
		} else {
			KdmLabel::timedDelay = -1;
			timed_label->setVisible( false );
		}
	}
}

void
KThemedGreeter::slotThemeActivated( const QString &id )
{
	if (id == "login_button")
		accept();
	else if (id == "session_button")
		slotSessMenu();
	else if (id == "system_button")
		slotActionMenu();
}

void
KThemedGreeter::slotSessMenu()
{
	sessMenu->popup( mapToGlobal( session_button->rect().center() ) );
}

void
KThemedGreeter::slotActionMenu()
{
	if (system_button)
		optMenu->popup( mapToGlobal( system_button->rect().center() ) );
	else
		optMenu->popup( mapToGlobal( rect().center() ) );
}

void
KThemedGreeter::keyPressEvent( QKeyEvent *e )
{
	inherited::keyPressEvent( e );
	if (!(e->modifiers() & ~Qt::KeypadModifier) &&
	    (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter))
		accept();
}

#include "kgreeter.moc"
