diff --git a/src/cursortheme/thememodel.cpp b/src/cursortheme/thememodel.cpp index 0dc04cf..65a558c 100644 --- a/src/cursortheme/thememodel.cpp +++ b/src/cursortheme/thememodel.cpp @@ -1,419 +1,414 @@ /* * Copyright © 2005-2007 Fredrik Höglund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 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. */ #include #include #include #include #include #include #include "thememodel.h" #include "thememodel.moc" #include "xcursortheme.h" #include #include // Check for older version #if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR) # define XCURSOR_LIB_MAJOR XCURSOR_MAJOR # define XCURSOR_LIB_MINOR XCURSOR_MINOR #endif CursorThemeModel::CursorThemeModel(QObject *parent) : QAbstractTableModel(parent) { insertThemes(); } CursorThemeModel::~CursorThemeModel() { qDeleteAll(list); list.clear(); } void CursorThemeModel::refreshList() { beginResetModel(); qDeleteAll(list); list.clear(); endResetModel(); insertThemes(); } QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const { // Only provide text for the headers if (role != Qt::DisplayRole) return QVariant(); // Horizontal header labels if (orientation == Qt::Horizontal) { switch (section) { case NameColumn: return i18n("Name"); case DescColumn: return i18n("Description"); default: return QVariant(); } } // Numbered vertical header lables return QString(section); } QVariant CursorThemeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= list.count()) return QVariant(); const CursorTheme *theme = list.at(index.row()); // Text label if (role == Qt::DisplayRole) { switch (index.column()) { case NameColumn: return theme->title(); case DescColumn: return theme->description(); default: return QVariant(); } } // Description for the first name column if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn) return theme->description(); // Icon for the name column if (role == Qt::DecorationRole && index.column() == NameColumn) return theme->icon(); return QVariant(); } void CursorThemeModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); Q_UNUSED(order); // Sorting of the model isn't implemented, as the KCM currently uses // a sorting proxy model. } const CursorTheme *CursorThemeModel::theme(const QModelIndex &index) { if (!index.isValid()) return NULL; if (index.row() < 0 || index.row() >= list.count()) return NULL; return list.at(index.row()); } QModelIndex CursorThemeModel::findIndex(const QString &name) { uint hash = qHash(name); for (int i = 0; i < list.count(); i++) { const CursorTheme *theme = list.at(i); if (theme->hash() == hash) return index(i, 0); } return QModelIndex(); } QModelIndex CursorThemeModel::defaultIndex() { return findIndex(defaultName); } const QStringList CursorThemeModel::searchPaths() { if (!baseDirs.isEmpty()) return baseDirs; #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1 // These are the default paths Xcursor will scan for cursor themes QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"); // If XCURSOR_PATH is set, use that instead of the default path char *xcursorPath = std::getenv("XCURSOR_PATH"); if (xcursorPath) path = xcursorPath; #else // Get the search path from Xcursor QString path = XcursorLibraryPath(); #endif // Separate the paths baseDirs = path.split(':', QString::SkipEmptyParts); // Remove duplicates QMutableStringListIterator i(baseDirs); while (i.hasNext()) { const QString path = i.next(); QMutableStringListIterator j(i); while (j.hasNext()) if (j.next() == path) j.remove(); } // Expand all occurrences of ~/ to the home dir baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/'); return baseDirs; } bool CursorThemeModel::hasTheme(const QString &name) const { const uint hash = qHash(name); foreach (const CursorTheme *theme, list) if (theme->hash() == hash) return true; return false; } bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth) { // Prevent infinite recursion if (depth > 10) return false; // Search each icon theme directory for 'theme' foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists() || !dir.cd(theme)) continue; // If there's a cursors subdir, we'll assume this is a cursor theme if (dir.exists("cursors")) return true; // If the theme doesn't have an index.theme file, it can't inherit any themes. if (!dir.exists("index.theme")) continue; // Open the index.theme file, so we can get the list of inherited themes KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); // Recurse through the list of inherited themes, to check if one of them // is a cursor theme. QStringList inherits = cg.readEntry("Inherits", QStringList()); foreach (const QString &inherit, inherits) { // Avoid possible DoS if (inherit == theme) continue; if (isCursorTheme(inherit, depth + 1)) return true; } } return false; } bool CursorThemeModel::handleDefault(const QDir &themeDir) { QFileInfo info(themeDir.path()); // If "default" is a symlink if (info.isSymLink()) { QFileInfo target(info.symLinkTarget()); if (target.exists() && (target.isDir() || target.isSymLink())) defaultName = target.fileName(); return true; } // If there's no cursors subdir, or if it's empty if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors") .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty()) { if (themeDir.exists("index.theme")) { XCursorTheme theme(themeDir); if (!theme.inherits().isEmpty()) defaultName = theme.inherits().at(0); } return true; } defaultName = QLatin1String("default"); return false; } void CursorThemeModel::processThemeDir(const QDir &themeDir) { bool haveCursors = themeDir.exists("cursors"); // Special case handling of "default", since it's usually either a // symlink to another theme, or an empty theme that inherits another // theme. if (defaultName.isNull() && themeDir.dirName() == "default") { if (handleDefault(themeDir)) return; } // If the directory doesn't have a cursors subdir and lacks an // index.theme file it can't be a cursor theme. if (!themeDir.exists("index.theme") && !haveCursors) return; - static bool isX11 = QX11Info::isPlatformX11(); - if (!isX11) { - // TODO: implement Wayland Cursor Theme support - return; - } // Create a cursor theme object for the theme dir XCursorTheme *theme = new XCursorTheme(themeDir); // Skip this theme if it's hidden. if (theme->isHidden()) { delete theme; return; } // If there's no cursors subdirectory we'll do a recursive scan // to check if the theme inherits a theme with one. if (!haveCursors) { bool foundCursorTheme = false; foreach (const QString &name, theme->inherits()) if ((foundCursorTheme = isCursorTheme(name))) break; if (!foundCursorTheme) { delete theme; return; } } // Append the theme to the list beginInsertRows(QModelIndex(), list.size(), list.size()); list.append(theme); endInsertRows(); } void CursorThemeModel::insertThemes() { // Scan each base dir for Xcursor themes and add them to the list. foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists()) continue; // Process each subdir in the directory foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { // Don't process the theme if a theme with the same name already exists // in the list. Xcursor will pick the first one it finds in that case, // and since we use the same search order, the one Xcursor picks should // be the one already in the list. if (hasTheme(name) || !dir.cd(name)) continue; processThemeDir(dir); dir.cdUp(); // Return to the base dir } } // The theme Xcursor will end up using if no theme is configured if (defaultName.isNull() || !hasTheme(defaultName)) defaultName = QLatin1String("KDE_Classic"); } bool CursorThemeModel::addTheme(const QDir &dir) { XCursorTheme *theme = new XCursorTheme(dir); // Don't add the theme to the list if it's hidden if (theme->isHidden()) { delete theme; return false; } // ### If the theme is hidden, the user will probably find it strange that it // doesn't appear in the list view. There also won't be a way for the user // to delete the theme using the KCM. Perhaps a warning about this should // be issued, and the user be given a chance to undo the installation. // If an item with the same name already exists in the list, // we'll remove it before inserting the new one. for (int i = 0; i < list.count(); i++) { if (list.at(i)->hash() == theme->hash()) { removeTheme(index(i, 0)); break; } } // Append the theme to the list beginInsertRows(QModelIndex(), rowCount(), rowCount()); list.append(theme); endInsertRows(); return true; } void CursorThemeModel::removeTheme(const QModelIndex &index) { if (!index.isValid()) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); delete list.takeAt(index.row()); endRemoveRows(); } diff --git a/src/cursortheme/xcursortheme.cpp b/src/cursortheme/xcursortheme.cpp index 44be89e..1e3517e 100644 --- a/src/cursortheme/xcursortheme.cpp +++ b/src/cursortheme/xcursortheme.cpp @@ -1,229 +1,232 @@ /* * Copyright © 2006-2007 Fredrik Höglund * * 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. */ #include #include #include #include #include #include #include #include #include #include #include "xcursortheme.h" // Static variable holding alternative names for some cursors QHash XCursorTheme::alternatives; XCursorTheme::XCursorTheme(const QDir &themeDir) : CursorTheme(themeDir.dirName()) { // Directory information setName(themeDir.dirName()); setPath(themeDir.path()); setIsWritable(QFileInfo(themeDir.path()).isWritable()); // ### perhaps this shouldn't be cached if (themeDir.exists("index.theme")) parseIndexFile(); QString cursorFile = path() + "/cursors/left_ptr"; QList sizeList; XcursorImages *images = XcursorFilenameLoadAllImages(qPrintable(cursorFile)); if (images) { for (int i = 0; i < images->nimage; ++i) { if (!sizeList.contains(images->images[i]->size)) sizeList.append(images->images[i]->size); }; XcursorImagesDestroy(images); qSort(sizeList.begin(), sizeList.end()); m_availableSizes = sizeList; }; if (!sizeList.isEmpty()) { QString sizeListString = QString::number(sizeList.takeFirst()); while (!sizeList.isEmpty()) { sizeListString.append(", "); sizeListString.append(QString::number(sizeList.takeFirst())); }; QString tempString = i18nc( "@info The argument is the list of available sizes (in pixel). Example: " "'Available sizes: 24' or 'Available sizes: 24, 36, 48'", "(Available sizes: %1)", sizeListString); if (m_description.isEmpty()) m_description = tempString; else m_description = m_description + ' ' + tempString; }; } void XCursorTheme::parseIndexFile() { KConfig config(path() + "/index.theme", KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); m_title = cg.readEntry("Name", m_title); m_description = cg.readEntry("Comment", m_description); m_sample = cg.readEntry("Example", m_sample); m_hidden = cg.readEntry("Hidden", false); m_inherits = cg.readEntry("Inherits", QStringList()); } QString XCursorTheme::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", "fcf1c3c7cd4491d801f1e1c78f100000"); alternatives.insert("size_fdiag", "c7088f0f3e6c8088236ef8e1e3e70000"); 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()); } XcursorImage *XCursorTheme::xcLoadImage(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImage(cursorName, themeName, size); } XcursorImages *XCursorTheme::xcLoadImages(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImages(cursorName, themeName, size); } int XCursorTheme::autodetectCursorSize() const { /* This code is basically borrowed from display.c of the XCursor library We can't use "int XcursorGetDefaultSize(Display *dpy)" because if previously the cursor size was set to a custom value, it would return this custom value. */ int size = 0; int dpi = 0; Display *dpy = QX11Info::display(); + // Fallback on wayland + if (!dpy) + return 24; // The string "v" is owned and will be destroyed by Xlib char *v = XGetDefault(dpy, "Xft", "dpi"); if (v) dpi = atoi(v); if (dpi) size = dpi * 16 / 72; if (size == 0) { int dim; if (DisplayHeight(dpy, DefaultScreen(dpy)) < DisplayWidth(dpy, DefaultScreen(dpy))) { dim = DisplayHeight(dpy, DefaultScreen(dpy)); } else { dim = DisplayWidth(dpy, DefaultScreen(dpy)); }; size = dim / 48; } return size; } qulonglong XCursorTheme::loadCursor(const QString &name, int size) const { if (size <= 0) size = autodetectCursorSize(); // Load the cursor images XcursorImages *images = xcLoadImages(name, size); if (!images) images = xcLoadImages(findAlternative(name), size); if (!images) return None; // Create the cursor Cursor handle = XcursorImagesLoadCursor(QX11Info::display(), images); XcursorImagesDestroy(images); setCursorName(handle, name); return handle; } QImage XCursorTheme::loadImage(const QString &name, int size) const { if (size <= 0) size = autodetectCursorSize(); // Load the image XcursorImage *xcimage = xcLoadImage(name, size); if (!xcimage) xcimage = xcLoadImage(findAlternative(name), size); 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; }