Skip to content

Commit

Permalink
Handle Unix signals
Browse files Browse the repository at this point in the history
This fixes a usability problem, when blobdrop sets the keep-below hint,
but then is forcefully terminated by the user pressing ^C, which means
that the keep-below hint is never lifted.

Fix this by catching terminating Unix signals and then performing a
last-second cleanup before actually quitting.

Catching Unix signals is trivial [0] if we use only async-signal-safe
methods [1]. Howver we acually need to do some Qt function calls, that
would definitely violate these restrictions, so instead we use a more
elaborate setup [2], where we perform some safe writes via Unix sockets
during the signal interception, which will later trigger a Qt signal on
a listening QSocketNotifier when we are out of the critical zone again.
When the QSocketNotifier::activated() triggers, we are then safe to do
any operations we like.

[0] https://gist.github.com/azadkuh/a2ac6869661ebd3f8588
[1] https://pubs.opengroup.org/onlinepubs/000095399/functions/xsh_chap02_04.html#tag_02_04_03
[2] https://doc.qt.io/qt-6/unix-signals.html
  • Loading branch information
vimpostor committed Oct 19, 2023
1 parent 270c74f commit aa3e472
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include <signal.h>

#include "getopts.hpp"
#include "signals.hpp"
#include "version.hpp"

int main(int argc, char *argv[]) {
QCoreApplication::setOrganizationName("blobdrop");
QCoreApplication::setApplicationName("blobdrop");
QCoreApplication::setApplicationVersion(Version::version_string());
QGuiApplication app(argc, argv);

// handle unix signals
Signals signal_handler {{SIGINT, SIGHUP, SIGTERM, SIGQUIT}};

if (!Getopts::parse(app)) {
return EXIT_FAILURE;
}
Expand Down
55 changes: 55 additions & 0 deletions src/signals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "signals.hpp"

#include <ranges>
#include <signal.h>
#include <sys/socket.h>

#include "backend.hpp"

Signals::Signals(const std::initializer_list<int> &sigs) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_fd)) {
qWarning() << "Could not create HUP socketpair";
}
sn = new QSocketNotifier(signal_fd[1], QSocketNotifier::Read, this);
connect(sn, &QSocketNotifier::activated, this, &Signals::handle_qt_signal);
setup_signal_handlers(sigs);
}

void Signals::handle_unix_signal(int) {
char a = 1;
// only very few, async-signal-safe methods are allowed in the signal handler
write(signal_fd[0], &a, sizeof(a));
}

void Signals::handle_qt_signal() {
sn->setEnabled(false);
char a;
read(signal_fd[1], &a, sizeof(a));

// remove the keep-below hint again, as we want to quit now
Backend::get()->restore_terminal();
// Duplicate quit attempts are required:
// The nice attempt over quit_delayed() is ignored, because a drag operation is active.
// The exit() forces the drag operation to close.
// Then the quit_delayed() causes the program to finally close.
QCoreApplication::exit();
Backend::get()->quit_delayed(0ms);

sn->setEnabled(true);
}

void Signals::setup_signal_handlers(const std::initializer_list<int> &sigs) {
struct sigaction act;

act.sa_handler = Signals::handle_unix_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_flags |= SA_RESTART;

std::ranges::for_each(sigs, [&](const auto &s) {
if (sigaction(SIGINT, &act, nullptr)) {
qWarning() << "Could not setup signal handler";
return;
}
});
}
20 changes: 20 additions & 0 deletions src/signals.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <QSocketNotifier>

class Signals : public QObject {
Q_OBJECT
public:
Signals(const std::initializer_list<int> &sigs);

// Unix signal handler
static void handle_unix_signal(int);
public slots:
// Qt signal handler
void handle_qt_signal();
private:
void setup_signal_handlers(const std::initializer_list<int> &sigs);

static inline int signal_fd[2];
QSocketNotifier *sn;
};

0 comments on commit aa3e472

Please sign in to comment.