/* Copyright © 2006-2007 Fredrik Höglund * (c)GPL2 (c)GPL3 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 or at your option version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * additional code: Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar) */ #include #include "crtheme.h" #include #include #include "cfgfile.h" #include #include #include // Static variable holding alternative names for some cursors static QHash alternatives; /////////////////////////////////////////////////////////////////////////////// XCursorThemeData::XCursorThemeData(const QDir &aDir) { mHidden = false; // parse configs, etc mPath = aDir.path(); setName(aDir.dirName()); if (aDir.exists("index.theme")) parseIndexFile(); if (mDescription.isEmpty()) mDescription = "no description"; if (mTitle.isEmpty()) mTitle = mName; } void XCursorThemeData::parseIndexFile() { QMultiMap cfg = loadCfgFile(mPath+"/index.theme", true); if (cfg.contains("icon theme/name")) mTitle = cfg.values("icon theme/name").at(0).trimmed(); if (cfg.contains("icon theme/comment")) mDescription = cfg.values("icon theme/comment").at(0).trimmed(); if (cfg.contains("icon theme/example")) mSample = cfg.values("icon theme/example").at(0).trimmed(); if (cfg.contains("icon theme/hidden")) { QString hiddenValue = cfg.values("icon theme/hidden").at(0).toLower(); mHidden = hiddenValue=="false" ? false : true; } if (cfg.contains("icon theme/inherits")) { QStringList i = cfg.values("icon theme/inherits"), res; for (int f = i.size()-1; f >= 0; f--) res << i.at(f).trimmed(); } if (mDescription.startsWith("- Converted by")) mDescription.remove(0, 2); } QString XCursorThemeData::findAlternative(const QString &name) const { if (alternatives.isEmpty()) { alternatives.reserve(18); // Qt uses non-standard names for some core cursors. // If Xcursor fails to load the cursor, Qt creates it with the correct name using the // core protcol instead (which in turn calls Xcursor). We emulate that process here. // Note that there's a core cursor called cross, but it's not the one Qt expects. alternatives.insert("cross", "crosshair"); alternatives.insert("up_arrow", "center_ptr"); alternatives.insert("wait", "watch"); alternatives.insert("ibeam", "xterm"); alternatives.insert("size_all", "fleur"); alternatives.insert("pointing_hand", "hand2"); // Precomputed MD5 hashes for the hardcoded bitmap cursors in Qt and KDE. // Note that the MD5 hash for left_ptr_watch is for the KDE version of that cursor. alternatives.insert("size_ver", "00008160000006810000408080010102"); alternatives.insert("size_hor", "028006030e0e7ebffc7f7070c0600140"); alternatives.insert("size_bdiag", "c7088f0f3e6c8088236ef8e1e3e70000"); alternatives.insert("size_fdiag", "fcf1c3c7cd4491d801f1e1c78f100000"); alternatives.insert("whats_this", "d9ce0ab605698f320427677b458ad60b"); alternatives.insert("split_h", "14fef782d02440884392942c11205230"); alternatives.insert("split_v", "2870a09082c103050810ffdffffe0204"); alternatives.insert("forbidden", "03b6e0fcb3499374a867c041f52298f0"); alternatives.insert("left_ptr_watch", "3ecb610c1bf2410f44200f48c40d3599"); alternatives.insert("hand2", "e29285e634086352946a0e7090d73106"); alternatives.insert("openhand", "9141b49c8149039304290b508d208c40"); alternatives.insert("closedhand", "05e88622050804100c20044008402080"); } return alternatives.value(name, QString()); } QPixmap XCursorThemeData::icon() const { if (mIcon.isNull()) mIcon = createIcon(); return mIcon; } QImage XCursorThemeData::autoCropImage(const QImage &image) const { // Compute an autocrop rectangle for the image QRect r(image.rect().bottomRight(), image.rect().topLeft()); const quint32 *pixels = reinterpret_cast(image.bits()); for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { if (*(pixels++)) { if (x < r.left()) r.setLeft(x); if (x > r.right()) r.setRight(x); if (y < r.top()) r.setTop(y); if (y > r.bottom()) r.setBottom(y); } } } // Normalize the rectangle return image.copy(r.normalized()); } static int nominalCursorSize(int iconSize) { for (int i = 512; i > 8; i /= 2) { if (i < iconSize) return i; if ((i*0.75) < iconSize) return int(i*0.75); } return 8; } QPixmap XCursorThemeData::createIcon() const { int iconSize = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize); int cursorSize = nominalCursorSize(iconSize); QSize size = QSize(iconSize, iconSize); QPixmap pixmap; QImage image = loadImage(sample(), cursorSize); if (image.isNull() && sample() != "left_ptr") image = loadImage("left_ptr", cursorSize); if (!image.isNull()) { // Scale the image if it's larger than the preferred icon size if (image.width() > size.width() || image.height() > size.height()) { image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); pixmap = QPixmap::fromImage(image); } } return pixmap; } XcursorImage *XCursorThemeData::xcLoadImage(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImage(cursorName, themeName, size); } XcursorImages *XCursorThemeData::xcLoadImages(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImages(cursorName, themeName, size); } unsigned long XCursorThemeData::loadCursorHandle(const QString &name, int size) const { if (size == -1) size = XcursorGetDefaultSize(QX11Info::display()); // Load the cursor images XcursorImages *images = xcLoadImages(name, size); if (!images) images = xcLoadImages(findAlternative(name), size); // Fall back to a legacy cursor //if (!images) return LegacyTheme::loadCursor(name); if (!images) return 0; // Create the cursor unsigned long handle = (unsigned long)XcursorImagesLoadCursor(QX11Info::display(), images); XcursorImagesDestroy(images); //setCursorName(cursor, name); return handle; } QImage XCursorThemeData::loadImage(const QString &name, int size) const { if (size == -1) size = XcursorGetDefaultSize(QX11Info::display()); // Load the image XcursorImage *xcimage = xcLoadImage(name, size); if (!xcimage) xcimage = xcLoadImage(findAlternative(name), size); // Fall back to a legacy cursor //if (!xcimage) return LegacyTheme::loadImage(name); if (!xcimage) return QImage(); // Convert the XcursorImage to a QImage, and auto-crop it QImage image((uchar *)xcimage->pixels, xcimage->width, xcimage->height, QImage::Format_ARGB32_Premultiplied); image = autoCropImage(image); XcursorImageDestroy(xcimage); return image; } bool XCursorThemeData::isWritable() const { QFileInfo fi(path()); return fi.isWritable(); } /////////////////////////////////////////////////////////////////////////////// bool haveXfixes() { bool result = false; int event_base, error_base; if (XFixesQueryExtension(QX11Info::display(), &event_base, &error_base)) { int major, minor; XFixesQueryVersion(QX11Info::display(), &major, &minor); result = (major >= 2); } return result; } bool applyTheme(const XCursorThemeData &theme) { // Require the Xcursor version that shipped with X11R6.9 or greater, since // in previous versions the Xfixes code wasn't enabled due to a bug in the // build system (freedesktop bug #975). if (!haveXfixes()) return false; QByteArray themeName = QFile::encodeName(theme.name()); // Set up the proper launch environment for newly started apps //k8:!!!:KToolInvocation::klauncher()->setLaunchEnv("XCURSOR_THEME", themeName); // Update the Xcursor X resources //k8:!!!:runRdb(0); // Reload the standard cursors QStringList names; // Qt cursors names << "left_ptr" << "up_arrow" << "cross" << "wait" << "left_ptr_watch" << "ibeam" << "size_ver" << "size_hor" << "size_bdiag" << "size_fdiag" << "size_all" << "split_v" << "split_h" << "pointing_hand" << "openhand" << "closedhand" << "forbidden" << "whats_this"; // X core cursors names << "X_cursor" << "right_ptr" << "hand1" << "hand2" << "watch" << "xterm" << "crosshair" << "left_ptr_watch" << "center_ptr" << "sb_h_double_arrow" << "sb_v_double_arrow" << "fleur" << "top_left_corner" << "top_side" << "top_right_corner" << "right_side" << "bottom_right_corner" << "bottom_side" << "bottom_left_corner" << "left_side" << "question_arrow" << "pirate"; //QX11Info x11Info; foreach (const QString &name, names) { Cursor cursor = (Cursor)theme.loadCursorHandle(name); XFixesChangeCursorByName(QX11Info::display(), cursor, QFile::encodeName(name)); // FIXME: do we need to free the cursor? } return true; } QString getCurrentTheme() { return XcursorGetTheme(QX11Info::display()); }