File indexing completed on 2024-04-21 04:49:07

0001 /*
0002  * SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "application.h"
0008 
0009 #include "actionsmodel.h"
0010 #include "audiosettings.h"
0011 #include "customcommandsmodel.h"
0012 #include "generalsettings.h"
0013 #include "global.h"
0014 #include "haruna-version.h"
0015 #include "informationsettings.h"
0016 #include "mousesettings.h"
0017 #include "mpvitem.h"
0018 #include "mpvpreview.h"
0019 #include "mpvproperties.h"
0020 #include "playbacksettings.h"
0021 #include "playlistmodel.h"
0022 #include "playlistsettings.h"
0023 #include "recentfilesmodel.h"
0024 #include "subtitlesfoldersmodel.h"
0025 #include "subtitlessettings.h"
0026 #include "tracksmodel.h"
0027 #include "videosettings.h"
0028 #include "worker.h"
0029 
0030 #include <QApplication>
0031 #include <QCommandLineParser>
0032 #include <QDesktopServices>
0033 #include <QDir>
0034 #include <QFileInfo>
0035 #include <QFontDatabase>
0036 #include <QGuiApplication>
0037 #include <QMimeDatabase>
0038 #include <QQuickView>
0039 #include <QStandardPaths>
0040 #include <QStyle>
0041 #include <QStyleFactory>
0042 #include <QThread>
0043 
0044 #include <KAboutData>
0045 #include <KColorSchemeManager>
0046 #include <KConfig>
0047 #include <KConfigGroup>
0048 #include <KFileItem>
0049 #include <KFileMetaData/Properties>
0050 #include <KLocalizedString>
0051 #include <KWindowConfig>
0052 #include <KWindowSystem>
0053 
0054 Application *Application::instance()
0055 {
0056     static Application app;
0057     return &app;
0058 }
0059 
0060 Application::Application()
0061     : m_app(qApp)
0062     , m_config(KSharedConfig::openConfig(Global::instance()->appConfigFilePath()))
0063     , m_schemes(new KColorSchemeManager(this))
0064     , m_systemDefaultStyle(m_app->style()->objectName())
0065 {
0066     // used to hide playlist when mouse leaves the application
0067     // while moving between monitors while in fullscreen
0068     auto *appEventFilter = new ApplicationEventFilter();
0069     m_app->installEventFilter(appEventFilter);
0070     QObject::connect(appEventFilter, &ApplicationEventFilter::applicationMouseLeave, this, &Application::qmlApplicationMouseLeave);
0071     QObject::connect(appEventFilter, &ApplicationEventFilter::applicationMouseEnter, this, &Application::qmlApplicationMouseEnter);
0072 
0073     if (GeneralSettings::guiStyle() != QStringLiteral("System")) {
0074         QApplication::setStyle(GeneralSettings::guiStyle());
0075     }
0076 
0077     setupWorkerThread();
0078     setupAboutData();
0079     setupCommandLineParser();
0080     setupQmlSettingsTypes();
0081 
0082     connect(Global::instance(), &Global::error, this, &Application::error);
0083     connect(this, &Application::saveWindowGeometryAsync, Worker::instance(), &Worker::saveWindowGeometry, Qt::QueuedConnection);
0084 }
0085 
0086 void Application::setupWorkerThread()
0087 {
0088     auto worker = Worker::instance();
0089     auto thread = new QThread();
0090     worker->moveToThread(thread);
0091     QObject::connect(thread, &QThread::finished, worker, &Worker::deleteLater);
0092     QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
0093     thread->start();
0094 }
0095 
0096 void Application::setupAboutData()
0097 {
0098     KAboutData m_aboutData;
0099     m_aboutData.setComponentName(QStringLiteral("haruna"));
0100     m_aboutData.setDisplayName(i18nc("application title/display name", "Haruna Media Player"));
0101     m_aboutData.setVersion(Application::version().toUtf8());
0102     m_aboutData.setShortDescription(i18nc("@title", "A configurable media player."));
0103     m_aboutData.setLicense(KAboutLicense::GPL_V3);
0104     m_aboutData.setCopyrightStatement(i18nc("copyright statement", "(c) 2019-2023"));
0105     m_aboutData.setHomepage(QStringLiteral("https://haruna.kde.org"));
0106     m_aboutData.setBugAddress(QStringLiteral("https://bugs.kde.org/enter_bug.cgi?product=Haruna").toUtf8());
0107     m_aboutData.setDesktopFileName(QStringLiteral("org.kde.haruna"));
0108 
0109     m_aboutData.addAuthor(i18nc("@info:credit", "George Florea Bănuș"),
0110                           i18nc("@info:credit", "Developer"),
0111                           QStringLiteral("georgefb899@gmail.com"),
0112                           QStringLiteral("https://georgefb.com"));
0113 
0114     KAboutData::setApplicationData(m_aboutData);
0115 }
0116 
0117 void Application::setupCommandLineParser()
0118 {
0119     m_parser = new QCommandLineParser();
0120     m_aboutData.setupCommandLine(m_parser);
0121     m_parser->addPositionalArgument(QStringLiteral("file"), i18nc("@info:shell", "File to open"));
0122 
0123     QCommandLineOption ytdlFormatSelectionOption(QStringList() << QStringLiteral("ytdl-format-selection") << QStringLiteral("ytdlfs"),
0124                                                  i18nc("@info:shell",
0125                                                        "Allows to temporarily override the ytdl format selection setting. "
0126                                                        "Will be overwritten if the setting is changed through the GUI"),
0127                                                  i18nc("@info:shell", "bestvideo+bestaudio/best"),
0128                                                  QString());
0129     m_parser->addOption(ytdlFormatSelectionOption);
0130 
0131     m_parser->process(*m_app);
0132     m_aboutData.processCommandLine(m_parser);
0133 
0134     for (auto i = 0; i < m_parser->positionalArguments().size(); ++i) {
0135         addUrl(i, m_parser->positionalArguments().at(i));
0136     }
0137 }
0138 
0139 void Application::setupQmlSettingsTypes()
0140 {
0141     qmlRegisterSingletonInstance("org.kde.haruna.mpvproperties", 1, 0, "MpvProperties", MpvProperties::self());
0142     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "AudioSettings", AudioSettings::self());
0143     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "GeneralSettings", GeneralSettings::self());
0144     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "InformationSettings", InformationSettings::self());
0145     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "MouseSettings", MouseSettings::self());
0146     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "PlaybackSettings", PlaybackSettings::self());
0147     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "PlaylistSettings", PlaylistSettings::self());
0148     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "SubtitlesSettings", SubtitlesSettings::self());
0149     qmlRegisterSingletonInstance("org.kde.haruna.settings", 1, 0, "VideoSettings", VideoSettings::self());
0150 }
0151 
0152 void Application::restoreWindowGeometry(QQuickWindow *window) const
0153 {
0154     if (!GeneralSettings::rememberWindowGeometry()) {
0155         return;
0156     }
0157     KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
0158     KConfigGroup windowGroup(&dataResource, QStringLiteral("Window"));
0159     KWindowConfig::restoreWindowSize(window, windowGroup);
0160     KWindowConfig::restoreWindowPosition(window, windowGroup);
0161 }
0162 
0163 void Application::saveWindowGeometry(QQuickWindow *window)
0164 {
0165     Q_EMIT saveWindowGeometryAsync(window);
0166 }
0167 
0168 bool Application::urlExists(const QUrl &url)
0169 {
0170     return pathExists(url.toLocalFile());
0171 }
0172 
0173 bool Application::pathExists(const QString &path)
0174 {
0175     QFileInfo fileInfo(path);
0176     if (fileInfo.exists()) {
0177         return true;
0178     }
0179     return false;
0180 }
0181 
0182 QUrl Application::configFilePath()
0183 {
0184     auto file = Global::instance()->appConfigFilePath();
0185     if (pathExists(file)) {
0186         return pathToUrl(file);
0187     }
0188     return QUrl();
0189 }
0190 
0191 QUrl Application::ccConfigFilePath()
0192 {
0193     auto file = Global::instance()->appConfigFilePath(Global::ConfigFile::CustomCommands);
0194     if (pathExists(file)) {
0195         return pathToUrl(file);
0196     }
0197     return QUrl();
0198 }
0199 
0200 QUrl Application::configFolderPath()
0201 {
0202     auto folder = Global::instance()->appConfigDirPath();
0203     if (pathExists(folder)) {
0204         return pathToUrl(folder);
0205     }
0206     return QUrl();
0207 }
0208 
0209 QUrl Application::pathToUrl(const QString &path)
0210 {
0211     auto url = QUrl::fromUserInput(path);
0212     if (!url.isValid()) {
0213         return QUrl();
0214     }
0215     return url;
0216 }
0217 
0218 bool Application::configFolderExists()
0219 {
0220     QFileInfo fi(Global::instance()->appConfigDirPath());
0221     return fi.exists();
0222 }
0223 
0224 QString Application::version()
0225 {
0226     return QStringLiteral(HARUNA_VERSION_STRING);
0227 }
0228 
0229 bool Application::hasYoutubeDl()
0230 {
0231     return !youtubeDlExecutable().isEmpty();
0232 }
0233 
0234 QString Application::youtubeDlExecutable()
0235 {
0236     auto ytDlp = QStandardPaths::findExecutable(QStringLiteral("yt-dlp"));
0237     if (!ytDlp.isEmpty()) {
0238         return ytDlp;
0239     }
0240 
0241     auto youtubeDl = QStandardPaths::findExecutable(QStringLiteral("youtube-dl"));
0242     if (!youtubeDl.isEmpty()) {
0243         return youtubeDl;
0244     }
0245 
0246     return QString();
0247 }
0248 
0249 QUrl Application::parentUrl(const QString &path)
0250 {
0251     QUrl url(path);
0252     if (!url.isValid()) {
0253         return QUrl(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
0254     }
0255     QFileInfo fileInfo;
0256     if (url.isLocalFile()) {
0257         fileInfo.setFile(url.toLocalFile());
0258     } else {
0259         fileInfo.setFile(url.toString());
0260     }
0261     QUrl parentFolderUrl(fileInfo.absolutePath());
0262     parentFolderUrl.setScheme(QStringLiteral("file"));
0263 
0264     return parentFolderUrl;
0265 }
0266 
0267 bool Application::isYoutubePlaylist(const QString &path)
0268 {
0269     return path.contains(QStringLiteral("youtube.com/playlist?list"));
0270 }
0271 
0272 QString Application::formatTime(const double time)
0273 {
0274     int totalNumberOfSeconds = static_cast<int>(time);
0275     int seconds = totalNumberOfSeconds % 60;
0276     int minutes = (totalNumberOfSeconds / 60) % 60;
0277     int hours = (totalNumberOfSeconds / 60 / 60);
0278 
0279     QString hoursString = QStringLiteral("%1").arg(hours, 2, 10, QLatin1Char('0'));
0280     QString minutesString = QStringLiteral("%1").arg(minutes, 2, 10, QLatin1Char('0'));
0281     QString secondsString = QStringLiteral("%1").arg(seconds, 2, 10, QLatin1Char('0'));
0282     QString timeString = QStringLiteral("%1:%2:%3").arg(hoursString, minutesString, secondsString);
0283 
0284     return timeString;
0285 }
0286 
0287 QUrl Application::url(int key)
0288 {
0289     return m_urls[key];
0290 }
0291 
0292 void Application::addUrl(int key, const QString &value)
0293 {
0294     m_urls.insert(key, QUrl::fromUserInput(value, QDir::currentPath()));
0295 }
0296 
0297 QString Application::getFileContent(const QString &file)
0298 {
0299     QFile f(file);
0300     f.open(QIODevice::ReadOnly);
0301     QString content = QString::fromUtf8(f.readAll());
0302     f.close();
0303     return content;
0304 }
0305 
0306 QString Application::mimeType(QUrl url)
0307 {
0308     KFileItem fileItem(url, KFileItem::NormalMimeTypeDetermination);
0309     return fileItem.mimetype();
0310 }
0311 
0312 void Application::handleSecondayInstanceMessage(const QByteArray &message)
0313 {
0314     auto msgString = QString::fromStdString(message.data());
0315     QFileInfo fileInfo{msgString};
0316     if (fileInfo.exists() && fileInfo.isFile()) {
0317         Q_EMIT openUrl(QUrl::fromUserInput(msgString));
0318         raiseWindow();
0319     }
0320 }
0321 
0322 QStringList Application::availableGuiStyles()
0323 {
0324     return QStyleFactory::keys();
0325 }
0326 
0327 void Application::setGuiStyle(const QString &style)
0328 {
0329     if (style == QStringLiteral("Default")) {
0330         QApplication::setStyle(m_systemDefaultStyle);
0331         return;
0332     }
0333     QApplication::setStyle(style);
0334 }
0335 
0336 QAbstractItemModel *Application::colorSchemesModel()
0337 {
0338     return m_schemes->model();
0339 }
0340 
0341 QQmlApplicationEngine *Application::qmlEngine() const
0342 {
0343     return m_qmlEngine;
0344 }
0345 
0346 void Application::setQmlEngine(QQmlApplicationEngine *_qmlEngine)
0347 {
0348     m_qmlEngine = _qmlEngine;
0349 }
0350 
0351 QCommandLineParser *Application::parser() const
0352 {
0353     return m_parser;
0354 }
0355 
0356 void Application::activateColorScheme(const QString &name)
0357 {
0358     m_schemes->activateScheme(m_schemes->indexForScheme(name));
0359 }
0360 
0361 void Application::openDocs(const QString &page)
0362 {
0363     QDesktopServices::openUrl(QUrl(page));
0364 }
0365 
0366 QStringList Application::getFonts()
0367 {
0368     static QFontDatabase *fontDB = new QFontDatabase();
0369     return fontDB->families();
0370 }
0371 
0372 int Application::frameworksVersionMinor()
0373 {
0374     return KCONFIG_VERSION_MINOR;
0375 }
0376 
0377 int Application::qtMajorVersion()
0378 {
0379     return QT_VERSION_MAJOR;
0380 }
0381 
0382 QString Application::platformName()
0383 {
0384     return QGuiApplication::platformName();
0385 }
0386 
0387 void Application::raiseWindow()
0388 {
0389     QObject *m_rootObject = m_qmlEngine->rootObjects().constFirst();
0390     if (!m_rootObject) {
0391         return;
0392     }
0393 
0394     // todo: replace deprecated methods
0395     QWindow *window = qobject_cast<QWindow *>(m_rootObject);
0396     if (window) {
0397         KWindowSystem::activateWindow(window);
0398     }
0399 }
0400 
0401 #include "moc_application.cpp"