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