File indexing completed on 2025-03-02 05:11:58
0001 /* 0002 SPDX-FileCopyrightText: 2006-2007 Fredrik Höglund <fredrik@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only 0005 */ 0006 0007 #include <KConfig> 0008 #include <KConfigGroup> 0009 #include <KLocalizedString> 0010 0011 #include <QCursor> 0012 #include <QDir> 0013 #include <QImage> 0014 #include <private/qtx11extras_p.h> 0015 0016 #include <X11/Xcursor/Xcursor.h> 0017 #include <X11/Xlib.h> 0018 0019 #include "xcursortheme.h" 0020 0021 using namespace Qt::StringLiterals; 0022 0023 // Static variable holding alternative names for some cursors 0024 QHash<QString, QString> XCursorTheme::alternatives; 0025 0026 XCursorTheme::XCursorTheme(const QDir &themeDir) 0027 : CursorTheme(themeDir.dirName()) 0028 { 0029 // Directory information 0030 setName(themeDir.dirName()); 0031 setPath(themeDir.path()); 0032 setIsWritable(QFileInfo(themeDir.path()).isWritable()); // ### perhaps this shouldn't be cached 0033 0034 if (themeDir.exists(QStringLiteral("index.theme"))) 0035 parseIndexFile(); 0036 0037 QString cursorFile = path() + "/cursors/left_ptr"; 0038 QList<int> sizeList; 0039 XcursorImages *images = XcursorFilenameLoadAllImages(qPrintable(cursorFile)); 0040 if (images) { 0041 for (int i = 0; i < images->nimage; ++i) { 0042 if (!sizeList.contains(images->images[i]->size)) 0043 sizeList.append(images->images[i]->size); 0044 }; 0045 XcursorImagesDestroy(images); 0046 std::sort(sizeList.begin(), sizeList.end()); 0047 m_availableSizes = sizeList; 0048 } 0049 if (!sizeList.isEmpty()) { 0050 QString sizeListString = QString::number(sizeList.takeFirst()); 0051 while (!sizeList.isEmpty()) { 0052 sizeListString.append(", "); 0053 sizeListString.append(QString::number(sizeList.takeFirst())); 0054 }; 0055 QString tempString = i18nc( 0056 "@info The argument is the list of available sizes (in pixel). Example: " 0057 "'Available sizes: 24' or 'Available sizes: 24, 36, 48'", 0058 "(Available sizes: %1)", 0059 sizeListString); 0060 if (m_description.isEmpty()) 0061 m_description = tempString; 0062 else 0063 m_description = m_description + ' ' + tempString; 0064 }; 0065 } 0066 0067 void XCursorTheme::parseIndexFile() 0068 { 0069 KConfig config(path() + "/index.theme", KConfig::NoGlobals); 0070 KConfigGroup cg(&config, u"Icon Theme"_s); 0071 0072 m_title = cg.readEntry("Name", m_title); 0073 m_description = cg.readEntry("Comment", m_description); 0074 m_sample = cg.readEntry("Example", m_sample); 0075 m_hidden = cg.readEntry("Hidden", false); 0076 m_inherits = cg.readEntry("Inherits", QStringList()); 0077 } 0078 0079 QString XCursorTheme::findAlternative(const QString &name) const 0080 { 0081 if (alternatives.isEmpty()) { 0082 alternatives.reserve(18); 0083 0084 // Qt uses non-standard names for some core cursors. 0085 // If Xcursor fails to load the cursor, Qt creates it with the correct name using the 0086 // core protocol instead (which in turn calls Xcursor). We emulate that process here. 0087 // Note that there's a core cursor called cross, but it's not the one Qt expects. 0088 alternatives.insert(QStringLiteral("cross"), QStringLiteral("crosshair")); 0089 alternatives.insert(QStringLiteral("up_arrow"), QStringLiteral("center_ptr")); 0090 alternatives.insert(QStringLiteral("wait"), QStringLiteral("watch")); 0091 alternatives.insert(QStringLiteral("ibeam"), QStringLiteral("xterm")); 0092 alternatives.insert(QStringLiteral("size_all"), QStringLiteral("fleur")); 0093 alternatives.insert(QStringLiteral("pointing_hand"), QStringLiteral("hand2")); 0094 0095 // Precomputed MD5 hashes for the hardcoded bitmap cursors in Qt and KDE. 0096 // Note that the MD5 hash for left_ptr_watch is for the KDE version of that cursor. 0097 alternatives.insert(QStringLiteral("size_ver"), QStringLiteral("00008160000006810000408080010102")); 0098 alternatives.insert(QStringLiteral("size_hor"), QStringLiteral("028006030e0e7ebffc7f7070c0600140")); 0099 alternatives.insert(QStringLiteral("size_bdiag"), QStringLiteral("fcf1c3c7cd4491d801f1e1c78f100000")); 0100 alternatives.insert(QStringLiteral("size_fdiag"), QStringLiteral("c7088f0f3e6c8088236ef8e1e3e70000")); 0101 alternatives.insert(QStringLiteral("whats_this"), QStringLiteral("d9ce0ab605698f320427677b458ad60b")); 0102 alternatives.insert(QStringLiteral("split_h"), QStringLiteral("14fef782d02440884392942c11205230")); 0103 alternatives.insert(QStringLiteral("split_v"), QStringLiteral("2870a09082c103050810ffdffffe0204")); 0104 alternatives.insert(QStringLiteral("forbidden"), QStringLiteral("03b6e0fcb3499374a867c041f52298f0")); 0105 alternatives.insert(QStringLiteral("left_ptr_watch"), QStringLiteral("3ecb610c1bf2410f44200f48c40d3599")); 0106 alternatives.insert(QStringLiteral("hand2"), QStringLiteral("e29285e634086352946a0e7090d73106")); 0107 alternatives.insert(QStringLiteral("openhand"), QStringLiteral("9141b49c8149039304290b508d208c40")); 0108 alternatives.insert(QStringLiteral("closedhand"), QStringLiteral("05e88622050804100c20044008402080")); 0109 } 0110 0111 return alternatives.value(name, QString()); 0112 } 0113 0114 XcursorImage *XCursorTheme::xcLoadImage(const QString &image, int size) const 0115 { 0116 QByteArray cursorName = QFile::encodeName(image); 0117 QByteArray themeName = QFile::encodeName(name()); 0118 0119 return XcursorLibraryLoadImage(cursorName, themeName, size); 0120 } 0121 0122 XcursorImages *XCursorTheme::xcLoadImages(const QString &image, int size) const 0123 { 0124 QByteArray cursorName = QFile::encodeName(image); 0125 QByteArray themeName = QFile::encodeName(name()); 0126 0127 return XcursorLibraryLoadImages(cursorName, themeName, size); 0128 } 0129 0130 int XCursorTheme::defaultCursorSize() const 0131 { 0132 // TODO: manage Wayland 0133 if (!QX11Info::isPlatformX11()) { 0134 return 32; 0135 } 0136 /* This code is basically borrowed from display.c of the XCursor library 0137 We can't use "int XcursorGetDefaultSize(Display *dpy)" because if 0138 previously the cursor size was set to a custom value, it would return 0139 this custom value. */ 0140 int size = 0; 0141 int dpi = 0; 0142 Display *dpy = QX11Info::display(); 0143 // The string "v" is owned and will be destroyed by Xlib 0144 char *v = XGetDefault(dpy, "Xft", "dpi"); 0145 if (v) 0146 dpi = atoi(v); 0147 if (dpi) 0148 size = dpi * 16 / 72; 0149 if (size == 0) { 0150 int dim; 0151 if (DisplayHeight(dpy, DefaultScreen(dpy)) < DisplayWidth(dpy, DefaultScreen(dpy))) { 0152 dim = DisplayHeight(dpy, DefaultScreen(dpy)); 0153 } else { 0154 dim = DisplayWidth(dpy, DefaultScreen(dpy)); 0155 } 0156 size = dim / 48; 0157 } 0158 return size; 0159 } 0160 0161 qulonglong XCursorTheme::loadCursor(const QString &name, int size) const 0162 { 0163 // TODO: manage Wayland 0164 if (!QX11Info::isPlatformX11()) { 0165 return None; 0166 } 0167 if (size <= 0) 0168 size = defaultCursorSize(); 0169 0170 // Load the cursor images 0171 XcursorImages *images = xcLoadImages(name, size); 0172 0173 if (!images) 0174 images = xcLoadImages(findAlternative(name), size); 0175 0176 if (!images) 0177 return None; 0178 0179 // Create the cursor 0180 Cursor handle = XcursorImagesLoadCursor(QX11Info::display(), images); 0181 XcursorImagesDestroy(images); 0182 0183 setCursorName(handle, name); 0184 return handle; 0185 } 0186 0187 QImage XCursorTheme::loadImage(const QString &name, int size) const 0188 { 0189 if (size <= 0) 0190 size = defaultCursorSize(); 0191 0192 // Load the image 0193 XcursorImage *xcimage = xcLoadImage(name, size); 0194 0195 if (!xcimage) 0196 xcimage = xcLoadImage(findAlternative(name), size); 0197 0198 if (!xcimage) { 0199 return QImage(); 0200 } 0201 0202 // Convert the XcursorImage to a QImage, and auto-crop it 0203 QImage image((uchar *)xcimage->pixels, xcimage->width, xcimage->height, QImage::Format_ARGB32_Premultiplied); 0204 0205 image = autoCropImage(image); 0206 XcursorImageDestroy(xcimage); 0207 0208 return image; 0209 } 0210 0211 std::vector<CursorTheme::CursorImage> XCursorTheme::loadImages(const QString &name, int size) const 0212 { 0213 if (size <= 0) 0214 size = defaultCursorSize(); 0215 0216 // Load the images 0217 XcursorImages *xcimages = xcLoadImages(name, size); 0218 0219 if (!xcimages) 0220 xcimages = xcLoadImages(findAlternative(name), size); 0221 0222 if (!xcimages) { 0223 return {}; 0224 } 0225 0226 std::vector<CursorImage> images; 0227 images.reserve(xcimages->nimage); 0228 for (int i = 0; i < xcimages->nimage; ++i) { 0229 // Convert the XcursorImage to a QImage, and auto-crop it 0230 const XcursorImage *xcimage = xcimages->images[i]; 0231 QImage image(reinterpret_cast<unsigned char *>(xcimage->pixels), xcimage->width, xcimage->height, QImage::Format_ARGB32_Premultiplied); 0232 images.push_back(CursorImage{autoCropImage(image), std::chrono::milliseconds{xcimage->delay}}); 0233 } 0234 0235 XcursorImagesDestroy(xcimages); 0236 0237 return images; 0238 }