aboutsummaryrefslogtreecommitdiff
path: root/singleapplication.cpp
blob: b18ba482ee1a35601cea35ff2090af4dd8ce1e57 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#include <cstdlib>

#include <QtCore/QMutex>
#include <QtCore/QSemaphore>
#include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalSocket>
#include <QtNetwork/QLocalServer>

#ifdef Q_OS_UNIX
    #include <signal.h>
    #include <unistd.h>
#endif

#include "singleapplication.h"

struct InstancesInfo {
    bool primary;
    uint8_t secondary;
};

class SingleApplicationPrivate {
public:
    Q_DECLARE_PUBLIC(SingleApplication)

    SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) {
        server = NULL;
    }

    ~SingleApplicationPrivate()
    {
       cleanUp();
    }

    void startPrimary( bool resetMemory )
    {
        Q_Q(SingleApplication);
#ifdef Q_OS_UNIX
        // Handle any further termination signals to ensure the
        // QSharedMemory block is deleted even if the process crashes
        crashHandler();
#endif
        // Successful creation means that no main process exists
        // So we start a QLocalServer to listen for connections
        QLocalServer::removeServer( memory->key() );
        server = new QLocalServer();
        server->listen( memory->key() );
        QObject::connect(
            server,
            &QLocalServer::newConnection,
            q,
            &SingleApplication::slotConnectionEstablished
        );

        // Reset the number of connections
        memory->lock();
        InstancesInfo* inst = (InstancesInfo*)memory->data();

        if( resetMemory ){
            inst->primary = true;
            inst->secondary = 0;
        } else {
            inst->primary = true;
        }

        memory->unlock();
    }

    void startSecondary()
    {
#ifdef Q_OS_UNIX
        // Handle any further termination signals to ensure the
        // QSharedMemory block is deleted even if the process crashes
        crashHandler();
#endif

        notifyPrimary();
    }

    void notifyPrimary()
    {
        // Connect to the Local Server of the main process to notify it
        // that a new process had been started
        QLocalSocket socket;
        socket.connectToServer( memory->key() );

        // Notify the parent that a new instance had been started;
        socket.waitForConnected( 100 );
        socket.close();
    }

#ifdef Q_OS_UNIX
    void crashHandler()
    {
        // This guarantees the program will work even with multiple
        // instances of SingleApplication in different threads.
        // Which in my opinion is idiotic, but lets handle that too.
        {
            sharedMemMutex.lock();
            sharedMem.append( this );
            sharedMemMutex.unlock();
        }

        // Handle any further termination signals to ensure the
        // QSharedMemory block is deleted even if the process crashes
        signal( SIGHUP, SingleApplicationPrivate::terminate );   // 1
        signal( SIGINT,  SingleApplicationPrivate::terminate );  // 2
        signal( SIGQUIT,  SingleApplicationPrivate::terminate ); // 3
        signal( SIGILL,  SingleApplicationPrivate::terminate );  // 4
        signal( SIGABRT, SingleApplicationPrivate::terminate );  // 6
        signal( SIGBUS,  SingleApplicationPrivate::terminate );  // 7
        signal( SIGFPE,  SingleApplicationPrivate::terminate );  // 8
        signal( SIGSEGV, SingleApplicationPrivate::terminate );  // 11
        signal( SIGSYS, SingleApplicationPrivate::terminate );   // 12
        signal( SIGPIPE, SingleApplicationPrivate::terminate );  // 13
        signal( SIGALRM, SingleApplicationPrivate::terminate );  // 14
        signal( SIGTERM, SingleApplicationPrivate::terminate );  // 15
        signal( SIGXCPU, SingleApplicationPrivate::terminate );  // 24
        signal( SIGXFSZ, SingleApplicationPrivate::terminate );  // 25
    }

    static void terminate( int signum )
    {
        while( ! sharedMem.empty() ) {
            delete sharedMem.back();
            sharedMem.pop_back();
        }
        ::exit( 128 + signum );
    }

    static QList<SingleApplicationPrivate*> sharedMem;
    static QMutex sharedMemMutex;
#endif

    void cleanUp() {
        memory->lock();
        InstancesInfo* inst = (InstancesInfo*)memory->data();
        if( server != NULL ) {
            server->close();
            inst->primary = false;
        } else {
            if( inst->secondary > 0 )
                inst->secondary -= 1;
        }
        memory->unlock();
        delete memory;
    }

    QSharedMemory *memory;
    SingleApplication *q_ptr;
    QLocalServer *server;
};

#ifdef Q_OS_UNIX
    QList<SingleApplicationPrivate*> SingleApplicationPrivate::sharedMem;
    QMutex SingleApplicationPrivate::sharedMemMutex;
#endif

/**
 * @brief Constructor. Checks and fires up LocalServer or closes the program
 * if another instance already exists
 * @param argc
 * @param argv
 */
SingleApplication::SingleApplication( int &argc, char *argv[], uint8_t secondaryInstances )
    : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
{
    Q_D(SingleApplication);

    // Check command line arguments for the force primary and secondary flags
#ifdef Q_OS_UNIX
    bool forcePrimary = false;
#endif
    bool secondary = false;
    for( int i = 0; i < argc; ++i ) {
        if( strcmp( argv[i], "--secondary" ) == 0 ) {
            secondary = true;
#ifndef Q_OS_UNIX
            break;
#endif
        }
#ifdef Q_OS_UNIX
        if( strcmp( argv[i], "--primary" ) == 0 ) {
            secondary = false;
            forcePrimary = true;
            break;
        }
#endif
    }

    QString serverName = app_t::organizationName() + app_t::applicationName();
    serverName.replace( QRegExp("[^\\w\\-. ]"), "" );

    // Guarantee thread safe behaviour with a shared memory block. Also by
    // attaching to it and deleting it we make sure that the memory i deleted
    // even if the process had crashed
    d->memory = new QSharedMemory( serverName );
    d->memory->attach();
    delete d->memory;
    d->memory = new QSharedMemory( serverName );

    // Create a shared memory block with a minimum size of 1 byte
#ifdef Q_OS_UNIX
    if( d->memory->create( sizeof(InstancesInfo) ) || forcePrimary ) {
#else
    if( d->memory->create( sizeof(InstancesInfo) ) ) {
#endif
        d->startPrimary( true );
        return;
    } else {
        // Attempt to attach to the memory segment
        if( d->memory->attach() ) {
            d->memory->lock();
            InstancesInfo* inst = (InstancesInfo*)d->memory->data();

            if( ! inst->primary ) {
                d->startPrimary( false );
                d->memory->unlock();
                return;
            }

            // Check if another instance can be started
            if( secondary && inst->secondary < secondaryInstances ) {
                inst->secondary += 1;
                d->startSecondary();
                d->memory->unlock();
                return;
            }

            d->memory->unlock();
        }
    }

    d->notifyPrimary();
    delete d;
    ::exit(EXIT_SUCCESS);
}

/**
 * @brief Destructor
 */
SingleApplication::~SingleApplication()
{
    Q_D(SingleApplication);
    delete d;
}

bool SingleApplication::isPrimary()
{
    Q_D(SingleApplication);
    return d->server != NULL;
}

bool SingleApplication::isSecondary()
{
    Q_D(SingleApplication);
    return d->server == NULL;
}

/**
 * @brief Executed when a connection has been made to the LocalServer
 */
void SingleApplication::slotConnectionEstablished()
{
    Q_D(SingleApplication);

    QLocalSocket *socket = d->server->nextPendingConnection();
    socket->close();
    delete socket;
    Q_EMIT showUp();
}