Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
utils.cpp 31.32 KiB
/*
 * Copyright (C) 2015-2020 by Savoir-faire Linux
 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com
 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "utils.h"

#ifdef Q_OS_WIN
#include <lmcons.h>
#include <shlguid.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <shobjidl.h>
#include <windows.h>
#endif

#include "globalsystemtray.h"
#include "jamiavatartheme.h"
#include "lrcinstance.h"
#include "pixbufmanipulator.h"
#include "version.h"

#include <globalinstances.h>
#include <qrencode.h>

#include <QApplication>
#include <QBitmap>
#include <QErrorMessage>
#include <QFile>
#include <QMessageBox>
#include <QObject>
#include <QPainter>
#include <QPropertyAnimation>
#include <QScreen>
#include <QStackedWidget>
#include <QSvgRenderer>
#include <QTranslator>
#include <QtConcurrent/QtConcurrent>

bool
Utils::CreateStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
    TCHAR szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);

    std::wstring programPath(szPath);

    TCHAR startupPath[MAX_PATH];
    SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);

    std::wstring linkPath(startupPath);
    linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
    return Utils::CreateLink(programPath.c_str(), linkPath.c_str());
#else
    return true;
#endif
}

bool
Utils::CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink)
{
#ifdef Q_OS_WIN
    HRESULT hres;
    IShellLink *psl;

    hres = CoCreateInstance(CLSID_ShellLink,
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            IID_IShellLink,
                            (LPVOID *) &psl);
    if (SUCCEEDED(hres)) {
        IPersistFile *ppf;
        psl->SetPath(lpszPathObj);
        psl->SetArguments(TEXT("--minimized"));

        hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *) &ppf);
        if (SUCCEEDED(hres)) {
            hres = ppf->Save(lpszPathLink, TRUE);
            ppf->Release();
        }
        psl->Release();
    }
    return hres;
#else
    Q_UNUSED(lpszPathObj)
    Q_UNUSED(lpszPathLink)
    return true;
#endif
}

void
Utils::DeleteStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
    TCHAR startupPath[MAX_PATH];
    SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);

    std::wstring linkPath(startupPath);
    linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));

    DeleteFile(linkPath.c_str());
#endif
}

bool
Utils::CheckStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
    TCHAR startupPath[MAX_PATH];
    SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);

    std::wstring linkPath(startupPath);
    linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
    return PathFileExists(linkPath.c_str());
#else
    return true;
#endif
}

const char *
Utils::WinGetEnv(const char *name)
{
#ifdef Q_OS_WIN
    const DWORD buffSize = 65535;
    static char buffer[buffSize];
    if (GetEnvironmentVariableA(name, buffer, buffSize)) {
        return buffer;
    } else {
        return 0;
    }
#else
    return 0;
#endif
}

void
Utils::removeOldVersions()
{
#ifdef Q_OS_WIN
    /*
     * As per: https://git.jami.net/savoirfairelinux/ring-client-windows/issues/429
     * NB: As only the 64-bit version of this application is distributed, we will only
     * remove 1. the configuration reg keys for Ring-x64, 2. the startup links for Ring,
     * 3. the winsparkle reg keys. The NSIS uninstall reg keys for Jami-x64 are removed
     * by the MSI installer.
     * Uninstallation of Ring, either 32 or 64 bit, is left to the user.
     * The current version of Jami will attempt to kill Ring.exe upon start if a startup
     * link is found.
     */
    QString node64 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node";
    QString hkcuSoftwareKey = "HKEY_CURRENT_USER\\Software\\";
    QString uninstKey = "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
    QString company = "Savoir-Faire Linux";

    /*
     * 1. Configuration reg keys for Ring-x64.
     */
    QSettings(hkcuSoftwareKey + "jami.net\\Ring", QSettings::NativeFormat).remove("");
    QSettings(hkcuSoftwareKey + "ring.cx", QSettings::NativeFormat).remove("");
    /*
     * 2. Unset Ring as a startup application.
     */
    if (Utils::CheckStartupLink(TEXT("Ring"))) {
        qDebug() << "Found startup link for Ring. Removing it and killing Ring.exe.";
        Utils::DeleteStartupLink(TEXT("Ring"));
        QProcess::execute("taskkill /im Ring.exe /f");
    }
    /*
     * 3. Remove registry entries for winsparkle(both Jami-x64 and Ring-x64).
     */
    QSettings(hkcuSoftwareKey + company, QSettings::NativeFormat).remove("");
#else
    return;
#endif
}

QString
Utils::GetRingtonePath()
{
#ifdef Q_OS_WIN
    TCHAR workingDirectory[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, workingDirectory);

    QString ringtonePath = QString::fromWCharArray(workingDirectory);
    ringtonePath += QStringLiteral("\\ringtones\\default.opus");
    return ringtonePath;
#else
    return QString("/usr/local");
#endif
}

QString
Utils::GenGUID()
{
#ifdef Q_OS_WIN
    GUID gidReference;
    wchar_t *str;
    HRESULT hCreateGuid = CoCreateGuid(&gidReference);
    if (hCreateGuid == S_OK) {
        StringFromCLSID(gidReference, &str);
        auto gStr = QString::fromWCharArray(str);
        return gStr.remove("{").remove("}").toLower();
    } else
        return QString();
#else
    return QString("");
#endif
}

QString
Utils::GetISODate()
{
#ifdef Q_OS_WIN
    SYSTEMTIME lt;
    GetSystemTime(&lt);
    return QString("%1-%2-%3T%4:%5:%6Z")
        .arg(lt.wYear)
        .arg(lt.wMonth, 2, 10, QChar('0'))
        .arg(lt.wDay, 2, 10, QChar('0'))
        .arg(lt.wHour, 2, 10, QChar('0'))
        .arg(lt.wMinute, 2, 10, QChar('0'))
        .arg(lt.wSecond, 2, 10, QChar('0'));
#else
    return QString();
#endif
}

void
Utils::InvokeMailto(const QString &subject, const QString &body, const QString &attachement)
{
#ifdef Q_OS_WIN
    HKEY hKey;
    LONG lRes = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"mailto", 0, KEY_READ, &hKey);
    if (lRes != ERROR_FILE_NOT_FOUND) {
        auto addr = QString("mailto:?subject=%1&body=%2").arg(subject).arg(body);
        if (not attachement.isEmpty())
            addr += QString("&attachement=%1").arg(attachement);
        ShellExecute(nullptr, L"open", addr.toStdWString().c_str(), NULL, NULL, SW_SHOWNORMAL);
    } else {
        QErrorMessage errorMessage;
        errorMessage.showMessage(QObject::tr("No default mail client found"));
    }
#endif
}

QString
Utils::getContactImageString(const QString &accountId, const QString &uid)
{
    return QString::fromLatin1(
        Utils::QImageToByteArray(
            Utils::conversationPhoto(uid, LRCInstance::getAccountInfo(accountId)))
            .toBase64()
            .data());
}

QImage
Utils::getCirclePhoto(const QImage original, int sizePhoto)
{
    QImage target(sizePhoto, sizePhoto, QImage::Format_ARGB32_Premultiplied);
    target.fill(Qt::transparent);

    QPainter painter(&target);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    painter.setBrush(QBrush(Qt::white));
    auto scaledPhoto = original
                           .scaled(sizePhoto,
                                   sizePhoto,
                                   Qt::KeepAspectRatioByExpanding,
                                   Qt::SmoothTransformation)
                           .convertToFormat(QImage::Format_ARGB32_Premultiplied);
    int margin = 0;
    if (scaledPhoto.width() > sizePhoto) {
        margin = (scaledPhoto.width() - sizePhoto) / 2;
    }
    painter.drawEllipse(0, 0, sizePhoto, sizePhoto);
    painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
    painter.drawImage(0, 0, scaledPhoto, margin, 0);
    return target;
}

void
Utils::setStackWidget(QStackedWidget *stack, QWidget *widget)
{
    if (stack->indexOf(widget) != -1 && stack->currentWidget() != widget) {
        stack->setCurrentWidget(widget);
    }
}

void
Utils::showSystemNotification(QWidget *widget,
                              const QString &message,
                              long delay,
                              const QString &triggeredAccountId)
{
    QSettings settings("jami.net", "Jami");
    if (settings.value(SettingsKey::enableNotifications).toBool()) {
        GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
        GlobalSystemTray::instance().showMessage(message, "", QIcon(":images/jami.png"));
        QApplication::alert(widget, delay);
    }
}

void
Utils::showSystemNotification(QWidget *widget,
                              const QString &sender,
                              const QString &message,
                              long delay,
                              const QString &triggeredAccountId)
{
    QSettings settings("jami.net", "Jami");
    if (settings.value(SettingsKey::enableNotifications).toBool()) {
        GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
        GlobalSystemTray::instance().showMessage(sender, message, QIcon(":images/jami.png"));
        QApplication::alert(widget, delay);
    }
}

QSize
Utils::getRealSize(QScreen *screen)
{
#ifdef Q_OS_WIN
    DEVMODE dmThisScreen;
    ZeroMemory(&dmThisScreen, sizeof(dmThisScreen));
    EnumDisplaySettings((const wchar_t *) screen->name().utf16(),
                        ENUM_CURRENT_SETTINGS,
                        (DEVMODE *) &dmThisScreen);
    return QSize(dmThisScreen.dmPelsWidth, dmThisScreen.dmPelsHeight);
#else
    return {};
#endif
}
void
Utils::forceDeleteAsync(const QString &path)
{
    /*
     * Keep deleting file until the process holding it let go,
     * or the file itself does not exist anymore.
     */
    QtConcurrent::run([path] {
        QFile file(path);
        if (!QFile::exists(path))
            return;
        int retries{0};
        while (!file.remove() && retries < 5) {
            qDebug().noquote() << "\n" << file.errorString() << "\n";
            QThread::msleep(10);
            ++retries;
        }
    });
}

UtilsAdapter &
UtilsAdapter::instance()
{
    static auto instance = new UtilsAdapter;
    return *instance;
}

QString
Utils::getChangeLog()
{
    QString logs;
    QFile changeLogFile(":/changelog.html");
    if (!changeLogFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug().noquote() << " Change log file failed to load";
        return {};
    }
    QTextStream in(&changeLogFile);
    in.setCodec("UTF-8");
    while (!in.atEnd()) {
        logs += in.readLine();
    }
    return logs;
}

QString
Utils::getProjectCredits()
{
    QString credits;
    QFile projectCreditsFile(":/projectcredits.html");
    if (!projectCreditsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug().noquote() << " Project Credits failed to load";
        return {};
    }
    QTextStream in(&projectCreditsFile);
    in.setCodec("UTF-8");
    while (!in.atEnd()) {
        QString currentLine = in.readLine();
        if (credits.isEmpty()) {
            credits += "<h3 align=\"center\" style=\" margin-top:0px; "
                       + QString("margin-bottom:0px; margin-left:0px; margin-right:0px; ")
                       + "-qt-block-indent:0; text-indent:0px;\"><span style=\" font-weight:600;\">"
                       + UtilsAdapter::tr("Created by:") + "</span></h3>";
        } else if (currentLine.contains("Marianne Forget")) {
            credits
                += "<h3 align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
                   + QString(
                       "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
                   + "<span style=\" font-weight:600;\">" + UtilsAdapter::tr("Artwork by:")
                   + "</span></h3>";
        }
        credits += currentLine;
    }
    credits += "<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
               + QString(
                   "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
               + UtilsAdapter::tr("Based on the SFLPhone project") + "</p>";

    return credits;
}

void
Utils::cleanUpdateFiles()
{
    /*
     * Delete all logs and msi in the %TEMP% directory before launching.
     */
    QString dir = QString(Utils::WinGetEnv("TEMP"));
    QDir log_dir(dir, {"jami*.log"});
    for (const QString &filename : log_dir.entryList()) {
        log_dir.remove(filename);
    }
    QDir msi_dir(dir, {"jami*.msi"});
    for (const QString &filename : msi_dir.entryList()) {
        msi_dir.remove(filename);
    }
    QDir version_dir(dir, {"version"});
    for (const QString &filename : version_dir.entryList()) {
        version_dir.remove(filename);
    }
}

void
Utils::checkForUpdates(bool withUI, QWidget *parent)
{
    Q_UNUSED(withUI)
    Q_UNUSED(parent)
    /*
     * TODO: check update logic.
     */
}

void
Utils::applyUpdates(bool updateToBeta, QWidget *parent)
{
    Q_UNUSED(updateToBeta)
    Q_UNUSED(parent)
    /*
     * TODO: update logic.
     */
}

inline QString
removeEndlines(const QString &str)
{
    QString trimmed(str);
    trimmed.remove(QChar('\n'));
    trimmed.remove(QChar('\r'));
    return trimmed;
}

QString
Utils::bestIdForConversation(const lrc::api::conversation::Info &conv,
                             const lrc::api::ConversationModel &model)
{
    auto contact = model.owner.contactModel->getContact(conv.participants[0]);
    if (!contact.registeredName.isEmpty()) {
        return removeEndlines(contact.registeredName);
    }
    return removeEndlines(contact.profileInfo.uri);
}

QString
Utils::bestIdForAccount(const lrc::api::account::Info &account)
{
    if (!account.registeredName.isEmpty()) {
        return removeEndlines(account.registeredName);
    }
    return removeEndlines(account.profileInfo.uri);
}

QString
Utils::bestNameForAccount(const lrc::api::account::Info &account)
{
    if (account.profileInfo.alias.isEmpty()) {
        return bestIdForAccount(account);
    }
    return account.profileInfo.alias;
}

QString
Utils::bestIdForContact(const lrc::api::contact::Info &contact)
{
    if (!contact.registeredName.isEmpty()) {
        return removeEndlines(contact.registeredName);
    }
    return removeEndlines(contact.profileInfo.uri);
}

QString
Utils::bestNameForContact(const lrc::api::contact::Info &contact)
{
    auto alias = removeEndlines(contact.profileInfo.alias);
    if (alias.length() == 0) {
        return bestIdForContact(contact);
    }
    return alias;
}

QString
Utils::bestNameForConversation(const lrc::api::conversation::Info &conv,
                               const lrc::api::ConversationModel &model)
{
    try {
        auto contact = model.owner.contactModel->getContact(conv.participants[0]);
        auto alias = removeEndlines(contact.profileInfo.alias);
        if (alias.length() == 0) {
            return bestIdForConversation(conv, model);
        }
        return alias;
    } catch (...) {
    }
    return {};
}

/*
 * Returns empty string if only infoHash is available, second best identifier otherwise.
 */
QString
Utils::secondBestNameForAccount(const lrc::api::account::Info &account)
{
    auto alias = removeEndlines(account.profileInfo.alias);
    auto registeredName = removeEndlines(account.registeredName);
    auto infoHash = account.profileInfo.uri;

    if (!alias.length() == 0) {
        /*
         * If alias exists.
         */
        if (!registeredName.length() == 0) {
            /*
             * If registeredName exists.
             */
            return registeredName;
        } else {
            return infoHash;
        }
    } else {
        if (!registeredName.length() == 0) {
            /*
             * If registeredName exists.
             */
            return infoHash;
        } else {
            return "";
        }
    }
}

lrc::api::profile::Type
Utils::profileType(const lrc::api::conversation::Info &conv,
                   const lrc::api::ConversationModel &model)
{
    try {
        auto contact = model.owner.contactModel->getContact(conv.participants[0]);
        return contact.profileInfo.type;
    } catch (...) {
        return lrc::api::profile::Type::INVALID;
    }
}

std::string
Utils::formatTimeString(const std::time_t &timestamp)
{
    std::time_t now = std::time(nullptr);
    char interactionDay[64];
    char nowDay[64];
    std::strftime(interactionDay, sizeof(interactionDay), "%D", std::localtime(&timestamp));
    std::strftime(nowDay, sizeof(nowDay), "%D", std::localtime(&now));
    if (std::string(interactionDay) == std::string(nowDay)) {
        char interactionTime[64];
        std::strftime(interactionTime, sizeof(interactionTime), "%R", std::localtime(&timestamp));
        return interactionTime;
    } else {
        return interactionDay;
    }
}

bool
Utils::isInteractionGenerated(const lrc::api::interaction::Type &type)
{
    return type == lrc::api::interaction::Type::CALL
           || type == lrc::api::interaction::Type::CONTACT;
}

bool
Utils::isContactValid(const QString &contactUid, const lrc::api::ConversationModel &model)
{
    auto contact = model.owner.contactModel->getContact(contactUid);
    return (contact.profileInfo.type == lrc::api::profile::Type::PENDING
            || contact.profileInfo.type == lrc::api::profile::Type::TEMPORARY
            || contact.profileInfo.type == lrc::api::profile::Type::RING
            || contact.profileInfo.type == lrc::api::profile::Type::SIP)
           && !contact.profileInfo.uri.isEmpty();
}

bool
Utils::getReplyMessageBox(QWidget *widget, const QString &title, const QString &text)
{
    if (QMessageBox::question(widget, title, text, QMessageBox::Yes | QMessageBox::No)
        == QMessageBox::Yes)
        return true;
    return false;
}

QImage
Utils::conversationPhoto(const QString &convUid,
                         const lrc::api::account::Info &accountInfo,
                         bool filtered)
{
    auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountInfo.id, filtered);
    if (!convInfo.uid.isEmpty()) {
        return GlobalInstances::pixmapManipulator()
            .decorationRole(convInfo, accountInfo)
            .value<QImage>();
    }
    return QImage();
}

QColor
Utils::getAvatarColor(const QString &canonicalUri)
{
    if (canonicalUri.isEmpty()) {
        return JamiAvatarTheme::defaultAvatarColor_;
    }
    auto h = QString(
        QCryptographicHash::hash(canonicalUri.toLocal8Bit(), QCryptographicHash::Md5).toHex());
    if (h.isEmpty() || h.isNull()) {
        return JamiAvatarTheme::defaultAvatarColor_;
    }
    auto colorIndex = std::string("0123456789abcdef").find(h.at(0).toLatin1());
    return JamiAvatarTheme::avatarColors_[colorIndex];
}

/* Generate a QImage representing a dummy user avatar, when user doesn't provide it.
 * Current rendering is a flat colored circle with a centered letter.
 * The color of the letter is computed from the circle color to be visible whaterver be the circle color.
 */
QImage
Utils::fallbackAvatar(const QSize size, const QString &canonicalUriStr, const QString &letterStr)
{
    /*
     * We start with a transparent avatar.
     */
    QImage avatar(size, QImage::Format_ARGB32);
    avatar.fill(Qt::transparent);

    /*
     * We pick a color based on the passed character.
     */
    QColor avColor = getAvatarColor(canonicalUriStr);

    /*
     * We draw a circle with this color.
     */
    QPainter painter(&avatar);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    painter.setPen(Qt::transparent);
    painter.setBrush(avColor.lighter(110));
    painter.drawEllipse(avatar.rect());

    /*
     * If a letter was passed, then we paint a letter in the circle,
     * otherwise we draw the default avatar icon.
     */
    QString letterStrCleaned(letterStr);
    letterStrCleaned.remove(QRegExp("[\\n\\t\\r]"));
    if (!letterStr.isEmpty()) {
        auto unicode = letterStr.toUcs4().at(0);
        if (unicode >= 0x1F000 && unicode <= 0x1FFFF) {
            /*
             * Is Emoticon.
             */
            auto letter = QString::fromUcs4(&unicode, 1);
            QFont font(QStringLiteral("Segoe UI Emoji"), avatar.height() / 2.66667, QFont::Medium);
            painter.setFont(font);
            QRect emojiRect(avatar.rect());
            emojiRect.moveTop(-6);
            painter.drawText(emojiRect, letter, QTextOption(Qt::AlignCenter));
        } else if (unicode >= 0x0000 && unicode <= 0x00FF) {
            /*
             * Is Basic Latin.
             */
            auto letter = letterStr.at(0).toUpper();
            QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
            painter.setFont(font);
            painter.setPen(Qt::white);
            painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
        } else {
            auto letter = QString::fromUcs4(&unicode, 1);
            QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
            painter.setFont(font);
            painter.setPen(Qt::white);
            painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
        }
    } else {
        QRect overlayRect = avatar.rect();
        qreal margin = (0.05 * overlayRect.width());
        overlayRect.moveLeft(overlayRect.left() + margin * 0.5);
        overlayRect.moveTop(overlayRect.top() + margin * 0.5);
        overlayRect.setWidth(overlayRect.width() - margin);
        overlayRect.setHeight(overlayRect.height() - margin);
        painter.drawPixmap(overlayRect, QPixmap(":/images/default_avatar_overlay.svg"));
    }

    return avatar;
}

QImage
Utils::fallbackAvatar(const QSize size, const std::string &alias, const std::string &uri)
{
    return fallbackAvatar(size, QString::fromStdString(uri), QString::fromStdString(alias));
}

QByteArray
Utils::QImageToByteArray(QImage image)
{
    QByteArray ba;
    QBuffer buffer(&ba);
    buffer.open(QIODevice::WriteOnly);
    image.save(&buffer, "PNG");
    return ba;
}

QImage
Utils::cropImage(const QImage &img)
{
    QRect rect;
    auto w = img.width();
    auto h = img.height();
    if (w > h) {
        return img.copy({(w - h) / 2, 0, h, h});
    }
    return img.copy({0, (h - w) / 2, w, w});
}

QPixmap
Utils::pixmapFromSvg(const QString &svg_resource, const QSize &size)
{
    QSvgRenderer svgRenderer(svg_resource);
    QPixmap pixmap(size);
    pixmap.fill(Qt::transparent);
    QPainter pixPainter(&pixmap);
    svgRenderer.render(&pixPainter);
    return pixmap;
}

QImage
Utils::setupQRCode(QString ringID, int margin)
{
    auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
                                     0,            // Let the version be decided by libqrencode
                                     QR_ECLEVEL_L, // Lowest level of error correction
                                     QR_MODE_8,    // 8-bit data mode
                                     1);
    if (not rcode) {
        qWarning() << "Failed to generate QR code: " << strerror(errno);
        return QImage();
    }

    int qrwidth = rcode->width + margin * 2;
    QImage result(QSize(qrwidth, qrwidth), QImage::Format_Mono);
    QPainter painter;
    painter.begin(&result);
    painter.setClipRect(QRect(0, 0, qrwidth, qrwidth));
    painter.setPen(QPen(Qt::black, 0.1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
    painter.setBrush(Qt::black);
    painter.fillRect(QRect(0, 0, qrwidth, qrwidth), Qt::white);
    unsigned char *p;
    p = rcode->data;
    for (int y = 0; y < rcode->width; y++) {
        unsigned char *row = (p + (y * rcode->width));
        for (int x = 0; x < rcode->width; x++) {
            if (*(row + x) & 0x1) {
                painter.drawRect(margin + x, margin + y, 1, 1);
            }
        }
    }
    painter.end();
    QRcode_free(rcode);
    return result;
}

float
Utils::getCurrentScalingRatio()
{
    return CURRENT_SCALING_RATIO;
}

void
Utils::setCurrentScalingRatio(float ratio)
{
    CURRENT_SCALING_RATIO = ratio;
}

QString
Utils::formattedTime(int duration)
{
    if (duration == 0)
        return {};
    std::string formattedString;
    auto minutes = duration / 60;
    auto seconds = duration % 60;
    if (minutes > 0) {
        formattedString += std::to_string(minutes) + ":";
        if (formattedString.length() == 2) {
            formattedString = "0" + formattedString;
        }
    } else {
        formattedString += "00:";
    }
    if (seconds < 10)
        formattedString += "0";
    formattedString += std::to_string(seconds);
    return QString::fromStdString(formattedString);
}

QByteArray
Utils::QByteArrayFromFile(const QString &filename)
{
    QFile file(filename);
    if (file.open(QIODevice::ReadOnly)) {
        return file.readAll();
    } else {
        qDebug() << "can't open file";
        return QByteArray();
    }
}

QPixmap
Utils::generateTintedPixmap(const QString &filename, QColor color)
{
    QPixmap px(filename);
    QImage tmpImage = px.toImage();
    for (int y = 0; y < tmpImage.height(); y++) {
        for (int x = 0; x < tmpImage.width(); x++) {
            color.setAlpha(tmpImage.pixelColor(x, y).alpha());
            tmpImage.setPixelColor(x, y, color);
        }
    }
    return QPixmap::fromImage(tmpImage);
}

QPixmap
Utils::generateTintedPixmap(const QPixmap &pix, QColor color)
{
    QPixmap px = pix;
    QImage tmpImage = px.toImage();
    for (int y = 0; y < tmpImage.height(); y++) {
        for (int x = 0; x < tmpImage.width(); x++) {
            color.setAlpha(tmpImage.pixelColor(x, y).alpha());
            tmpImage.setPixelColor(x, y, color);
        }
    }
    return QPixmap::fromImage(tmpImage);
}

QImage
Utils::scaleAndFrame(const QImage photo, const QSize &size)
{
    return photo.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

QImage
Utils::accountPhoto(const lrc::api::account::Info &accountInfo, const QSize &size)
{
    QImage photo;
    if (!accountInfo.profileInfo.avatar.isEmpty()) {
        QByteArray ba = accountInfo.profileInfo.avatar.toLocal8Bit();
        photo = GlobalInstances::pixmapManipulator().personPhoto(ba, nullptr).value<QImage>();
    } else {
        auto bestId = bestIdForAccount(accountInfo);
        auto bestName = bestNameForAccount(accountInfo);
        QString letterStr = bestId == bestName ? QString() : bestName;
        QString prefix = accountInfo.profileInfo.type == lrc::api::profile::Type::RING ? "ring:"
                                                                                       : "sip:";
        photo = fallbackAvatar(size, prefix + accountInfo.profileInfo.uri, letterStr);
    }
    return scaleAndFrame(photo, size);
}
QString
Utils::humanFileSize(qint64 fileSize)
{
    float fileSizeF = static_cast<float>(fileSize);
    float thresh = 1024;

    if (abs(fileSizeF) < thresh) {
        return QString::number(fileSizeF) + " B";
    }
    QString units[] = {"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
    int unit_position = -1;
    do {
        fileSizeF /= thresh;
        ++unit_position;
    } while (abs(fileSizeF) >= thresh && unit_position < units->size() - 1);
    /*
     * Round up to two decimal.
     */
    fileSizeF = roundf(fileSizeF * 100) / 100;
    return QString::number(fileSizeF) + " " + units[unit_position];
}

const QString
UtilsAdapter::getBestName(const QString &accountId, const QString &uid)
{
    auto convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
    return Utils::bestNameForConversation(LRCInstance::getConversationFromConvUid(uid, accountId),
                                          *convModel);
}

const QString
UtilsAdapter::getBestId(const QString &accountId, const QString &uid)
{
    auto convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
    return Utils::bestIdForConversation(LRCInstance::getConversationFromConvUid(uid, accountId),
                                        *convModel);
}

int
UtilsAdapter::getTotalUnreadMessages()
{
    int totalUnreadMessages{0};
    if (LRCInstance::getCurrentAccountInfo().profileInfo.type != lrc::api::profile::Type::SIP) {
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto ringConversations = convModel->getFilteredConversations(lrc::api::profile::Type::RING);
        std::for_each(ringConversations.begin(),
                      ringConversations.end(),
                      [&totalUnreadMessages, convModel](const auto &conversation) {
                          totalUnreadMessages += conversation.unreadMessages;
                      });
    }
    return totalUnreadMessages;
}

int
UtilsAdapter::getTotalPendingRequest()
{
    auto &accountInfo = LRCInstance::getCurrentAccountInfo();
    return accountInfo.contactModel->pendingRequestCount();
}

void
UtilsAdapter::setConversationFilter(const QString &filter)
{
    LRCInstance::getCurrentConversationModel()->setFilter(filter);
}

void
UtilsAdapter::clearConversationHistory(const QString &accountId, const QString &uid)
{
    LRCInstance::getAccountInfo(accountId).conversationModel->clearHistory(uid);
}

void
UtilsAdapter::removeConversation(const QString &accountId, const QString &uid, bool banContact)
{
    LRCInstance::getAccountInfo(accountId).conversationModel->removeConversation(uid, banContact);
}

const QString
UtilsAdapter::getCurrAccId()
{
    return LRCInstance::getCurrAccId();
}

const QStringList
UtilsAdapter::getCurrAccList()
{
    return LRCInstance::accountModel().getAccountList();
}

int
UtilsAdapter::getAccountListSize()
{
    return getCurrAccList().size();
}

void
UtilsAdapter::setCurrentCall(const QString &accountId, const QString &convUid)
{
    auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId);
    auto &accInfo = LRCInstance::getAccountInfo(accountId);
    accInfo.callModel->setCurrentCall(convInfo.callId);
}

void
UtilsAdapter::startPreviewing(bool force)
{
    LRCInstance::renderer()->startPreviewing(force);
}

void
UtilsAdapter::stopPreviewing()
{
    if (!LRCInstance::hasVideoCall()) {
        LRCInstance::renderer()->stopPreviewing();
    }
}

bool
UtilsAdapter::hasVideoCall()
{
    return LRCInstance::hasVideoCall();
}

const QString
UtilsAdapter::getCallId(const QString &accountId, const QString &convUid)
{
    auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId);
    if (convInfo.uid.isEmpty()) {
        return "";
    }

    auto call = LRCInstance::getCallInfoForConversation(convInfo, false);
    if (!call) {
        return "";
    }

    return call->id;
}

// returns true if name is valid registered name
bool
UtilsAdapter::validateRegNameForm(const QString &regName)
{
    QRegularExpression regExp(" ");

    if (regName.size() > 2 && !regName.contains(regExp)) {
        return true;

    } else {
        return false;
    }
}

QString
UtilsAdapter::getStringUTF8(QString string)
{
    return string.toUtf8();
}

QString
UtilsAdapter::getRecordQualityString(int value)
{
    return value ? QString::number(static_cast<float>(value) / 100, 'f', 1) + " Mbps" : "Default";
}

QString
UtilsAdapter::getCurrentPath()
{
    return QDir::currentPath();
}