#define USE_KWIN

#include "kdialogd.h"
#include <iostream>
#include <kdiroperator.h>
#include <kuniqueapplication.h>
#include <QtCore/QSocketNotifier>
#include <QtGui/QX11Info>
#include <kio/netaccess.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kconfig.h>
#include <kurlcombobox.h>
#ifdef USE_KWIN
#include <kwindowsystem.h>
#else
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <fixx11h.h>
#endif
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <kdebug.h>
#include <kdeversion.h>
#ifdef KDIALOGD_APP
#include <QtCore/QTimer>
#include <kcmdlineargs.h>
#include <kaboutdata.h>
#endif

KConfig *KDialogD::theirConfig=NULL;

#define CFG_KEY_DIALOG_SIZE "KDialogDSize"
#define CFG_TIMEOUT_GROUP   "General"
#ifdef KDIALOGD_APP
#define CFG_TIMEOUT_KEY     "Timeout"
#define DEFAULT_TIMEOUT     30
#endif

static QString groupName(const QString &app, bool fileDialog=true)
{
    return QString(fileDialog ? "KFileDialog " : "KDirSelectDialog ")+app;
}

// from kdebase/kdesu
typedef unsigned ksocklen_t;

static int createSocket()
{
    int         socketFd;
    ksocklen_t  addrlen;
    struct stat s;
    const char  *sock=getSockName();
    int stat_err=lstat(sock, &s);

    if(!stat_err && S_ISLNK(s.st_mode))
    {
        kWarning() << "Someone is running a symlink attack on you" ;
        if(unlink(sock))
        {
            kWarning() << "Could not delete symlink" ;
            return -1;
        }
    }

    if (!access(sock, R_OK|W_OK))
    {
        kWarning() << "stale socket exists" ;
        if (unlink(sock))
        {
            kWarning() << "Could not delete stale socket" ;
            return -1;
        }
    }

    socketFd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (socketFd < 0)
    {
        kError() << "socket(): " << strerror(errno);
        return -1;
    }

    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1);
    addr.sun_path[sizeof(addr.sun_path)-1] = '\000';
    addrlen = SUN_LEN(&addr);
    if (bind(socketFd, (struct sockaddr *)&addr, addrlen) < 0)
    {
        kError() << "bind(): " << strerror(errno);
        return -1;
    }

    struct linger lin;
    lin.l_onoff = lin.l_linger = 0;
    if (setsockopt(socketFd, SOL_SOCKET, SO_LINGER, (char *) &lin,
                   sizeof(linger)) < 0)
    {
        kError() << "setsockopt(SO_LINGER): " << strerror(errno);
        return -1;
    }

    int opt = 1;
    if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt,
                   sizeof(opt)) < 0)
    {
        kError() << "setsockopt(SO_REUSEADDR): " << strerror(errno);
        return -1;
    }
    opt = 1;
    if (setsockopt(socketFd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt,
                   sizeof(opt)) < 0)
    {
        kError() << "setsockopt(SO_KEEPALIVE): " << strerror(errno);
        return -1;
    }
    chmod(sock, 0600);
    if (listen(socketFd, 1) < 0)
    {
        kError() << "listen(): " << strerror(errno);
        return -1;
    }

    return socketFd;
}

static void urls2Local(KUrl::List &urls, QStringList &items, QWidget *parent)
{
    KUrl::List::Iterator it(urls.begin()),
                         end(urls.end());

    for(; it!=end; ++it)
    {
        kDebug() << "URL:" << *it << " local? " << (*it).isLocalFile();
        if((*it).isLocalFile())
            items.append((*it).path());
        else
        {
            KUrl url(KIO::NetAccess::mostLocalUrl(*it, parent));

            kDebug() << "mostLocal:" << url << " local? " << url.isLocalFile();
            if(url.isLocalFile())
                items.append(url.path());
            else
                break;
        }
    }
}

KDialogD::KDialogD(QObject *parent)
        : QObject(parent),
#ifdef KDIALOGD_APP
          itsTimer(NULL),
          itsTimeoutVal(DEFAULT_TIMEOUT),
#endif
          itsFd(::createSocket()),
          itsNumConnections(0)
{
    if(itsFd<0)
    {
        kError() << "KDialogD could not create socket";
#ifdef KDIALOGD_APP
        kapp->exit();
#endif
    }
    else
    {
        if(!theirConfig)
            theirConfig=new KConfig("kdialogd4rc", KConfig::OnlyLocal);

        connect(new QSocketNotifier(itsFd, QSocketNotifier::Read, this),
                SIGNAL(activated(int)), this, SLOT(newConnection()));

#ifdef KDIALOGD_APP
        if(theirConfig->hasGroup(CFG_TIMEOUT_GROUP))
        {
            itsTimeoutVal=KConfigGroup(theirConfig, CFG_TIMEOUT_GROUP).readEntry(CFG_TIMEOUT_KEY, DEFAULT_TIMEOUT);
            if(itsTimeoutVal<0)
                itsTimeoutVal=DEFAULT_TIMEOUT;
        }
        kDebug() << "Timeout:" << itsTimeoutVal;
        if(itsTimeoutVal)
        {
            connect(itsTimer=new QTimer(this), SIGNAL(timeout()), this, SLOT(timeout()));
            itsTimer->setSingleShot(true);
        }
#endif
    }
}

KDialogD::~KDialogD()
{
    if(-1!=itsFd)
        close(itsFd);
    if(theirConfig)
        delete theirConfig;
    theirConfig=NULL;
}

void KDialogD::newConnection()
{
    kDebug() << "New connection";

    ksocklen_t         addrlen = 64;
    struct sockaddr_un clientname;
    int                connectedFD;

    if((connectedFD=::accept(itsFd, (struct sockaddr *) &clientname, &addrlen))>=0)
    {
        int appNameLen;

        if(readBlock(connectedFD, (char *)&appNameLen, 4))
        {
            bool     ok=true;
            QByteArray appName;

            if(0==appNameLen)
                appName="Generic";
            else
            {
                appName.resize(appNameLen);
                ok=readBlock(connectedFD, appName.data(), appNameLen);
            }

            if(ok)
            {
                itsNumConnections++;
#ifdef KDIALOGD_APP
                if(itsTimer)
                    itsTimer->stop();
#endif
                connect(new KDialogDClient(connectedFD, appName, this),
                        SIGNAL(error(KDialogDClient *)),
                        this, SLOT(deleteConnection(KDialogDClient *)));
            }
        }
    }
}

void KDialogD::deleteConnection(KDialogDClient *client)
{
    kDebug() << "Delete client";
    delete client;

#ifdef KDIALOGD_APP
    if(0==--itsNumConnections)
        if(itsTimeoutVal)
            itsTimer->start(itsTimeoutVal*1000);  // Only single shot...
        else
            timeout();
#endif
}

void KDialogD::timeout()
{
#ifdef KDIALOGD_APP
    if(0==itsNumConnections)
        if(grabLock(0)>0)   // 0=> no wait...
        {
            kDebug() << "Timeout occured, and no connections, so exit";
            kapp->exit();
        }
        else     //...unlock lock file...
        {
            kDebug() << "Timeout occured, but unable to grab lock file - app must be connecting";
            releaseLock();
        }
#endif
}

KDialogDClient::KDialogDClient(int sock, const QString &an, QObject *parent)
              : QObject(parent),
                itsFd(sock),
                itsDlg(NULL),
                itsXid(0),
                itsAccepted(false),
                itsAppName(an)
{
    kDebug() << "new client..." << itsAppName << " (" << itsFd << ")";
    connect(new QSocketNotifier(itsFd, QSocketNotifier::Read, this), SIGNAL(activated(int)), this, SLOT(read()));
    connect(new QSocketNotifier(itsFd, QSocketNotifier::Exception, this), SIGNAL(activated(int)), this, SLOT(close()));
}

KDialogDClient::~KDialogDClient()
{
    kDebug() << "Deleted client";
    if(-1!=itsFd)
        ::close(itsFd);
    itsDlg=NULL;
    if(KDialogD::config())
        KDialogD::config()->sync();
}

void KDialogDClient::close()
{
    kDebug() << "close" << itsFd;

    ::close(itsFd);
    itsFd=-1;
    if(itsDlg)
    {
        itsDlg->close();
        itsDlg->delayedDestruct();
        itsDlg=NULL;
        itsXid=0;
    }

    emit error(this);
}

void KDialogDClient::read()
{
    kDebug() << "read" << itsFd;

    if(-1==itsFd)
        return;

    char         request;
    QString      caption;

    if(!itsDlg && readData(&request, 1) && request>=(char)OP_FILE_OPEN && request<=(char)OP_FOLDER &&
       readData((char *)&itsXid, 4) && readString(caption))
    {
        if("."==caption)
            switch((Operation)request)
            {
                case OP_FILE_OPEN:
                case OP_FILE_OPEN_MULTIPLE:
                    caption=i18n("Open");
                    break;
                case OP_FILE_SAVE:
                    caption=i18n("Save As");
                    break;
                case OP_FOLDER:
                    caption=i18n("Select Folder");
                    break;
                default:
                    break;
            }

        if(OP_FOLDER==(Operation)request)
        {
            QString intialFolder;

            if(readString(intialFolder))
            {
                initDialog(caption, new KDialogDDirSelectDialog(itsAppName, intialFolder, true, NULL, false));
                return;
            }
        }
        else
        {
            QString intialFolder,
                    filter;
            char    overW=0;

            if(readString(intialFolder) && readString(filter) &&
               (OP_FILE_SAVE!=(Operation)request || readData(&overW, 1)))
            {
                initDialog(caption, new KDialogDFileDialog(itsAppName, (Operation)request, intialFolder,
                                                           filter, overW ? true : false));
                return;
            }
        }
    }

    kDebug() << "Comms error, closing connection..." << itsFd;
    // If we get here something was wrong, close connection...
    close();
}

void KDialogDClient::finished()
{
    if(-1==itsFd)
        return;

    //
    // * finished is emitted when a dialog is ok'ed/cancel'ed/closed
    // * if the user just closes the dialog - neither ok nor cancel are emitted
    // * the dir select dialog doesnt seem to set the QDialog result parameter
    //   when it is accepted - so for this reason if ok is clicked we store an
    //   'accepted' value there, and check for that after the dialog is finished.
    kDebug() << "finished " << (int)itsDlg << itsAccepted << (itsDlg ? QDialog::Accepted==itsDlg->result() : false);

    if(itsDlg && !(itsAccepted || QDialog::Accepted==itsDlg->result()))
        cancel();
}

void KDialogDClient::ok(const QStringList &items)
{
    kDebug() << "ok";

    int                        num=items.count();
    QStringList::ConstIterator it(items.begin()),
                               end(items.end());
    bool                       error=!writeData((char *)&num, 4);

    for(; !error && it!=end; ++it)
    {
        kDebug() << "writeString " << *it;
        error=!writeString(*it);
    }

    if(error)
        close();
    else
        itsAccepted=true;
    if(itsDlg)
        itsDlg->delayedDestruct();
    itsDlg=NULL;
}

void KDialogDClient::cancel()
{
    kDebug() << "cancel";

    if(itsDlg)
    {
        kDebug() << "send cancel";

        int rv=0;

        if(!writeData((char *)&rv, 4))
        {
            kDebug() << "failed to write data!";
            close();
        }
        if(itsDlg)
            itsDlg->delayedDestruct();
        itsDlg=NULL;
    }
}

bool KDialogDClient::readData(QByteArray &buffer, int size)
{
    kDebug() << "readData" << itsFd;
    buffer.resize(size);
    return ::readBlock(itsFd, buffer.data(), size);
}

bool KDialogDClient::readString(QString &str)
{
    kDebug() << "readString" << itsFd;

    int size;

    if(!readData((char *)&size, 4))
        return false;

    QByteArray buffer;
    buffer.resize(size);

    if(!readData(buffer.data(), size))
        return false;

    str=QString::fromUtf8(buffer.data());
    return true;
}

bool KDialogDClient::writeString(const QString &str)
{
    kDebug() << "writeString" << itsFd;

    QByteArray utf8(str.toUtf8());

    int size=utf8.length()+1;

    return writeData((char *)&size, 4) && writeData(utf8.data(), size);
}

void KDialogDClient::initDialog(const QString &caption, KDialog *d)
{
    kDebug() << "initDialog" << itsFd;

    itsAccepted=false;
    itsDlg=d;

    if(!caption.isEmpty())
        itsDlg->setPlainCaption(caption);

    if(itsXid)
        itsDlg->installEventFilter(this);

    connect(itsDlg, SIGNAL(okClicked()), itsDlg, SLOT(slotOk()));
    connect(itsDlg, SIGNAL(ok(const QStringList &)), this, SLOT(ok(const QStringList &)));
    connect(itsDlg, SIGNAL(finished()), this, SLOT(finished()));
    itsDlg->show();
}

bool KDialogDClient::eventFilter(QObject *object, QEvent *event)
{
    if(object==itsDlg && QEvent::ShowToParent==event->type())
    {
#ifdef USE_KWIN
        KWindowSystem::setMainWindow(itsDlg, itsXid);
        KWindowSystem::setState(itsDlg->winId(), NET::Modal|NET::SkipTaskbar|NET::SkipPager);

        KWindowInfo wi(KWindowSystem::windowInfo(itsXid, NET::WMGeometry, NET::WM2UserTime));
        QRect       geom(wi.geometry());
        int         rx=geom.x(),
                    ry=geom.y();

        rx=(rx+(geom.width()/2))-(itsDlg->width()/2);
        if(rx<0)
            rx=0;
        ry=(ry+(geom.height()/2))-(itsDlg->height()/2);
        if(ry<0)
            ry=0;
        itsDlg->move(rx, ry);

        QPixmap icon=KWindowSystem::icon(itsXid, 16, 16, true, KWindowSystem::NETWM | KWindowSystem::WMHints);
        if(!icon.isNull())
            itsDlg->setWindowIcon(QIcon(icon));
#else
        XWindowAttributes attr;
        int               rx, ry;
        Window            junkwin;

        XSetTransientForHint(QX11Info::display(), itsDlg->winId(), itsXid);
        if(XGetWindowAttributes(QX11Info::display(), itsXid, &attr))
        {
            XTranslateCoordinates(QX11Info::display(), itsXid, attr.root,
                                    -attr.border_width, -16,
                                    &rx, &ry, &junkwin);

            rx=(rx+(attr.width/2))-(itsDlg->width()/2);
            if(rx<0)
                rx=0;
            ry=(ry+(attr.height/2))-(itsDlg->height()/2);
            if(ry<0)
                ry=0;
            itsDlg->move(rx, ry);
        }

//         unsigned long num;
//         unsigned long *data = NULL; 
//         Atom prop = XInternAtom(QX11Info::display(), "_NET_WM_ICON", False);
//         Atom typeRet;
//         int formatRet;
//         unsigned long afterRet;
//         if(XGetWindowProperty(QX11Info::display(), itsXid, prop, 0, 0x7FFFFFFF, False, XA_CARDINAL,
//                               &typeRet, &formatRet, &num, &afterRet, (unsigned char **)&data))
//         {
//             kDebug() << "GOT ICON!!!";
//         }
//         else
//             kDebug() << "FAILED TO GET ICON!!!";
#endif
        itsDlg->removeEventFilter(this);
    }

    return false;
}

KDialogDFileDialog::KDialogDFileDialog(QString &an, Operation op, const QString &startDir,
                                       const QString &filter, bool confirmOw)
                  : KFileDialog(KUrl(startDir.isEmpty() || "~"==startDir ? QDir::homePath() : startDir),
                                filter, NULL),
                    itsConfirmOw(confirmOw),
                    itsDone(false),
                    itsAppName(an)
{
    setModal(false);
    kDebug() << "startDir:" << startDir;

    switch(op)
    {
        case OP_FILE_OPEN:
            setOperationMode(KFileDialog::Opening);
            setMode(KFile::File|KFile::ExistingOnly);
            break;
        case OP_FILE_OPEN_MULTIPLE:
            setOperationMode(KFileDialog::Opening);
            setMode(KFile::Files|KFile::ExistingOnly);
            break;
        case OP_FILE_SAVE:
            setOperationMode(KFileDialog::Saving);
            setMode(KFile::File);
            break;
        default:
            break;
    }

    if(KDialogD::config())
    {
        KConfigGroup cfg(KDialogD::config(), groupName(itsAppName));

        //TODO !!! readConfig(KDialogD::config(), grp);
        resize(cfg.readEntry(CFG_KEY_DIALOG_SIZE, QSize(600, 400)));
    }

    //TODO !!! ops->clearHistory();
}

void KDialogDFileDialog::slotOk()
{
    // For some reason - when pressing 'ok' this slot can get called twice. Probably via the 'okClicked' signal, and
    // the virtual override of KFileDialog::slotOk
    if(itsDone)
        return;
    else
        itsDone=true;

    // Hmmm... Double clicking an icon causes this slot to be called.
    //         Pressging OK, then KFileDialog is accepted...
    if(QDialog::Accepted!=result())
        KFileDialog::slotOk();

    kDebug() << "KDialogDFileDialog::slotOk" << selectedUrls().count() << ' ' << mode() << ' ' << selectedUrl().prettyUrl();
    KUrl::List  urls;
    QStringList items;
    bool        good=true;

    if(mode()&KFile::Files)
        urls=selectedUrls();
    else // TODO if(!locationEdit->currentText().isEmpty())
        urls.append(selectedUrl());

    if(urls.count())
    {
        urls2Local(urls, items, this);

        if(urls.count()!=items.count())
        {
            KMessageBox::sorry(this, i18n("You can only select local files."),
                               i18n("Remote Files Not Accepted"));
            good=false;
        }
        else if(itsConfirmOw && KFileDialog::Saving==operationMode())
            good=!KIO::NetAccess::exists(urls.first(), KIO::NetAccess::DestinationSide, this) ||
                 KMessageBox::Continue==KMessageBox::warningContinueCancel(this,
                                            i18n("File %1 exits.\nDo you want to replace it?")
                                                 .arg(urls.first().prettyUrl()),
                                            i18n("File Exists"),
                                            KGuiItem(i18n("Replace"), "filesaveas"), KStandardGuiItem::cancel(), QString(),
                                            KMessageBox::Notify|KMessageBox::PlainCaption);

        if(good)
        {
            QString filter(currentFilter());

            if(!filter.isEmpty())
                items.append(filter);

            emit ok(items);
            hide();
            //KFileDialog::accept();
       }
       else
            setResult(QDialog::Rejected);
    }
}

KDialogDFileDialog::~KDialogDFileDialog()
{
    kDebug() << "~KDialogDFileDialog";

    if(KDialogD::config())
    {
        KConfigGroup cfg(KDialogD::config(), groupName(itsAppName));

        //TODO !!! writeConfig(KDialogD::config(), grp);
        cfg.writeEntry(CFG_KEY_DIALOG_SIZE, size());
    }
}

KDialogDDirSelectDialog::KDialogDDirSelectDialog(QString &an, const QString &startDir, bool localOnly,
                                                 QWidget *parent, bool modal)
                       : KDirSelectDialog(KUrl(startDir.isEmpty() || "~"==startDir
                                                   ? QDir::homePath() : startDir),
                                          localOnly, parent),
                         itsAppName(an)
{
    kDebug() << "startDir:" << startDir;

    setModal(false);
    if(KDialogD::config())
    {
        KConfigGroup cfg(KDialogD::config(), groupName(itsAppName, false));

        //TODO !!! readConfig(KDialogD::config(), grp);
        resize(cfg.readEntry(CFG_KEY_DIALOG_SIZE, QSize(600, 400)));
    }
}

KDialogDDirSelectDialog::~KDialogDDirSelectDialog()
{
    kDebug() << "~KDialogDDirSelectDialog";

    if(KDialogD::config())
    {
        KConfigGroup cfg(KDialogD::config(), groupName(itsAppName, false));

        //TODO !!! writeConfig(KDialogD::config(), grp);
        cfg.writeEntry(CFG_KEY_DIALOG_SIZE, size());
    }
}

void KDialogDDirSelectDialog::slotOk()
{
    kDebug() << "KDialogDDirSelectDialog::slotOk";

    KUrl::List  urls;
    QStringList items;

    urls.append(url());
    urls2Local(urls, items, this);

    if(urls.count()!=items.count())
            KMessageBox::sorry(this, i18n("You can only select local folders."),
                               i18n("Remote Folders Not Accepted"));
    else
    {
        emit ok(items);
        hide();
    }
}

#ifdef KDIALOGD_APP
static KAboutData aboutData("kdialogd4", "kdialogd4", ki18n("KDialog Daemon"), VERSION,
                            ki18n("Use KDE dialogs from non-KDE apps."),
                            KAboutData::License_GPL,
                            ki18n("(c) Craig Drummond, 2006-2007"));

int main(int argc, char **argv)
{
    KCmdLineArgs::init(argc, argv, &aboutData);

    KUniqueApplication *app=new KUniqueApplication;
    KDialogD           kdialogd;

    QApplication::setQuitOnLastWindowClosed(false);

    int rv=app->exec();

    delete app;

    unlink(getSockName());
    releaseLock();
    return rv;
}
#else
extern "C"
{
    KDE_EXPORT KDEDModule *create_kdialogd()
    {
        return new KDialogDKDED();
    }
};

KDialogDKDED::KDialogDKDED()
            : KDEDModule()
{
    new KDialogD(this);
}
#endif

#include "kdialogd.moc"



syntax highlighted by Code2HTML, v. 0.9.1