521 lines
19 KiB
C++
521 lines
19 KiB
C++
/*
|
|
* Copyright (c) 2011-2024 Meltytech, LLC
|
|
*
|
|
* 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 "ConsoleAppender.h"
|
|
#include "FileAppender.h"
|
|
#include "Logger.h"
|
|
#include "mainwindow.h"
|
|
#include "settings.h"
|
|
|
|
#include <framework/mlt_log.h>
|
|
#include <QCommandLineParser>
|
|
#include <QFile>
|
|
#include <QProcess>
|
|
#include <QQuickStyle>
|
|
#include <QQuickWindow>
|
|
#include <QSysInfo>
|
|
#include <QtGlobal>
|
|
#include <QtWidgets>
|
|
|
|
#ifdef Q_OS_MAC
|
|
#include "macos.h"
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <windows.h>
|
|
#if defined(QT_DEBUG) && !defined(__ARM_ARCH)
|
|
#include <exchndl.h>
|
|
#endif
|
|
extern "C" {
|
|
// Inform the driver we could make use of the discrete gpu
|
|
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
|
__declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
|
|
}
|
|
#endif
|
|
|
|
static const int kMaxCacheCount = 5000;
|
|
|
|
static void mlt_log_handler(void *service, int mlt_level, const char *format, va_list args)
|
|
{
|
|
if (mlt_level > mlt_log_get_level())
|
|
return;
|
|
|
|
enum Logger::LogLevel cuteLoggerLevel = Logger::Fatal;
|
|
switch (mlt_level) {
|
|
case MLT_LOG_DEBUG:
|
|
cuteLoggerLevel = Logger::Trace;
|
|
break;
|
|
case MLT_LOG_ERROR:
|
|
case MLT_LOG_FATAL:
|
|
case MLT_LOG_PANIC:
|
|
cuteLoggerLevel = Logger::Error;
|
|
break;
|
|
case MLT_LOG_INFO:
|
|
cuteLoggerLevel = Logger::Info;
|
|
break;
|
|
case MLT_LOG_VERBOSE:
|
|
cuteLoggerLevel = Logger::Debug;
|
|
break;
|
|
case MLT_LOG_WARNING:
|
|
cuteLoggerLevel = Logger::Warning;
|
|
break;
|
|
}
|
|
QString message;
|
|
mlt_properties properties = service ? MLT_SERVICE_PROPERTIES((mlt_service) service) : NULL;
|
|
if (properties) {
|
|
char *mlt_type = mlt_properties_get(properties, "mlt_type");
|
|
char *service_name = mlt_properties_get(properties, "mlt_service");
|
|
char *resource = mlt_properties_get(properties, "resource");
|
|
if (!resource || resource[0] != '<' || resource[strlen(resource) - 1] != '>')
|
|
mlt_type = mlt_properties_get(properties, "mlt_type");
|
|
if (service_name)
|
|
message = QStringLiteral("[%1 %2] ").arg(mlt_type, service_name);
|
|
else
|
|
message = QString::asprintf("[%s %p] ", mlt_type, service);
|
|
if (resource)
|
|
message.append(QStringLiteral("\"%1\" ").arg(resource));
|
|
message.append(QString::vasprintf(format, args));
|
|
message.replace('\n', "");
|
|
} else {
|
|
message = QString::vasprintf(format, args);
|
|
message.replace('\n', "");
|
|
}
|
|
cuteLogger->write(cuteLoggerLevel,
|
|
__FILE__,
|
|
__LINE__,
|
|
"MLT",
|
|
cuteLogger->defaultCategory().toLatin1().constData(),
|
|
message);
|
|
}
|
|
|
|
class Application : public QApplication
|
|
{
|
|
public:
|
|
MainWindow *mainWindow{nullptr};
|
|
QTranslator qtTranslator;
|
|
QTranslator qtBaseTranslator;
|
|
QTranslator shotcutTranslator;
|
|
QStringList resourceArg;
|
|
bool isFullScreen;
|
|
QString appDirArg;
|
|
|
|
Application(int &argc, char **argv)
|
|
: QApplication(argc, argv)
|
|
{
|
|
auto appPath = applicationDirPath();
|
|
QDir dir(appPath);
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <winbase.h>
|
|
SetDllDirectoryA(appPath.toLocal8Bit());
|
|
CreateMutexA(NULL, FALSE, "Meltytech Shotcut Running Mutex");
|
|
#else
|
|
dir.cdUp();
|
|
#endif
|
|
#ifdef Q_OS_MAC
|
|
dir.cd("PlugIns");
|
|
dir.cd("qt");
|
|
#else
|
|
dir.cd("lib");
|
|
dir.cd("qt6");
|
|
#endif
|
|
addLibraryPath(dir.absolutePath());
|
|
setOrganizationName("Meltytech");
|
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
|
setOrganizationDomain("shotcut.org");
|
|
setDesktopFileName("org.shotcut.Shotcut");
|
|
#else
|
|
setOrganizationDomain("meltytech.com");
|
|
#endif
|
|
setApplicationName("Shotcut");
|
|
setApplicationVersion(SHOTCUT_VERSION);
|
|
|
|
// Process command line options.
|
|
QCommandLineParser parser;
|
|
parser.addHelpOption();
|
|
parser.addVersionOption();
|
|
#ifndef Q_OS_WIN
|
|
QCommandLineOption fullscreenOption(
|
|
"fullscreen",
|
|
QCoreApplication::translate("main", "Fill the screen with the Shotcut window."));
|
|
parser.addOption(fullscreenOption);
|
|
#endif
|
|
QCommandLineOption noupgradeOption(
|
|
"noupgrade", QCoreApplication::translate("main", "Hide upgrade prompt and menu item."));
|
|
parser.addOption(noupgradeOption);
|
|
QCommandLineOption
|
|
glaxnimateOption("glaxnimate",
|
|
QCoreApplication::translate("main",
|
|
"Run Glaxnimate instead of Shotcut."));
|
|
parser.addOption(glaxnimateOption);
|
|
QCommandLineOption gpuOption("gpu",
|
|
QCoreApplication::translate("main", "Use GPU processing."));
|
|
parser.addOption(gpuOption);
|
|
QCommandLineOption clearRecentOption("clear-recent",
|
|
QCoreApplication::translate("main",
|
|
"Clear Recent on Exit"));
|
|
parser.addOption(clearRecentOption);
|
|
QCommandLineOption appDataOption(
|
|
"appdata",
|
|
QCoreApplication::translate("main", "The directory for app configuration and data."),
|
|
QCoreApplication::translate("main", "directory"));
|
|
parser.addOption(appDataOption);
|
|
QCommandLineOption
|
|
scaleOption("QT_SCALE_FACTOR",
|
|
QCoreApplication::translate("main",
|
|
"The scale factor for a high-DPI screen"),
|
|
QCoreApplication::translate("main", "number"));
|
|
parser.addOption(scaleOption);
|
|
scaleOption
|
|
= QCommandLineOption("QT_SCREEN_SCALE_FACTORS",
|
|
QCoreApplication::translate(
|
|
"main",
|
|
"A semicolon-separated list of scale factors for each screen"),
|
|
QCoreApplication::translate("main", "list"));
|
|
parser.addOption(scaleOption);
|
|
QCommandLineOption scalePolicyOption(
|
|
"QT_SCALE_FACTOR_ROUNDING_POLICY",
|
|
QCoreApplication::translate("main", "How to handle a fractional display scale: %1")
|
|
.arg("Round, Ceil, Floor, RoundPreferFloor, PassThrough"),
|
|
QCoreApplication::translate("main", "string"),
|
|
"PassThrough");
|
|
parser.addOption(scalePolicyOption);
|
|
#if defined(Q_OS_WIN)
|
|
QCommandLineOption sdlAudioDriverOption(
|
|
"SDL_AUDIODRIVER",
|
|
QCoreApplication::translate("main", "Which operating system audio API to use: %1")
|
|
.arg("directsound, wasapi, winmm"),
|
|
QCoreApplication::translate("main", "string"),
|
|
"wasapi");
|
|
parser.addOption(sdlAudioDriverOption);
|
|
#elif defined(Q_OS_LINUX)
|
|
QCommandLineOption sdlAudioDriverOption(
|
|
"SDL_AUDIODRIVER",
|
|
QCoreApplication::translate("main", "Which operating system audio API to use: %1")
|
|
.arg("alsa, arts, dsp, esd, jack, pipewire, pulseaudio"),
|
|
QCoreApplication::translate("main", "string"),
|
|
"pulseaudio");
|
|
parser.addOption(sdlAudioDriverOption);
|
|
#endif
|
|
parser.addPositionalArgument(
|
|
"[FILE]...",
|
|
QCoreApplication::translate("main", "Zero or more files or folders to open"));
|
|
parser.process(arguments());
|
|
if (parser.isSet(glaxnimateOption)) {
|
|
QStringList args = arguments();
|
|
if (!args.isEmpty())
|
|
args.removeFirst();
|
|
args.removeAll("--glaxnimate");
|
|
QProcess child;
|
|
if (child.startDetached(Settings.glaxnimatePath(), args))
|
|
::exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
isFullScreen = false;
|
|
#else
|
|
isFullScreen = parser.isSet(fullscreenOption);
|
|
#endif
|
|
setProperty("noupgrade", parser.isSet(noupgradeOption));
|
|
setProperty("clearRecent", parser.isSet(clearRecentOption));
|
|
if (!parser.value(appDataOption).isEmpty()) {
|
|
appDirArg = parser.value(appDataOption);
|
|
ShotcutSettings::setAppDataForSession(appDirArg);
|
|
}
|
|
if (parser.isSet(gpuOption))
|
|
Settings.setProcessingMode(ShotcutSettings::Linear10GpuCpu);
|
|
if (!parser.positionalArguments().isEmpty())
|
|
resourceArg = parser.positionalArguments();
|
|
|
|
// Startup logging.
|
|
dir.setPath(Settings.appDataLocation());
|
|
if (!dir.exists())
|
|
dir.mkpath(dir.path());
|
|
const auto logFileName = dir.filePath("shotcut-log.txt");
|
|
if (QFile::exists(logFileName)) {
|
|
const auto previousLogName = dir.filePath("shotcut-log.bak");
|
|
if (QFile::exists(previousLogName))
|
|
QFile::remove(previousLogName);
|
|
if (!QFile::rename(logFileName, previousLogName))
|
|
LOG_WARNING() << "Failed to rename backup log file" << previousLogName;
|
|
}
|
|
FileAppender *fileAppender = new FileAppender(logFileName);
|
|
fileAppender->setFormat("[%{type:-7}] <%{function}> %{message}\n");
|
|
cuteLogger->registerAppender(fileAppender);
|
|
#ifndef NDEBUG
|
|
// Only log to console in dev debug builds.
|
|
ConsoleAppender *consoleAppender = new ConsoleAppender();
|
|
consoleAppender->setFormat(fileAppender->format());
|
|
cuteLogger->registerAppender(consoleAppender);
|
|
|
|
mlt_log_set_level(MLT_LOG_VERBOSE);
|
|
#else
|
|
mlt_log_set_level(MLT_LOG_INFO);
|
|
#endif
|
|
mlt_log_set_callback(mlt_log_handler);
|
|
cuteLogger->logToGlobalInstance("qml", true);
|
|
|
|
#if defined(Q_OS_WIN)
|
|
dir.setPath(appPath);
|
|
#elif !defined(Q_OS_MAC)
|
|
if (Settings.drawMethod() == Qt::AA_UseSoftwareOpenGL && !Settings.playerGPU()) {
|
|
::qputenv("LIBGL_ALWAYS_SOFTWARE", "1");
|
|
}
|
|
#endif
|
|
// Load translations
|
|
QString locale = Settings.language();
|
|
dir.setPath(appPath);
|
|
#if defined(Q_OS_MAC)
|
|
dir.cdUp();
|
|
dir.cd("Resources");
|
|
dir.cd("shotcut");
|
|
dir.cd("translations");
|
|
#elif defined(Q_OS_WIN)
|
|
dir.cd("share");
|
|
dir.cd("translations");
|
|
#else
|
|
dir.cdUp();
|
|
dir.cd("share");
|
|
dir.cd("shotcut");
|
|
dir.cd("translations");
|
|
#endif
|
|
if (locale.startsWith("pt_"))
|
|
locale = "pt";
|
|
else if (locale.startsWith("en_"))
|
|
locale = "en";
|
|
if (qtTranslator.load("qt_" + locale, QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
|
|
installTranslator(&qtTranslator);
|
|
else if (qtTranslator.load("qt_" + locale, dir.absolutePath()))
|
|
installTranslator(&qtTranslator);
|
|
if (qtBaseTranslator.load("qtbase_" + locale,
|
|
QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
|
|
installTranslator(&qtBaseTranslator);
|
|
else if (qtBaseTranslator.load("qtbase_" + locale, dir.absolutePath()))
|
|
installTranslator(&qtBaseTranslator);
|
|
if (shotcutTranslator.load("shotcut_" + Settings.language(), dir.absolutePath()))
|
|
installTranslator(&shotcutTranslator);
|
|
}
|
|
|
|
~Application()
|
|
{
|
|
delete mainWindow;
|
|
LOG_DEBUG() << "exiting";
|
|
}
|
|
|
|
protected:
|
|
bool event(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::FileOpen) {
|
|
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
|
|
resourceArg << openEvent->file();
|
|
return true;
|
|
} else
|
|
return QApplication::event(event);
|
|
}
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
#if defined(Q_OS_WIN) && defined(QT_DEBUG) && !defined(__ARM_ARCH)
|
|
ExcHndlInit();
|
|
#endif
|
|
#ifndef QT_DEBUG
|
|
::qputenv("QT_LOGGING_RULES", "*.warning=false");
|
|
#endif
|
|
for (int i = 1; i + 1 < argc; i++) {
|
|
if (!::qstrcmp("--QT_SCALE_FACTOR", argv[i])
|
|
|| !::qstrcmp("--QT_SCREEN_SCALE_FACTORS", argv[i])) {
|
|
QByteArray value(argv[i + 1]);
|
|
::qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0");
|
|
::qputenv(value.contains(';') ? "QT_SCREEN_SCALE_FACTORS" : "QT_SCALE_FACTOR", value);
|
|
break;
|
|
}
|
|
}
|
|
if (!::qEnvironmentVariableIsSet("QT_SCALE_FACTOR_ROUNDING_POLICY")) {
|
|
for (int i = 1; i + 1 < argc; i++) {
|
|
if (!::qstrcmp("--QT_SCALE_FACTOR_ROUNDING_POLICY", argv[i])) {
|
|
::qputenv("QT_SCALE_FACTOR_ROUNDING_POLICY", argv[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
|
for (int i = 1; i + 1 < argc; i++) {
|
|
if (!::qstrcmp("--SDL_AUDIODRIVER", argv[i])) {
|
|
::qputenv("SDL_AUDIODRIVER", argv[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// The ffmpeg backend (default as of Qt 6.5) is likely using a different version of FFmpeg.
|
|
if (!qEnvironmentVariableIsSet("QT_MEDIA_BACKEND"))
|
|
#if defined(Q_OS_MAC)
|
|
qputenv("QT_MEDIA_BACKEND", "darwin");
|
|
#elif defined(Q_OS_WIN)
|
|
qputenv("QT_MEDIA_BACKEND", "windows");
|
|
if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM"))
|
|
qputenv("QT_QPA_PLATFORM", "windows:altgr");
|
|
#else
|
|
;
|
|
#endif
|
|
|
|
#ifdef Q_OS_MAC
|
|
// Launcher and Spotlight on macOS are not setting this environment
|
|
// variable needed by setlocale() as used by MLT.
|
|
if (QProcessEnvironment::systemEnvironment().value(MLT_LC_NAME).isEmpty()) {
|
|
qputenv(MLT_LC_NAME, QLocale().name().toUtf8());
|
|
|
|
QLocale localeByName(
|
|
QLocale(QLocale().language(), QLocale().script(), QLocale().territory()));
|
|
if (QLocale().decimalPoint() != localeByName.decimalPoint()) {
|
|
// If region's numeric format does not match the language's, then we run
|
|
// into problems because we told MLT and libc to use a different numeric
|
|
// locale than actually in use by Qt because it is unable to give numeric
|
|
// locale as a set of ISO-639 codes.
|
|
QLocale::setDefault(localeByName);
|
|
qputenv("LANG", QLocale().name().toUtf8());
|
|
}
|
|
}
|
|
removeMacosTabBar();
|
|
#endif
|
|
|
|
#if defined(Q_OS_WIN)
|
|
// Windows can use Direct3D or OpenGL
|
|
#elif defined(Q_OS_MAC)
|
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::Metal);
|
|
QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
|
#else
|
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
|
|
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
|
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
|
#endif
|
|
|
|
Application a(argc, argv);
|
|
int result = EXIT_SUCCESS;
|
|
#ifdef Q_OS_WIN
|
|
if (::qEnvironmentVariableIsSet("QSG_RHI_BACKEND")) {
|
|
#endif
|
|
QSplashScreen splash(QPixmap(":/icons/shotcut-logo-320x320.png"));
|
|
|
|
// Log some basic info.
|
|
LOG_INFO() << "Starting Shotcut version" << SHOTCUT_VERSION;
|
|
#if defined(Q_OS_WIN)
|
|
LOG_INFO() << "Windows version" << QSysInfo::productVersion();
|
|
#elif defined(Q_OS_MAC)
|
|
LOG_INFO() << "macOS version" << QSysInfo::productVersion();
|
|
#else
|
|
LOG_INFO() << "Linux version" << QSysInfo::productVersion();
|
|
;
|
|
#endif
|
|
LOG_INFO() << "number of logical cores =" << QThread::idealThreadCount();
|
|
LOG_INFO() << "locale =" << QLocale();
|
|
LOG_INFO() << "install dir =" << a.applicationDirPath();
|
|
Settings.log();
|
|
|
|
// Expire old items from the qmlcache
|
|
splash.showMessage(QCoreApplication::translate("main", "Expiring cache..."),
|
|
Qt::AlignRight | Qt::AlignVCenter);
|
|
splash.show();
|
|
a.processEvents();
|
|
auto dir = QDir(
|
|
QStandardPaths::standardLocations(QStandardPaths::CacheLocation).constFirst());
|
|
if (dir.exists() && dir.cd("qmlcache")) {
|
|
auto ls = dir.entryList(QDir::Files | QDir::Readable | QDir::NoDotAndDotDot, QDir::Time);
|
|
if (qMax(0, ls.size() - kMaxCacheCount) > 0) {
|
|
LOG_INFO() << "removing" << qMax(0, ls.size() - kMaxCacheCount) << "from"
|
|
<< dir.path();
|
|
}
|
|
for (int i = kMaxCacheCount; i < ls.size(); i++) {
|
|
QString filePath = dir.filePath(ls[i]);
|
|
if (!QFile::remove(filePath)) {
|
|
LOG_WARNING() << "failed to delete" << filePath;
|
|
}
|
|
if (i % 1000 == 0) {
|
|
a.processEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
splash.showMessage(QCoreApplication::translate("main", "Loading plugins..."),
|
|
Qt::AlignRight | Qt::AlignVCenter);
|
|
a.processEvents();
|
|
|
|
a.setProperty("system-style", a.style()->objectName());
|
|
MainWindow::changeTheme(Settings.theme());
|
|
QQuickStyle::setStyle("Fusion");
|
|
|
|
a.mainWindow = &MAIN;
|
|
if (!a.appDirArg.isEmpty())
|
|
a.mainWindow->hideSetDataDirectory();
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
a.mainWindow->setProperty("windowOpacity", 0.0);
|
|
#endif
|
|
a.mainWindow->show();
|
|
a.processEvents();
|
|
a.mainWindow->setFullScreen(a.isFullScreen);
|
|
splash.finish(a.mainWindow);
|
|
|
|
if (!a.resourceArg.isEmpty()) {
|
|
QStringList ls;
|
|
for (auto &s : a.resourceArg)
|
|
ls << QFileInfo(QDir::currentPath(), s).filePath();
|
|
a.mainWindow->openMultiple(ls);
|
|
} else {
|
|
a.mainWindow->open(a.mainWindow->untitledFileName());
|
|
}
|
|
|
|
result = a.exec();
|
|
|
|
if (EXIT_RESTART == result || EXIT_RESET == result) {
|
|
LOG_DEBUG() << "restarting app";
|
|
::qunsetenv("QT_QUICK_CONTROLS_CONF"); // See MainWindow::changeTheme()
|
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
|
::qputenv("LIBGL_ALWAYS_SOFTWARE",
|
|
Settings.drawMethod() == Qt::AA_UseSoftwareOpenGL && !Settings.playerGPU()
|
|
? "1"
|
|
: "0");
|
|
#endif
|
|
QProcess *restart = new QProcess;
|
|
QStringList args = a.arguments();
|
|
if (!args.isEmpty())
|
|
args.removeFirst();
|
|
restart->start(a.applicationFilePath(), args, QIODevice::NotOpen);
|
|
result = EXIT_SUCCESS;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
} else { // if (::qEnvironmentVariableIsSet("QSG_RHI_BACKEND"))
|
|
// Run as a parent process to check if the child crashes on startup
|
|
QProcess *child = new QProcess;
|
|
QStringList args = a.arguments();
|
|
if (!args.isEmpty())
|
|
args.removeFirst();
|
|
::qputenv("QSG_RHI_BACKEND", "d3d11");
|
|
child->start(a.applicationFilePath(), args, QIODevice::NotOpen);
|
|
child->waitForFinished();
|
|
if (QProcess::CrashExit == child->exitStatus() || child->exitCode()) {
|
|
LOG_WARNING() << "child process failed, restarting in OpenGL mode";
|
|
::qputenv("QSG_RHI_BACKEND", "opengl");
|
|
child->start(a.applicationFilePath(), args, QIODevice::NotOpen);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|