add more spacing
[personal-kdebase.git] / workspace / kcontrol / kfontinst / lib / DisabledFonts.cpp
blob124372ad855649d6506db528a09b857a892afd05
1 /*
2 * KFontInst - KDE Font Installer
4 * Copyright 2003-2007 Craig Drummond <craig@kde.org>
6 * ----
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
24 #include "DisabledFonts.h"
25 #include "Fc.h"
26 #include "Misc.h"
27 #include "KfiConstants.h"
28 #include <QtCore/QDir>
29 #include <QtXml/QDomDocument>
30 #include <QtXml/QDomElement>
31 #include <QtXml/QDomNode>
32 #include <QtCore/QFile>
33 #include <QtCore/QTextStream>
34 #include <QtCore/QTemporaryFile>
35 #include <KDE/KLocale>
36 #include <KDE/KStandardDirs>
37 #include <fontconfig/fontconfig.h>
38 #include <stdio.h>
39 #include <unistd.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43 #include <errno.h>
44 #include <signal.h>
46 namespace KFI
49 #define FILE_NAME "disabledfonts"
50 #define DISABLED_DOC "disabledfonts"
51 #define FONT_TAG "font"
52 #define FILE_TAG "file"
53 #define PATH_ATTR "path"
54 #define FOUNDRY_ATTR "foundry"
55 #define FAMILY_ATTR "family"
56 #define WEIGHT_ATTR "weight"
57 #define WIDTH_ATTR "width"
58 #define SLANT_ATTR "slant"
59 #define FACE_ATTR "face"
60 #define LANGS_ATTR "langs"
61 #define LANG_SEP ","
63 // Simple lock file class...
64 class CLockFile
66 public:
68 CLockFile(const QString &fileName)
69 : itsName(QFile::encodeName(fileName+".lock")),
70 itsFd(-1)
74 ~CLockFile()
76 if(itsFd>=0)
78 close(itsFd);
79 ::unlink(itsName.constData());
83 bool lock()
85 int pid(0);
87 itsFd=open(itsName.constData(), O_WRONLY|O_CREAT|O_EXCL, 0644);
89 if(itsFd<0 && EEXIST==errno)
91 // Open file read only to ascertain pid of process that currently is locking the file...
92 int fd(open(itsName.constData(), O_RDONLY));
94 if(fd>=0)
96 read(fd, &pid, sizeof(int));
97 close(fd);
99 if(0==kill(pid, 0)) // Process is still alive, give it 5 seconds to release the lock...
100 sleep(5);
101 else // Process is not alive, remove file...
102 ::unlink(itsName.constData());
104 // else could not open file - perhaps it was removed in between the 2 open calls?
106 // Try to lock again...
107 itsFd=open(itsName.constData(), O_WRONLY|O_CREAT|O_EXCL, 0644);
110 if(itsFd<0)
111 return false;
113 pid=getpid();
114 write(itsFd, &pid, sizeof(int));
115 return true;
118 private:
120 QByteArray itsName;
121 int itsFd;
124 static QString changeName(const QString &f, bool enable)
126 QString file(Misc::getFile(f)),
127 dest;
129 if(enable && Misc::isHidden(file))
130 dest=Misc::getDir(f)+file.mid(1);
131 else if (!enable && !Misc::isHidden(file))
132 dest=Misc::getDir(f)+QChar('.')+file;
133 else
134 dest=f;
135 return dest;
138 static bool changeFileStatus(const QString &f, bool enable)
140 QString dest(changeName(f, enable));
142 if(dest==f) // File is already enabled/disabled
143 return true;
145 if(Misc::fExists(dest) && !Misc::fExists(f)) // File is already enabled/disabled
146 return true;
148 if(0==::rename(QFile::encodeName(f).data(), QFile::encodeName(dest).data()))
150 QStringList files;
152 Misc::getAssociatedFiles(f, files);
154 if(files.count())
156 QStringList::const_iterator fIt,
157 fEnd=files.constEnd();
159 for(fIt=files.constBegin(); fIt!=fEnd; ++fIt)
160 ::rename(QFile::encodeName(*fIt).data(),
161 QFile::encodeName(changeName(*fIt, enable)).data());
163 return true;
165 return false;
168 static bool changeStatus(const CDisabledFonts::TFileList &files, bool enable)
170 QStringList mods;
171 CDisabledFonts::TFileList::ConstIterator it(files.begin()),
172 end(files.end());
174 for(; it!=end; ++it)
175 if(changeFileStatus((*it).path, enable))
176 mods.append((*it).path);
177 else
178 break;
180 if(mods.count()!=files.count())
183 // Failed to enable/disable a file - so need to revert any
184 // previous changes...
185 QStringList::ConstIterator sit(mods.begin()),
186 send(mods.end());
187 for(; sit!=send; ++sit)
188 changeFileStatus(*sit, !enable);
189 return false;
191 return true;
194 CDisabledFonts::LangWritingSystemMap CDisabledFonts::theirLanguageForWritingSystem[]=
196 { QFontDatabase::Latin, (const FcChar8 *)"en" },
197 { QFontDatabase::Greek, (const FcChar8 *)"el" },
198 { QFontDatabase::Cyrillic, (const FcChar8 *)"ru" },
199 { QFontDatabase::Armenian, (const FcChar8 *)"hy" },
200 { QFontDatabase::Hebrew, (const FcChar8 *)"he" },
201 { QFontDatabase::Arabic, (const FcChar8 *)"ar" },
202 { QFontDatabase::Syriac, (const FcChar8 *)"syr" },
203 { QFontDatabase::Thaana, (const FcChar8 *)"div" },
204 { QFontDatabase::Devanagari, (const FcChar8 *)"hi" },
205 { QFontDatabase::Bengali, (const FcChar8 *)"bn" },
206 { QFontDatabase::Gurmukhi, (const FcChar8 *)"pa" },
207 { QFontDatabase::Gujarati, (const FcChar8 *)"gu" },
208 { QFontDatabase::Oriya, (const FcChar8 *)"or" },
209 { QFontDatabase::Tamil, (const FcChar8 *)"ta" },
210 { QFontDatabase::Telugu, (const FcChar8 *)"te" },
211 { QFontDatabase::Kannada, (const FcChar8 *)"kn" },
212 { QFontDatabase::Malayalam, (const FcChar8 *)"ml" },
213 { QFontDatabase::Sinhala, (const FcChar8 *)"si" },
214 { QFontDatabase::Thai, (const FcChar8 *)"th" },
215 { QFontDatabase::Lao, (const FcChar8 *)"lo" },
216 { QFontDatabase::Tibetan, (const FcChar8 *)"bo" },
217 { QFontDatabase::Myanmar, (const FcChar8 *)"my" },
218 { QFontDatabase::Georgian, (const FcChar8 *)"ka" },
219 { QFontDatabase::Khmer, (const FcChar8 *)"km" },
220 { QFontDatabase::SimplifiedChinese, (const FcChar8 *)"zh-cn" },
221 { QFontDatabase::TraditionalChinese, (const FcChar8 *)"zh-tw" },
222 { QFontDatabase::Japanese, (const FcChar8 *)"ja" },
223 { QFontDatabase::Korean, (const FcChar8 *)"ko" },
224 { QFontDatabase::Vietnamese, (const FcChar8 *)"vi" },
225 { QFontDatabase::Other, NULL },
227 // The following is only used to save writing system data for disabled fonts...
228 { QFontDatabase::Telugu, (const FcChar8 *)"Qt-Telugu" },
229 { QFontDatabase::Kannada, (const FcChar8 *)"Qt-Kannada" },
230 { QFontDatabase::Malayalam, (const FcChar8 *)"Qt-Malayalam" },
231 { QFontDatabase::Sinhala, (const FcChar8 *)"Qt-Sinhala" },
232 { QFontDatabase::Myanmar, (const FcChar8 *)"Qt-Myanmar" },
233 { QFontDatabase::Ogham, (const FcChar8 *)"Qt-Ogham" },
234 { QFontDatabase::Runic, (const FcChar8 *)"Qt-Runic" },
236 { QFontDatabase::Any, NULL }
239 // Cache qstring->ws value
241 static QMap<QString, qulonglong> constWritingSystemMap;
243 void CDisabledFonts::createWritingSystemMap()
245 // check if we have created the cache yet...
246 if(constWritingSystemMap.isEmpty())
247 for(int i=0; QFontDatabase::Any!=theirLanguageForWritingSystem[i].ws; ++i)
248 if(theirLanguageForWritingSystem[i].lang)
249 constWritingSystemMap[(const char *)theirLanguageForWritingSystem[i].lang]=
250 ((qulonglong)1)<<theirLanguageForWritingSystem[i].ws;
253 CDisabledFonts::CDisabledFonts(bool sys)
254 : itsTimeStamp(0),
255 itsModified(false),
256 itsMods(0)
258 QString path;
260 createWritingSystemMap();
262 if(Misc::root() || sys)
263 path=KFI_ROOT_CFG_DIR;
264 else
266 path=KGlobal::dirs()->localxdgconfdir();
268 if(!Misc::dExists(path))
269 Misc::createDir(path);
272 itsFileName=path+'/'+FILE_NAME".xml";
274 itsModifiable=Misc::fWritable(itsFileName) ||
275 (!Misc::fExists(itsFileName) && Misc::dWritable(Misc::getDir(itsFileName)));
277 load();
278 if(itsModified)
279 save();
282 void CDisabledFonts::reload()
284 save();
285 itsTimeStamp=0;
286 load();
287 save(); // This will only do a second save, if the 'load' set the modfified flag...
290 bool CDisabledFonts::refresh()
292 time_t ts=Misc::getTimeStamp(itsFileName);
294 if(!ts || ts!=itsTimeStamp)
296 save();
297 load();
298 return true;
300 return false;
304 // Do not always lock during a load, as we may be trying to read global file (but not as root),
305 // or this load might be being called within the save() - so cant lock as is already!
306 void CDisabledFonts::load(bool lock)
308 CLockFile lf(itsFileName);
310 lock=lock && itsModifiable;
312 if(!lock || lf.lock())
314 time_t ts=Misc::getTimeStamp(itsFileName);
316 if(!ts || ts!=itsTimeStamp)
318 itsTimeStamp=ts;
320 QFile f(itsFileName);
322 if(f.open(QIODevice::ReadOnly))
324 itsFonts.clear();
325 itsModified=false;
327 QDomDocument doc;
329 if(doc.setContent(&f))
330 for(QDomNode n=doc.documentElement().firstChild(); !n.isNull(); n=n.nextSibling())
332 QDomElement e=n.toElement();
334 if(FONT_TAG==e.tagName())
336 TFont font;
338 if(font.load(e, itsModified))
339 itsFonts.add(font);
340 else
341 itsModified=true;
345 f.close();
351 bool CDisabledFonts::save()
353 bool rv(true);
355 if(itsModified)
357 CLockFile lf(itsFileName);
359 if(lf.lock())
361 time_t ts=Misc::getTimeStamp(itsFileName);
363 if(Misc::fExists(itsFileName) && ts!=itsTimeStamp)
365 // Timestamps differ, so possibly file was modified by another process...
366 merge(CDisabledFonts(*this));
369 QTemporaryFile temp(itsFileName);
371 temp.setAutoRemove(false);
373 if(!temp.open())
374 return false;
376 QFile file(temp.fileName());
378 if(!file.open(QIODevice::WriteOnly))
380 temp.setAutoRemove(true);
381 return false;
384 QTextStream str(&file);
386 str << "<"DISABLED_DOC">" << endl;
388 TFontList::Iterator it(itsFonts.begin()),
389 end(itsFonts.end());
391 for(; it!=end; ++it)
392 str << (*it);
393 str << "</"DISABLED_DOC">" << endl;
394 itsModified=false;
395 file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|
396 QFile::ReadGroup|QFile::ReadOther);
397 file.close();
398 rv=::rename(QFile::encodeName(file.fileName()), QFile::encodeName(itsFileName));
399 if(rv)
401 itsTimeStamp=Misc::getTimeStamp(itsFileName);
402 itsMods=0;
407 return rv;
410 static QString expandHome(const QString &path)
412 QString mpath = path;
413 return !mpath.isEmpty() && '~'==mpath[0]
414 ? 1==mpath.length() ? QDir::homePath() : mpath.replace(0, 1, QDir::homePath())
415 : mpath;
418 bool CDisabledFonts::TFile::load(QDomElement &elem)
420 if(elem.hasAttribute(PATH_ATTR))
422 bool ok(false);
424 path=expandHome(elem.attribute(PATH_ATTR));
425 foundry=elem.attribute(FOUNDRY_ATTR);
427 if(elem.hasAttribute(FACE_ATTR))
428 face=elem.attribute(FACE_ATTR).toInt(&ok);
430 if(!ok || face<0)
431 face=0;
432 return Misc::fExists(path);
435 return false;
438 QString CDisabledFonts::TFileList::toString(bool skipFirst) const
440 QString s;
441 QTextStream str(&s, QIODevice::WriteOnly);
442 ConstIterator it(begin()),
443 e(end());
445 if(skipFirst && it!=e)
446 ++it;
448 for(; it!=e; ++it)
449 str << (*it).path << endl
450 << (*it).foundry << endl
451 << (*it).face << endl;
453 return s;
456 void CDisabledFonts::TFileList::fromString(QString &s)
458 clear();
459 QTextStream str(&s, QIODevice::ReadOnly);
461 while(!str.atEnd())
463 QString path(str.readLine()),
464 foundry(str.readLine()),
465 face(str.readLine());
467 if(face.isEmpty())
468 break;
469 else
470 append(TFile(path, face.toInt(), foundry));
474 bool CDisabledFonts::TFont::load(QDomElement &elem, bool &modified)
476 if(elem.hasAttribute(FAMILY_ATTR))
478 bool ok(false);
479 int weight(KFI_NULL_SETTING), width(KFI_NULL_SETTING), slant(KFI_NULL_SETTING),
480 tmp(KFI_NULL_SETTING);
482 family=elem.attribute(FAMILY_ATTR);
484 if(elem.hasAttribute(WEIGHT_ATTR))
486 tmp=elem.attribute(WEIGHT_ATTR).toInt(&ok);
487 if(ok)
488 weight=tmp;
490 if(elem.hasAttribute(WIDTH_ATTR))
492 tmp=elem.attribute(WIDTH_ATTR).toInt(&ok);
493 if(ok)
494 width=tmp;
497 if(elem.hasAttribute(SLANT_ATTR))
499 tmp=elem.attribute(SLANT_ATTR).toInt(&ok);
500 if(ok)
501 slant=tmp;
504 styleInfo=FC::createStyleVal(weight, width, slant);
506 if(elem.hasAttribute(LANGS_ATTR))
508 QStringList langs(elem.attribute(LANGS_ATTR).split(LANG_SEP, QString::SkipEmptyParts));
510 QStringList::ConstIterator it(langs.begin()),
511 end(langs.end());
513 for(; it!=end; ++it)
514 writingSystems|=constWritingSystemMap[*it];
517 if(elem.hasAttribute(PATH_ATTR))
519 TFile file;
521 if(file.load(elem))
522 files.add(file);
523 else
524 modified=true;
526 else
527 for(QDomNode n=elem.firstChild(); !n.isNull(); n=n.nextSibling())
529 QDomElement ent=n.toElement();
531 if(FILE_TAG==ent.tagName())
533 TFile file;
535 if(file.load(ent))
536 files.add(file);
537 else
538 modified=true;
542 return files.count()>0;
545 return false;
548 const QString & CDisabledFonts::TFont::getName() const
550 if(name.isEmpty())
551 name=FC::createName(family, styleInfo);
552 return name;
555 CDisabledFonts::TFontList::Iterator CDisabledFonts::TFontList::locate(const TFont &t)
557 return find(t);
560 CDisabledFonts::TFontList::Iterator CDisabledFonts::TFontList::locate(const Misc::TFont &t)
562 return locate((TFont &)t);
565 void CDisabledFonts::TFontList::add(const TFont &t) const
567 (const_cast<TFontList *>(this))->insert(t);
570 bool CDisabledFonts::disable(const TFont &font)
572 static const int constMaxMods=100;
574 TFontList::Iterator it=itsFonts.locate(font);
576 if(it==itsFonts.end())
578 TFont newFont(font.family, font.styleInfo, font.writingSystems);
580 if(changeStatus(font.files, false))
582 TFileList::ConstIterator it(font.files.begin()),
583 end(font.files.end());
585 for(; it!=end; ++it)
586 newFont.files.add(TFile(changeName((*it).path, false), (*it).face, (*it).foundry));
588 itsFonts.add(newFont);
589 itsModified=true;
591 if(++itsMods>constMaxMods)
592 save();
593 return true;
595 else
597 TFileList::ConstIterator it(font.files.begin()),
598 end(font.files.end());
600 for(; it!=end; ++it)
602 QString modName(changeName((*it).path, false));
603 if(Misc::fExists(modName))
604 newFont.files.add(TFile(modName, (*it).face, (*it).foundry));
605 else
606 break;
609 if(newFont.files.count()==font.files.count())
611 itsFonts.add(newFont);
612 itsModified=true;
613 if(++itsMods>constMaxMods)
614 save();
615 return true;
619 else
620 return true; // Already disabled...
622 return false;
625 bool CDisabledFonts::enable(TFontList::Iterator font)
627 if(font!=itsFonts.end())
629 if(changeStatus((*font).files, true))
631 itsFonts.erase(font);
632 itsModified=true;
633 return true;
635 else
637 QStringList mod;
638 TFileList::ConstIterator fit((*font).files.begin()),
639 fend((*font).files.end());
641 for(; fit!=fend; ++fit)
643 QString modName(changeName((*fit).path, true));
644 if(Misc::fExists(modName))
645 mod.append((*fit).path);
646 else
647 break;
650 if(mod.count()==(*font).files.count())
652 itsFonts.erase(font);
653 itsModified=true;
654 return true;
658 else
659 return true; // Already enabled...
661 return false;
664 CDisabledFonts::TFontList::Iterator CDisabledFonts::find(const QString &name, int face)
666 TFontList::Iterator it(itsFonts.begin()),
667 end(itsFonts.end());
668 QString fontName(name);
670 if('.'==fontName[0])
671 fontName=fontName.mid(1);
673 for(; it!=end; ++it)
674 if((*it).getName()==fontName)
675 break;
677 if(it==end && '.'==name[0])
678 for(it=itsFonts.begin(); it!=end ; ++it)
680 TFileList::ConstIterator fit((*it).files.begin()),
681 fend((*it).files.end());
683 for(; fit!=fend; ++fit)
684 if(Misc::getFile((*fit).path)==name && (*fit).face==face)
685 return it;
687 return it;
691 // This constrcutor is only used internally, and is called in ::save() when it has been
692 // detected that the file has been modified by another process...
693 CDisabledFonts::CDisabledFonts(const CDisabledFonts &o)
694 : itsFileName(o.itsFileName),
695 itsTimeStamp(o.itsTimeStamp),
696 itsModified(false),
697 itsModifiable(false)
699 load(false);
702 void CDisabledFonts::merge(const CDisabledFonts &other)
704 TFontList::ConstIterator it(other.itsFonts.begin()),
705 end(other.itsFonts.end());
707 for(; it!=end; ++it)
709 TFontList::Iterator existing(itsFonts.locate(*it));
711 if(existing!=itsFonts.end())
713 TFileList::ConstIterator fit((*it).files.begin()),
714 fend((*it).files.end());
716 for(; fit!=fend; ++fit)
717 if(!(*existing).files.contains(*fit))
719 (*existing).files.add(*fit);
720 itsModified=true;
723 else
725 itsFonts.add(*it);
726 itsModified=true;
733 QTextStream & operator<<(QTextStream &s, const KFI::CDisabledFonts::TFile &f)
735 s << PATH_ATTR"=\"" << KFI::Misc::encodeText(KFI::Misc::contractHome(f.path), s) << "\" "
736 << FOUNDRY_ATTR"=\"" << KFI::Misc::encodeText(f.foundry, s) << "\" ";
738 if(f.face>0)
739 s << FACE_ATTR"=\"" << f.face << "\" ";
741 return s;
744 QTextStream & operator<<(QTextStream &s, const KFI::CDisabledFonts::TFont &f)
746 int weight, width, slant;
748 KFI::FC::decomposeStyleVal(f.styleInfo, weight, width, slant);
750 s << " <"FONT_TAG" "FAMILY_ATTR"=\"" << KFI::Misc::encodeText(f.family, s) << "\" ";
752 if(KFI_NULL_SETTING!=weight)
753 s << WEIGHT_ATTR"=\"" << weight << "\" ";
754 if(KFI_NULL_SETTING!=width)
755 s << WIDTH_ATTR"=\"" << width << "\" ";
756 if(KFI_NULL_SETTING!=slant)
757 s << SLANT_ATTR"=\"" << slant << "\" ";
759 QStringList ws;
760 QMap<QString, qulonglong>::ConstIterator wit(KFI::constWritingSystemMap.begin()),
761 wend(KFI::constWritingSystemMap.end());
763 for(; wit!=wend; ++wit)
764 if(f.writingSystems&wit.value())
765 ws+=wit.key();
767 if(ws.count())
768 s << LANGS_ATTR"=\"" << ws.join(LANG_SEP) << "\" ";
770 if(1==f.files.count())
771 s << *(f.files.begin()) << "/>" << endl;
772 else
774 KFI::CDisabledFonts::TFileList::ConstIterator it(f.files.begin()),
775 end(f.files.end());
777 s << '>' << endl;
778 for(; it!=end; ++it)
779 s << " <"FILE_TAG" " << *it << "/>" << endl;
780 s << " </"FONT_TAG">" << endl;
783 return s;