Exit on SIGTERM
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
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
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user