/* This file is part of Clementine. Copyright 2010, David Sansome Clementine 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. Clementine 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 Clementine. If not, see . */ #include #ifdef Q_OS_WIN32 # define _WIN32_WINNT 0x0600 # include # include #endif // Q_OS_WIN32 #include "config.h" #include "core/application.h" #include "core/commandlineoptions.h" #include "core/crashreporting.h" #include "core/encoding.h" #include "core/database.h" #include "core/logging.h" #include "core/mac_startup.h" #include "core/metatypes.h" #include "core/network.h" #include "core/networkproxyfactory.h" #include "core/potranslator.h" #include "core/song.h" #include "core/ubuntuunityhack.h" #include "core/utilities.h" #include "covers/amazoncoverprovider.h" #include "covers/coverproviders.h" #include "covers/discogscoverprovider.h" #include "covers/musicbrainzcoverprovider.h" #include "engines/enginebase.h" #include "smartplaylists/generator.h" #include "ui/iconloader.h" #include "ui/mainwindow.h" #include "ui/systemtrayicon.h" #include "version.h" #include "widgets/osd.h" #include "qtsingleapplication.h" #include "qtsinglecoreapplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using boost::scoped_ptr; #include #ifdef HAVE_SPOTIFY_DOWNLOADER #include #endif #ifdef Q_OS_DARWIN #include #include #endif #ifdef HAVE_LIBLASTFM #include "internet/lastfmservice.h" #else class LastFMService; #endif #ifdef HAVE_DBUS #include "core/mpris.h" #include "core/mpris2.h" #include #include QDBusArgument& operator<< (QDBusArgument& arg, const QImage& image); const QDBusArgument& operator>> (const QDBusArgument& arg, QImage& image); #endif #ifdef Q_OS_WIN32 # include #endif // Load sqlite plugin on windows and mac. #ifdef HAVE_STATIC_SQLITE # include Q_IMPORT_PLUGIN(qsqlite) #endif void LoadTranslation(const QString& prefix, const QString& path, const QString& override_language = QString()) { #if QT_VERSION < 0x040700 // QTranslator::load will try to open and read "clementine" if it exists, // without checking if it's a file first. // This was fixed in Qt 4.7 QFileInfo maybe_clementine_directory(path + "/clementine"); if (maybe_clementine_directory.exists() && !maybe_clementine_directory.isFile()) return; #endif #if QT_VERSION >= 0x040800 QString system_language = QLocale::system().uiLanguages().empty() ? QLocale::system().name() : QLocale::system().uiLanguages()[0]; #else QString system_language = QLocale::system().name(); #endif QString language = override_language.isEmpty() ? system_language : override_language; QTranslator* t = new PoTranslator; if (t->load(prefix + "_" + language, path)) QCoreApplication::installTranslator(t); else delete t; QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); } void IncreaseFDLimit() { #ifdef Q_OS_DARWIN // Bump the soft limit for the number of file descriptors from the default of 256 to // the maximum (usually 10240). struct rlimit limit; getrlimit(RLIMIT_NOFILE, &limit); // getrlimit() lies about the hard limit so we have to check sysctl. int max_fd = 0; size_t len = sizeof(max_fd); sysctlbyname("kern.maxfilesperproc", &max_fd, &len, NULL, 0); limit.rlim_cur = max_fd; int ret = setrlimit(RLIMIT_NOFILE, &limit); if (ret == 0) { qLog(Debug) << "Max fd:" << max_fd; } #endif } void SetEnv(const char *key, const QString &value) { #ifdef Q_OS_WIN32 putenv(QString("%1=%2").arg(key, value).toLocal8Bit().constData()); #else setenv(key, value.toLocal8Bit().constData(), 1); #endif } // This must be done early so that the spotify blob process also picks up // these environment variables. void SetGstreamerEnvironment() { QString scanner_path; QString plugin_path; QString registry_filename; // On windows and mac we bundle the gstreamer plugins with clementine #if defined(Q_OS_DARWIN) scanner_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gst-plugin-scanner"; plugin_path = QCoreApplication::applicationDirPath() + "/../PlugIns/gstreamer"; #elif defined(Q_OS_WIN32) plugin_path = QCoreApplication::applicationDirPath() + "/gstreamer-plugins"; #endif #if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) registry_filename = Utilities::GetConfigPath(Utilities::Path_GstreamerRegistry); #endif if (!scanner_path.isEmpty()) SetEnv("GST_PLUGIN_SCANNER", scanner_path); if (!plugin_path.isEmpty()) { SetEnv("GST_PLUGIN_PATH", plugin_path); // Never load plugins from anywhere else. SetEnv("GST_PLUGIN_SYSTEM_PATH", plugin_path); } if (!registry_filename.isEmpty()) { SetEnv("GST_REGISTRY", registry_filename); } #ifdef Q_OS_DARWIN SetEnv("GIO_EXTRA_MODULES", QCoreApplication::applicationDirPath() + "/../PlugIns/gio-modules"); #endif } #ifdef HAVE_GIO # undef signals // Clashes with GIO, and not needed in this file # include void ScanGIOModulePath() { QString gio_module_path; #if defined(Q_OS_WIN32) gio_module_path = QCoreApplication::applicationDirPath() + "/gio-modules"; #endif if (!gio_module_path.isEmpty()) { qLog(Debug) << "Adding GIO module path:" << gio_module_path; QByteArray bytes = gio_module_path.toLocal8Bit(); g_io_modules_scan_all_in_directory(bytes.data()); } } #endif int main(int argc, char *argv[]) { if (CrashReporting::SendCrashReport(argc, argv)) { return 0; } CrashReporting crash_reporting; #ifdef Q_OS_DARWIN // Do Mac specific startup to get media keys working. // This must go before QApplication initialisation. mac::MacMain(); #endif QCoreApplication::setApplicationName("Clementine"); QCoreApplication::setApplicationVersion(CLEMENTINE_VERSION_DISPLAY); QCoreApplication::setOrganizationName("Clementine"); QCoreApplication::setOrganizationDomain("clementine-player.org"); #ifdef Q_OS_DARWIN // Must happen after QCoreApplication::setOrganizationName(). setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1); if (mac::MigrateLegacyConfigFiles()) { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(Utilities::GetConfigPath( Utilities::Path_Root) + "/" + Database::kDatabaseFilename); db.open(); QSqlQuery query( "UPDATE songs SET art_manual = replace(" "art_manual, '.config', 'Library/Application Support') " "WHERE art_manual LIKE '%.config%'", db); query.exec(); db.close(); QSqlDatabase::removeDatabase(db.connectionName()); } #endif // This makes us show up nicely in gnome-volume-control g_type_init(); g_set_application_name(QCoreApplication::applicationName().toLocal8Bit()); RegisterMetaTypes(); #ifdef HAVE_LIBLASTFM lastfm::ws::ApiKey = LastFMService::kApiKey; lastfm::ws::SharedSecret = LastFMService::kSecret; lastfm::setNetworkAccessManager(new NetworkAccessManager); #endif CommandlineOptions options(argc, argv); { // Only start a core application now so we can check if there's another // Clementine running without needing an X server. // This MUST be done before parsing the commandline options so QTextCodec // gets the right system locale for filenames. QtSingleCoreApplication a(argc, argv); crash_reporting.SetApplicationPath(a.applicationFilePath()); // Parse commandline options - need to do this before starting the // full QApplication so it works without an X server if (!options.Parse()) return 1; if (a.isRunning()) { if (options.is_empty()) { qLog(Info) << "Clementine is already running - activating existing window"; } if (a.sendMessage(options.Serialize(), 5000)) { return 0; } // Couldn't send the message so start anyway } } // Initialise logging logging::Init(); logging::SetLevels(options.log_levels()); g_log_set_default_handler(reinterpret_cast(&logging::GLog), NULL); // Seed the random number generators. time_t t = time(NULL); srand(t); qsrand(t); IncreaseFDLimit(); QtSingleApplication a(argc, argv); // A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced // with the default value of 3 in QApplicationPrivate::initialize. { QSettings qt_settings(QSettings::UserScope, "Trolltech"); qt_settings.beginGroup("Qt"); QApplication::setWheelScrollLines( qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines()).toInt()); } #ifdef Q_OS_DARWIN QCoreApplication::setLibraryPaths( QStringList() << QCoreApplication::applicationDirPath() + "/../PlugIns"); #endif a.setQuitOnLastWindowClosed(false); // Do this check again because another instance might have started by now if (a.isRunning() && a.sendMessage(options.Serialize(), 5000)) { return 0; } #ifndef Q_OS_DARWIN // Gnome on Ubuntu has menu icons disabled by default. I think that's a bad // idea, and makes some menus in Clementine look confusing. QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false); #else QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true); // Fixes focus issue with NSSearchField, see QTBUG-11401 QCoreApplication::setAttribute(Qt::AA_NativeWindows, true); #endif SetGstreamerEnvironment(); // Set the permissions on the config file on Unix - it can contain passwords // for internet services so it's important that other users can't read it. // On Windows these are stored in the registry instead. #ifdef Q_OS_UNIX { QSettings s; // Create the file if it doesn't exist already if (!QFile::exists(s.fileName())) { QFile file(s.fileName()); file.open(QIODevice::WriteOnly); } // Set -rw------- QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner); } #endif #ifdef HAVE_SPOTIFY_DOWNLOADER QCA::Initializer qca_initializer; #endif // Resources Q_INIT_RESOURCE(data); Q_INIT_RESOURCE(translations); // Grooveshark uses GoDaddy to sign its SSL certificates, which are in turn // signed by a ValiCert CA. This CA certificate isn't installed by default // in Windows, it's only added by windows update, or manually browsing to a // website with a certificate signed by ValiCert. Here we explicitly add // that CA to the default list used by QSslSocket, so it always works in // Clementine. QSslSocket::addDefaultCaCertificates( QSslCertificate::fromPath(":/grooveshark-valicert-ca.pem", QSsl::Pem)); // Has the user forced a different language? QString language = options.language(); if (language.isEmpty()) { QSettings s; s.beginGroup("General"); language = s.value("language").toString(); } // Translations LoadTranslation("qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath), language); LoadTranslation("clementine", ":/translations", language); LoadTranslation("clementine", a.applicationDirPath(), language); LoadTranslation("clementine", QDir::currentPath(), language); #ifdef Q_OS_WIN32 // Set the language for qtsparkle qtsparkle::LoadTranslations(language); #endif // Icons IconLoader::Init(); Application app; Echonest::Config::instance()->setAPIKey("DFLFLJBUF4EGTXHIG"); Echonest::Config::instance()->setNetworkAccessManager(new NetworkAccessManager); // Network proxy QNetworkProxyFactory::setApplicationProxyFactory( NetworkProxyFactory::Instance()); // Initialize the repository of cover providers. Last.fm registers itself // when its service is created. app.cover_providers()->AddProvider(new AmazonCoverProvider); app.cover_providers()->AddProvider(new DiscogsCoverProvider); app.cover_providers()->AddProvider(new MusicbrainzCoverProvider); #ifdef Q_OS_LINUX // In 11.04 Ubuntu decided that the system tray should be reserved for certain // whitelisted applications. Clementine will override this setting and insert // itself into the list of whitelisted apps. UbuntuUnityHack hack; #endif // Q_OS_LINUX // Create the tray icon and OSD scoped_ptr tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); OSD osd(tray_icon.get(), &app); #ifdef HAVE_DBUS mpris::Mpris mpris(&app); #endif // Window MainWindow w(&app, tray_icon.get(), &osd); #ifdef Q_OS_DARWIN mac::EnableFullScreen(w); #endif // Q_OS_DARWIN #ifdef HAVE_GIO ScanGIOModulePath(); #endif #ifdef HAVE_DBUS QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise())); #endif QObject::connect(&a, SIGNAL(messageReceived(QByteArray)), &w, SLOT(CommandlineOptionsReceived(QByteArray))); w.CommandlineOptionsReceived(options); int ret = a.exec(); #ifdef Q_OS_LINUX // The nvidia driver would cause Clementine (or any application that used // opengl) to use 100% cpu on shutdown. See: // http://code.google.com/p/clementine-player/issues/detail?id=2088 // https://bugs.gentoo.org/show_bug.cgi?id=375615 // Work around this problem by exiting immediately (and not running the buggy // nvidia atexit shutdown handler) if we're using one of the affected versions // of the nvidia driver. QFile self_maps("/proc/self/maps"); if (self_maps.open(QIODevice::ReadOnly)) { QByteArray data = self_maps.readAll(); if (data.contains("libnvidia-tls.so.")) { qLog(Warning) << "Exiting immediately to work around NVIDIA driver bug"; _exit(ret); } self_maps.close(); } #endif return ret; }