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.
lxqt-config-packaging/liblxqt-config-cursor/xcr/xcrtheme.cpp

743 lines
20 KiB

/* 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 "xcrtheme.h"
#include <unistd.h>
#include <QStringList>
#include <QTextStream>
#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;
}