diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index cef9ff833..f44d67384 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -28,6 +28,7 @@ set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/../common/logger/logger.h ${CLIENT_ROOT_DIR}/utils/qmlUtils.h ${CLIENT_ROOT_DIR}/core/api/apiUtils.h + ${CLIENT_ROOT_DIR}/core/osSignalHandler.h ) # Mozilla headres @@ -79,6 +80,7 @@ set(SOURCES ${SOURCES} ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp ${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp ${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp + ${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp ) # Mozilla sources diff --git a/client/core/osSignalHandler.cpp b/client/core/osSignalHandler.cpp new file mode 100644 index 000000000..0ebe1baab --- /dev/null +++ b/client/core/osSignalHandler.cpp @@ -0,0 +1,159 @@ +#include "osSignalHandler.h" + +#include +#include + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + #include + #include + #include + #include +#elif defined(Q_OS_MACOS) + #include + #include + #include +#endif + +#ifdef Q_OS_WIN + #include + #include +#endif + +namespace +{ + + static bool initialized = false; + +#ifdef Q_OS_WIN + static BOOL WINAPI consoleHandler(DWORD signal) + { + switch (signal) { + case CTRL_CLOSE_EVENT: + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + if (QCoreApplication::instance()) { + QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); + } + return TRUE; + default: return FALSE; + } + } +#endif + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + static int signalFd = -1; + static QSocketNotifier *socketNotifier = nullptr; + + static void setupUnixSignalHandler() + { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + + pthread_sigmask(SIBLOCK, &set, nullptr); + + signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC); + if (signalFd < 0) + return; + + socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance()); + + QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) { + signalfd_siginfo fdsi; + ::read(signalFd, &fdsi, sizeof(fdsi)); + + if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) { + QCoreApplication::quit(); + } + }); + } +#elif defined(Q_OS_MACX) + static int signalPipe[2] = { -1, -1 }; + static QSocketNotifier *socketNotifier = nullptr; + + static void macSignalHandler(int) + { + const char ch = 1; + ::write(signalPipe[1], &ch, sizeof(ch)); + } + + static void setupUnixSignalHandler() + { + if (::pipe(signalPipe) != 0) + return; + + ::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK); + ::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK); + + struct sigaction sa {}; + sa.sa_handler = macSignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); + + socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance()); + + QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) { + char buf[16]; + ::read(signalPipe[0], buf, sizeof(buf)); + QCoreApplication::quit(); + }); + } +#endif + + static void cleanupUnixSignalHandler() + { +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + if (socketNotifier) { + socketNotifier->setEnabled(false); + } + + if (signalFd >= 0) { + ::close(signalFd); + signalFd = -1; + } + +#elif defined(Q_OS_MACOS) + if (socketNotifier) { + socketNotifier->setEnabled(false); + } + + if (signalPipe[0] >= 0) { + ::close(signalPipe[0]); + signalPipe[0] = -1; + } + + if (signalPipe[1] >= 0) { + ::close(signalPipe[1]); + signalPipe[1] = -1; + } +#endif + } +} + +OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent) +{ +} + +void OsSignalHandler::setup() +{ + if (initialized) + return; + + initialized = true; + +#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACX) + setupUnixSignalHandler(); +#endif + +#ifdef Q_OS_WIN + SetConsoleCtrlHandler(consoleHandler, TRUE); +#endif + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); }); +} diff --git a/client/core/osSignalHandler.h b/client/core/osSignalHandler.h new file mode 100644 index 000000000..96f1f11ee --- /dev/null +++ b/client/core/osSignalHandler.h @@ -0,0 +1,17 @@ +#ifndef OSSIGNALHANDLER_H +#define OSSIGNALHANDLER_H + +#include + +class OsSignalHandler : public QObject +{ + Q_OBJECT +public: + static void setup(); + +private: + explicit OsSignalHandler(QObject *parent = nullptr); + static void handleSignal(int signal); +}; + +#endif // OSSIGNALHANDLER_H diff --git a/client/main.cpp b/client/main.cpp index 40af3f7e3..23d39fc54 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -2,6 +2,7 @@ #include #include "amnezia_application.h" +#include "core/osSignalHandler.h" #include "migrations.h" #include "version.h" @@ -44,6 +45,7 @@ int main(int argc, char *argv[]) #endif AmneziaApplication app(argc, argv); + OsSignalHandler::setup(); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) if (isAnotherInstanceRunning()) {