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