#define USE_KWIN #include "kdialogd.h" #include #include #include #include #include #include #include #include #include #include #ifdef USE_KWIN #include #else #include #include #include #endif #include #include #include #include #include #include #ifdef KDIALOGD_APP #include #include #include #endif #include #include 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 { std::ofstream f(getPidFileName()); if(f) { f << getpid(); f.close(); } 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 " << (void *)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); #if 0 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); #endif QPixmap icon=KWindowSystem::icon(itsXid, 16, 16, true, KWindowSystem::NETWM | KWindowSystem::WMHints); if(!icon.isNull()) itsDlg->setWindowIcon(QIcon(icon)); #else XSetTransientForHint(QX11Info::display(), itsDlg->winId(), itsXid); #if 0 XWindowAttributes attr; int rx, ry; Window junkwin; 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); } #endif // 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); setSelection(startDir); 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::accept() { fileWidget()->accept(); kDebug() << "KDialogDFileDialog::slotOk" << selectedUrls().count() << ' ' << mode() << ' ' << selectedUrl().prettyUrl(); KUrl::List urls(selectedUrls()); QStringList items; bool good=true; 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"