/*

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.

*/

//alex: include to get defines
#include <config-workspace.h>
#include <config-kdm.h>
#include <config-X11.h>

#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 "utils.h" //alex

#include <QApplication>
//#include <KColorScheme>
#include <QSettings> //<KConfigGroup>
//#include <klocale.h>
#include <QFrame> //<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 <QX11Info> //alex
#include <QProcess> //alex
#include <QTextCodec> //alex
#include <QStandardPaths>

#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>
#include <fixx11h.h>
#ifdef HAVE_XKB
#include <X11/XKBlib.h> /*alex*/
static XkbDescPtr xkb_desc=nullptr;
static short      xkb_got =-1;
#endif

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

    mutable QSize cachedSizeHint;

  protected:
    QSize sizeHint() const override
    {
        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;
    }

    void keyPressEvent(QKeyEvent *event) override
    {
        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;
        }
    }

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

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

    void mouseDoubleClickEvent(QMouseEvent *event) override
    {
        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(QString::/*fromUtf8*/fromLocal8Bit(dname))
    , userView(nullptr)
    , userList(nullptr)
    , nNormals(0)
    , nSpecials(0)
    , curPrev(nullptr)
    , prevValid(true)
    , needLoad(false)
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //alex

    stsGroup = new QSettings/*KConfigGroup( KSharedConfig::openConfig*/( _stsFile /*)*/,
                              /*"PrevUser"*/QSettings::IniFormat,this ); stsGroup->beginGroup("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);
    }

	QApplication::restoreOverrideCursor(); //alex
}

KGreeter::~KGreeter()
{
//fprintf(stderr,"destructor for KGreeter\n"); //alex
    hide();
    if (userList) delete userList;
    if (verify)   delete verify;
    if (stsGroup) delete stsGroup;
    //alex: _stsFile has deafult rights "rw-r-r", set "rw-r--" here
#ifndef DONT_SAVE_PREV_USER_ALWAYS
    QFile(_stsFile).setPermissions(QFileDevice::ReadOwner|QFileDevice::WriteOwner|QFileDevice::ReadGroup|QFileDevice::WriteOwner);
#endif
}

#define FILE_LIMIT_ICON 100 //*alex: was to small - 20 Kb
#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;
	if (ps && ps->pw_uid>=2500) //alex: diff ald's users by red spot
	{
	  p=default_pix;
	  if (p.width()>=4 && p.height()>=4)
	  {
	    unsigned int pix=0x880000;
	    p.setPixel(0,1,pix); p.setPixel(0,2,pix);
	    p.setPixel(1,0,pix); p.setPixel(1,1,pix); p.setPixel(1,2,pix); p.setPixel(1,3,pix);
	    p.setPixel(2,0,pix); p.setPixel(2,1,pix); p.setPixel(2,2,pix); p.setPixel(2,3,pix);
	    p.setPixel(3,1,pix); p.setPixel(3,2,pix);
	  }
	}
	else
	{
    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*/QString::fromLocal8Bit( 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()) != nullptr;) {
            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()) != nullptr;) {
                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, const QString& ico)
{
    int prio = exe ? (!strcmp(exe, "default") ? 0 :
//                    !strcmp(exe, "custom") ? 1 : //alex: from debian patch for kde 4.3.4
                      !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, ico));
}

void
KGreeter::insertSessions()
{
    //alex: get localized name
    QLocale           l  = locale();
    QLocale::Language lg = l.language();

    for (char **dit = _sessionsDirs; *dit; ++dit) {
        const QString sessionDir = QString::fromUtf8(*dit);
        foreach (QString ent, QDir(sessionDir).entryList()) {
            if (ent.endsWith(".desktop")) {
                /*KConfigGroup*/QSettings dsk(
                    /*KSharedConfig::openConfig(*/
                        QString(sessionDir).append('/').append(ent)/*)*/,
                    /*"Desktop Entry"*/QSettings::IniFormat,this); dsk.beginGroup("Desktop Entry");

                //alex: get localized name
                dsk.setIniCodec(QTextCodec::codecForLocale());
                QString lname;
                if (lg==QLocale::Russian) lname = dsk.value("Name[ru]").toString();
                if (lname.isEmpty())      lname = dsk.value("Name").toString();

                putSession(ent.left(ent.length() - 8),
                            lname,
                            (dsk.value/*readEntry*/("Hidden", false).toBool() ||
                             (dsk.contains("TryExec") &&
                              QStandardPaths::findExecutable(dsk.value/*readEntry*/("TryExec").toString()).isEmpty())),
                            dsk.value/*readEntry*/("Exec").toString().toLocal8Bit().data(),
                            dsk.value("Icon").toString()); //alex
            }
        }
    }
//  putSession("default", /*i18nc("@item:inlistbox session type",*/tr("Default"), false, "default"); //alex: no default item - fly is default
//  putSession("custom",  /*i18nctr("@item:inlistbox session type",*/tr("Custom"), false, "custom");  //alex: from debian patch for kde 4.3.4
//  putSession("failsafe", /*i18nctr("@item:inlistbox session type",*/tr("Failsafe"), false, "failsafe", "utilities-terminal");
    std::sort(sessionTypes.begin(), sessionTypes.end());
    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);
        if (!sessionTypes[i].iconName.isEmpty())
        sessionTypes[i].action->setIcon(
                    sessionTypes[i].iconName.contains("/")?
                    QIcon(sessionTypes[i].iconName) :
                    QIcon::fromTheme(sessionTypes[i].iconName)); //alex
        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",*/
                              tr("%1 (previous)").arg(
                              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::CoreBusy/*CoreIdle*/) {
        needLoad = true;
        return;
    }
    needLoad = false;

    prevValid = true;
    name = curUser.toLocal8Bit();
    debug("send G_ReadDmrc\n");
    gSendInt(G_ReadDmrc);
    gSendStr(name.data());
    gRecvInt(); // ignore status code ...
    debug("recv respond\n");
    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
                ///alex: fake prev session!
                ///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 {
            const QString sessionType = QString::fromUtf8(sess);
            for (int i = 0; i < sessionTypes.count() && !sessionTypes[i].hid; i++)
                if (sessionTypes[i].type == sessionType) {
                    free(sess);
                    setPrevWM(sessionTypes[i].action);
                    return;
                }
           
            if (strcmp(sess,"default")) /* alex: default session may be */
            if (!sessGroup->checkedAction())
                KFMsgBox::box(this, sorrybox,
                              tr("Your saved session type '%1' is not valid any more.\n"
                                   "Please select a new one, otherwise 'default' will be used.").arg(QString::fromLocal8Bit(sess)));
            free(sess);
            prevValid = false;
        }
    }
    setPrevWM(nullptr);
}

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

    if (_preselUser != PRESEL_PREV)
        stsGroup->remove/*deleteEntry*/( verify->entitiesLocal() ? dName : dn/*, false*/);

    if (_preselUser != PRESEL_NONE && verify->entityPresettable())
    {
        if (verify->entitiesLocal())
        {
            ent = _preselUser == PRESEL_PREV ?
                stsGroup->value/*readEntry*/(dName, QString()).toString() : _defaultUser;
        }
        else {
            ent = _preselUser == PRESEL_PREV ?
                stsGroup->value/*readEntry*/(dn, QString()).toString() :
                verify->getConf(nullptr, (pn + ".DefaultEntity").toLatin1(), QVariant()).toString();
        }

        if (!ent.isEmpty()) { //alex: check if user exists
            struct passwd * pwd = getpwnam(ent.toLocal8Bit().data());
            if (!pwd) ent = _defaultUser;
            else {
                UserList noUsers(_noUsers);
                if (noUsers.hasUser(ent.toLocal8Bit().data())) ent = _defaultUser;
            }
        }

        field = verify->entitiesFielded() ?
            verify->getConf(nullptr, (pn + ".FocusField").toLatin1(), QVariant(0)).toInt() :
            _focusPasswd;
    }

    verify->presetEntity(ent, field);
    if (userList)
        verify->loadUsers(*userList);

    stsGroup->sync();
}

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()
{
#ifndef DONT_SAVE_PREV_USER_ALWAYS
    if (/*_preselUser == PRESEL_PREV &&*/ verify->entityPresettable()) //alex: save previous user always
#else
    if (_preselUser == PRESEL_PREV && verify->entityPresettable())
#endif
        stsGroup->setValue/*writeEntry*/(verify->entitiesLocal() ?
                                 dName :
                                 dName + '_' + verify->pluginName(),
                             verify->getEntity());
    stsGroup->sync();

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

        if (!firstSession.isEmpty()) //alex
             gSendStr(firstSession.toLocal8Bit().constData());
        else gSendStr(/*"fly"*/"default"); //alex: no default item - fly is default - revert: use default to replace in Xsession script
    }
    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();
}

static bool checkXaccessForLocalhost() //as in fly-shutdown-dialog
{
    bool localHostOnly=true;
    //check remote login enabled (as in fly-wm)
    FILE * fp = fopen("/etc/X11/fly-dm/Xaccess", "r");
    if (fp) {
      char buf[256];
      while (fgets(buf,sizeof(buf)-1,fp)) {
        if (*buf=='#' || *buf=='\n' || *buf=='\0') continue;
        if (strncmp(buf,"localhost ", 10)  && strncmp(buf,"localhost\t",10)  &&
            strncmp(buf,"127.0.0.1 ", 10)  && strncmp(buf,"127.0.0.1\t",10)) {
            localHostOnly=false;
            break;
        }
      }
      fclose(fp);
    }
    return localHostOnly;
}


KStdGreeter::KStdGreeter()
    : KGreeter()
    , clock(nullptr)
    , pixLabel(nullptr)
{
    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(
            tr("Warning: this is an unsecured session"), this);
        complainLabel->setToolTip(
            tr("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",*/tr("L&ogin"), this);
    goButton->setDefault(true);
    connect(goButton, SIGNAL(clicked()), SLOT(accept()));
    QPushButton *menuButton = new QPushButton(/*i18nc("@action:button",*/tr("&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;

	QFrame *sep = new QFrame(this); sep->setFrameStyle(QFrame::HLine);
    inner_box->addWidget(sep/*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",*/tr("Session &Type"), Qt::ALT + Qt::Key_T, sessMenu);
        needSep = true;
    }

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

#ifdef XDMCP
    completeMenu(LOGIN_LOCAL_ONLY, ex_choose,
                 /*i18nc("@action:inmenu",*/checkXaccessForLocalhost()?QString::null:tr("&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(tr("Login failed"));
    timed_label = themer->findNode("timed-label");

    zamok_warning = themer->findNode("zamok-warning"); //alex
    xkb_layout_rect  = themer->findNode( "xkb-layout-rect" );//alex
    xkb_layout_item  = themer->findNode( "xkb-layout-label" );//alex
    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 (zamok_warning) //alex
    {
      if (getenv("ZUSERNAME")) zamok_warning->setVisible(true);
      else                     zamok_warning->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 = nullptr;
    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 = nullptr;
        }
		else //alex: add menu popup shortcut
		{
			inserten(QKeySequence(Qt::ALT + Qt::Key_T),SLOT(slotSessMenu()),-1);/*or so:
			QAction *action = new QAction( this );
			action->setShortcut( QKeySequence(Qt::ALT+Qt::Key_T) );
			connect( action, SIGNAL(triggered()), this, SLOT(slotSessMenu()) );
			addAction( action );*/
		}
    } else {
        if (sessMenu->actions().count() > 1) {
            inserten(/*i18nc("@title:menu",*/tr("Session &Type"), Qt::ALT + Qt::Key_T, sessMenu);
            needSep = true;
        }
    }

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

#ifdef XDMCP
    completeMenu(LOGIN_LOCAL_ONLY, ex_choose,
                 /*i18nc("@action:inmenu",*/checkXaccessForLocalhost()?QString::null:tr("&Remote Login"),
                 Qt::ALT + Qt::Key_R);
#else
    completeMenu();
#endif

    if ((system_button = themer->findNode("system_button"))) {
        if (optMenu)
		{
			inserten(QKeySequence(Qt::ALT+Qt::Key_O),SLOT(slotActionMenu()),-1); //alex
			inserten(QKeySequence(Qt::ALT+Qt::Key_M),SLOT(slotActionMenu()),-1); //alex
			/*or so: 
			QAction *action = new QAction( this );
			action->setShortcut( QKeySequence(Qt::ALT+Qt::Key_O) );
			connect( action, SIGNAL(triggered()), this, SLOT(slotActionMenu()) );
			addAction( action );*/

            addAction(optMenu->menuAction());
		}
        else
            system_button->setVisible(false);
    }

    vkb_rect  = themer->findNode( "vkb-rect" );//alex
    if (vkb_rect) vkb_rect->setVisible( QFile("/usr/bin/fly-vkbd").exists() ); //alex

    pluginSetup();

    verify->start();
}

KThemedGreeter::~KThemedGreeter()
{
//#ifdef HAVE_XKB
//    if (xkb_desc) { XkbFreeClientMap((XkbDescPtr)xkb_desc,XkbAllComponentsMask,True); xkb_desc = NULL; }
//    xkb_got = 0;
//#endif
//    fprintf(stderr,"destructor fo KThemedGreeter\n"); //alex

    themer->setWidget(nullptr);
}

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

//alex:-------
#ifdef HAVE_XKB
void KThemedGreeter::updateXkbStatus()
{
	KdmLabel * xkb_layout_label;

	if (xkb_got==0)
	{
	  if (xkb_layout_item && xkb_layout_rect)
	  {
	    xkb_layout_rect->setVisible( false );
	    xkb_layout_label=qobject_cast<KdmLabel *>(xkb_layout_item);
	    xkb_layout_label->setVisible( false );
	  }
	  return;
	}

	if (xkb_layout_item && xkb_layout_rect)
	{
	  xkb_layout_rect->setVisible( false );

	  xkb_layout_label=qobject_cast<KdmLabel *>(xkb_layout_item);

	  if (xkb_layout_label)
	  {
		Display *dpy = QX11Info::display();
		XkbStateRec xkbs;
		int ret;

		if (xkb_got==-1)
		{
			int major, minor, opcode_ret, error_ret, xkbEventId;

			xkb_got=0;
			major = XkbMajorVersion;
			minor = XkbMinorVersion;

			if (!XkbLibraryVersion(&major,&minor)) 
				logWarn( "XkbLibraryVersion() failed\n");
			else if (!XkbQueryExtension(dpy,&opcode_ret,&xkbEventId,&error_ret,&major,&minor))
				logWarn( "XkbQueryExtension() failed\n");
			else if ((xkb_desc = XkbGetMap(dpy,XkbAllComponentsMask,XkbUseCoreKbd))==nullptr)
				logWarn( "XkbGetMap() failed\n");
			else if ((ret=XkbGetNames(dpy,XkbAllNamesMask,(XkbDescPtr)xkb_desc))!=Success || ! ((XkbDescPtr)xkb_desc)->names) //XkbGroupNamesMask
				logWarn( "XkbGetNames() failed or returned no names ret=%d\n",ret);
			else 
			{
			    xkb_got=1;
			    //moved to config as Preloader field value
			    //system("setxkbmap -model pc104 -layout us,ru -variant ,"); //ugly workaround (cant' do initial switch by indicator, see also 837456 bug on ubuntu launchpad)
			}
		}
		if (xkb_got==1)
		{

			if (XkbGetState(dpy,XkbUseCoreKbd,&xkbs)!=Success)
				logWarn( "XkbGetState() failed\n");
			//FIXME: sometimes BadLength error code (16), memory corruption? no valgrind info!
			/*else if ((ret=XkbGetNames(dpy,XkbAllNamesMask,(XkbDescPtr)xkb_desc))!=Success || ! ((XkbDescPtr)xkb_desc)->names || //XkbGroupNamesMask
				! ((XkbDescPtr)xkb_desc)->names->groups[xkbs.locked_group])
				logWarn( "XkbGetNames() failed or returned no names code=%d\n",ret);*/
			else 
			{
				bool freeit=true;
				char *gname = XGetAtomName(dpy,((XkbDescPtr)xkb_desc)->names->groups[xkbs.locked_group]);
				if (gname)
				{
					xkb_layout_rect->setVisible( true );
					xkb_layout_label->setVisible( true );
					     if (strstr(gname,"Rus")) { XFree(gname); freeit=false; gname="Ru"; }
					else if (strstr(gname,"Eng")) { XFree(gname); freeit=false; gname="En"; }
					else if (strstr(gname,"USA")) { XFree(gname); freeit=false; gname="En"; }
					xkb_layout_label->setText(QString::fromLocal8Bit(gname));
					xkb_layout_label->update();
					if (freeit) XFree(gname);
				}
			}
		}
	  }
	}
}

void KThemedGreeter::slotSwitchXkb() //alex
{
	XkbStateRec xkbs;
	Display *dpy = QX11Info::display();

	if (XkbGetState(dpy,XkbUseCoreKbd,&xkbs)!=Success)
				logWarn( "XkbGetState() failed\n");
	else
	{	
		//FIXME: for a while we use 2 group only
		XkbLockGroup(dpy, XkbUseCoreKbd, (xkbs.locked_group==0?1:0));
		updateXkbStatus();
	}
}
#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);
        }
    }
	//alex------------------
#ifdef HAVE_XKB
	updateXkbStatus();
#else
    if (xkb_layout_rect) xkb_layout_rect->setVisible( false );
#endif
	//--------------------------------------------
}

void
KThemedGreeter::slotThemeActivated(const QString &id)
{
    if (id == "login_button")
        accept();
    else if (id == "session_button")
        slotSessMenu();
    else if (id == "system_button")
        slotActionMenu();
#ifdef HAVE_XKB
	else if (id == "xkb-layout-rect") //alex
		slotSwitchXkb();
#endif
    else if (id == "vkb-rect") slotVkbd();//alex
}

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"
