Exit on SIGTERM
This commit is contained in:
@@ -1218,6 +1218,10 @@ set(UI
|
|||||||
src/device/deviceviewcontainer.ui
|
src/device/deviceviewcontainer.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
optional_source(UNIX SOURCES src/core/unixsignalwatcher.cpp HEADERS src/core/unixsignalwatcher.h)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
optional_source(APPLE
|
optional_source(APPLE
|
||||||
SOURCES
|
SOURCES
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
void ToggleSearchCoverAuto(const bool checked);
|
void ToggleSearchCoverAuto(const bool checked);
|
||||||
void SaveGeometry();
|
void SaveGeometry();
|
||||||
|
|
||||||
void Exit();
|
|
||||||
void DoExit();
|
void DoExit();
|
||||||
|
|
||||||
void HandleNotificationPreview(const OSDSettings::Type type, const QString &line1, const QString &line2);
|
void HandleNotificationPreview(const OSDSettings::Type type, const QString &line1, const QString &line2);
|
||||||
@@ -280,6 +279,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void CommandlineOptionsReceived(const QByteArray &string_options);
|
void CommandlineOptionsReceived(const QByteArray &string_options);
|
||||||
void Raise();
|
void Raise();
|
||||||
|
void Exit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
|||||||
168
src/core/unixsignalwatcher.cpp
Normal file
168
src/core/unixsignalwatcher.cpp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2026, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "unixsignalwatcher.h"
|
||||||
|
|
||||||
|
UnixSignalWatcher *UnixSignalWatcher::sInstance = nullptr;
|
||||||
|
|
||||||
|
UnixSignalWatcher::UnixSignalWatcher(QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
signal_fd_{-1, -1},
|
||||||
|
socket_notifier_(nullptr) {
|
||||||
|
|
||||||
|
Q_ASSERT(!sInstance);
|
||||||
|
|
||||||
|
// Create a socket pair for the self-pipe trick
|
||||||
|
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, signal_fd_) != 0) {
|
||||||
|
qLog(Error) << "Failed to create socket pair for signal handling:" << ::strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(signal_fd_[0] != -1);
|
||||||
|
|
||||||
|
// Set the read end to non-blocking mode
|
||||||
|
// Non-blocking mode is important to prevent HandleSignalNotification from blocking
|
||||||
|
int flags = ::fcntl(signal_fd_[0], F_GETFL, 0);
|
||||||
|
if (flags == -1) {
|
||||||
|
qLog(Error) << "Failed to get socket flags:" << ::strerror(errno);
|
||||||
|
}
|
||||||
|
else if (::fcntl(signal_fd_[0], F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||||
|
qLog(Error) << "Failed to set socket to non-blocking:" << ::strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the write end to non-blocking mode as well (used in signal handler)
|
||||||
|
// Non-blocking mode prevents the signal handler from blocking if buffer is full
|
||||||
|
flags = ::fcntl(signal_fd_[1], F_GETFL, 0);
|
||||||
|
if (flags == -1) {
|
||||||
|
qLog(Error) << "Failed to get socket flags for write end:" << ::strerror(errno);
|
||||||
|
}
|
||||||
|
else if (::fcntl(signal_fd_[1], F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||||
|
qLog(Error) << "Failed to set write end of socket to non-blocking:" << ::strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up QSocketNotifier to monitor the read end of the socket
|
||||||
|
socket_notifier_ = new QSocketNotifier(signal_fd_[0], QSocketNotifier::Read, this);
|
||||||
|
QObject::connect(socket_notifier_, &QSocketNotifier::activated, this, &UnixSignalWatcher::HandleSignalNotification);
|
||||||
|
|
||||||
|
sInstance = this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UnixSignalWatcher::~UnixSignalWatcher() {
|
||||||
|
|
||||||
|
if (socket_notifier_) {
|
||||||
|
socket_notifier_->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original signal handlers
|
||||||
|
for (int i = 0; i < watched_signals_.size(); ++i) {
|
||||||
|
if (::sigaction(watched_signals_[i], &original_signal_actions_[i], nullptr) != 0) {
|
||||||
|
qLog(Error) << "Failed to restore signal handler for signal" << watched_signals_[i] << ":" << ::strerror(errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal_fd_[0] != -1) {
|
||||||
|
::close(signal_fd_[0]);
|
||||||
|
signal_fd_[0] = -1;
|
||||||
|
}
|
||||||
|
if (signal_fd_[1] != -1) {
|
||||||
|
::close(signal_fd_[1]);
|
||||||
|
signal_fd_[1] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sInstance = nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSignalWatcher::WatchForSignal(const int signal) {
|
||||||
|
|
||||||
|
// Check if socket pair was created successfully
|
||||||
|
if (signal_fd_[0] == -1 || signal_fd_[1] == -1) {
|
||||||
|
qLog(Error) << "Cannot watch for signal: socket pair not initialized";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (watched_signals_.contains(signal)) {
|
||||||
|
qLog(Error) << "Already watching for signal" << signal;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction signal_action{};
|
||||||
|
::memset(&signal_action, 0, sizeof(signal_action));
|
||||||
|
sigemptyset(&signal_action.sa_mask);
|
||||||
|
signal_action.sa_handler = UnixSignalWatcher::SignalHandler;
|
||||||
|
signal_action.sa_flags = SA_RESTART;
|
||||||
|
|
||||||
|
struct sigaction old_signal_action{};
|
||||||
|
::memset(&old_signal_action, 0, sizeof(old_signal_action));
|
||||||
|
if (::sigaction(signal, &signal_action, &old_signal_action) != 0) {
|
||||||
|
qLog(Error) << "sigaction error:" << ::strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
watched_signals_ << signal;
|
||||||
|
original_signal_actions_ << old_signal_action;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSignalWatcher::SignalHandler(const int signal) {
|
||||||
|
|
||||||
|
if (!sInstance || sInstance->signal_fd_[1] == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the signal number to the socket pair (async-signal-safe)
|
||||||
|
// This is the only operation we perform in the signal handler
|
||||||
|
// Ignore errors as there's nothing we can safely do about them in a signal handler
|
||||||
|
(void)::write(sInstance->signal_fd_[1], &signal, sizeof(signal));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSignalWatcher::HandleSignalNotification() {
|
||||||
|
|
||||||
|
// Read all pending signals from the socket
|
||||||
|
// Multiple signals could arrive before the notifier triggers
|
||||||
|
while (true) {
|
||||||
|
int signal = 0;
|
||||||
|
const ssize_t bytes_read = ::read(signal_fd_[0], &signal, sizeof(signal));
|
||||||
|
if (bytes_read == sizeof(signal)) {
|
||||||
|
qLog(Debug) << "Caught signal:" << signal;
|
||||||
|
Q_EMIT UnixSignal(signal);
|
||||||
|
}
|
||||||
|
else if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
||||||
|
// No more data available (expected with non-blocking socket)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Error occurred or partial read
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
src/core/unixsignalwatcher.h
Normal file
53
src/core/unixsignalwatcher.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2026, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UNIXSIGNALWATCHER_H
|
||||||
|
#define UNIXSIGNALWATCHER_H
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
class QSocketNotifier;
|
||||||
|
|
||||||
|
class UnixSignalWatcher : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UnixSignalWatcher(QObject *parent = nullptr);
|
||||||
|
~UnixSignalWatcher() override;
|
||||||
|
|
||||||
|
void WatchForSignal(const int signal);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void UnixSignal(const int signal);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void SignalHandler(const int signal);
|
||||||
|
void HandleSignalNotification();
|
||||||
|
|
||||||
|
static UnixSignalWatcher *sInstance;
|
||||||
|
int signal_fd_[2];
|
||||||
|
QSocketNotifier *socket_notifier_;
|
||||||
|
QList<int> watched_signals_;
|
||||||
|
QList<struct sigaction> original_signal_actions_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UNIXSIGNALWATCHER_H
|
||||||
12
src/main.cpp
12
src/main.cpp
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2026, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -76,6 +76,10 @@
|
|||||||
|
|
||||||
#include <kdsingleapplication.h>
|
#include <kdsingleapplication.h>
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
#include "core/unixsignalwatcher.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_QTSPARKLE
|
#ifdef HAVE_QTSPARKLE
|
||||||
# include <qtsparkle-qt6/Updater>
|
# include <qtsparkle-qt6/Updater>
|
||||||
#endif // HAVE_QTSPARKLE
|
#endif // HAVE_QTSPARKLE
|
||||||
@@ -365,6 +369,12 @@ int main(int argc, char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
options);
|
options);
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
UnixSignalWatcher unix_signal_watcher;
|
||||||
|
unix_signal_watcher.WatchForSignal(SIGTERM);
|
||||||
|
QObject::connect(&unix_signal_watcher, &UnixSignalWatcher::UnixSignal, &w, &MainWindow::Exit);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
mac::EnableFullScreen(w);
|
mac::EnableFullScreen(w);
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|||||||
Reference in New Issue
Block a user