diff --git a/examples/qt/02_posix-signal-watcher/CMakeLists.txt b/examples/qt/02_posix-signal-watcher/CMakeLists.txt new file mode 100644 index 0000000..88d72d2 --- /dev/null +++ b/examples/qt/02_posix-signal-watcher/CMakeLists.txt @@ -0,0 +1,83 @@ +# Название основной цели в текущем каталоге +set(TRGT example-qt-posix-signal-watcher) + +# Список файлов исходных текстов +set(TRGT_cpp ${CMAKE_CURRENT_SOURCE_DIR}/posix_signal_watcher.cpp) + +if(MYXLIB_BUILD_EXAMPLES) + # Путь поиска библиотек внутри проекта + link_directories(${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) + + # Цель для создания исполняемого файла + add_executable(${TRGT} ${TRGT_cpp}) + common_target_properties(${TRGT}) + + # Создание цели для проверки утилитой clang-tidy + add_clang_tidy_check(${TRGT} ${TRGT_cpp}) + + # Создание цели для проверки утилитой clang-analyze + add_clang_analyze_check(${TRGT} ${TRGT_cpp}) + + # Создание цели для проверки утилитой clazy + add_clazy_check(${TRGT} ${TRGT_cpp}) + + # Создание цели для проверки утилитой pvs-studio + add_pvs_check(${TRGT}) + + # Создание цели для автоматического форматирования кода + add_format_sources(${TRGT} ${TRGT_cpp}) + + # Qt5 + target_include_directories(${TRGT} PRIVATE ${CMAKE_SOURCE_DIR}/src) + target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) + + target_include_directories(${TRGT} SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/src) + add_dependencies(${TRGT} base qt) + + target_link_libraries(${TRGT} base_static qt_static) + + target_link_libraries(${TRGT} Qt5::Core) + target_link_libraries(${TRGT} Threads::Threads) + + # Имя выходного файла для цели + set_target_properties(${TRGT} PROPERTIES OUTPUT_NAME qt-posix-signal-watcher) + + add_sanitizers(${TRGT}) + + cotire(${TRGT}) + + add_dependencies(${TRGT} create_auxilary_symlinks) + + # Правила для установки + install(TARGETS ${TRGT} COMPONENT examples RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +if(MYXLIB_BUILD_EXAMPLES_HO) + set(TRGT_moc_hpp + ${CMAKE_SOURCE_DIR}/src/myx/qt/posix_signal_watcher.hpp + ${CMAKE_SOURCE_DIR}/src/myx/qt/posix_signal_watcher_p.hpp) + qt5_wrap_cpp(TRGT_moc_cpp ${TRGT_moc_hpp}) + + # Цель для создания исполняемого файла + add_executable(${TRGT}-ho ${TRGT_cpp} ${TRGT_moc_cpp}) + common_target_properties(${TRGT}-ho) + + target_include_directories(${TRGT}-ho PRIVATE ${CMAKE_SOURCE_DIR}/src) + target_include_directories(${TRGT}-ho SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) + + add_dependencies(${TRGT}-ho base-header-only qt-header-only) + + target_link_libraries(${TRGT}-ho Qt5::Core) + target_link_libraries(${TRGT}-ho Threads::Threads) + + # Имя выходного файла для цели + set_target_properties(${TRGT}-ho PROPERTIES OUTPUT_NAME qt-posix-signal-watcher-ho) + + add_sanitizers(${TRGT}-ho) + cotire(${TRGT}-ho) + + add_dependencies(${TRGT}-ho create_auxilary_symlinks) + + # Правила для установки + install(TARGETS ${TRGT}-ho COMPONENT examples RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() diff --git a/examples/qt/02_posix-signal-watcher/posix_signal_watcher.cpp b/examples/qt/02_posix-signal-watcher/posix_signal_watcher.cpp new file mode 100644 index 0000000..9fb9ef6 --- /dev/null +++ b/examples/qt/02_posix-signal-watcher/posix_signal_watcher.cpp @@ -0,0 +1,21 @@ +#include + +#include +#include + +namespace MQ = myx::qt; + +int main( int argc, char* argv[] ) +{ + QCoreApplication app( argc, argv ); + qDebug() << "Hello from process" << QCoreApplication::applicationPid(); + + MQ::PosixSignalWatcher sigwatch; + sigwatch.watchForSignal( SIGINT ); + sigwatch.watchForSignal( SIGTERM ); + QObject::connect( &sigwatch, &MQ::PosixSignalWatcher::posixSignal, &app, &QCoreApplication::quit ); + + int exitcode = app.exec(); + qDebug() << "Goodbye"; + return( exitcode ); +} diff --git a/examples/qt/CMakeLists.txt b/examples/qt/CMakeLists.txt index ff85cdb..b222784 100644 --- a/examples/qt/CMakeLists.txt +++ b/examples/qt/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(01_translators) +add_subdirectory(02_posix-signal-watcher) diff --git a/src/myx/qt/CMakeLists.txt b/src/myx/qt/CMakeLists.txt index e501637..2582397 100644 --- a/src/myx/qt/CMakeLists.txt +++ b/src/myx/qt/CMakeLists.txt @@ -4,23 +4,36 @@ set(TRGT qt) # cmake-format: off # Список файлов исходных текстов set(TRGT_cpp - ${CMAKE_CURRENT_SOURCE_DIR}/translators.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/posix_signal_watcher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/translators.cpp + ) + +set(TRGT_moc_hpp + ${CMAKE_CURRENT_SOURCE_DIR}/posix_signal_watcher.hpp + ) + +set(TRGT_moc_private_hpp + ${CMAKE_CURRENT_SOURCE_DIR}/posix_signal_watcher_p.hpp + ) -# Список заголовочных файлов (используется для установки) set(TRGT_hpp ${CMAKE_CURRENT_SOURCE_DIR}/backports.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/translators.hpp) + ${CMAKE_CURRENT_SOURCE_DIR}/translators.hpp + ) -set(TRGT_headers ${TRGT_hpp}) +set(TRGT_headers ${TRGT_moc_hpp} ${TRGT_hpp}) # cmake-format: on +qt5_wrap_cpp(TRGT_moc_cpp ${TRGT_moc_private_hpp} ${TRGT_moc_hpp}) + add_library(${TRGT}-header-only INTERFACE) target_include_directories( ${TRGT}-header-only SYSTEM INTERFACE "$" + "$" "$") if(MYXLIB_BUILD_LIBRARIES) - add_common_library(${TRGT} OUTPUT_NAME myx-${TRGT} SOURCES ${TRGT_cpp} ${TRGT_headers}) + add_common_library(${TRGT} OUTPUT_NAME myx-${TRGT} SOURCES ${TRGT_cpp} ${TRGT_moc_cpp} ${TRGT_moc_private_hpp} ${TRGT_headers}) common_target_properties(${TRGT}) # Создание цели для проверки утилитой clang-tidy @@ -36,11 +49,12 @@ if(MYXLIB_BUILD_LIBRARIES) add_pvs_check(${TRGT}) # Создание цели для автоматического форматирования кода - add_format_sources(${TRGT} ${TRGT_cpp} ${TRGT_headers}) + add_format_sources(${TRGT} ${TRGT_cpp} ${TRGT_headers} ${TRGT_moc_private_hpp}) target_compile_definitions(${TRGT} PUBLIC MYXLIB_BUILD_LIBRARIES) target_include_directories(${TRGT} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) target_include_directories(${TRGT} SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/src) + target_include_directories(${TRGT} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) cotire(${TRGT}) install(TARGETS ${TRGT}_static COMPONENT libs-dev ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/myx/qt/posix_signal_watcher.cpp b/src/myx/qt/posix_signal_watcher.cpp new file mode 100644 index 0000000..5688572 --- /dev/null +++ b/src/myx/qt/posix_signal_watcher.cpp @@ -0,0 +1,136 @@ +#ifndef MYX_QT_POSIX_SIGNAL_WATCHER_CPP_ +#define MYX_QT_POSIX_SIGNAL_WATCHER_CPP_ + +#include + +#ifndef MYXLIB_HEADER_ONLY +#include +#include +#else +#pragma once +#endif + + +namespace myx { + +namespace qt { + +int PosixSignalWatcherPrivate::m_sockpair[2] = {0, 0}; + +PosixSignalWatcherPrivate::~PosixSignalWatcherPrivate() = default; + +PosixSignalWatcherPrivate::PosixSignalWatcherPrivate( PosixSignalWatcher* q ) : + q_ptr( q ) +{ + #if MYX_QT_HAS_POSIX_SIGNALS + // Create socket pair + if ( ::socketpair( AF_UNIX, SOCK_STREAM, 0, m_sockpair ) ) + { + qDebug() << "PosixSignalWatcher: socketpair: " << ::strerror( errno ); + return; + } + #endif + + // Create a notifier for the read end of the pair + m_notifier.reset( new QSocketNotifier( m_sockpair[1], QSocketNotifier::Read ) ); + + + // Called when the signal handler has written to the socket pair. + // Emits the Posix signal as a Qt signal. + connect( m_notifier.get(), &QSocketNotifier::activated, q, [this]( int sockfd ) { + Q_Q( PosixSignalWatcher ); + + int signal = 0; + (void)::read( sockfd, &signal, sizeof( signal ) ); + qDebug() << "Caught signal: " << ::strsignal( signal ); + Q_EMIT q->posixSignal( signal ); + } ); + + + m_notifier->setEnabled( true ); +} + + +/*! + * Registers a handler for the given Posix \a signal. The handler will write to + * a socket pair, the other end of which is connected to a QSocketNotifier. + * This provides a way to break out of the asynchronous context from which the + * signal handler is called and back into the Qt event loop. + */ +void PosixSignalWatcherPrivate::watchForSignal( int signal ) +{ + if ( m_watchedSignals.contains( signal ) ) + { + qDebug() << "Already watching for signal " << signal; + return; + } + + #if MYX_QT_HAS_POSIX_SIGNALS + // Register a sigaction which will write to the socket pair + struct sigaction sigact; + sigact.sa_handler = PosixSignalWatcherPrivate::signalHandler; + sigact.sa_flags = 0; + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags |= SA_RESTART; + if ( ::sigaction( signal, &sigact, nullptr ) ) + { + qDebug() << "PosixSignalWatcher: sigaction: " << ::strerror( errno ); + return; + } + #endif + + m_watchedSignals.append( signal ); +} // PosixSignalWatcherPrivate::watchForSignal + + +/*! + * Called when a Posix \a signal is received. Write to the socket to wake up the + * QSocketNotifier. + */ +MYXLIB_INLINE void PosixSignalWatcherPrivate::signalHandler( int signal ) +{ + (void)::write( m_sockpair[0], &signal, sizeof( signal ) ); +} + + +/*! + * Create a new PosixSignalWatcher as a child of the given \a parent. + */ +MYXLIB_INLINE PosixSignalWatcher::PosixSignalWatcher( QObject* parent ) : + QObject( parent ), + d_ptr ( new PosixSignalWatcherPrivate( this ) ) +{ +} + + +MYXLIB_INLINE PosixSignalWatcher::~PosixSignalWatcher() = default; + +/*! + * Register a signal handler for the given \a signal. + * + * After calling this method you can \c connect() to the POSIXSignal() Qt signal + * to be notified when the Posix signal is received. + */ +MYXLIB_INLINE void PosixSignalWatcher::watchForSignal( int signal ) +{ + Q_D( PosixSignalWatcher ); + d->watchForSignal( signal ); +} + + +/*! + * \fn void PosixSignalWatcher::posixSignal(int signal) + * Emitted when the given Posix \a signal is received. + * + * watchForSignal() must be called for each Posix signal that you want to receive + * via the POSIXSignal() Qt signal. If a watcher is watching multiple signals, + * POSIXSignal() will be emitted whenever *any* of the watched Posix signals are + * received, and the \a signal argument can be inspected to find out which one + * was actually received. + */ + +} // namespace qt + +} // namespace myx + +#endif // ifndef MYX_QT_POSIX_SIGNAL_WATCHER_CPP_ diff --git a/src/myx/qt/posix_signal_watcher.hpp b/src/myx/qt/posix_signal_watcher.hpp new file mode 100644 index 0000000..511bca5 --- /dev/null +++ b/src/myx/qt/posix_signal_watcher.hpp @@ -0,0 +1,55 @@ +#ifndef MYX_QT_POSIX_SIGNAL_WATCHER_HPP_ +#define MYX_QT_POSIX_SIGNAL_WATCHER_HPP_ + +#pragma once + +#include + +#include + +#include + +namespace myx { + +namespace qt { + +class PosixSignalWatcherPrivate; + +#if defined( Q_OS_WIN ) +const int SIGINT = 2; +const int SIGTERM = 15; +#endif + + +/*! + * \brief The PosixSignalWatcher class converts Posix signals to Qt signals. + * + * To watch for a given signal, e.g. \c SIGINT, call \c watchForSignal(SIGINT) + * and \c connect() your handler to posixSignal(). + */ +class PosixSignalWatcher : public QObject +{ + Q_OBJECT + +public: + explicit PosixSignalWatcher( QObject* parent = nullptr ); + ~PosixSignalWatcher(); + + void watchForSignal( int signal ); + Q_SIGNAL void posixSignal( int signal ); + +private: + PosixSignalWatcherPrivate* const d_ptr = nullptr; + Q_DECLARE_PRIVATE( PosixSignalWatcher ) +}; // class PosixSignalWatcher + +} // namespace qt + +} // namespace myx + +#ifdef MYXLIB_HEADER_ONLY +#include "posix_signal_watcher_p.hpp" +#include "posix_signal_watcher.cpp" +#endif + +#endif // ifndef MYX_QT_POSIX_SIGNAL_WATCHER_HPP_ diff --git a/src/myx/qt/posix_signal_watcher_p.hpp b/src/myx/qt/posix_signal_watcher_p.hpp new file mode 100644 index 0000000..067f94c --- /dev/null +++ b/src/myx/qt/posix_signal_watcher_p.hpp @@ -0,0 +1,62 @@ +#ifndef MYX_QT_POSIX_SIGNAL_WATCHER_P_HPP_ +#define MYX_QT_POSIX_SIGNAL_WATCHER_P_HPP_ + +#include + +#include + +#include +#include +#include +#include + +#include + +#ifdef Q_OS_WIN +#define MYX_QT_HAS_POSIX_SIGNALS 0 +#else +#define MYX_QT_HAS_POSIX_SIGNALS 1 +#endif + +#if MYX_QT_HAS_POSIX_SIGNALS +#include +#include +#include +#include +#endif + +namespace myx { + +namespace qt { + +/*! + * \brief The PosixSignalWatcherPrivate class implements the back-end signal + * handling for the PosixSignalWatcher. + * + * \see http://qt-project.org/doc/qt-5.0/qtdoc/POSIX-signals.html + */ +class PosixSignalWatcherPrivate : public QObject +{ + Q_OBJECT + +public: + PosixSignalWatcherPrivate( PosixSignalWatcher* q ); + ~PosixSignalWatcherPrivate(); + + void watchForSignal( int signal ); + static void signalHandler( int signal ); + +private: + PosixSignalWatcher* const q_ptr = nullptr; + Q_DECLARE_PUBLIC( PosixSignalWatcher ) + + static int m_sockpair[2]; + std::unique_ptr< QSocketNotifier > m_notifier; + QList< int > m_watchedSignals; +}; // class PosixSignalWatcherPrivate + +} // namespace qt + +} // namespace myx + +#endif // ifndef MYX_QT_POSIX_SIGNAL_WATCHER_P_HPP_