/*

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 <QShortcut>
#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;

    if (!getuid()) {
        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();
    }

    if (!getuid()) {
        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);

    if (_allowThemeDebug)
        new QShortcut(QKeySequence(QLatin1String("Ctrl+Alt+D")),
                      this, SLOT(slotDebugToggled()));

    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);
}

void
KThemedGreeter::slotDebugToggled()
{
    if ((debugLevel ^= DEBUG_THEMING))
        themer->slotNeedPlacement();
}

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"
