You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
10 KiB
353 lines
10 KiB
10 years ago
|
/* 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 <QDebug>
|
||
|
//#include <QtCore>
|
||
|
|
||
|
#include "xcrthemexp.h"
|
||
|
|
||
|
#include <unistd.h>
|
||
|
#include <zlib.h>
|
||
|
|
||
|
#include <QSet>
|
||
|
#include <QStringList>
|
||
|
#include <QTextStream>
|
||
|
|
||
|
#include "xcrimg.h"
|
||
|
#include "xcrxcur.h"
|
||
|
#include "xcrtheme.h"
|
||
|
#include "xcrthemefx.h"
|
||
|
|
||
|
|
||
|
static const char *curShapeName[] = {
|
||
|
"Arrow",
|
||
|
"Cross",
|
||
|
"Hand",
|
||
|
"IBeam",
|
||
|
"UpArrow",
|
||
|
"SizeNWSE",
|
||
|
"SizeNESW",
|
||
|
"SizeWE",
|
||
|
"SizeNS",
|
||
|
"Help",
|
||
|
"Handwriting",
|
||
|
"AppStarting",
|
||
|
"SizeAll",
|
||
|
"Wait",
|
||
|
"NO",
|
||
|
0
|
||
|
};
|
||
|
|
||
|
|
||
|
static const char *findCurShapeName (const QString &s) {
|
||
|
QByteArray ba(s.toUtf8());
|
||
|
const char *name = ba.constData();
|
||
|
const char **nlst = curShapeName;
|
||
|
while (*nlst) {
|
||
|
if (!strcasecmp(name, *nlst)) return *nlst;
|
||
|
nlst++;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static QString findFile (const QDir &dir, const QString &name, bool fullName=false) {
|
||
|
QFileInfoList lst = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden);
|
||
|
foreach (const QFileInfo &fi, lst) {
|
||
|
if (!name.compare(fi.fileName(), Qt::CaseInsensitive)) {
|
||
|
if (fullName) return fi.absoluteFilePath();
|
||
|
return fi.fileName();
|
||
|
}
|
||
|
}
|
||
|
return QString();
|
||
|
}
|
||
|
|
||
|
|
||
|
class CursorInfo {
|
||
|
public:
|
||
|
CursorInfo () { clear(); curSection.clear(); }
|
||
|
|
||
|
void clear () {
|
||
|
frameCnt = 1; delay = 50; xhot = yhot = 0;
|
||
|
script.clear();
|
||
|
wasFrameCnt = wasXHot = wasYHot = wasDelay = wasScript = wasAStyle = wasStdCur = false;
|
||
|
isStdCursor = false;
|
||
|
isLooped = true; is2way = false;
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
quint32 frameCnt, delay, xhot, yhot;
|
||
|
QString script;
|
||
|
bool wasFrameCnt, wasXHot, wasYHot, wasDelay, wasScript, wasAStyle, wasStdCur;
|
||
|
bool isStdCursor;
|
||
|
bool isLooped, is2way;
|
||
|
QString curSection;
|
||
|
QString nextSection;
|
||
|
};
|
||
|
|
||
|
|
||
|
/* true: EOF */
|
||
|
static bool readNextSection (QTextStream &stream, CursorInfo &info) {
|
||
|
info.clear();
|
||
|
if (info.nextSection.isEmpty()) {
|
||
|
// find next section
|
||
|
//qDebug() << "searching section...";
|
||
|
for (;;) {
|
||
|
QString s;
|
||
|
info.curSection.clear();
|
||
|
info.nextSection.clear();
|
||
|
for (;;) {
|
||
|
s = stream.readLine();
|
||
|
if (s.isNull()) return true;
|
||
|
s = s.trimmed();
|
||
|
//qDebug() << "*" << s;
|
||
|
if (s.isEmpty() || s[0] == '#' || s[0] == ';') continue;
|
||
|
if (s[0] == '[') break;
|
||
|
}
|
||
|
int len = s.length()-1;
|
||
|
if (s[len] == ']') len--;
|
||
|
s = s.mid(1, len);
|
||
|
const char *csn = findCurShapeName(s);
|
||
|
if (!csn) continue;
|
||
|
// section found
|
||
|
info.curSection = csn;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
info.curSection = info.nextSection;
|
||
|
info.nextSection.clear();
|
||
|
}
|
||
|
// section found; read it
|
||
|
for (;;) {
|
||
|
QString s = stream.readLine();
|
||
|
if (s.isNull()) return true;
|
||
|
s = s.trimmed();
|
||
|
//qDebug() << "+" << s;
|
||
|
if (s.isEmpty() || s[0] == '#' || s[0] == ';') continue;
|
||
|
if (s[0] == '[') {
|
||
|
int len = s.length()-1;
|
||
|
if (s[len] == ']') len--;
|
||
|
s = s.mid(1, len);
|
||
|
const char *csn = findCurShapeName(s);
|
||
|
if (csn) info.nextSection = csn; else info.nextSection.clear();
|
||
|
break;
|
||
|
}
|
||
|
QStringList nv(s.split('='));
|
||
|
if (nv.size() != 2) continue; // invalid
|
||
|
QString name = nv[0].simplified().toLower();
|
||
|
quint32 num = 0;
|
||
|
bool numOk = XCursorThemeFX::str2num(nv[1].trimmed(), num);
|
||
|
if (!numOk) num = 0;
|
||
|
if (name == "frames") {
|
||
|
info.frameCnt = qMax(num, (quint32)1);
|
||
|
info.wasFrameCnt = true;
|
||
|
} else if (name == "interval") {
|
||
|
info.delay = qMax(qMin(num, (quint32)0x7fffffffL), (quint32)10);
|
||
|
info.wasDelay = true;
|
||
|
} else if (name == "animation style") {
|
||
|
info.isLooped = true;
|
||
|
info.is2way = (num != 0);
|
||
|
info.wasAStyle = true;
|
||
|
} else if (name == "hot spot x") {
|
||
|
info.xhot = num;
|
||
|
info.wasXHot = true;
|
||
|
} else if (name == "hot spot y") {
|
||
|
info.yhot = num;
|
||
|
info.wasYHot = true;
|
||
|
} else if (name == "framescript") {
|
||
|
// 1 or 0
|
||
|
} else if (name == "stdcursor") {
|
||
|
info.isStdCursor = (num!=0);
|
||
|
info.wasStdCur = true;
|
||
|
} else if (name == "hot spot x2" || name == "hot spot y2") {
|
||
|
} else if (name == "stdcursor" || name == "hot spot x2" || name == "hot spot y2") {
|
||
|
// nothing
|
||
|
} else {
|
||
|
qDebug() << "unknown param:" << name << nv[1];
|
||
|
qWarning() << "unknown param:" << name << nv[1];
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
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());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* returns temporary dir or empty string
|
||
|
*/
|
||
|
static QString unzipFile (const QString &zipFile) {
|
||
|
QStringList args;
|
||
|
|
||
|
char tmpDirName[18];
|
||
|
strcpy(tmpDirName, "/tmp/unzXXXXXX");
|
||
|
char *td = mkdtemp(tmpDirName);
|
||
|
if (!td) return QString();
|
||
|
|
||
|
QDir dir(td);
|
||
|
|
||
|
args << "-b"; // all files are binary
|
||
|
args << "-D"; // skip timestamps
|
||
|
args << "-n"; // never overwrite (just in case)
|
||
|
args << "-qq"; // be very quiet
|
||
|
args << zipFile;
|
||
|
args << "-d" << dir.absolutePath(); // dest dir
|
||
|
|
||
|
QProcess pr;
|
||
|
pr.setStandardInputFile("/dev/null");
|
||
|
pr.setStandardOutputFile("/dev/null");
|
||
|
pr.setStandardErrorFile("/dev/null");
|
||
|
|
||
|
pr.start("unzip", args);
|
||
|
|
||
|
if (pr.waitForStarted()) {
|
||
|
if (pr.waitForFinished()) return QLatin1String(td);
|
||
|
}
|
||
|
|
||
|
// cleanup
|
||
|
removeFilesAndDirs(dir);
|
||
|
dir.cd("..");
|
||
|
QString s = QLatin1String(strchr(td+1, '/')+1);
|
||
|
//qDebug() << s;
|
||
|
dir.rmdir(s);
|
||
|
|
||
|
return QString();
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
XCursorThemeXP::XCursorThemeXP (const QString &aFileName) : XCursorTheme() {
|
||
|
QFileInfo fi(aFileName);
|
||
|
if (fi.exists() && fi.isReadable()) {
|
||
|
QString dst = unzipFile(aFileName);
|
||
|
if (!dst.isEmpty()) {
|
||
|
QDir d(dst);
|
||
|
if (!parseCursorXPTheme(d)) {
|
||
|
qDeleteAll(mList);
|
||
|
mList.clear();
|
||
|
}
|
||
|
qDebug() << "doing cleanup...";
|
||
|
dst.remove(0, dst.indexOf('/', 1)+1);
|
||
|
removeFilesAndDirs(d);
|
||
|
d.cd("..");
|
||
|
qDebug() << dst;
|
||
|
d.rmdir(dst);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool XCursorThemeXP::parseCursorXPTheme (const QDir &thDir) {
|
||
|
qDebug() << "loading" << thDir.path();
|
||
|
QString ifn = findFile(thDir, "scheme.ini", true);
|
||
|
if (ifn.isEmpty()) return false;
|
||
|
qDebug() << "reading scheme:" << ifn;
|
||
|
//
|
||
|
QFile fl(ifn);
|
||
|
if (!fl.open(QIODevice::ReadOnly)) return false; // no scheme --> no fun!
|
||
|
QTextStream stream;
|
||
|
stream.setDevice(&fl);
|
||
|
stream.setCodec("UTF-8");
|
||
|
CursorInfo info;
|
||
|
QSet<QString> sectionsSeen;
|
||
|
bool eof = false;
|
||
|
do {
|
||
|
eof = readNextSection(stream, info);
|
||
|
if (info.curSection.isEmpty()) break;
|
||
|
/*
|
||
|
qDebug() << "section:" << info.curSection <<
|
||
|
"\n stdcr was:" << info.wasStdCur << "value:" << info.isStdCursor <<
|
||
|
"\n frame was:" << info.wasFrameCnt << "value:" << info.frameCnt <<
|
||
|
"\n delay was:" << info.wasDelay << "value:" << info.delay <<
|
||
|
"\n xhot was:" << info.wasXHot << "value:" << info.xhot <<
|
||
|
"\n yhot was:" << info.wasYHot << "value:" << info.yhot <<
|
||
|
"\n style was:" << info.wasAStyle << "loop:" << info.isLooped << "2way:" << info.is2way <<
|
||
|
"\n scrpt was:" << info.wasScript << "value:" << info.script <<
|
||
|
"\n next section:" << info.nextSection
|
||
|
;
|
||
|
*/
|
||
|
const char ** nlst = XCursorTheme::findCursorRecord(info.curSection, 0);
|
||
|
QString imgFile = findFile(thDir, info.curSection+".png", true);
|
||
|
if (!sectionsSeen.contains(info.curSection) && nlst && !imgFile.isEmpty()) {
|
||
|
qDebug() << "section" << info.curSection << "file:" << imgFile;
|
||
|
sectionsSeen << info.curSection;
|
||
|
//TODO: script
|
||
|
QList<XCursorThemeFX::tAnimSeq> aseq;
|
||
|
{
|
||
|
// create 'standard' animseq
|
||
|
XCursorThemeFX::tAnimSeq a;
|
||
|
a.from = 0; a.to = info.frameCnt-1; a.delay = info.delay;
|
||
|
aseq << a;
|
||
|
// and back if 'alternate' flag set
|
||
|
if (info.is2way) {
|
||
|
a.from = info.frameCnt-1; a.to = 0;
|
||
|
aseq << a;
|
||
|
}
|
||
|
}
|
||
|
// load image
|
||
|
QImage img(imgFile);
|
||
|
if (!img.isNull()) {
|
||
|
XCursorImages *cim = new XCursorImages(*nlst);
|
||
|
quint32 frameWdt = img.width()/info.frameCnt;
|
||
|
qDebug() << "frameWdt:" << frameWdt << "left:" << img.width()%(frameWdt*info.frameCnt);
|
||
|
// now build animation sequence
|
||
|
int fCnt = 0;
|
||
|
foreach (const XCursorThemeFX::tAnimSeq &a, aseq) {
|
||
|
bool back = a.from > a.to; // going backwards
|
||
|
quint32 fNo = a.from;
|
||
|
for (;; fCnt++) {
|
||
|
//k8:qDebug() << " frame:" << fNo << "delay:" << a.delay;
|
||
|
// copy frame
|
||
|
QImage frame(img.copy(fNo*frameWdt, 0, frameWdt, img.height()));
|
||
|
//frame.save(QString("_png/%1_%2.png").arg(cim->name()).arg(QString::number(f)));
|
||
|
XCursorImage *i = new XCursorImage(QString("%1%2").arg(cim->name()).arg(QString::number(fCnt)),
|
||
|
frame, info.xhot, info.yhot, a.delay, 1
|
||
|
);
|
||
|
cim->append(i);
|
||
|
//
|
||
|
if (fNo == a.to) break;
|
||
|
if (back) fNo--; else fNo++;
|
||
|
}
|
||
|
}
|
||
|
// append if not empty
|
||
|
if (cim->count()) {
|
||
|
// now check if it is looped and cancel looping if necessary
|
||
|
if (!info.isLooped) {
|
||
|
// not looped
|
||
|
qDebug() << " anti-loop fix";
|
||
|
XCursorImage *i = cim->item(cim->count()-1);
|
||
|
i->setDelay(0x7fffffffL);
|
||
|
//i->setCSize(2); // ???
|
||
|
}
|
||
|
mList << cim;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} while (!eof);
|
||
|
return true;
|
||
|
}
|