1 /*
2 * KMix -- KDE's full featured mini mixer
3 *
4 * Copyright (C) 2000 Stefan Schimanski <schimmi@kde.org>
5 * Copyright (C) 2001 Preston Brown <pbrown@kde.org>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This program 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 GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this program; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "kmixapp.h"
23
24 #include <qapplication.h>
25 #include <qcommandlineparser.h>
26
27 #include "kmix_debug.h"
28 #include "core/ControlManager.h"
29 #include "apps/kmixwindow.h"
30 #include "settings.h"
31
32
33 static bool firstCaller = true;
34
35
36 // Originally this class was a subclass of KUniqueApplication.
37 // Since, now that a unique application is enforced earlier by KDBusService,
38 // the only purpose of this class is to receive the activateRequested()
39 // signal. It can therefore be a simple QObject.
40
KMixApp()41 KMixApp::KMixApp()
42 : QObject(),
43 m_kmix(nullptr),
44 creationLock(QMutex::Recursive)
45 {
46 // We must disable QuitOnLastWindowClosed. Rationale:
47 // 1) The normal state of KMix is to only have the dock icon shown.
48 // 2a) The dock icon gets reconstructed, whenever a soundcard is hotplugged or unplugged.
49 // 2b) The dock icon gets reconstructed, when the user selects a new master.
50 // 3) During the reconstruction, it can easily happen that no window is present => KMix would quit
51 // => disable QuitOnLastWindowClosed
52 qApp->setQuitOnLastWindowClosed(false);
53 }
54
~KMixApp()55 KMixApp::~KMixApp()
56 {
57 qCDebug(KMIX_LOG) << "Deleting KMixApp";
58 ControlManager::instance().shutdownNow();
59 delete m_kmix;
60 m_kmix = nullptr;
61 Settings::self()->save();
62 }
63
createWindowOnce(bool hasArgKeepvisibility,bool reset)64 void KMixApp::createWindowOnce(bool hasArgKeepvisibility, bool reset)
65 {
66 // Create window, if it was not yet created (e.g. via autostart or manually)
67 if (m_kmix==nullptr)
68 {
69 qCDebug(KMIX_LOG) << "Creating new KMix window";
70 m_kmix = new KMixWindow(hasArgKeepvisibility, reset);
71 }
72 }
73
restoreSessionIfApplicable(bool hasArgKeepvisibility,bool reset)74 bool KMixApp::restoreSessionIfApplicable(bool hasArgKeepvisibility, bool reset)
75 {
76 /**
77 * We should lock session creation. Rationale:
78 * KMix can be started multiple times during session start. By "autostart" and "session restore". The order is
79 * undetermined, as KMix will initialize in the background of KDE session startup (Hint: As a
80 * KUniqueApplication it decouples from the startkde process!).
81 *
82 * Now we must make sure that window creation is definitely done, before the "other" process comes, as it might
83 * want to restore the session. Working on a half-created window would not be smart! Why can this happen? It
84 * depends on implementation details inside Qt, which COULD potentially lead to the following scenarios:
85 * 1) "Autostart" and "session restore" run concurrently in 2 different Threads.
86 * 2) The current "main/gui" thread "pops up" a "session restore" message from the Qt event dispatcher.
87 * This means that "Autostart" and "session restore" run interleaved in a single Thread.
88 */
89 creationLock.lock();
90
91 bool restore = qApp->isSessionRestored(); // && KMainWindow::canBeRestored(0);
92 qCDebug(KMIX_LOG) << "Starting KMix using keepvisibility=" << hasArgKeepvisibility << ", failsafe=" << reset << ", sessionRestore=" << restore;
93 int createCount = 0;
94 if (restore)
95 {
96 if (reset)
97 {
98 qCWarning(KMIX_LOG) << "Reset cannot be performed while KMix is running. Please quit KMix and retry then.";
99 }
100 int n = 1;
101 while (KMainWindow::canBeRestored(n))
102 {
103 qCDebug(KMIX_LOG) << "Restoring window " << n;
104 if (n > 1)
105 {
106 // This code path is "impossible". It is here only for analyzing possible issues with session restoring.
107 // KMix is a single-instance app. If more than one instance is created we have a bug.
108 qCWarning(KMIX_LOG) << "KDE session management wants to restore multiple instances of KMix. Please report this as a bug.";
109 break;
110 }
111 else
112 {
113 // Create window, if it was not yet created (e.g. via autostart or manually)
114 createWindowOnce(hasArgKeepvisibility, reset);
115 // #restore() is called with the parameter of "show == false", as KMixWindow itself decides on it.
116 m_kmix->restore(n, false);
117 createCount++;
118 n++;
119 }
120 }
121 }
122
123 if (createCount == 0)
124 {
125 // Normal start, or if nothing could be restored
126 createWindowOnce(hasArgKeepvisibility, reset);
127 }
128
129 creationLock.unlock();
130 return restore;
131 }
132
newInstance(const QStringList & arguments,const QString & workingDirectory)133 void KMixApp::newInstance(const QStringList &arguments, const QString &workingDirectory)
134 {
135 qCDebug(KMIX_LOG);
136
137 /**
138 * There are 3 cases when starting KMix:
139 * Autostart : Cases 1) or 3) below
140 * Session restore : Cases 1) or 2a) below
141 * Manual start by user : Cases 1) or 2b) below
142 *
143 * Each may be the creator a new instance, but only if the instance did not exist yet.
144 */
145
146
147 //qCDebug(KMIX_LOG) << "KMixApp::newInstance() isRestored()=" << isRestored() << "_keepVisibility=" << _keepVisibility;
148 /**
149 * NB See https://qa.mandriva.com/show_bug.cgi?id=56893#c3
150 *
151 * It is important to track this via a separate variable and not
152 * based on m_kmix to handle this race condition.
153 * Specific protection for the activation-prior-to-full-construction
154 * case exists above in the 'already running case'
155 */
156 creationLock.lock(); // Guard a complete construction
157 bool first = firstCaller;
158 firstCaller = false;
159
160 if (first)
161 {
162 /** CASE 1 *******************************************************
163 *
164 * Typical case: Normal start. KMix was not running yet => create a new KMixWindow
165 */
166 restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset);
167 }
168 else
169 {
170 if (!m_hasArgKeepvisibility)
171 {
172 /** CASE 2 ******************************************************
173 *
174 * KMix is running, AND the *USER* starts it again (w/o --keepvisibilty)
175 * 2a) Restored the KMix main window will be shown.
176 * 2b) Not restored
177 */
178
179 /*
180 * Restore Session. This may look strange to you, as the instance already exists. But the following
181 * sequence might happen:
182 * 1) Autostart (no restore) => create m_kmix instance (via CASE 1)
183 * 2) Session restore => we are here at this line of code (CASE 2). m_kmix exists, but still must be restored
184 *
185 */
186 bool wasRestored = restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset);
187 if (!wasRestored)
188 {
189 //
190 // Use standard newInstances(), which shows and activates the main window. But skip it for the
191 // special "restored" case, as we should not override the session rules.
192 // TODO: what should be done for KF5?
193 //KUniqueApplication::newInstance();
194 }
195 // else: Do nothing, as session restore has done it.
196 }
197 else
198 {
199 /** CASE 3 ******************************************************
200 *
201 * KMix is running, AND launched again with --keepvisibilty
202 *
203 * Typical use case: Autostart
204 *
205 * => We don't want to change the visibility, thus we don't call show() here.
206 *
207 * Hint: --keepVisibility is used in kmix_autostart.desktop. It was used in history by KMilo
208 * (see BKO 58901), but nowadays Mixer Applets might want to use it, though they should
209 * use KMixD instead.
210 */
211 qCDebug(KMIX_LOG)
212 << "KMixApp::newInstance() REGULAR_START _keepVisibility="
213 << m_hasArgKeepvisibility;
214 }
215 }
216
217 creationLock.unlock();
218 }
219
220
parseOptions(const QCommandLineParser & parser)221 void KMixApp::parseOptions(const QCommandLineParser &parser)
222 {
223 m_hasArgKeepvisibility = parser.isSet("keepvisibility");
224 m_hasArgReset = parser.isSet("failsafe");
225 }
226