/* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar) * (c)DWTFYW * * This program is free software. It comes without any warranty, to * the extent permitted by applicable law. You can redistribute it * and/or modify it under the terms of the Do What The Fuck You Want * To Public License, Version 2, as published by Sam Hocevar. See * http://sam.zoy.org/wtfpl/COPYING for more details. */ #include //#include #include "xcrtheme.h" #include #include #include #include "xcrimg.h" #include "xcrxcur.h" /* 0 standard arrow 1 help arrow (the one with '?') 2 working arrow 3 busy cursor 4 precision select 5 text select 6 handwriting 7 unavailable 8 north (vert) resize 9 south resize 10 west (vert-means horiz???) resize 11 east resize 12 north-west resize 13 south-east resize 14 north-east resize 15 south-west resize 16 move 17 alternate select 18 hand 19 button */ static const char *nameTransTbl[] = { // curFX idx, curXP name, [...altnames], finalname, 0 // end with 0 "", // standard arrow (it's \x00) "Arrow", "left_ptr", "X_cursor", "right_ptr", "top_left_arrow", "move", "4498f0e0c1937ffe01fd06f973665830", 0, // "\x04", // precision select "Cross", "tcross", "cross", "crosshair", "cross_reverse", "draped_box", 0, // "\x12", // hand "Hand", "hand", "hand1", "hand2", "9d800788f1b08800ae810202380a0822", "e29285e634086352946a0e7090d73106", 0, // "\x05", // text select "IBeam", "xterm", 0, // "\x11", // alternate select "UpArrow", "center_ptr", 0, // "\x0c", "SizeNWSE", // north-west resize "bottom_right_corner", "top_left_corner", "bd_double_arrow", "lr_angle", "c7088f0f3e6c8088236ef8e1e3e70000", 0, // "\x0e", // north-east resize "SizeNESW", "bottom_left_corner", "top_right_corner", "fd_double_arrow", "ll_angle", "fcf1c3c7cd4491d801f1e1c78f100000", 0, // "\x0a", // west resize "SizeWE", "sb_h_double_arrow", "left_side", "right_side", "h_double_arrow", "028006030e0e7ebffc7f7070c0600140", "14fef782d02440884392942c11205230", 0, // "\x08", // north resize "SizeNS", "double_arrow", "bottom_side", "top_side", "v_double_arrow", "sb_v_double_arrow", "00008160000006810000408080010102", "2870a09082c103050810ffdffffe0204", 0, // "\x01", // help arrow "Help", "question_arrow", "d9ce0ab605698f320427677b458ad60b", 0, // "\x06", // handwriting "Handwriting", "pencil", 0, // "\x02", // working arrow "AppStarting", "left_ptr_watch", "08e8e1c95fe2fc01f976f1e063a24ccd", "3ecb610c1bf2410f44200f48c40d3599", 0, // "\x10", // move (???) "SizeAll", "fleur", 0, // "\x03", // busy cursor "Wait", "watch", 0, // "\x07", // unavailable "NO", "crossed_circle", "03b6e0fcb3499374a867c041f52298f0", 0, 0 }; /* static QString convertFXIndexToXPName (qint32 idx) { int f = 0; do { if (nameTransTbl[f][0] == idx) return QString(nameTransTbl[f+1]); f += 2; while (nameTransTbl[f]) ++f; ++f; } while (nameTransTbl[f]); return QString(); } */ static void removeFilesAndDirs (QDir &dir) { //qDebug() << "dir:" << dir.path(); // files QFileInfoList lst = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden); foreach (const QFileInfo &fi, lst) { //qDebug() << "removing" << fi.fileName() << fi.absoluteFilePath(); dir.remove(fi.fileName()); } // dirs lst = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden); foreach (const QFileInfo &fi, lst) { dir.cd(fi.fileName()); removeFilesAndDirs(dir); dir.cd(".."); //qDebug() << "removing dir" << fi.fileName(); dir.rmdir(fi.fileName()); } } static void removeFiles (QDir &dir) { // QFileInfoList lst = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden); foreach (const QFileInfo &fi, lst) { qDebug() << "removing" << fi.fileName() << fi.absoluteFilePath(); QFile fl(fi.absoluteFilePath()); fl.remove(); } // } static void removeCursorFiles (QDir &dir) { QString pt = dir.path(); if (!pt.isEmpty() && pt != "/") pt += "/"; const char **nlst = nameTransTbl; while (*nlst) { nlst += 2; // skip non-files while (*nlst) { QString n(*nlst); QFile fl(pt+n); qDebug() << "removing" << fl.fileName(); fl.remove(); nlst++; } nlst++; } } /////////////////////////////////////////////////////////////////////////////// const char **XCursorTheme::findCursorByFXId (int id) { if (id < 0 || id > 19) return 0; // invalid id const char **nlst = nameTransTbl; while (*nlst) { int lid = (**nlst)&0xff; nlst += 2; // skip unnecessary if (lid == id) return nlst; while (nlst[-1]) nlst++ ; // skip } return 0; // none found } /* * type: * 0: by CursorXP name * 1: by Xcursor name * return: * pointer to Xcursor name list */ const char **XCursorTheme::findCursorRecord (const QString &cname, int type) { QByteArray ba(cname.toUtf8()); const char *name = ba.constData(); const char **nlst = nameTransTbl; while (*nlst) { nlst += 2; // skip unnecessary if (!type) { // by CursorXP name if (!strcmp(name, nlst[-1])) return nlst; } else { // by Xcursor name // find name const char **nx = nlst; while (*nx && strcmp(*nx, name)) nx++; if (*nx) return nlst; // we got it! } while (nlst[-1]) nlst++ ; // skip } return 0; // none found } QString XCursorTheme::findCursorFile (const QDir &dir, const char *name) { QString d(dir.path()); if (d != "/") d += "/"; d += "cursors/"; const char **nlst = nameTransTbl; while (*nlst) { nlst += 2; // skip unnecessary // find name const char **nx = nlst; while (*nx && strcmp(*nx, name)) nx++; if (*nx) { // we got it! //qDebug() << "found" << *nx << "(" << *nlst << ")"; nx = nlst; while (*nx) { QString s(*nx); //qDebug() << "checking" << s; QFileInfo fl(d+s); if (fl.exists() && fl.isReadable()) { //qDebug() << " ok" << s; return s; } nx++; } } while (nlst[-1]) nlst++ ; // skip } return QString(); // none found } /////////////////////////////////////////////////////////////////////////////// XCursorTheme::XCursorTheme () : mName(""), mPath(""), mTitle(""), mAuthor(""), mLicense(""), mEMail(""), mSite(""), mDescr(""), mIM(""), mSample("left_ptr") { } XCursorTheme::XCursorTheme (const QDir &aDir, const QString &aName) : mName(aName), mPath(aDir.path()), mTitle(""), mAuthor(""), mLicense(""), mEMail(""), mSite(""), mDescr(""), mIM(""), mSample("left_ptr") { parseXCursorTheme(aDir); } XCursorTheme::~XCursorTheme () { qDeleteAll(mList); mList.clear(); } bool XCursorTheme::writeToDir (const QDir &destDir) { bool res = true; QDir dir (destDir); dir.mkdir("cursors"); if (!dir.exists("cursors")) return false; dir.cd("cursors"); removeCursorFiles(dir); // foreach (const XCursorImages *ci, mList) { const char **nlst = findCursorRecord(ci->name()); if (!nlst) continue; // unknown cursor, skip it qDebug() << "writing" << *nlst; { QByteArray ba(ci->genXCursor()); QFile fo(dir.path()+"/"+ci->name()); if (fo.open(QIODevice::WriteOnly)) { fo.write(ba); fo.close(); } else { res = false; break; } } // make symlinks const char *orig = *nlst; nlst++; while (*nlst) { qDebug() << "symlinking to" << orig << "as" << *nlst; QByteArray newName(QFile::encodeName(dir.path()+"/"+(*nlst))); qDebug() << "old" << orig << "new" << newName.constData(); if (symlink(orig, newName.constData())) { res = false; break; } nlst++; } if (!res) break; nlst++; } if (res) res = writeIndexTheme(destDir); if (!res) removeCursorFiles(dir); return res; } void XCursorTheme::parseThemeIndex (const QDir &dir) { QString ifn = dir.path(); if (!ifn.isEmpty() && ifn != "/") ifn += "/"; ifn += "index.theme"; qDebug() << "reading theme index:" << ifn; QFile fl(ifn); // QString cmt; mInherits.clear(); // read config file, drop out 'icon theme' section (IT'S BAD!) if (fl.open(QIODevice::ReadOnly)) { QTextStream stream; stream.setDevice(&fl); stream.setCodec("UTF-8"); bool inIconTheme = false; QString curPath; while (1) { QString s = stream.readLine(); if (s.isNull()) break; QString orig(s); s = s.trimmed(); if (s.isEmpty() || s[0] == '#' || s[0] == ';') continue; if (s[0] == '[') { // new path int len = s.length()-1; if (s[len] == ']') len--; s = s.mid(1, len).simplified(); curPath = s.toLower(); inIconTheme = (curPath == "icon theme"); continue; } if (!inIconTheme) continue; int eqp = s.indexOf('='); if (eqp < 0) continue; // invalid entry QString name = s.left(eqp).simplified().toLower(); QString value = s.mid(eqp+1).simplified(); qDebug() << name << value; if (name == "name" && !value.isEmpty()) mTitle = value; else if (name == "comment" && !value.isEmpty()) cmt = value; else if (name == "author" && !value.isEmpty()) mAuthor = value; else if (name == "url" && !value.isEmpty()) mSite = value; else if (name == "description" && !value.isEmpty()) mDescr = value; else if (name == "example" && !value.isEmpty()) mSample = value; else if (name == "inherits" && !value.isEmpty()) mInherits << value; } fl.close(); } if (mDescr.isEmpty() && !cmt.isEmpty()) mDescr = cmt; if (mSample.isEmpty()) mSample = "left_ptr"; mInherits.removeDuplicates(); } void XCursorTheme::dumpInfo () { /* qDebug() << "INFO:" << "\n name:" << mName << "\n path:" << mPath << "\n title:" << mTitle << "\n author:" << mAuthor << "\n license:" << mLicense << "\n mail:" << mEMail << "\n site:" << mSite << "\n dscr:" << mDescr << "\n im:" << mIM << "\n sample:" << mSample << "\n inherits:" << mInherits ; */ } void XCursorTheme::parseXCursorTheme (const QDir &dir) { parseThemeIndex(dir); dumpInfo(); const char **nlst = nameTransTbl; QDir dr(dir); dr.cd("cursors"); while (*nlst) { //qDebug() << "CurFX: (" << nlst[1] << ")"; nlst += 2; // skip unnecessary //qDebug() << "searching" << *nlst; QString fn = findCursorFile(dir, *nlst); if (fn.isEmpty()) continue; // no such file //qDebug() << " Xcrusor: (" << nlst[0] << ")"; while (nlst[-1]) nlst++ ; // skip //qDebug() << " skiped: (" << nlst[1] << ")"; qDebug() << "loading" << fn; XCursorImages *ci = new XCursorImagesXCur(dr, fn); if (ci->count()) { qDebug() << " OK:" << fn << "name:" << ci->name(); if (mTitle.isEmpty() && !ci->title().isEmpty()) mTitle = ci->title(); if (mAuthor.isEmpty() && !ci->author().isEmpty()) mAuthor = ci->author(); if (mLicense.isEmpty() && !ci->license().isEmpty()) mLicense = ci->license(); if (mEMail.isEmpty() && !ci->mail().isEmpty()) mEMail = ci->mail(); if (mSite.isEmpty() && !ci->site().isEmpty()) mSite = ci->site(); if (mDescr.isEmpty() && !ci->descr().isEmpty()) mDescr = ci->descr(); if (mIM.isEmpty() && !ci->im().isEmpty()) mIM = ci->im(); mList << ci; dumpInfo(); } else { qWarning() << "can't load" << fn << nlst[-2]; delete ci; } } dumpInfo(); fixInfoFields(); dumpInfo(); } void XCursorTheme::fixInfoFields () { foreach (XCursorImages *ci, mList) { if (!mTitle.isEmpty() && ci->title().isEmpty()) ci->setTitle(title()); if (!mAuthor.isEmpty() && ci->author().isEmpty()) ci->setAuthor(author()); if (!mLicense.isEmpty() && ci->license().isEmpty()) ci->setLicense(license()); if (!mEMail.isEmpty() && ci->mail().isEmpty()) ci->setMail(mail()); if (!mSite.isEmpty() && ci->site().isEmpty()) ci->setSite(site()); if (!mDescr.isEmpty() && ci->descr().isEmpty()) ci->setDescr(descr()); if (!mIM.isEmpty() && ci->im().isEmpty()) ci->setIM(im()); } } bool XCursorTheme::writeIndexTheme (const QDir &destDir) { QString ifn = destDir.path(); if (!ifn.isEmpty() && ifn != "/") ifn += "/"; ifn += "index.theme"; qDebug() << "writing theme index:" << ifn; QFile fl(ifn); // QStringList cfg, inhs, iconOther; QString name, cmt, author, url, dscr, sample; inhs.append(mInherits); // read config file, drop out 'icon theme' section (IT'S BAD!) if (fl.open(QIODevice::ReadOnly)) { QTextStream stream; stream.setDevice(&fl); stream.setCodec("UTF-8"); QString curPath; while (1) { QString s = stream.readLine(); if (s.isNull()) break; QString orig(s); s = s.trimmed(); if (s.isEmpty() || s[0] == '#' || s[0] == ';') { if (curPath != "icon theme") cfg << orig; continue; } if (s[0] == '[') { // new path int len = s.length()-1; if (s[len] == ']') len--; s = s.mid(1, len).simplified(); curPath = s.toLower(); if (curPath != "icon theme") cfg << orig; continue; } if (curPath != "icon theme") cfg << orig; else { int eqp = s.indexOf('='); if (eqp < 0) { // invalid entry iconOther << orig; continue; } QString name = s.left(eqp).simplified(); QString value = s.mid(eqp+1).simplified(); if (name.isEmpty()) { // invalid entry iconOther << orig; continue; } QString nn(name.toLower()); if (nn == "name") name = value; else if (nn == "comment") cmt = value; else if (nn == "author") author = value; else if (nn == "url") url = value; else if (nn == "description") dscr = value; else if (nn == "example") sample = value; else if (nn == "inherits") { if (!value.isEmpty()) inhs << value; } else iconOther << orig; /* Name=Fire Dragon Comment=Fire Dragon Cursor Theme -- part of the 'Four Dragons' suite; (c) Sleeping Dragon, http://sleeping-dragon.deviantart.com/art/Fire-Dragon-30419542 Author=Sleeping Dragon Url=http://sleeping-dragon.deviantart.com/art/Fire-Dragon-30419542 Description=Fire Dragon Cursor Theme -- part of the 'Four Dragons' suite Example=left_ptr Inherits=core */ } } fl.close(); } // theme file parsed; rewrite it! if (cfg.size() > 0 && !cfg.at(cfg.size()-1).isEmpty()) cfg << ""; if (!fl.open(QIODevice::WriteOnly)) return false; if (name.isEmpty()) name = mTitle; if (author.isEmpty()) author = mAuthor; if (url.isEmpty()) url = mSite; if (dscr.isEmpty()) dscr = mDescr; if (cmt.isEmpty()) cmt = dscr; /*if (sample.isEmpty())*/ sample = "left_ptr"; if (inhs.size() == 0) inhs << "core"; inhs.removeDuplicates(); dumpInfo(); /* qDebug() << "***INFO:" << "\n name:" << name << "\n cmt:" << cmt << "\n author:" << author << "\n site:" << url << "\n dscr:" << dscr << "\n sample:" << mSample << "\n inherits:" << inhs ; */ { QTextStream stream; stream.setDevice(&fl); stream.setCodec("UTF-8"); foreach (const QString &s, cfg) stream << s << "\n"; stream << "[Icon Theme]\n"; stream << "Name=" << name << "\n"; stream << "Comment=" << cmt << "\n"; stream << "Author=" << author << "\n"; stream << "Url=" << url << "\n"; stream << "Description=" << dscr << "\n"; stream << "Example=" << mSample << "\n"; foreach (const QString &s, inhs) stream << "Inherits=" << s << "\n"; } fl.close(); return true; } /////////////////////////////////////////////////////////////////////////////// static bool removeXCTheme (const QDir &thDir) { if (thDir.exists("cursors")) { QDir d(thDir); d.cd("cursors"); //removeCursorFiles(d); removeFiles(d); } thDir.rmdir("cursors"); // check if there are some other files QFileInfoList lst = thDir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden); bool cantKill = false; foreach (const QFileInfo &fi, lst) { QString s(fi.fileName()); if (s != "icon-theme.cache" && s != "index.theme") { cantKill = true; break; } } // can kill this? if (!cantKill) { QDir d(thDir); d.remove("icon-theme.cache"); d.remove("index.theme"); } return true; } bool removeXCursorTheme (const QDir &thDir, const QString &name) { qDebug() << "to kill:" << thDir.path() << name; QDir d(thDir); if (!d.exists(name)) return false; qDebug() << "removing" << d.path() << name; d.cd(name); removeXCTheme(d); d.cd(".."); d.rmdir(name); return true; } bool removeXCursorTheme (const QString &name) { QDir d(QDir::homePath()); return removeXCursorTheme(d, name); } bool removeXCursorTheme (const QDir &thDir) { QString name(thDir.path()); while (!name.isEmpty() && name.endsWith('/')) name.chop(1); int i = name.lastIndexOf('/'); if (i < 1) return false; name = name.mid(i+1); QDir d(thDir); d.cd(".."); return removeXCursorTheme(d, name); } /////////////////////////////////////////////////////////////////////////////// /* * returns temporary dir or empty string */ static bool tarDir (const QString &destFName, const QDir &pth, const QString &dir) { QStringList args; QFile fl(destFName); fl.remove(); args << "-c"; // create archive... args << "-z"; // and gzip it QString ps(pth.path()); if (!ps.isEmpty() && ps != ".") { args << "-C"; // dir to go args << ps; } args << "-f"; // to file args << destFName; QString s(dir); if (!s.endsWith('/')) s += '/'; args << s; QProcess pr; pr.setStandardInputFile("/dev/null"); pr.setStandardOutputFile("/dev/null"); pr.setStandardErrorFile("/dev/null"); pr.start("tar", args); if (pr.waitForStarted()) { if (pr.waitForFinished()) return true; } // cleanup fl.remove(); return false; } //tar -c -C /home/ketmar/k8prj/xcurtheme/src -f z.tar xcr/ bool packXCursorTheme (const QString &destFName, const QDir &thDir, const QString &thName, bool removeTheme) { if (destFName.isEmpty() || thName.isEmpty()) return false; QDir d(thDir); if (!d.cd(thName)) return false; bool res = tarDir(destFName, thDir, thName); if (res && removeTheme) { removeFilesAndDirs(d); d.cd(".."); d.rmdir(thName); } return res; } bool XCursorTheme::writeXPTheme (const QDir &destDir) { QString ifn = destDir.path(); if (!ifn.isEmpty() && ifn != "/") ifn += '/'; QFile fl(ifn+"Scheme.ini"); if (fl.open(QIODevice::WriteOnly)) { QTextStream stream; stream.setDevice(&fl); stream.setCodec("UTF-8"); stream << "[General]\r\n"; stream << "Version=130\r\n"; qDebug() << "writing images..."; foreach (XCursorImages *ci, mList) { const char **nlst = findCursorRecord(ci->name()); if (!nlst) continue; // unknown cursor, skip it qDebug() << "image:" << *(nlst-1); QImage img(ci->buildImage()); if (!img.save(ifn+(*(nlst-1))+".png")) return false; stream << "["+QString(*(nlst-1))+"]\r\n"; stream << "StdCursor=0\r\n"; stream << "Frames=" << ci->count() << "\r\n"; stream << "Hot spot x=" << ci->at(0)->xhot() << "\r\n"; stream << "Hot spot y=" << ci->at(0)->yhot() << "\r\n"; stream << "Interval=" << (ci->at(0)->delay() == 2147483647 ? 100 : ci->at(0)->delay()) << "\r\n"; // x2, y2? wtf? if (ci->count() > 1) { stream << "Frames=" << ci->count() << "\r\n"; stream << "Animation style=0\r\n"; } else { stream << "Frames=1\r\n"; stream << "Animation style=0\r\n"; } } stream << "[[Description]\r\n"; if (!mName.isEmpty()) stream << mName << "\r\n"; if (!mTitle.isEmpty()) stream << mTitle << "\r\n"; if (!mAuthor.isEmpty()) stream << mAuthor << "\r\n"; if (!mLicense.isEmpty()) stream << mLicense << "\r\n"; if (!mEMail.isEmpty()) stream << mEMail << "\r\n"; if (!mSite.isEmpty()) stream << mSite << "\r\n"; if (!mDescr.isEmpty()) stream << mDescr << "\r\n"; if (!mIM.isEmpty()) stream << mIM << "\r\n"; } fl.close(); return true; }