1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry 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  * Strawberry 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 Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 #include "version.h"
24 
25 #include <QtGlobal>
26 
27 #include <glib.h>
28 #include <cstdlib>
29 #include <memory>
30 #include <ctime>
31 
32 #ifdef Q_OS_UNIX
33 #  include <unistd.h>
34 #endif
35 
36 #ifdef Q_OS_MACOS
37 #  include <sys/resource.h>
38 #  include <sys/sysctl.h>
39 #endif
40 
41 #ifdef Q_OS_WIN32
42   #ifndef _WIN32_WINNT
43     #define _WIN32_WINNT 0x0600
44   #endif
45   #include <windows.h>
46   #include <iostream>
47 #endif  // Q_OS_WIN32
48 
49 #include <QObject>
50 #include <QApplication>
51 #include <QCoreApplication>
52 #include <QSysInfo>
53 #include <QStandardPaths>
54 #include <QLibraryInfo>
55 #include <QFileDevice>
56 #include <QIODevice>
57 #include <QByteArray>
58 #include <QNetworkProxy>
59 #include <QFile>
60 #include <QDir>
61 #include <QString>
62 #include <QImage>
63 #include <QSettings>
64 #include <QLoggingCategory>
65 #include <QtDebug>
66 #ifdef HAVE_TRANSLATIONS
67 #  include <QTranslator>
68 #endif
69 
70 #include "main.h"
71 
72 #include "core/logging.h"
73 
74 #include <singleapplication.h>
75 #include <singlecoreapplication.h>
76 
77 #ifdef HAVE_QTSPARKLE
78 #  if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
79 #    include <qtsparkle-qt6/Updater>
80 #  else
81 #    include <qtsparkle-qt5/Updater>
82 #  endif
83 #endif  // HAVE_QTSPARKLE
84 
85 #ifdef HAVE_DBUS
86 #  include "core/mpris2.h"
87 #endif
88 #include "core/utilities.h"
89 #include "core/metatypes.h"
90 #include "core/iconloader.h"
91 #include "core/mainwindow.h"
92 #include "core/commandlineoptions.h"
93 #include "core/application.h"
94 #include "core/networkproxyfactory.h"
95 #ifdef Q_OS_MACOS
96 #  include "core/macsystemtrayicon.h"
97 #else
98 #  include "core/qtsystemtrayicon.h"
99 #endif
100 #ifdef HAVE_TRANSLATIONS
101 #  include "core/translations.h"
102 #endif
103 #include "settings/behavioursettingspage.h"
104 #include "settings/appearancesettingspage.h"
105 
106 #if defined(Q_OS_MACOS)
107 #  include "osd/osdmac.h"
108 #elif defined(HAVE_DBUS)
109 #  include "osd/osddbus.h"
110 #else
111 #  include "osd/osdbase.h"
112 #endif
113 
main(int argc,char * argv[])114 int main(int argc, char *argv[]) {
115 
116 #ifdef Q_OS_MACOS
117   // Do Mac specific startup to get media keys working.
118   // This must go before QApplication initialization.
119   mac::MacMain();
120 #endif
121 
122 #if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
123   QCoreApplication::setApplicationName("Strawberry");
124   QCoreApplication::setOrganizationName("Strawberry");
125 #else
126   QCoreApplication::setApplicationName("strawberry");
127   QCoreApplication::setOrganizationName("strawberry");
128 #endif
129   QCoreApplication::setApplicationVersion(STRAWBERRY_VERSION_DISPLAY);
130   QCoreApplication::setOrganizationDomain("strawberrymusicplayer.org");
131 
132 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
133   QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
134   QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
135 #endif
136 
137   // This makes us show up nicely in gnome-volume-control
138   g_set_application_name(QCoreApplication::applicationName().toLocal8Bit());
139 
140   RegisterMetaTypes();
141 
142   // Initialize logging.  Log levels are set after the commandline options are parsed below.
143   logging::Init();
144   g_log_set_default_handler(reinterpret_cast<GLogFunc>(&logging::GLog), nullptr);
145 
146   CommandlineOptions options(argc, argv);
147   {
148     // Only start a core application now so we can check if there's another instance without requiring an X server.
149     // This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
150     SingleCoreApplication core_app(argc, argv, true, SingleCoreApplication::Mode::User | SingleCoreApplication::Mode::ExcludeAppVersion | SingleCoreApplication::Mode::ExcludeAppPath);
151     // Parse commandline options - need to do this before starting the full QApplication so it works without an X server
152     if (!options.Parse()) return 1;
153     logging::SetLevels(options.log_levels());
154     if (core_app.isSecondary()) {
155       if (options.is_empty()) {
156         qLog(Info) << "Strawberry is already running - activating existing window (1)";
157       }
158       if (!core_app.sendMessage(options.Serialize(), 5000)) {
159         qLog(Error) << "Could not send message to primary instance.";
160       }
161       return 0;
162     }
163   }
164 
165 #ifdef Q_OS_MACOS
166   // Must happen after QCoreApplication::setOrganizationName().
167   setenv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation).toLocal8Bit().constData(), 1);
168 #endif
169 
170   // Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
171   qLog(Info) << "Strawberry" << STRAWBERRY_VERSION_DISPLAY;
172   qLog(Info) << QString("%1 %2 - (%3 %4) [%5]").arg(QSysInfo::prettyProductName(), QSysInfo::productVersion(), QSysInfo::kernelType(), QSysInfo::kernelVersion(), QSysInfo::currentCpuArchitecture());
173 
174   // Seed the random number generators.
175   time_t t = time(nullptr);
176   srand(t);
177 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
178   qsrand(t);
179 #endif
180 
181   Utilities::IncreaseFDLimit();
182 
183   // important: Do not remove this.
184   // This must also be done as a SingleApplication, in case SingleCoreApplication was compiled with a different appdata.
185   SingleApplication a(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppVersion | SingleApplication::Mode::ExcludeAppPath);
186   if (a.isSecondary()) {
187     if (options.is_empty()) {
188       qLog(Info) << "Strawberry is already running - activating existing window (2)";
189     }
190     if (!a.sendMessage(options.Serialize(), 5000)) {
191       qLog(Error) << "Could not send message to primary instance.";
192     }
193     return 0;
194   }
195   QGuiApplication::setQuitOnLastWindowClosed(false);
196 
197 #if defined(USE_BUNDLE) && (defined(Q_OS_LINUX) || defined(Q_OS_MACOS))
198   qLog(Debug) << "Looking for resources in" << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
199   QCoreApplication::setLibraryPaths(QStringList() << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR);
200 #endif
201 
202   // Gnome on Ubuntu has menu icons disabled by default.  I think that's a bad idea, and makes some menus in Strawberry look confusing.
203   QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);
204 
205   {
206     QSettings s;
207     s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
208     QString style = s.value(AppearanceSettingsPage::kStyle, "default").toString();
209     s.endGroup();
210     if (style != "default") {
211       QApplication::setStyle(style);
212     }
213     if (QApplication::style()) qLog(Debug) << "Style:" << QApplication::style()->objectName();
214   }
215 
216   // 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.
217   // On Windows these are stored in the registry instead.
218 #ifdef Q_OS_UNIX
219   {
220     QSettings s;
221 
222     // Create the file if it doesn't exist already
223     if (!QFile::exists(s.fileName())) {
224       QFile file(s.fileName());
225       if (file.open(QIODevice::WriteOnly)) {
226         file.close();
227       }
228       else {
229         qLog(Error) << "Could not open settings file" << file.fileName() << "for writing:" << file.errorString();
230       }
231     }
232 
233     // Set -rw-------
234     QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner);
235   }
236 #endif
237 
238   // Resources
239   Q_INIT_RESOURCE(data);
240   Q_INIT_RESOURCE(icons);
241 #if defined(HAVE_TRANSLATIONS) && !defined(INSTALL_TRANSLATIONS)
242   Q_INIT_RESOURCE(translations);
243 #endif
244 
245 #ifndef QT_NO_DEBUG_OUTPUT
246   QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, true);
247 #endif
248 
249   IconLoader::Init();
250 
251 #ifdef HAVE_TRANSLATIONS
252   QString override_language = options.language();
253   if (override_language.isEmpty()) {
254     QSettings s;
255     s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
256     override_language = s.value("language").toString();
257     s.endGroup();
258   }
259 
260   QString system_language = QLocale::system().uiLanguages().empty() ? QLocale::system().name() : QLocale::system().uiLanguages().first();
261   // uiLanguages returns strings with "-" as separators for language/region; however QTranslator needs "_" separators
262   system_language.replace("-", "_");
263 
264   const QString language = override_language.isEmpty() ? system_language : override_language;
265 
266   std::unique_ptr<Translations> translations(new Translations);
267 
268 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
269   translations->LoadTranslation("qt", QLibraryInfo::path(QLibraryInfo::TranslationsPath), language);
270 #else
271   translations->LoadTranslation("qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath), language);
272 #endif
273   translations->LoadTranslation("strawberry", ":/translations", language);
274   translations->LoadTranslation("strawberry", TRANSLATIONS_DIR, language);
275   translations->LoadTranslation("strawberry", QCoreApplication::applicationDirPath(), language);
276   translations->LoadTranslation("strawberry", QDir::currentPath(), language);
277 
278 #ifdef HAVE_QTSPARKLE
279   qtsparkle::LoadTranslations(language);
280 #endif
281 
282 #endif
283 
284   Application app;
285 
286   // Network proxy
287   QNetworkProxyFactory::setApplicationProxyFactory(NetworkProxyFactory::Instance());
288 
289   // Create the tray icon and OSD
290   std::shared_ptr<SystemTrayIcon> tray_icon = std::make_shared<SystemTrayIcon>();
291 
292 #if defined(Q_OS_MACOS)
293   OSDMac osd(tray_icon, &app);
294 #elif defined(HAVE_DBUS)
295   OSDDBus osd(tray_icon, &app);
296 #else
297   OSDBase osd(tray_icon, &app);
298 #endif
299 
300 #ifdef HAVE_DBUS
301   mpris::Mpris2 mpris2(&app);
302 #endif
303 
304   // Window
305   MainWindow w(&app, tray_icon, &osd, options);
306 
307 #ifdef Q_OS_MACOS
308   mac::EnableFullScreen(w);
309 #endif  // Q_OS_MACOS
310 
311 #ifdef HAVE_DBUS
312   QObject::connect(&mpris2, &mpris::Mpris2::RaiseMainWindow, &w, &MainWindow::Raise);
313 #endif
314   QObject::connect(&a, &SingleApplication::receivedMessage, &w, QOverload<quint32, const QByteArray&>::of(&MainWindow::CommandlineOptionsReceived));
315 
316   int ret = QCoreApplication::exec();
317 
318   return ret;
319 }
320