Exit on SIGTERM

This commit is contained in:
Jonas Kvinge
2026-01-03 21:42:38 +01:00
parent 17519076f5
commit 1d82977441
5 changed files with 237 additions and 2 deletions

View File

@@ -1218,6 +1218,10 @@ set(UI
src/device/deviceviewcontainer.ui
)
if(UNIX)
optional_source(UNIX SOURCES src/core/unixsignalwatcher.cpp HEADERS src/core/unixsignalwatcher.h)
endif()
if(APPLE)
optional_source(APPLE
SOURCES

View File

@@ -245,7 +245,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void ToggleSearchCoverAuto(const bool checked);
void SaveGeometry();
void Exit();
void DoExit();
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:
void CommandlineOptionsReceived(const QByteArray &string_options);
void Raise();
void Exit();
private:
void SaveSettings();

View 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;
}
}
}

View 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

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* 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
* it under the terms of the GNU General Public License as published by
@@ -76,6 +76,10 @@
#include <kdsingleapplication.h>
#ifdef Q_OS_UNIX
#include "core/unixsignalwatcher.h"
#endif
#ifdef HAVE_QTSPARKLE
# include <qtsparkle-qt6/Updater>
#endif // HAVE_QTSPARKLE
@@ -365,6 +369,12 @@ int main(int argc, char *argv[]) {
#endif
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
mac::EnableFullScreen(w);
#endif // Q_OS_MACOS