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.
277 lines
10 KiB
277 lines
10 KiB
/* Copyright © 2006-2007 Fredrik Höglund <fredrik@kde.org>
|
|
* (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 <QDebug>
|
|
|
|
#include "crtheme.h"
|
|
|
|
#include <QStyle>
|
|
#include <QX11Info>
|
|
|
|
#include "cfgfile.h"
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
#include <X11/extensions/Xfixes.h>
|
|
|
|
// Static variable holding alternative names for some cursors
|
|
static QHash<QString, QString> 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<QString, QString> 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<const quint32*>(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());
|
|
}
|