1 /* This file is part of Clementine.
2    Copyright 2010, David Sansome <me@davidsansome.com>
3 
4    Clementine is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    Clementine is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include <memory>
19 
20 #include <QtGlobal>
21 
22 #ifdef Q_OS_WIN32
23 #  ifndef _WIN32_WINNT
24 #    define _WIN32_WINNT 0x0600
25 #  endif
26 #  include <windows.h>
27 #  include <iostream>
28 #endif  // Q_OS_WIN32
29 
30 #ifdef Q_OS_UNIX
31 #include <unistd.h>
32 #endif  // Q_OS_UNIX
33 
34 #include <QDir>
35 #include <QFont>
36 #include <QLibraryInfo>
37 #include <QNetworkProxyFactory>
38 #include <QSslSocket>
39 #include <QSqlDatabase>
40 #include <QSqlQuery>
41 #include <QSysInfo>
42 #include <QTextCodec>
43 #include <QTranslator>
44 #include <QtConcurrentRun>
45 #include <QtDebug>
46 
47 #include "config.h"
48 #include "core/application.h"
49 #include "core/commandlineoptions.h"
50 #include "core/crashreporting.h"
51 #include "core/database.h"
52 #include "core/logging.h"
53 #include "core/mac_startup.h"
54 #include "core/metatypes.h"
55 #include "core/network.h"
56 #include "core/networkproxyfactory.h"
57 #include "core/potranslator.h"
58 #include "core/song.h"
59 #include "core/ubuntuunityhack.h"
60 #include "core/utilities.h"
61 #include "engines/enginebase.h"
62 #include "smartplaylists/generator.h"
63 #include "ui/iconloader.h"
64 #include "ui/mainwindow.h"
65 #include "ui/systemtrayicon.h"
66 #include "version.h"
67 #include "widgets/osd.h"
68 
69 #include "tagreadermessages.pb.h"
70 
71 #include "qtsingleapplication.h"
72 #include "qtsinglecoreapplication.h"
73 
74 #include <glib-object.h>
75 #include <glib.h>
76 #include <gst/gst.h>
77 
78 #ifdef Q_OS_DARWIN
79 #include <sys/resource.h>
80 #include <sys/sysctl.h>
81 #endif
82 
83 #ifdef HAVE_LIBLASTFM
84 #include "internet/lastfm/lastfmservice.h"
85 #else
86 class LastFMService;
87 #endif
88 
89 #ifdef HAVE_DBUS
90 #include "core/mpris.h"
91 #include "core/mpris2.h"
92 #include <QDBusArgument>
93 #include <QImage>
94 
95 QDBusArgument& operator<<(QDBusArgument& arg, const QImage& image);
96 const QDBusArgument& operator>>(const QDBusArgument& arg, QImage& image);
97 #endif
98 
99 // Load sqlite plugin on windows and mac.
100 #include <QtPlugin>
101 Q_IMPORT_PLUGIN(QSQLiteDriverPlugin)
102 
103 namespace {
104 
LoadTranslation(const QString & prefix,const QString & path,const QString & language)105 void LoadTranslation(const QString& prefix, const QString& path,
106                      const QString& language) {
107   QTranslator* t = new PoTranslator;
108   if (t->load(prefix + "_" + language, path))
109     QCoreApplication::installTranslator(t);
110   else
111     delete t;
112 }
113 
IncreaseFDLimit()114 void IncreaseFDLimit() {
115 #ifdef Q_OS_DARWIN
116   // Bump the soft limit for the number of file descriptors from the default of
117   // 256 to
118   // the maximum (usually 10240).
119   struct rlimit limit;
120   getrlimit(RLIMIT_NOFILE, &limit);
121 
122   // getrlimit() lies about the hard limit so we have to check sysctl.
123   int max_fd = 0;
124   size_t len = sizeof(max_fd);
125   sysctlbyname("kern.maxfilesperproc", &max_fd, &len, nullptr, 0);
126 
127   limit.rlim_cur = max_fd;
128   int ret = setrlimit(RLIMIT_NOFILE, &limit);
129 
130   if (ret == 0) {
131     qLog(Debug) << "Max fd:" << max_fd;
132   }
133 #endif
134 }
135 
SetEnv(const char * key,const QString & value)136 void SetEnv(const char* key, const QString& value) {
137 #ifdef Q_OS_WIN32
138   putenv(QString("%1=%2").arg(key, value).toLocal8Bit().constData());
139 #else
140   setenv(key, value.toLocal8Bit().constData(), 1);
141 #endif
142 }
143 
144 // This must be done early so that the spotify blob process also picks up
145 // these environment variables.
SetGstreamerEnvironment()146 void SetGstreamerEnvironment() {
147   QString scanner_path;
148   QString plugin_path;
149   QString registry_filename;
150 
151 // On windows and mac we bundle the gstreamer plugins with clementine
152 #ifdef USE_BUNDLE
153 #if defined(Q_OS_DARWIN)
154   scanner_path =
155       QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR + "/gst-plugin-scanner";
156   plugin_path =
157       QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR + "/gstreamer";
158 #elif defined(Q_OS_WIN32)
159   plugin_path = QCoreApplication::applicationDirPath() + "/gstreamer-plugins";
160 #endif
161 #endif
162 
163 #if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
164   registry_filename =
165       Utilities::GetConfigPath(Utilities::Path_GstreamerRegistry);
166 #endif
167 
168   if (!scanner_path.isEmpty()) SetEnv("GST_PLUGIN_SCANNER", scanner_path);
169 
170   if (!plugin_path.isEmpty()) {
171     SetEnv("GST_PLUGIN_PATH", plugin_path);
172     // Never load plugins from anywhere else.
173     SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path);
174   }
175 
176   if (!registry_filename.isEmpty()) {
177     SetEnv("GST_REGISTRY", registry_filename);
178   }
179 
180 #if defined(Q_OS_DARWIN) && defined(USE_BUNDLE)
181   SetEnv("GIO_EXTRA_MODULES",
182          QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR + "/gio-modules");
183 #endif
184 
185   SetEnv("PULSE_PROP_media.role", "music");
186 }
187 
ParseAProto()188 void ParseAProto() {
189   const QByteArray data = QByteArray::fromHex(
190       "08001a8b010a8801b2014566696c653a2f2f2f453a2f4d7573696b2f28414c42554d2"
191       "9253230476f74616e25323050726f6a6563742532302d253230416d6269656e742532"
192       "304c6f756e67652e6d786dba012a28414c42554d2920476f74616e2050726f6a65637"
193       "4202d20416d6269656e74204c6f756e67652e6d786dc001c7a7efd104c801bad685e4"
194       "04d001eeca32");
195   pb::tagreader::Message message;
196   message.ParseFromArray(data.constData(), data.size());
197 }
198 
CheckPortable()199 void CheckPortable() {
200   QFile f(QApplication::applicationDirPath() + QDir::separator() + "data");
201   if (f.exists()) {
202     // We are portable. Set the bool and change the qsettings path
203     Application::kIsPortable = true;
204 
205     QSettings::setDefaultFormat(QSettings::IniFormat);
206     QSettings::setPath(QSettings::IniFormat, QSettings::UserScope,
207                        f.fileName());
208   }
209 }
210 
211 }  // namespace
212 
213 #ifdef HAVE_GIO
214 #undef signals  // Clashes with GIO, and not needed in this file
215 #include <gio/gio.h>
216 
217 namespace {
218 
ScanGIOModulePath()219 void ScanGIOModulePath() {
220   QString gio_module_path;
221 
222 #if defined(Q_OS_WIN32)
223   gio_module_path = QCoreApplication::applicationDirPath() + "/gio-modules";
224 #endif
225 
226   if (!gio_module_path.isEmpty()) {
227     qLog(Debug) << "Adding GIO module path:" << gio_module_path;
228     QByteArray bytes = gio_module_path.toLocal8Bit();
229     g_io_modules_scan_all_in_directory(bytes.data());
230   }
231 }
232 
233 }  // namespace
234 #endif  // HAVE_GIO
235 
main(int argc,char * argv[])236 int main(int argc, char* argv[]) {
237   if (CrashReporting::SendCrashReport(argc, argv)) {
238     return 0;
239   }
240 
241   CrashReporting crash_reporting;
242 
243 #ifdef Q_OS_DARWIN
244   // Do Mac specific startup to get media keys working.
245   // This must go before QApplication initialisation.
246   mac::MacMain();
247 
248   if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) {
249     // Work around 10.9 issue.
250     // https://bugreports.qt.io/browse/QTBUG-32789
251     QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
252   }
253 #endif
254 
255   QCoreApplication::setApplicationName("Clementine");
256   QCoreApplication::setApplicationVersion(CLEMENTINE_VERSION_DISPLAY);
257   QCoreApplication::setOrganizationName("Clementine");
258   QCoreApplication::setOrganizationDomain("clementine-player.org");
259 
260 // This makes us show up nicely in gnome-volume-control
261 #if !GLIB_CHECK_VERSION(2, 36, 0)
262   g_type_init();  // Deprecated in glib 2.36.0
263 #endif
264   g_set_application_name(QCoreApplication::applicationName().toLocal8Bit());
265 
266   RegisterMetaTypes();
267 
268   // Initialise logging.  Log levels are set after the commandline options are
269   // parsed below.
270   logging::Init();
271   g_log_set_default_handler(reinterpret_cast<GLogFunc>(&logging::GLog),
272                             nullptr);
273 
274   CommandlineOptions options(argc, argv);
275 
276   {
277     // Only start a core application now so we can check if there's another
278     // Clementine running without needing an X server.
279     // This MUST be done before parsing the commandline options so QTextCodec
280     // gets the right system locale for filenames.
281     QtSingleCoreApplication a(argc, argv);
282     CheckPortable();
283     crash_reporting.SetApplicationPath(a.applicationFilePath());
284 
285     // Parse commandline options - need to do this before starting the
286     // full QApplication so it works without an X server
287     if (!options.Parse()) return 1;
288     logging::SetLevels(options.log_levels());
289 
290     if (a.isRunning()) {
291       if (options.is_empty()) {
292         qLog(Info)
293             << "Clementine is already running - activating existing window";
294       }
295 
296       QByteArray serializedOptions = options.Serialize();
297       if (a.sendMessage(serializedOptions, 5000)) {
298         qLog(Info) << "Options found, sent message to running instance";
299         return 0;
300       }
301       // Couldn't send the message so start anyway
302     }
303   }
304 
305   // Output the version, so when people attach log output to bug reports they
306   // don't have to tell us which version they're using.
307   qLog(Info) << "Clementine-qt5" << CLEMENTINE_VERSION_DISPLAY;
308 
309   // Seed the random number generators.
310   time_t t = time(nullptr);
311   srand(t);
312   qsrand(t);
313 
314   IncreaseFDLimit();
315 
316   QtSingleApplication a(argc, argv);
317 
318 #ifdef HAVE_LIBLASTFM
319   lastfm::ws::ApiKey = LastFMService::kApiKey;
320   lastfm::ws::SharedSecret = LastFMService::kSecret;
321   lastfm::setNetworkAccessManager(new NetworkAccessManager);
322 #endif
323 
324   // A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced
325   // with the default value of 3 in QApplicationPrivate::initialize.
326   {
327     QSettings qt_settings(QSettings::UserScope, "Trolltech");
328     qt_settings.beginGroup("Qt");
329     QApplication::setWheelScrollLines(
330         qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines())
331             .toInt());
332   }
333 
334 #if defined(Q_OS_DARWIN) && defined(USE_BUNDLE)
335   qLog(Debug) << "Looking for resources in" + QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
336   QCoreApplication::setLibraryPaths(
337       QStringList() << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR);
338 #endif
339 
340   a.setQuitOnLastWindowClosed(false);
341   // Do this check again because another instance might have started by now
342   if (a.isRunning() &&
343       a.sendMessage(QString::fromLatin1(options.Serialize()), 5000)) {
344     return 0;
345   }
346 
347 #ifndef Q_OS_DARWIN
348   // Gnome on Ubuntu has menu icons disabled by default.  I think that's a bad
349   // idea, and makes some menus in Clementine look confusing.
350   QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);
351 #else
352   QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true);
353 #endif
354 
355   SetGstreamerEnvironment();
356 
357 // Set the permissions on the config file on Unix - it can contain passwords
358 // for internet services so it's important that other users can't read it.
359 // On Windows these are stored in the registry instead.
360 #ifdef Q_OS_UNIX
361   {
362     QSettings s;
363 
364     // Create the file if it doesn't exist already
365     if (!QFile::exists(s.fileName())) {
366       QFile file(s.fileName());
367       file.open(QIODevice::WriteOnly);
368     }
369 
370     // Set -rw-------
371     QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner);
372   }
373 #endif
374 
375   // Resources
376   Q_INIT_RESOURCE(data);
377 #ifdef HAVE_TRANSLATIONS
378   Q_INIT_RESOURCE(translations);
379 #endif
380 
381   // Add root CA cert for SoundCloud, whose certificate is missing on OS X.
382   QSslSocket::addDefaultCaCertificates(
383       QSslCertificate::fromPath(":/soundcloud-ca.pem", QSsl::Pem));
384   QSslSocket::addDefaultCaCertificates(QSslCertificate::fromPath(
385       ":/Equifax_Secure_Certificate_Authority.pem", QSsl::Pem));
386 
387   // Has the user forced a different language?
388   QString override_language = options.language();
389   if (override_language.isEmpty()) {
390     QSettings s;
391     s.beginGroup("General");
392     override_language = s.value("language").toString();
393   }
394 
395   const QString language = override_language.isEmpty()
396                                ? Utilities::SystemLanguageName()
397                                : override_language;
398 
399   // Translations
400   LoadTranslation("qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath),
401                   language);
402   LoadTranslation("clementine", ":/translations", language);
403   LoadTranslation("clementine", a.applicationDirPath(), language);
404   LoadTranslation("clementine", QDir::currentPath(), language);
405 
406   // Icons
407   IconLoader::Init();
408 
409   // This is a nasty hack to ensure that everything in libprotobuf is
410   // initialised in the main thread.  It fixes issue 3265 but nobody knows why.
411   // Don't remove this unless you can reproduce the error that it fixes.
412   ParseAProto();
413   QtConcurrent::run(&ParseAProto);
414 
415   Application app;
416   QObject::connect(&a, SIGNAL(aboutToQuit()), &app, SLOT(SaveSettings_()));
417   app.set_language_name(language);
418 
419   // Network proxy
420   QNetworkProxyFactory::setApplicationProxyFactory(
421       NetworkProxyFactory::Instance());
422 
423 #ifdef Q_OS_LINUX
424   // In 11.04 Ubuntu decided that the system tray should be reserved for certain
425   // whitelisted applications.  Clementine will override this setting and insert
426   // itself into the list of whitelisted apps.
427   UbuntuUnityHack hack;
428 #endif  // Q_OS_LINUX
429 
430   // Create the tray icon and OSD
431   std::unique_ptr<SystemTrayIcon> tray_icon(
432       SystemTrayIcon::CreateSystemTrayIcon());
433   OSD osd(tray_icon.get(), &app);
434 
435 #ifdef HAVE_DBUS
436   mpris::Mpris mpris(&app);
437 #endif
438 
439   // Window
440   MainWindow w(&app, tray_icon.get(), &osd, options);
441 #ifdef Q_OS_DARWIN
442   mac::EnableFullScreen(w);
443 #endif  // Q_OS_DARWIN
444 #ifdef HAVE_GIO
445   ScanGIOModulePath();
446 #endif
447 #ifdef HAVE_DBUS
448   QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise()));
449 #endif
450   QObject::connect(&a, SIGNAL(messageReceived(QString)), &w,
451                    SLOT(CommandlineOptionsReceived(QString)));
452 
453   int ret = a.exec();
454 
455 #ifdef Q_OS_LINUX
456   // The nvidia driver would cause Clementine (or any application that used
457   // opengl) to use 100% cpu on shutdown.  See:
458   //   http://code.google.com/p/clementine-player/issues/detail?id=2088
459   //   https://bugs.gentoo.org/show_bug.cgi?id=375615
460   // Work around this problem by exiting immediately (and not running the buggy
461   // nvidia atexit shutdown handler) if we're using one of the affected versions
462   // of the nvidia driver.
463 
464   QFile self_maps("/proc/self/maps");
465   if (self_maps.open(QIODevice::ReadOnly)) {
466     QByteArray data = self_maps.readAll();
467     if (data.contains("libnvidia-tls.so.304.37") ||
468         data.contains("libnvidia-tls.so.285.03") ||
469         data.contains("libnvidia-tls.so.280.13") ||
470         data.contains("libnvidia-tls.so.275.28") ||
471         data.contains("libnvidia-tls.so.275.19")) {
472       qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug";
473       _exit(ret);
474     }
475     self_maps.close();
476   }
477 #endif
478 
479   return ret;
480 }
481