File indexing completed on 2022-09-27 14:35:02

0001 /*
0002  * KMix -- KDE's full featured mini mixer
0003  *
0004  * Copyright (C) 2000 Stefan Schimanski <schimmi@kde.org>
0005  * Copyright (C) 2001 Preston Brown <pbrown@kde.org>
0006  *
0007  * This program is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public
0018  * License along with this program; if not, write to the Free
0019  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0020  */
0021 
0022 #include "kmixapp.h"
0023 
0024 #include <qapplication.h>
0025 #include <qcommandlineparser.h>
0026 
0027 #include "kmix_debug.h"
0028 #include "core/ControlManager.h"
0029 #include "apps/kmixwindow.h"
0030 #include "settings.h"
0031 
0032 
0033 static bool firstCaller = true;
0034 
0035 
0036 // Originally this class was a subclass of KUniqueApplication.
0037 // Since, now that a unique application is enforced earlier by KDBusService,
0038 // the only purpose of this class is to receive the activateRequested()
0039 // signal.  It can therefore be a simple QObject.
0040 
0041 KMixApp::KMixApp()
0042     : QObject(),
0043       m_kmix(nullptr)
0044 {
0045     // We must disable QuitOnLastWindowClosed. Rationale:
0046     // 1) The normal state of KMix is to only have the dock icon shown.
0047     // 2a) The dock icon gets reconstructed, whenever a soundcard is hotplugged or unplugged.
0048     // 2b) The dock icon gets reconstructed, when the user selects a new master.
0049     // 3) During the reconstruction, it can easily happen that no window is present => KMix would quit
0050     // => disable QuitOnLastWindowClosed
0051     qApp->setQuitOnLastWindowClosed(false);
0052 }
0053 
0054 KMixApp::~KMixApp()
0055 {
0056     qCDebug(KMIX_LOG) << "Deleting KMixApp";
0057     ControlManager::instance().shutdownNow();
0058     delete m_kmix;
0059     m_kmix = nullptr;
0060     Settings::self()->save();
0061 }
0062 
0063 void KMixApp::createWindowOnce(bool hasArgKeepvisibility, bool reset)
0064 {
0065     // Create window, if it was not yet created (e.g. via autostart or manually)
0066     if (m_kmix==nullptr)
0067     {
0068         qCDebug(KMIX_LOG) << "Creating new KMix window";
0069         m_kmix = new KMixWindow(hasArgKeepvisibility, reset);
0070     }
0071 }
0072 
0073 bool KMixApp::restoreSessionIfApplicable(bool hasArgKeepvisibility, bool reset)
0074 {
0075     /**
0076      * We should lock session creation. Rationale:
0077      * KMix can be started multiple times during session start. By "autostart" and "session restore". The order is
0078      * undetermined, as KMix will initialize in the background of KDE session startup (Hint: As a
0079      * KUniqueApplication it decouples from the startkde process!).
0080      *
0081      * Now we must make sure that window creation is definitely done, before the "other" process comes, as it might
0082      * want to restore the session. Working on a half-created window would not be smart! Why can this happen? It
0083      * depends on implementation details inside Qt, which COULD potentially lead to the following scenarios:
0084      * 1) "Autostart" and "session restore" run concurrently in 2 different Threads.
0085      * 2) The current "main/gui" thread "pops up" a "session restore" message from the Qt event dispatcher.
0086      *    This means that  "Autostart" and "session restore" run interleaved in a single Thread.
0087      */
0088     creationLock.lock();
0089 
0090     bool restore = qApp->isSessionRestored(); // && KMainWindow::canBeRestored(0);
0091     qCDebug(KMIX_LOG) << "Starting KMix using keepvisibility=" << hasArgKeepvisibility << ", failsafe=" << reset << ", sessionRestore=" << restore;
0092     int createCount = 0;
0093     if (restore)
0094     {
0095         if (reset)
0096         {
0097             qCWarning(KMIX_LOG) << "Reset cannot be performed while KMix is running. Please quit KMix and retry then.";
0098         }
0099         int n = 1;
0100         while (KMainWindow::canBeRestored(n))
0101         {
0102             qCDebug(KMIX_LOG) << "Restoring window " << n;
0103             if (n > 1)
0104             {
0105                 // This code path is "impossible". It is here only for analyzing possible issues with session restoring.
0106                 // KMix is a single-instance app. If more than one instance is created we have a bug.
0107                 qCWarning(KMIX_LOG) << "KDE session management wants to restore multiple instances of KMix. Please report this as a bug.";
0108                 break;
0109             }
0110             else
0111             {
0112                 // Create window, if it was not yet created (e.g. via autostart or manually)
0113                 createWindowOnce(hasArgKeepvisibility, reset);
0114                 // #restore() is called with the parameter of "show == false", as KMixWindow itself decides on it.
0115                 m_kmix->restore(n, false);
0116                 createCount++;
0117                 n++;
0118             }
0119         }
0120     }
0121 
0122     if (createCount == 0)
0123     {
0124         // Normal start, or if nothing could be restored
0125         createWindowOnce(hasArgKeepvisibility, reset);
0126     }
0127 
0128     creationLock.unlock();
0129     return restore;
0130 }
0131 
0132 void KMixApp::newInstance(const QStringList &arguments, const QString &workingDirectory)
0133 {
0134     qCDebug(KMIX_LOG);
0135 
0136     /**
0137      * There are 3 cases when starting KMix:
0138      * Autostart            : Cases 1) or 3) below
0139      * Session restore      : Cases 1) or 2a) below
0140      * Manual start by user : Cases 1) or 2b) below
0141      *
0142      * Each may be the creator a new instance, but only if the instance did not exist yet.
0143      */
0144 
0145 
0146     //qCDebug(KMIX_LOG) <<  "KMixApp::newInstance() isRestored()=" << isRestored() << "_keepVisibility=" << _keepVisibility;
0147     /**
0148      *      NB See https://qa.mandriva.com/show_bug.cgi?id=56893#c3
0149      *
0150      *      It is important to track this via a separate variable and not
0151      *      based on m_kmix to handle this race condition.
0152      *      Specific protection for the activation-prior-to-full-construction
0153      *      case exists above in the 'already running case'
0154      */
0155     creationLock.lock(); // Guard a complete construction
0156     bool first = firstCaller;
0157     firstCaller = false;
0158 
0159     if (first)
0160     {
0161         /** CASE 1 *******************************************************
0162          *
0163          * Typical case: Normal start. KMix was not running yet => create a new KMixWindow
0164          */
0165         restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset);
0166     }
0167     else
0168     {
0169         if (!m_hasArgKeepvisibility)
0170         {
0171             /** CASE 2 ******************************************************
0172              *
0173              * KMix is running, AND the *USER* starts it again (w/o --keepvisibilty)
0174              * 2a) Restored the KMix main window will be shown.
0175              * 2b) Not restored
0176              */
0177 
0178             /*
0179              * Restore Session. This may look strange to you, as the instance already exists. But the following
0180              * sequence might happen:
0181              * 1) Autostart (no restore) => create m_kmix instance (via CASE 1)
0182              * 2) Session restore => we are here at this line of code (CASE 2). m_kmix exists, but still must be restored
0183              *
0184              */
0185             bool wasRestored = restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset);
0186             if (!wasRestored)
0187             {
0188                 //
0189                 // Use standard newInstances(), which shows and activates the main window. But skip it for the
0190                 // special "restored" case, as we should not override the session rules.
0191                 // TODO: what should be done for KF5?
0192                 //KUniqueApplication::newInstance();
0193             }
0194             // else: Do nothing, as session restore has done it.
0195         }
0196         else
0197         {
0198             /** CASE 3 ******************************************************
0199              *
0200              * KMix is running, AND launched again with --keepvisibilty
0201              *
0202              * Typical use case: Autostart
0203              *
0204              * =>  We don't want to change the visibility, thus we don't call show() here.
0205              *
0206              * Hint: --keepVisibility is used in kmix_autostart.desktop. It was used in history by KMilo
0207              *       (see BKO 58901), but nowadays Mixer Applets might want to use it, though they should
0208              *       use KMixD instead.
0209              */
0210             qCDebug(KMIX_LOG)
0211             << "KMixApp::newInstance() REGULAR_START _keepVisibility="
0212                     << m_hasArgKeepvisibility;
0213         }
0214     }
0215 
0216     creationLock.unlock();
0217 }
0218 
0219 
0220 void KMixApp::parseOptions(const QCommandLineParser &parser)
0221 {
0222     m_hasArgKeepvisibility = parser.isSet("keepvisibility");
0223     m_hasArgReset = parser.isSet("failsafe");
0224 }