keepassxc

Форк
0
/
Application.cpp 
421 строка · 12.6 Кб
1
/*
2
 *  Copyright (C) 2012 Tobias Tangemann
3
 *  Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
4
 *  Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
5
 *
6
 *  This program is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 2 or (at your option)
9
 *  version 3 of the License.
10
 *
11
 *  This program is distributed in the hope that it will be useful,
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 *  GNU General Public License for more details.
15
 *
16
 *  You should have received a copy of the GNU General Public License
17
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19

20
#include "Application.h"
21

22
#include "core/Bootstrap.h"
23
#include "gui/MainWindow.h"
24
#include "gui/MessageBox.h"
25
#include "gui/osutils/OSUtils.h"
26
#include "gui/styles/dark/DarkStyle.h"
27
#include "gui/styles/light/LightStyle.h"
28

29
#include <QFileInfo>
30
#include <QFileOpenEvent>
31
#include <QLocalSocket>
32
#include <QLockFile>
33
#include <QPixmapCache>
34
#include <QSocketNotifier>
35
#include <QStandardPaths>
36

37
#if defined(Q_OS_UNIX)
38
#include <csignal>
39
#include <sys/socket.h>
40
#include <unistd.h>
41
#endif
42

43
namespace
44
{
45
    constexpr int WaitTimeoutMSec = 150;
46
    const char BlockSizeProperty[] = "blockSize";
47
} // namespace
48

49
Application::Application(int& argc, char** argv)
50
    : QApplication(argc, argv)
51
#ifdef Q_OS_UNIX
52
    , m_unixSignalNotifier(nullptr)
53
#endif
54
    , m_alreadyRunning(false)
55
    , m_lockFile(nullptr)
56
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
57
{
58
#else
59
{
60
#endif
61
#if defined(Q_OS_UNIX)
62
    registerUnixSignals();
63
#endif
64

65
    QString userName = qgetenv("USER");
66
    if (userName.isEmpty()) {
67
        userName = qgetenv("USERNAME");
68
    }
69
    QString identifier = "keepassxc";
70
    if (!userName.isEmpty()) {
71
        identifier += "-" + userName;
72
    }
73
#ifdef QT_DEBUG
74
    // In DEBUG mode don't interfere with Release instances
75
    identifier += "-DEBUG";
76
#endif
77
    QString lockName = identifier + ".lock";
78
    m_socketName = identifier + ".socket";
79

80
    // According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect
81
    // this and creates sockets in TempLocation, so let's be consistent.
82
    m_lockFile = new QLockFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + lockName);
83
    m_lockFile->setStaleLockTime(0);
84
    m_lockFile->tryLock();
85

86
    m_lockServer.setSocketOptions(QLocalServer::UserAccessOption);
87
    connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted()));
88
    connect(&m_lockServer, SIGNAL(newConnection()), this, SLOT(processIncomingConnection()));
89

90
    switch (m_lockFile->error()) {
91
    case QLockFile::NoError:
92
        // No existing lock was found, start listener
93
        m_lockServer.listen(m_socketName);
94
        break;
95
    case QLockFile::LockFailedError: {
96
        if (config()->get(Config::SingleInstance).toBool()) {
97
            // Attempt to connect to the existing instance
98
            QLocalSocket client;
99
            for (int i = 0; i < 3; ++i) {
100
                client.connectToServer(m_socketName);
101
                if (client.waitForConnected(WaitTimeoutMSec)) {
102
                    // Connection succeeded, this will raise the existing window if minimized
103
                    client.abort();
104
                    m_alreadyRunning = true;
105
                    break;
106
                }
107
            }
108

109
            if (!m_alreadyRunning) {
110
                // If we get here then the original instance is likely dead
111
                qWarning() << QObject::tr("Existing single-instance lock file is invalid. Launching new instance.")
112
                                  .toUtf8()
113
                                  .constData();
114

115
                // forceably reset the lock file
116
                m_lockFile->removeStaleLockFile();
117
                m_lockFile->tryLock();
118
                // start the listen server
119
                m_lockServer.listen(m_socketName);
120
            }
121
        }
122
        break;
123
    }
124
    default:
125
        qWarning()
126
            << QObject::tr("The lock file could not be created. Single-instance mode disabled.").toUtf8().constData();
127
    }
128

129
    connect(osUtils, &OSUtilsBase::interfaceThemeChanged, this, [this]() {
130
        if (config()->get(Config::GUI_ApplicationTheme).toString() != "classic") {
131
            applyTheme();
132
        }
133
    });
134
}
135

136
Application::~Application()
137
{
138
    m_lockServer.close();
139
    if (m_lockFile) {
140
        m_lockFile->unlock();
141
        delete m_lockFile;
142
    }
143
}
144

145
/**
146
 * Perform early application bootstrapping such as setting up search paths,
147
 * configuration OS security properties, and loading translators.
148
 * A QApplication object has to be instantiated before calling this function.
149
 */
150
void Application::bootstrap(const QString& uiLanguage)
151
{
152
    Bootstrap::bootstrap(uiLanguage);
153

154
#ifdef Q_OS_WIN
155
    // Qt on Windows uses "MS Shell Dlg 2" as the default font for many widgets, which resolves
156
    // to Tahoma 8pt, whereas the correct font would be "Segoe UI" 9pt.
157
    // Apparently, some widgets are already using the correct font. Thanks, MuseScore for this neat fix!
158
    QApplication::setFont(QApplication::font("QMessageBox"));
159
#endif
160

161
    osUtils->registerNativeEventFilter();
162
    MessageBox::initializeButtonDefs();
163

164
#ifdef Q_OS_MACOS
165
    // Don't show menu icons on OSX
166
    QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
167
#endif
168
}
169

170
void Application::applyTheme()
171
{
172
    auto appTheme = config()->get(Config::GUI_ApplicationTheme).toString();
173
    if (appTheme == "auto") {
174
        appTheme = osUtils->isDarkMode() ? "dark" : "light";
175
#ifdef Q_OS_WIN
176
        if (winUtils()->isHighContrastMode()) {
177
            appTheme = "classic";
178
        }
179
#endif
180
    }
181
    QPixmapCache::clear();
182
    if (appTheme == "light") {
183
        auto* s = new LightStyle;
184
        setPalette(s->standardPalette());
185
        setStyle(s);
186
        m_darkTheme = false;
187
    } else if (appTheme == "dark") {
188
        auto* s = new DarkStyle;
189
        setPalette(s->standardPalette());
190
        setStyle(s);
191
        m_darkTheme = true;
192
    } else {
193
        // Classic mode, don't check for dark theme on Windows
194
        // because Qt 5.x does not support it
195
#if defined(Q_OS_WIN)
196
        m_darkTheme = false;
197
#else
198
        m_darkTheme = osUtils->isDarkMode();
199
#endif
200
        QFile stylesheetFile(":/styles/base/classicstyle.qss");
201
        if (stylesheetFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
202
            setStyleSheet(stylesheetFile.readAll());
203
            stylesheetFile.close();
204
        }
205
    }
206
}
207

208
bool Application::event(QEvent* event)
209
{
210
    // Handle Apple QFileOpenEvent from finder (double click on .kdbx file)
211
    if (event->type() == QEvent::FileOpen) {
212
        emit openFile(static_cast<QFileOpenEvent*>(event)->file());
213
        return true;
214
    }
215
#ifdef Q_OS_MACOS
216
    // restore main window when clicking on the docker icon
217
    else if (event->type() == QEvent::ApplicationActivate) {
218
        emit applicationActivated();
219
    }
220
#endif
221

222
    return QApplication::event(event);
223
}
224

225
#if defined(Q_OS_UNIX)
226
int Application::unixSignalSocket[2];
227

228
void Application::registerUnixSignals()
229
{
230
    int result = ::socketpair(AF_UNIX, SOCK_STREAM, 0, unixSignalSocket);
231
    Q_ASSERT(0 == result);
232
    if (0 != result) {
233
        // do not register handles when socket creation failed, otherwise
234
        // application will be unresponsive to signals such as SIGINT or SIGTERM
235
        return;
236
    }
237

238
    QVector<int> const handledSignals = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
239
    for (auto s : handledSignals) {
240
        struct sigaction sigAction;
241

242
        sigAction.sa_handler = handleUnixSignal;
243
        sigemptyset(&sigAction.sa_mask);
244
        sigAction.sa_flags = 0 | SA_RESTART;
245
        sigaction(s, &sigAction, nullptr);
246
    }
247

248
    m_unixSignalNotifier = new QSocketNotifier(unixSignalSocket[1], QSocketNotifier::Read, this);
249
    connect(m_unixSignalNotifier, SIGNAL(activated(int)), this, SLOT(quitBySignal()));
250
}
251

252
void Application::handleUnixSignal(int sig)
253
{
254
    switch (sig) {
255
    case SIGQUIT:
256
    case SIGINT:
257
    case SIGTERM: {
258
        char buf = 0;
259
        Q_UNUSED(!::write(unixSignalSocket[0], &buf, sizeof(buf)));
260
        return;
261
    }
262
    case SIGHUP:
263
        return;
264
    }
265
}
266

267
void Application::quitBySignal()
268
{
269
    m_unixSignalNotifier->setEnabled(false);
270
    char buf;
271
    Q_UNUSED(!::read(unixSignalSocket[1], &buf, sizeof(buf)));
272
    emit quitSignalReceived();
273
}
274
#endif
275

276
void Application::processIncomingConnection()
277
{
278
    if (m_lockServer.hasPendingConnections()) {
279
        QLocalSocket* socket = m_lockServer.nextPendingConnection();
280
        socket->setProperty(BlockSizeProperty, 0);
281
        connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));
282
    }
283
}
284

285
void Application::socketReadyRead()
286
{
287
    auto socket = qobject_cast<QLocalSocket*>(sender());
288
    if (!socket) {
289
        return;
290
    }
291

292
    QDataStream in(socket);
293
    in.setVersion(QDataStream::Qt_5_0);
294

295
    int blockSize = socket->property(BlockSizeProperty).toInt();
296
    if (blockSize == 0) {
297
        // Relies on the fact that QDataStream format streams a quint32 into sizeof(quint32) bytes
298
        if (socket->bytesAvailable() < qint64(sizeof(quint32))) {
299
            return;
300
        }
301
        in >> blockSize;
302
    }
303

304
    if (socket->bytesAvailable() < blockSize || in.atEnd()) {
305
        socket->setProperty(BlockSizeProperty, blockSize);
306
        return;
307
    }
308

309
    QStringList fileNames;
310
    quint32 id;
311
    in >> id;
312

313
    // TODO: move constants to enum
314
    switch (id) {
315
    case 1:
316
        in >> fileNames;
317
        for (const QString& fileName : asConst(fileNames)) {
318
            const QFileInfo fInfo(fileName);
319
            if (fInfo.isFile() && fInfo.suffix().toLower() == "kdbx") {
320
                emit openFile(fileName);
321
            }
322
        }
323

324
        break;
325
    case 2:
326
        getMainWindow()->lockAllDatabases();
327
        break;
328
    }
329

330
    socket->deleteLater();
331
}
332

333
bool Application::isAlreadyRunning() const
334
{
335
#ifdef QT_DEBUG
336
    // In DEBUG mode we can run unlimited instances
337
    return false;
338
#endif
339
    return config()->get(Config::SingleInstance).toBool() && m_alreadyRunning;
340
}
341

342
/**
343
 * Send to-open file names to the running UI instance
344
 *
345
 * @param fileNames - list of file names to open
346
 * @return true if all operations succeeded (connection made, data sent, connection closed)
347
 */
348
bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
349
{
350
    QLocalSocket client;
351
    client.connectToServer(m_socketName);
352
    const bool connected = client.waitForConnected(WaitTimeoutMSec);
353
    if (!connected) {
354
        return false;
355
    }
356

357
    QByteArray data;
358
    QDataStream out(&data, QIODevice::WriteOnly);
359
    out.setVersion(QDataStream::Qt_5_0);
360
    out << quint32(0); // reserve space for block size
361
    out << quint32(1); // ID for file name send. TODO: move to enum
362
    out << fileNames; // send file names to be opened
363
    out.device()->seek(0);
364
    out << quint32(data.size() - sizeof(quint32)); // replace the previous constant 0 with block size
365

366
    const bool writeOk = client.write(data) != -1 && client.waitForBytesWritten(WaitTimeoutMSec);
367
    client.disconnectFromServer();
368
    const bool disconnected =
369
        client.state() == QLocalSocket::UnconnectedState || client.waitForDisconnected(WaitTimeoutMSec);
370
    return writeOk && disconnected;
371
}
372

373
/**
374
 * Locks all open databases in the running instance
375
 *
376
 * @return true if the "please lock" signal was sent successfully
377
 */
378
bool Application::sendLockToInstance()
379
{
380
    // Make a connection to avoid SIGSEGV
381
    QLocalSocket client;
382
    client.connectToServer(m_socketName);
383
    const bool connected = client.waitForConnected(WaitTimeoutMSec);
384
    if (!connected) {
385
        return false;
386
    }
387

388
    // Send lock signal
389
    QByteArray data;
390
    QDataStream out(&data, QIODevice::WriteOnly);
391
    out.setVersion(QDataStream::Qt_5_0);
392
    out << quint32(0); // reserve space for block size
393
    out << quint32(2); // ID for database lock. TODO: move to enum
394
    out.device()->seek(0);
395
    out << quint32(data.size() - sizeof(quint32)); // replace the previous constant 0 with block size
396

397
    // Finish gracefully
398
    const bool writeOk = client.write(data) != -1 && client.waitForBytesWritten(WaitTimeoutMSec);
399
    client.disconnectFromServer();
400
    const bool disconnected =
401
        client.state() == QLocalSocket::UnconnectedState || client.waitForConnected(WaitTimeoutMSec);
402
    return writeOk && disconnected;
403
}
404

405
bool Application::isDarkTheme() const
406
{
407
    return m_darkTheme;
408
}
409

410
void Application::restart()
411
{
412
    // Disable single instance
413
    m_lockServer.close();
414
    if (m_lockFile) {
415
        m_lockFile->unlock();
416
        delete m_lockFile;
417
        m_lockFile = nullptr;
418
    }
419

420
    exit(RESTART_EXITCODE);
421
}
422

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.