File indexing completed on 2024-05-12 04:58:28

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "qztools.h"
0019 #include "datapaths.h"
0020 #include "settings.h"
0021 #include "mainapplication.h"
0022 
0023 #include <QTextDocument>
0024 #include <QDateTime>
0025 #include <QByteArray>
0026 #include <QPixmap>
0027 #include <QPainter>
0028 #include <QPainterPath>
0029 #include <QBuffer>
0030 #include <QFile>
0031 #include <QDir>
0032 #include <QWidget>
0033 #include <QApplication>
0034 #include <QSslCertificate>
0035 #include <QScreen>
0036 #include <QUrl>
0037 #include <QIcon>
0038 #include <QFileIconProvider>
0039 #include <QTemporaryFile>
0040 #include <QHash>
0041 #include <QSysInfo>
0042 #include <QProcess>
0043 #include <QMessageBox>
0044 #include <QUrlQuery>
0045 #include <QtGuiVersion>
0046 
0047 #ifdef QZ_WS_X11
0048 #include <xcb/xcb.h>
0049 #endif
0050 
0051 #ifdef Q_OS_WIN
0052 #include <windows.h>
0053 #else
0054 #include <unistd.h>
0055 #endif
0056 
0057 #ifdef Q_OS_MACOS
0058 #include <CoreServices/CoreServices.h>
0059 #endif
0060 
0061 QByteArray QzTools::pixmapToByteArray(const QPixmap &pix)
0062 {
0063     QByteArray bytes;
0064     QBuffer buffer(&bytes);
0065     buffer.open(QIODevice::WriteOnly);
0066     if (pix.save(&buffer, "PNG")) {
0067         return buffer.buffer().toBase64();
0068     }
0069 
0070     return {};
0071 }
0072 
0073 QPixmap QzTools::pixmapFromByteArray(const QByteArray &data)
0074 {
0075     QPixmap image;
0076     QByteArray bArray = QByteArray::fromBase64(data);
0077     image.loadFromData(bArray);
0078 
0079     return image;
0080 }
0081 
0082 QUrl QzTools::pixmapToDataUrl(const QPixmap &pix)
0083 {
0084     const QString data(QString::fromLatin1(pixmapToByteArray(pix)));
0085     return data.isEmpty() ? QUrl() : QUrl(QSL("data:image/png;base64,") + data);
0086 }
0087 
0088 QPixmap QzTools::dpiAwarePixmap(const QString &path)
0089 {
0090     const QIcon icon(path);
0091     if (icon.availableSizes().isEmpty()) {
0092         return QPixmap(path);
0093     }
0094     return icon.pixmap(icon.availableSizes().at(0));
0095 }
0096 
0097 QString QzTools::readAllFileContents(const QString &filename)
0098 {
0099     return QString::fromUtf8(readAllFileByteContents(filename));
0100 }
0101 
0102 QByteArray QzTools::readAllFileByteContents(const QString &filename)
0103 {
0104     QFile file(filename);
0105 
0106     if (!filename.isEmpty() && file.open(QFile::ReadOnly)) {
0107         const QByteArray a = file.readAll();
0108         file.close();
0109         return a;
0110     }
0111 
0112     return {};
0113 }
0114 
0115 void QzTools::centerWidgetOnScreen(QWidget* w)
0116 {
0117     QRect screen = w->screen()->geometry();
0118     const QRect size = w->geometry();
0119     w->move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2);
0120 }
0121 
0122 // Very, very, very simplified QDialog::adjustPosition from qdialog.cpp
0123 void QzTools::centerWidgetToParent(QWidget* w, QWidget* parent)
0124 {
0125     if (!parent || !w) {
0126         return;
0127     }
0128 
0129     QPoint p;
0130     parent = parent->window();
0131     QPoint pp = parent->mapToGlobal(QPoint(0, 0));
0132     p = QPoint(pp.x() + parent->width() / 2, pp.y() + parent->height() / 2);
0133     p = QPoint(p.x() - w->width() / 2, p.y() - w->height() / 2 - 20);
0134 
0135     w->move(p);
0136 }
0137 
0138 bool QzTools::removeRecursively(const QString &filePath)
0139 {
0140     const QFileInfo fileInfo(filePath);
0141     if (!fileInfo.exists() && !fileInfo.isSymLink()) {
0142         return true;
0143     }
0144     if (fileInfo.isDir() && !fileInfo.isSymLink()) {
0145         QDir dir(filePath);
0146         dir.setPath(dir.canonicalPath());
0147         if (dir.isRoot() || dir.path() == QDir::home().canonicalPath()) {
0148             qCritical() << "Attempt to remove root/home directory" << dir;
0149             return false;
0150         }
0151         const QStringList fileNames = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
0152         for (const QString &fileName : fileNames) {
0153             if (!removeRecursively(filePath + QLatin1Char('/') + fileName)) {
0154                 return false;
0155             }
0156         }
0157         if (!QDir::root().rmdir(dir.path())) {
0158             return false;
0159         }
0160     } else if (!QFile::remove(filePath)) {
0161         return false;
0162     }
0163     return true;
0164 }
0165 
0166 bool QzTools::copyRecursively(const QString &sourcePath, const QString &targetPath)
0167 {
0168     const QFileInfo srcFileInfo(sourcePath);
0169     if (srcFileInfo.isDir() && !srcFileInfo.isSymLink()) {
0170         QDir targetDir(targetPath);
0171         targetDir.cdUp();
0172         if (!targetDir.mkdir(QFileInfo(targetPath).fileName())) {
0173             return false;
0174         }
0175         const QStringList fileNames = QDir(sourcePath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
0176         for (const QString &fileName : fileNames) {
0177             const QString newSourcePath = sourcePath + QL1C('/') + fileName;
0178             const QString newTargetPath = targetPath + QL1C('/') + fileName;
0179             if (!copyRecursively(newSourcePath, newTargetPath)) {
0180                 return false;
0181             }
0182         }
0183 #ifndef Q_OS_WIN
0184     } else if (srcFileInfo.isSymLink()) {
0185         const QByteArray pathData = sourcePath.toLocal8Bit();
0186         char buf[1024];
0187         ssize_t len = readlink(pathData.constData(), buf, sizeof(buf) - 1);
0188         if (len < 0) {
0189             qWarning() << "Error getting symlink path" << pathData;
0190             return false;
0191         }
0192         buf[len] = '\0';
0193         return QFile::link(QString::fromLocal8Bit(buf), targetPath);
0194 #endif
0195     } else if (!QFile::copy(sourcePath, targetPath)) {
0196         return false;
0197     }
0198     return true;
0199 }
0200 
0201 /* Finds same part of @one in @other from the beginning */
0202 QString QzTools::samePartOfStrings(const QString &one, const QString &other)
0203 {
0204     int maxSize = qMin(one.size(), other.size());
0205     if (maxSize <= 0) {
0206         return {};
0207     }
0208 
0209     int i = 0;
0210     while (one.at(i) == other.at(i)) {
0211         i++;
0212         if (i == maxSize) {
0213             break;
0214         }
0215     }
0216     return one.left(i);
0217 }
0218 
0219 QString QzTools::urlEncodeQueryString(const QUrl &url)
0220 {
0221     QString returnString = url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment);
0222 
0223     if (url.hasQuery()) {
0224         returnString += QLatin1Char('?') + url.query(QUrl::FullyEncoded);
0225     }
0226 
0227     if (url.hasFragment()) {
0228         returnString += QLatin1Char('#') + url.fragment(QUrl::FullyEncoded);
0229     }
0230 
0231     returnString.replace(QLatin1Char(' '), QLatin1String("%20"));
0232 
0233     return returnString;
0234 }
0235 
0236 QString QzTools::fromPunycode(const QString &str)
0237 {
0238     if (!str.startsWith(QL1S("xn--")))
0239         return str;
0240 
0241     // QUrl::fromAce will only decode domains from idn whitelist
0242     const QString decoded = QUrl::fromAce(str.toUtf8() + QByteArray(".org"));
0243     return decoded.left(decoded.size() - 4);
0244 }
0245 
0246 QString QzTools::escapeSqlGlobString(QString urlString)
0247 {
0248     urlString.replace(QL1C('['), QStringLiteral("[["));
0249     urlString.replace(QL1C(']'), QStringLiteral("[]]"));
0250     urlString.replace(QStringLiteral("[["), QStringLiteral("[[]"));
0251     urlString.replace(QL1C('*'), QStringLiteral("[*]"));
0252     urlString.replace(QL1C('?'), QStringLiteral("[?]"));
0253     return urlString;
0254 }
0255 
0256 QString QzTools::ensureUniqueFilename(const QString &name, const QString &appendFormat)
0257 {
0258     Q_ASSERT(appendFormat.contains(QL1S("%1")));
0259 
0260     QFileInfo info(name);
0261 
0262     if (!info.exists())
0263         return name;
0264 
0265     const QDir dir = info.absoluteDir();
0266     const QString fileName = info.fileName();
0267 
0268     int i = 1;
0269 
0270     while (info.exists()) {
0271         QString file = fileName;
0272         int index = file.lastIndexOf(QL1C('.'));
0273         const QString appendString = appendFormat.arg(i);
0274         if (index == -1)
0275             file.append(appendString);
0276         else
0277             file = file.left(index) + appendString + file.mid(index);
0278         info.setFile(dir, file);
0279         i++;
0280     }
0281 
0282     return info.absoluteFilePath();
0283 }
0284 
0285 QString QzTools::getFileNameFromUrl(const QUrl &url)
0286 {
0287     QString fileName = url.toString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemovePort);
0288 
0289     if (fileName.endsWith(QLatin1Char('/'))) {
0290         fileName = fileName.mid(0, fileName.length() - 1);
0291     }
0292 
0293     if (fileName.indexOf(QLatin1Char('/')) != -1) {
0294         int pos = fileName.lastIndexOf(QLatin1Char('/'));
0295         fileName = fileName.mid(pos);
0296         fileName.remove(QLatin1Char('/'));
0297     }
0298 
0299     fileName = filterCharsFromFilename(fileName);
0300 
0301     if (fileName.isEmpty()) {
0302         fileName = filterCharsFromFilename(url.host());
0303     }
0304 
0305     return fileName;
0306 }
0307 
0308 QString QzTools::filterCharsFromFilename(const QString &name)
0309 {
0310     QString value = name;
0311 
0312     value.replace(QLatin1Char('/'), QLatin1Char('-'));
0313     value.remove(QLatin1Char('\\'));
0314     value.remove(QLatin1Char(':'));
0315     value.remove(QLatin1Char('*'));
0316     value.remove(QLatin1Char('?'));
0317     value.remove(QLatin1Char('"'));
0318     value.remove(QLatin1Char('<'));
0319     value.remove(QLatin1Char('>'));
0320     value.remove(QLatin1Char('|'));
0321 
0322     return value;
0323 }
0324 
0325 QString QzTools::lastPathForFileDialog(const QString &dialogName, const QString &fallbackPath)
0326 {
0327     Settings settings;
0328     settings.beginGroup(QSL("LastFileDialogsPaths"));
0329     QString path = settings.value(QSL("FileDialogs/") + dialogName).toString();
0330     settings.endGroup();
0331 
0332     return path.isEmpty() ? fallbackPath : path;
0333 }
0334 
0335 void QzTools::saveLastPathForFileDialog(const QString &dialogName, const QString &path)
0336 {
0337     if (path.isEmpty()) {
0338         return;
0339     }
0340 
0341     Settings settings;
0342     settings.beginGroup(QSL("LastFileDialogsPaths"));
0343     settings.setValue(dialogName, path);
0344     settings.endGroup();
0345 }
0346 
0347 QString QzTools::alignTextToWidth(const QString &string, const QString &text, const QFontMetrics &metrics, int width)
0348 {
0349     int pos = 0;
0350     QString returnString;
0351 
0352     while (pos <= string.size()) {
0353         QString part = string.mid(pos);
0354         QString elidedLine = metrics.elidedText(part, Qt::ElideRight, width);
0355 
0356         if (elidedLine.isEmpty()) {
0357             break;
0358         }
0359 
0360         if (elidedLine.size() != part.size()) {
0361             elidedLine = elidedLine.left(elidedLine.size() - 3);
0362         }
0363 
0364         if (!returnString.isEmpty()) {
0365             returnString += text;
0366         }
0367 
0368         returnString += elidedLine;
0369         pos += elidedLine.size();
0370     }
0371 
0372     return returnString;
0373 }
0374 
0375 QString QzTools::fileSizeToString(qint64 size)
0376 {
0377     if (size < 0) {
0378         return QObject::tr("Unknown size");
0379     }
0380 
0381     double _size = size / 1024.0; // KB
0382     if (_size < 1000) {
0383         return QString::number(_size > 1 ? _size : 1, 'f', 0) + QLatin1Char(' ') + QObject::tr("KB");
0384     }
0385 
0386     _size /= 1024; // MB
0387     if (_size < 1000) {
0388         return QString::number(_size, 'f', 1) + QLatin1Char(' ') + QObject::tr("MB");
0389     }
0390 
0391     _size /= 1024; // GB
0392     return QString::number(_size, 'f', 2) + QLatin1Char(' ') + QObject::tr("GB");
0393 }
0394 
0395 QPixmap QzTools::createPixmapForSite(const QIcon &icon, const QString &title, const QString &url)
0396 {
0397     const QFontMetricsF fontMetrics(QApplication::font());
0398     const int padding = 4;
0399     const int maxWidth = fontMetrics.horizontalAdvance(title.length() > url.length() ? title : url) + 3 * padding + 16;
0400     const int width = qMin(maxWidth, 150);
0401     const int height = fontMetrics.height() * 2 + fontMetrics.leading() + 2 * padding;
0402 
0403     QPixmap pixmap(width * qApp->devicePixelRatio(), height * qApp->devicePixelRatio());
0404     pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
0405 
0406     QPainter painter(&pixmap);
0407     painter.setRenderHint(QPainter::Antialiasing);
0408 
0409     // Draw background
0410     QPen pen(Qt::black);
0411     pen.setWidth(1);
0412     painter.setPen(pen);
0413 
0414     QPainterPath path;
0415     path.addRect(QRect(0, 0, width, height));
0416 
0417     painter.fillPath(path, Qt::white);
0418     painter.drawPath(path);
0419 
0420     // Draw icon
0421     QRect iconRect(padding, 0, 16, height);
0422     icon.paint(&painter, iconRect);
0423 
0424     // Draw title
0425     QRect titleRect(iconRect.right() + padding, padding, width - padding - iconRect.right(), fontMetrics.height());
0426     painter.drawText(titleRect, fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width()));
0427 
0428     // Draw url
0429     QRect urlRect(titleRect.x(), titleRect.bottom() + fontMetrics.leading(), titleRect.width(), titleRect.height());
0430     painter.setPen(QApplication::palette().color(QPalette::Link));
0431     painter.drawText(urlRect, fontMetrics.elidedText(url, Qt::ElideRight, urlRect.width()));
0432 
0433     return pixmap;
0434 }
0435 
0436 QString QzTools::applyDirectionToPage(QString &pageContents)
0437 {
0438     QString direction = QLatin1String("ltr");
0439     QString right_str = QLatin1String("right");
0440     QString left_str = QLatin1String("left");
0441 
0442     if (QApplication::isRightToLeft()) {
0443         direction = QLatin1String("rtl");
0444         right_str = QLatin1String("left");
0445         left_str = QLatin1String("right");
0446     }
0447 
0448     pageContents.replace(QLatin1String("%DIRECTION%"), direction);
0449     pageContents.replace(QLatin1String("%RIGHT_STR%"), right_str);
0450     pageContents.replace(QLatin1String("%LEFT_STR%"), left_str);
0451 
0452     return pageContents;
0453 }
0454 
0455 QString QzTools::truncatedText(const QString &text, int size)
0456 {
0457     if (text.length() > size) {
0458         return text.left(size) + QL1S("..");
0459     }
0460     return text;
0461 }
0462 
0463 // Thanks to http://www.qtcentre.org/threads/3205-Toplevel-widget-with-rounded-corners?p=17492#post17492
0464 QRegion QzTools::roundedRect(const QRect &rect, int radius)
0465 {
0466     QRegion region;
0467 
0468     // middle and borders
0469     region += rect.adjusted(radius, 0, -radius, 0);
0470     region += rect.adjusted(0, radius, 0, -radius);
0471 
0472     // top left
0473     QRect corner(rect.topLeft(), QSize(radius * 2, radius * 2));
0474     region += QRegion(corner, QRegion::Ellipse);
0475 
0476     // top right
0477     corner.moveTopRight(rect.topRight());
0478     region += QRegion(corner, QRegion::Ellipse);
0479 
0480     // bottom left
0481     corner.moveBottomLeft(rect.bottomLeft());
0482     region += QRegion(corner, QRegion::Ellipse);
0483 
0484     // bottom right
0485     corner.moveBottomRight(rect.bottomRight());
0486     region += QRegion(corner, QRegion::Ellipse);
0487 
0488     return region;
0489 }
0490 
0491 QIcon QzTools::iconFromFileName(const QString &fileName)
0492 {
0493     static QHash<QString, QIcon> iconCache;
0494 
0495     QFileInfo tempInfo(fileName);
0496     if (iconCache.contains(tempInfo.suffix())) {
0497         return iconCache.value(tempInfo.suffix());
0498     }
0499 
0500     QFileIconProvider iconProvider;
0501     QTemporaryFile tempFile(DataPaths::path(DataPaths::Temp) + QSL("/XXXXXX.") + tempInfo.suffix());
0502     tempFile.open();
0503     tempInfo.setFile(tempFile.fileName());
0504 
0505     QIcon icon(iconProvider.icon(tempInfo));
0506     iconCache.insert(tempInfo.suffix(), icon);
0507 
0508     return icon;
0509 }
0510 
0511 QString QzTools::resolveFromPath(const QString &name)
0512 {
0513     const QString path = QString::fromUtf8(qgetenv("PATH").trimmed());
0514 
0515     if (path.isEmpty()) {
0516         return {};
0517     }
0518 
0519     const QStringList dirs = path.split(QLatin1Char(':'), Qt::SkipEmptyParts);
0520 
0521     for (const QString &dir : dirs) {
0522         QDir d(dir);
0523         if (d.exists(name)) {
0524             return d.absoluteFilePath(name);
0525         }
0526     }
0527 
0528     return QString();
0529 }
0530 
0531 // http://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c
0532 bool QzTools::isUtf8(const char* string)
0533 {
0534     if (!string) {
0535         return 0;
0536     }
0537 
0538     const unsigned char* bytes = (const unsigned char*)string;
0539     while (*bytes) {
0540         if ((// ASCII
0541                 bytes[0] == 0x09 ||
0542                 bytes[0] == 0x0A ||
0543                 bytes[0] == 0x0D ||
0544                 (0x20 <= bytes[0] && bytes[0] <= 0x7F)
0545             )
0546            ) {
0547             bytes += 1;
0548             continue;
0549         }
0550 
0551         if ((// non-overlong 2-byte
0552                 (0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
0553                 (0x80 <= bytes[1] && bytes[1] <= 0xBF)
0554             )
0555            ) {
0556             bytes += 2;
0557             continue;
0558         }
0559 
0560         if ((// excluding overlongs
0561                 bytes[0] == 0xE0 &&
0562                 (0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
0563                 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
0564             ) ||
0565             (// straight 3-byte
0566                 ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
0567                  bytes[0] == 0xEE ||
0568                  bytes[0] == 0xEF) &&
0569                 (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
0570                 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
0571             ) ||
0572             (// excluding surrogates
0573                 bytes[0] == 0xED &&
0574                 (0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
0575                 (0x80 <= bytes[2] && bytes[2] <= 0xBF)
0576             )
0577            ) {
0578             bytes += 3;
0579             continue;
0580         }
0581 
0582         if ((// planes 1-3
0583                 bytes[0] == 0xF0 &&
0584                 (0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
0585                 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
0586                 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
0587             ) ||
0588             (// planes 4-15
0589                 (0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
0590                 (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
0591                 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
0592                 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
0593             ) ||
0594             (// plane 16
0595                 bytes[0] == 0xF4 &&
0596                 (0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
0597                 (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
0598                 (0x80 <= bytes[3] && bytes[3] <= 0xBF)
0599             )
0600            ) {
0601             bytes += 4;
0602             continue;
0603         }
0604 
0605         return false;
0606     }
0607 
0608     return true;
0609 }
0610 
0611 bool QzTools::containsSpace(const QString &str)
0612 {
0613     for (const QChar &c : str) {
0614         if (c.isSpace())
0615             return true;
0616     }
0617     return false;
0618 }
0619 
0620 QString QzTools::getExistingDirectory(const QString &name, QWidget* parent, const QString &caption, const QString &dir, QFileDialog::Options options)
0621 {
0622     Settings settings;
0623     settings.beginGroup(QSL("FileDialogPaths"));
0624 
0625     QString lastDir = settings.value(name, dir).toString();
0626 
0627     QString path = QFileDialog::getExistingDirectory(parent, caption, lastDir, options);
0628 
0629     if (!path.isEmpty()) {
0630         settings.setValue(name, QFileInfo(path).absolutePath());
0631     }
0632 
0633     settings.endGroup();
0634     return path;
0635 }
0636 
0637 static QString getFilename(const QString &path)
0638 {
0639     QFileInfo info(path);
0640 
0641     if (info.isFile()) {
0642         return info.fileName();
0643     }
0644 
0645     if (info.isDir()) {
0646         return {};
0647     }
0648 
0649     if (info.dir().exists()) {
0650         return info.fileName();
0651     }
0652 
0653     return {};
0654 }
0655 
0656 QString QzTools::getOpenFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
0657 {
0658     Settings settings;
0659     settings.beginGroup(QSL("FileDialogPaths"));
0660 
0661     QString lastDir = settings.value(name, QString()).toString();
0662     QString fileName = getFilename(dir);
0663 
0664     if (lastDir.isEmpty()) {
0665         lastDir = dir;
0666     }
0667     else {
0668         lastDir.append(QDir::separator() + fileName);
0669     }
0670 
0671     QString path = QFileDialog::getOpenFileName(parent, caption, lastDir, filter, selectedFilter, options);
0672 
0673     if (!path.isEmpty()) {
0674         settings.setValue(name, QFileInfo(path).absolutePath());
0675     }
0676 
0677     settings.endGroup();
0678     return path;
0679 }
0680 
0681 QStringList QzTools::getOpenFileNames(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
0682 {
0683     Settings settings;
0684     settings.beginGroup(QSL("FileDialogPaths"));
0685 
0686     QString lastDir = settings.value(name, QString()).toString();
0687     QString fileName = getFilename(dir);
0688 
0689     if (lastDir.isEmpty()) {
0690         lastDir = dir;
0691     }
0692     else {
0693         lastDir.append(QDir::separator() + fileName);
0694     }
0695 
0696     QStringList paths = QFileDialog::getOpenFileNames(parent, caption, lastDir, filter, selectedFilter, options);
0697 
0698     if (!paths.isEmpty()) {
0699         settings.setValue(name, QFileInfo(paths.at(0)).absolutePath());
0700     }
0701 
0702     settings.endGroup();
0703     return paths;
0704 }
0705 
0706 QString QzTools::getSaveFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options)
0707 {
0708     Settings settings;
0709     settings.beginGroup(QSL("FileDialogPaths"));
0710 
0711     QString lastDir = settings.value(name, QString()).toString();
0712     QString fileName = getFilename(dir);
0713 
0714     if (lastDir.isEmpty()) {
0715         lastDir = dir;
0716     }
0717     else {
0718         lastDir.append(QDir::separator() + fileName);
0719     }
0720 
0721     QString path = QFileDialog::getSaveFileName(parent, caption, lastDir, filter, selectedFilter, options);
0722 
0723     if (!path.isEmpty()) {
0724         settings.setValue(name, QFileInfo(path).absolutePath());
0725     }
0726 
0727     settings.endGroup();
0728     return path;
0729 }
0730 
0731 // Matches domain (assumes both pattern and domain not starting with dot)
0732 //  pattern = domain to be matched
0733 //  domain = site domain
0734 bool QzTools::matchDomain(const QString &pattern, const QString &domain)
0735 {
0736     if (pattern == domain) {
0737         return true;
0738     }
0739 
0740     if (!domain.endsWith(pattern)) {
0741         return false;
0742     }
0743 
0744     int index = domain.indexOf(pattern);
0745 
0746     return index > 0 && domain[index - 1] == QLatin1Char('.');
0747 }
0748 
0749 QKeySequence QzTools::actionShortcut(const QKeySequence &shortcut, const QKeySequence &fallBack, const QKeySequence &shortcutRtl, const QKeySequence &fallbackRtl)
0750 {
0751     if (QApplication::isRightToLeft() && (!shortcutRtl.isEmpty() || !fallbackRtl.isEmpty()))
0752         return shortcutRtl.isEmpty() ? fallbackRtl : shortcutRtl;
0753 
0754     return shortcut.isEmpty() ? fallBack : shortcut;
0755 }
0756 
0757 static inline bool isQuote(const QChar &c)
0758 {
0759     return (c == QLatin1Char('"') || c == QLatin1Char('\''));
0760 }
0761 
0762 // Function  splits command line into arguments
0763 // eg. /usr/bin/foo -o test -b "bar bar" -s="sed sed"
0764 //  => '/usr/bin/foo' '-o' 'test' '-b' 'bar bar' '-s=sed sed'
0765 QStringList QzTools::splitCommandArguments(const QString &command)
0766 {
0767     QString line = command.trimmed();
0768 
0769     if (line.isEmpty()) {
0770         return {};
0771     }
0772 
0773     QChar SPACE(QL1C(' '));
0774     QChar EQUAL(QL1C('='));
0775     QChar BSLASH(QL1C('\\'));
0776     QChar QUOTE(QL1C('"'));
0777     QStringList r;
0778 
0779     int equalPos = -1; // Position of = in opt="value"
0780     int startPos = isQuote(line.at(0)) ? 1 : 0;
0781     bool inWord = !isQuote(line.at(0));
0782     bool inQuote = !inWord;
0783 
0784     if (inQuote) {
0785         QUOTE = line.at(0);
0786     }
0787 
0788     const int strlen = line.length();
0789     for (int i = 0; i < strlen; ++i) {
0790         const QChar c = line.at(i);
0791 
0792         if (inQuote && c == QUOTE && i > 0 && line.at(i - 1) != BSLASH) {
0793             QString str = line.mid(startPos, i - startPos);
0794             if (equalPos > -1) {
0795                 str.remove(equalPos - startPos + 1, 1);
0796             }
0797 
0798             inQuote = false;
0799             if (!str.isEmpty()) {
0800                 r.append(str);
0801             }
0802             continue;
0803         }
0804         else if (!inQuote && isQuote(c)) {
0805             inQuote = true;
0806             QUOTE = c;
0807 
0808             if (!inWord) {
0809                 startPos = i + 1;
0810             }
0811             else if (i > 0 && line.at(i - 1) == EQUAL) {
0812                 equalPos = i - 1;
0813             }
0814         }
0815 
0816         if (inQuote) {
0817             continue;
0818         }
0819 
0820         if (inWord && (c == SPACE || i == strlen - 1)) {
0821             int len = (i == strlen - 1) ? -1 : i - startPos;
0822             const QString str = line.mid(startPos, len);
0823 
0824             inWord = false;
0825             if (!str.isEmpty()) {
0826                 r.append(str);
0827             }
0828         }
0829         else if (!inWord && c != SPACE) {
0830             inWord = true;
0831             startPos = i;
0832         }
0833     }
0834 
0835     // Unmatched quote
0836     if (inQuote) {
0837         return {};
0838     }
0839 
0840     return r;
0841 }
0842 
0843 bool QzTools::startExternalProcess(const QString &executable, const QString &args)
0844 {
0845     const QStringList arguments = splitCommandArguments(args);
0846 
0847     bool success = QProcess::startDetached(executable, arguments);
0848 
0849     if (!success) {
0850         QString info = QSL("<ul><li><b>%1</b>%2</li><li><b>%3</b>%4</li></ul>");
0851         info = info.arg(QObject::tr("Executable: "), executable,
0852                         QObject::tr("Arguments: "), arguments.join(QLatin1Char(' ')));
0853 
0854         QMessageBox::critical(nullptr, QObject::tr("Cannot start external program"),
0855                               QObject::tr("Cannot start external program! %1").arg(info));
0856     }
0857 
0858     return success;
0859 }
0860 
0861 #ifdef QZ_WS_X11
0862 static xcb_connection_t *getXcbConnection()
0863 {
0864     const QNativeInterface::QX11Application *x11App = qApp->nativeInterface<QNativeInterface::QX11Application>();
0865     if (x11App == nullptr)
0866         return nullptr;
0867     return x11App->connection();
0868 }
0869 #endif
0870 
0871 void QzTools::setWmClass(const QString &name, const QWidget* widget)
0872 {
0873 #ifdef QZ_WS_X11
0874     if (QGuiApplication::platformName() != QL1S("xcb"))
0875         return;
0876 
0877     xcb_connection_t *connection = getXcbConnection();
0878     if (connection == nullptr)
0879         return;
0880 
0881     const QByteArray nameData = name.toUtf8();
0882     const QByteArray classData = mApp->wmClass().isEmpty() ? QByteArrayLiteral("Falkon") : mApp->wmClass();
0883 
0884     uint32_t class_len = nameData.length() + 1 + classData.length() + 1;
0885     char *class_hint = (char*) malloc(class_len);
0886 
0887     qstrcpy(class_hint, nameData.constData());
0888     qstrcpy(class_hint + nameData.length() + 1, classData.constData());
0889 
0890     xcb_change_property(connection, XCB_PROP_MODE_REPLACE, widget->winId(),
0891                         XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, class_len, class_hint);
0892 
0893     free(class_hint);
0894 
0895 #else
0896     Q_UNUSED(name)
0897     Q_UNUSED(widget)
0898 #endif
0899 }
0900 
0901 QString QzTools::operatingSystem()
0902 {
0903 #ifdef Q_OS_MACOS
0904     QString str = QSL("Mac OS X");
0905 
0906     SInt32 majorVersion;
0907     SInt32 minorVersion;
0908 
0909     if (Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr && Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr) {
0910         str.append(QSL(" %1.%2").arg(majorVersion).arg(minorVersion));
0911     }
0912 
0913     return str;
0914 #endif
0915 #ifdef Q_OS_LINUX
0916     return QSL("Linux");
0917 #endif
0918 #ifdef Q_OS_BSD4
0919     return QSL("BSD 4.4");
0920 #endif
0921 #ifdef Q_OS_BSDI
0922     return QSL("BSD/OS");
0923 #endif
0924 #ifdef Q_OS_FREEBSD
0925     return QSL("FreeBSD");
0926 #endif
0927 #ifdef Q_OS_HPUX
0928     return QSL("HP-UX");
0929 #endif
0930 #ifdef Q_OS_HURD
0931     return QSL("GNU Hurd");
0932 #endif
0933 #ifdef Q_OS_LYNX
0934     return QSL("LynxOS");
0935 #endif
0936 #ifdef Q_OS_NETBSD
0937     return QSL("NetBSD");
0938 #endif
0939 #ifdef Q_OS_OS2
0940     return QSL("OS/2");
0941 #endif
0942 #ifdef Q_OS_OPENBSD
0943     return QSL("OpenBSD");
0944 #endif
0945 #ifdef Q_OS_OSF
0946     return QSL("HP Tru64 UNIX");
0947 #endif
0948 #ifdef Q_OS_SOLARIS
0949     return QSL("Sun Solaris");
0950 #endif
0951 #ifdef Q_OS_UNIXWARE
0952     return QSL("UnixWare 7 / Open UNIX 8");
0953 #endif
0954 #ifdef Q_OS_UNIX
0955     return QSL("Unix");
0956 #endif
0957 #ifdef Q_OS_HAIKU
0958     return QSL("Haiku");
0959 #endif
0960 #ifdef Q_OS_WIN32
0961     QString str = QSL("Windows");
0962 
0963     switch (QSysInfo::windowsVersion()) {
0964     case QSysInfo::WV_NT:
0965         str.append(QSL(" NT"));
0966         break;
0967 
0968     case QSysInfo::WV_2000:
0969         str.append(QSL(" 2000"));
0970         break;
0971 
0972     case QSysInfo::WV_XP:
0973         str.append(QSL(" XP"));
0974         break;
0975     case QSysInfo::WV_2003:
0976         str.append(QSL(" XP Pro x64"));
0977         break;
0978 
0979     case QSysInfo::WV_VISTA:
0980         str.append(QSL(" Vista"));
0981         break;
0982 
0983     case QSysInfo::WV_WINDOWS7:
0984         str.append(QSL(" 7"));
0985         break;
0986 
0987     case QSysInfo::WV_WINDOWS8:
0988         str.append(QSL(" 8"));
0989         break;
0990 
0991     case QSysInfo::WV_WINDOWS8_1:
0992         str.append(QSL(" 8.1"));
0993         break;
0994 
0995     case QSysInfo::WV_WINDOWS10:
0996         str.append(QSL(" 10"));
0997         break;
0998 
0999     default:
1000         break;
1001     }
1002 
1003     return str;
1004 #endif
1005 }
1006 
1007 QString QzTools::cpuArchitecture()
1008 {
1009     return QSysInfo::currentCpuArchitecture();
1010 }
1011 
1012 QString QzTools::operatingSystemLong()
1013 {
1014     const QString arch = cpuArchitecture();
1015     if (arch.isEmpty())
1016         return QzTools::operatingSystem();
1017     return QzTools::operatingSystem() + QSL(" ") + arch;
1018 }
1019 
1020 void QzTools::paintDropIndicator(QWidget *widget, const QRect &r)
1021 {
1022     // Modified code from KFilePlacesView
1023     QColor color = widget->palette().brush(QPalette::Normal, QPalette::Highlight).color();
1024     const int x = (r.left() + r.right()) / 2;
1025     const int thickness = qRound(r.width() / 2.0);
1026     int alpha = 255;
1027     const int alphaDec = alpha / (thickness + 1);
1028     QStylePainter p(widget);
1029     for (int i = 0; i < thickness; i++) {
1030         color.setAlpha(alpha);
1031         alpha -= alphaDec;
1032         p.setPen(color);
1033         p.drawLine(x - i, r.top(), x - i, r.bottom());
1034         p.drawLine(x + i, r.top(), x + i, r.bottom());
1035     }
1036 }