1 /******************************************************************************
2     QtAV Player Demo:  this file is part of QtAV examples
3     Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
4 
5 *   This file is part of QtAV (from 2014)
6 
7     This program is free software: you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation, either version 3 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 ******************************************************************************/
20 
21 #include "common.h"
22 #include <cstdio>
23 #include <cstdlib>
24 #include <QtCore/QSettings>
25 #include <QFileOpenEvent>
26 #include <QtCore/QLocale>
27 #include <QtCore/QTranslator>
28 #include <QtCore/QCoreApplication>
29 #include <QtCore/QDir>
30 #include <QtCore/QFile>
31 #include <QtCore/QTextStream>
32 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
33 #include <QtGui/QDesktopServices>
34 #else
35 #include <QtCore/QStandardPaths>
36 #endif
37 #include <QtDebug>
38 #include <QMutex>
39 
40 #ifdef Q_OS_WINRT
41 #include <wrl.h>
42 #include <windows.foundation.h>
43 #include <windows.storage.pickers.h>
44 #include <Windows.ApplicationModel.activation.h>
45 #include <qfunctions_winrt.h>
46 using namespace Microsoft::WRL;
47 using namespace Microsoft::WRL::Wrappers;
48 using namespace ABI::Windows::ApplicationModel::Activation;
49 using namespace ABI::Windows::Foundation;
50 using namespace ABI::Windows::Foundation::Collections;
51 using namespace ABI::Windows::Storage;
52 using namespace ABI::Windows::Storage::Pickers;
53 
54 #define COM_LOG_COMPONENT "WinRT"
55 #define COM_ENSURE(f, ...) COM_CHECK(f, return __VA_ARGS__;)
56 #define COM_WARN(f) COM_CHECK(f)
57 #define COM_CHECK(f, ...) \
58     do { \
59         HRESULT hr = f; \
60         if (FAILED(hr)) { \
61             qWarning() << QString::fromLatin1(COM_LOG_COMPONENT " error@%1. " #f ": (0x%2) %3").arg(__LINE__).arg(hr, 0, 16).arg(qt_error_string(hr)); \
62             __VA_ARGS__ \
63         } \
64     } while (0)
65 
UrlFromFileArgs(IInspectable * args)66 QString UrlFromFileArgs(IInspectable *args)
67 {
68     ComPtr<IFileActivatedEventArgs> fileArgs;
69     COM_ENSURE(args->QueryInterface(fileArgs.GetAddressOf()), QString());
70     ComPtr<IVectorView<IStorageItem*>> files;
71     COM_ENSURE(fileArgs->get_Files(&files), QString());
72     ComPtr<IStorageItem> item;
73     COM_ENSURE(files->GetAt(0, &item), QString());
74     HString path;
75     COM_ENSURE(item->get_Path(path.GetAddressOf()), QString());
76 
77     quint32 pathLen;
78     const wchar_t *pathStr = path.GetRawBuffer(&pathLen);
79     const QString filePath = QString::fromWCharArray(pathStr, pathLen);
80     qDebug() << "file path: " << filePath;
81     item->AddRef(); //ensure we can access it later. TODO: how to release?
82     return QString::fromLatin1("winrt:@%1:%2").arg((qint64)(qptrdiff)item.Get()).arg(filePath);
83 }
84 #endif
85 
86 Q_GLOBAL_STATIC(QFile, fileLogger)
87 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
88 class QMessageLogContext {};
89 typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
qInstallMessageHandler(QtMessageHandler h)90 QtMsgHandler qInstallMessageHandler(QtMessageHandler h) {
91     static QtMessageHandler hh;
92     hh = h;
93     struct MsgHandlerWrapper {
94         static void handler(QtMsgType type, const char *msg) {
95             static QMessageLogContext ctx;
96             hh(type, ctx, QString::fromUtf8(msg));
97         }
98     };
99     return qInstallMsgHandler(MsgHandlerWrapper::handler);
100 }
101 #endif
102 
103 QMutex loggerMutex;
Logger(QtMsgType type,const QMessageLogContext &,const QString & qmsg)104 void Logger(QtMsgType type, const QMessageLogContext &, const QString& qmsg)
105 {
106     // QFile is not thread-safe
107     QMutexLocker locker(&loggerMutex);
108 
109     const QByteArray msgArray = qmsg.toUtf8();
110     const char* msg = msgArray.constData();
111      switch (type) {
112      case QtDebugMsg:
113          printf("Debug: %s\n", msg);
114          fileLogger()->write(QByteArray("Debug: "));
115          break;
116      case QtWarningMsg:
117          printf("Warning: %s\n", msg);
118          fileLogger()->write(QByteArray("Warning: "));
119          break;
120      case QtCriticalMsg:
121          fprintf(stderr, "Critical: %s\n", msg);
122          fileLogger()->write(QByteArray("Critical: "));
123          break;
124      case QtFatalMsg:
125          fprintf(stderr, "Fatal: %s\n", msg);
126          fileLogger()->write(QByteArray("Fatal: "));
127          abort();
128      }
129      fflush(0);
130      fileLogger()->write(msgArray);
131      fileLogger()->write(QByteArray("\n"));
132      //fileLogger()->flush(); // crash in qt5.7
133 }
134 
get_common_options()135 QOptions get_common_options()
136 {
137     static QOptions ops = QOptions().addDescription(QString::fromLatin1("Options for QtAV players"))
138             .add(QString::fromLatin1("common options"))
139             ("help,h", QLatin1String("print this"))
140             ("ao", QString(), QLatin1String("audio output. Can be ordered combination of available backends (-ao help). Leave empty to use the default setting. Set 'null' to disable audio."))
141             ("-egl", QLatin1String("Use EGL. Only works for Qt>=5.5+XCB"))
142             ("-gl", QLatin1String("OpenGL backend for Qt>=5.4(windows). can be 'desktop', 'opengles' and 'software'"))
143             ("x", 0, QString())
144             ("y", 0, QLatin1String("y"))
145             ("-width", 800, QLatin1String("width of player"))
146             ("height", 450, QLatin1String("height of player"))
147             ("fullscreen", QLatin1String("fullscreen"))
148             ("decoder", QLatin1String("FFmpeg"), QLatin1String("use a given decoder"))
149             ("decoders,-vd", QLatin1String("cuda;vaapi;vda;dxva;cedarv;ffmpeg"), QLatin1String("decoder name list in priority order separated by ';'"))
150             ("file,f", QString(), QLatin1String("file or url to play"))
151             ("language", QString(), QLatin1String("language on UI. can be 'system' and locale name e.g. zh_CN"))
152             ("log", QString(), QLatin1String("log level. can be 'off', 'fatal', 'critical', 'warning', 'debug', 'all'"))
153             ("logfile"
154 #if defined(Q_OS_IOS)
155              , appDataDir().append(QString::fromLatin1("/log-%1.txt"))
156 #elif defined(Q_OS_WINRT) || defined(Q_OS_ANDROID)
157              , QString()
158 #else
159              , QString::fromLatin1("log-%1.txt")
160 #endif
161              , QString::fromLatin1("log to file. Set empty to disable log file (-logfile '')"))
162             ;
163     return ops;
164 }
165 
do_common_options_before_qapp(const QOptions & options)166 void do_common_options_before_qapp(const QOptions& options)
167 {
168 #ifdef Q_OS_LINUX
169     QSettings cfg(Config::defaultConfigFile(), QSettings::IniFormat);
170     const bool set_egl = cfg.value("opengl/egl").toBool();
171     //https://bugreports.qt.io/browse/QTBUG-49529
172     // it's too late if qApp is created. but why ANGLE is not?
173     if (options.value(QString::fromLatin1("egl")).toBool() || set_egl) { //FIXME: Config is constructed too early because it requires qApp
174         // only apply to current run. no config change
175         qputenv("QT_XCB_GL_INTEGRATION", "xcb_egl");
176     } else {
177         qputenv("QT_XCB_GL_INTEGRATION", "xcb_glx");
178     }
179     qDebug() << "QT_XCB_GL_INTEGRATION: " << qgetenv("QT_XCB_GL_INTEGRATION");
180 #endif //Q_OS_LINUX
181 }
182 
do_common_options(const QOptions & options,const QString & appName)183 void do_common_options(const QOptions &options, const QString& appName)
184 {
185     if (options.value(QString::fromLatin1("help")).toBool()) {
186         options.print();
187         exit(0);
188     }
189     // has no effect if qInstallMessageHandler() called
190     //qSetMessagePattern("%{function} @%{line}: %{message}");
191 #if !defined(Q_OS_WINRT) && !defined(Q_OS_ANDROID)
192     QString app(appName);
193     if (app.isEmpty() && qApp)
194         app = qApp->applicationName();
195     QString logfile(options.option(QString::fromLatin1("logfile")).value().toString().arg(app));
196     if (!logfile.isEmpty()) {
197         if (QDir(logfile).isRelative()) {
198             QString log_path(QString::fromLatin1("%1/%2").arg(qApp->applicationDirPath()).arg(logfile));
199             QFile f(log_path);
200             if (!f.open(QIODevice::WriteOnly)) {
201                 log_path = QString::fromLatin1("%1/%2").arg(appDataDir()).arg(logfile);
202                 qDebug() << "executable dir is not writable. log to " << log_path;
203             }
204             logfile = log_path;
205         }
206         qDebug() << "set log file: " << logfile;
207         fileLogger()->setFileName(logfile);
208         if (fileLogger()->open(QIODevice::WriteOnly)) {
209             qInstallMessageHandler(Logger);
210         } else {
211             qWarning() << "Failed to open log file '" << fileLogger()->fileName() << "': " << fileLogger()->errorString();
212         }
213     }
214 #endif
215     QByteArray level(options.value(QString::fromLatin1("log")).toByteArray());
216     if (level.isEmpty())
217         level = Config::instance().logLevel().toLatin1();
218     if (!level.isEmpty())
219         qputenv("QTAV_LOG", level);
220 }
221 
load_qm(const QStringList & names,const QString & lang)222 void load_qm(const QStringList &names, const QString& lang)
223 {
224     QString l(Config::instance().language());
225     if (!lang.isEmpty())
226         l = lang;
227     if (l.toLower() == QLatin1String("system"))
228         l = QLocale::system().name();
229     QStringList qms(names);
230     qms << QLatin1String("QtAV") << QLatin1String("qt");
231     foreach(QString qm, qms) {
232         QTranslator *ts = new QTranslator(qApp);
233         QString path = qApp->applicationDirPath() + QLatin1String("/i18n/") + qm + QLatin1String("_") + l;
234         //qDebug() << "loading qm: " << path;
235         if (ts->load(path)) {
236             qApp->installTranslator(ts);
237         } else {
238             path = QString::fromUtf8(":/i18n/%1_%2").arg(qm).arg(l);
239             //qDebug() << "loading qm: " << path;
240             if (ts->load(path))
241                 qApp->installTranslator(ts);
242             else
243                 delete ts;
244         }
245     }
246     QTranslator qtts;
247     if (qtts.load(QLatin1String("qt_") + QLocale::system().name()))
248         qApp->installTranslator(&qtts);
249 }
250 
set_opengl_backend(const QString & glopt,const QString & appname)251 void set_opengl_backend(const QString& glopt, const QString &appname)
252 {
253     QString gl = appname.toLower().replace(QLatin1String("\\"), QLatin1String("/"));
254     int idx = gl.lastIndexOf(QLatin1String("/"));
255     if (idx >= 0)
256         gl = gl.mid(idx + 1);
257     idx = gl.lastIndexOf(QLatin1String("."));
258     if (idx > 0)
259         gl = gl.left(idx);
260     if (gl.indexOf(QLatin1String("-desktop")) > 0)
261         gl = QLatin1String("desktop");
262     else if (gl.indexOf(QLatin1String("-es")) > 0 || gl.indexOf(QLatin1String("-angle")) > 0)
263         gl = gl.mid(gl.indexOf(QLatin1String("-es")) + 1);
264     else if (gl.indexOf(QLatin1String("-sw")) > 0 || gl.indexOf(QLatin1String("-software")) > 0)
265         gl = QLatin1String("software");
266     else
267         gl = glopt.toLower();
268     if (gl.isEmpty()) {
269         switch (Config::instance().openGLType()) {
270         case Config::Desktop:
271             gl = QLatin1String("desktop");
272             break;
273         case Config::OpenGLES:
274             gl = QLatin1String("es");
275             break;
276         case Config::Software:
277             gl = QLatin1String("software");
278             break;
279         default:
280             break;
281         }
282     }
283     if (gl == QLatin1String("es") || gl == QLatin1String("angle") || gl == QLatin1String("opengles")) {
284         gl = QLatin1String("es_");
285         gl.append(Config::instance().getANGLEPlatform().toLower());
286     }
287 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
288     if (gl.startsWith(QLatin1String("es"))) {
289         qApp->setAttribute(Qt::AA_UseOpenGLES);
290 #ifdef QT_OPENGL_DYNAMIC
291         qputenv("QT_OPENGL", "angle");
292 #endif
293 #ifdef Q_OS_WIN
294         if (gl.endsWith(QLatin1String("d3d11")))
295             qputenv("QT_ANGLE_PLATFORM", "d3d11");
296         else if (gl.endsWith(QLatin1String("d3d9")))
297             qputenv("QT_ANGLE_PLATFORM", "d3d9");
298         else if (gl.endsWith(QLatin1String("warp")))
299             qputenv("QT_ANGLE_PLATFORM", "warp");
300 #endif
301     } else if (gl == QLatin1String("desktop")) {
302         qApp->setAttribute(Qt::AA_UseDesktopOpenGL);
303     } else if (gl == QLatin1String("software")) {
304         qApp->setAttribute(Qt::AA_UseSoftwareOpenGL);
305     }
306 #endif
307 }
308 
309 
appDataDir()310 QString appDataDir()
311 {
312 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
313     return QDesktopServices::storageLocation(QDesktopServices::DataLocation);
314 #else
315 #if QT_VERSION < QT_VERSION_CHECK(5, 4, 0)
316     return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
317 #else
318     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
319 #endif //5.4.0
320 #endif // 5.0.0
321 }
322 
AppEventFilter(QObject * player,QObject * parent)323 AppEventFilter::AppEventFilter(QObject *player, QObject *parent)
324     : QObject(parent)
325     , m_player(player)
326 {}
327 
eventFilter(QObject * obj,QEvent * ev)328 bool AppEventFilter::eventFilter(QObject *obj, QEvent *ev)
329 {
330     //qDebug() << __FUNCTION__ << " watcher: " << obj << ev;
331     if (obj != qApp)
332         return false;
333     if (ev->type() == QEvent::WinEventAct) {
334         // winrt file open/pick. since qt5.6.1
335         qDebug("QEvent::WinEventAct");
336 #ifdef Q_OS_WINRT
337         class QActivationEvent : public QEvent {
338         public:
339             void* args() const {return d;} //IInspectable*
340         };
341         QActivationEvent *ae = static_cast<QActivationEvent*>(ev);
342         const QString url(UrlFromFileArgs((IInspectable*)ae->args()));
343         if (!url.isEmpty()) {
344             qDebug() << "winrt url: " << url;
345             if (m_player)
346                 QMetaObject::invokeMethod(m_player, "play", Q_ARG(QUrl, QUrl(url)));
347         }
348         return true;
349 #endif
350     }
351     if (ev->type() != QEvent::FileOpen)
352         return false;
353     QFileOpenEvent *foe = static_cast<QFileOpenEvent*>(ev);
354     if (m_player)
355         QMetaObject::invokeMethod(m_player, "play", Q_ARG(QUrl, QUrl(foe->url())));
356     return true;
357 }
358 
initResources()359 static void initResources() {
360     Q_INIT_RESOURCE(theme);
361 }
362 
363 namespace {
364     struct ResourceLoader {
365     public:
ResourceLoader__anon8918ccc80111::ResourceLoader366         ResourceLoader() { initResources(); }
367     } qrc;
368 }
369 
370 
371