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