mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 09:09:03 -04:00
signal handlers for INT, QUIT, WINCH #27
This commit is contained in:
parent
f14ee490f2
commit
ee2e16736c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
debian
|
||||
obj-x86_64-linux-gnu
|
@ -117,6 +117,13 @@ typedef struct notcurses_options {
|
||||
// If smcup/rmcup capabilities are indicated, notcurses defaults to making
|
||||
// use of the "alternate screen". This flag inhibits use of smcup/rmcup.
|
||||
bool inhibit_alternate_screen;
|
||||
// We typically install a signal handler for SIGINT and SIGQUIT that restores
|
||||
// the screen, and then calls the old signal handler. Set this to inhibit
|
||||
// registration of any signal handlers.
|
||||
bool no_quit_sighandlers;
|
||||
// We typically install a signal handler for SIGWINCH that generates a resize
|
||||
// event in the notcurses_getc() queue. Set this to inhibit the handler.
|
||||
bool no_winch_sighandler;
|
||||
} notcurses_options;
|
||||
|
||||
// Initialize a notcurses context, corresponding to a connected terminal.
|
||||
|
@ -68,7 +68,7 @@ typedef struct notcurses_options {
|
||||
// the environment variable TERM is used. Failure to open the terminal
|
||||
// definition will result in failure to initialize notcurses.
|
||||
const char* termtype;
|
||||
// A file descriptor for this terminal on which we will generate output.
|
||||
// A file descriptor for this terminal, on which we will generate output.
|
||||
// Must be a valid file descriptor attached to a terminal, or notcurses will
|
||||
// refuse to start. You'll usually want STDOUT_FILENO.
|
||||
int outfd;
|
||||
@ -78,6 +78,13 @@ typedef struct notcurses_options {
|
||||
// By default, we hide the cursor if possible. This flag inhibits use of
|
||||
// the civis capability, retaining the cursor.
|
||||
bool retain_cursor;
|
||||
// We typically install a signal handler for SIGINT and SIGQUIT that restores
|
||||
// the screen, and then calls the old signal handler. Set this to inhibit
|
||||
// registration of any signal handlers.
|
||||
bool no_quit_sighandlers;
|
||||
// We typically install a signal handler for SIGWINCH that generates a resize
|
||||
// event in the notcurses_getc() queue. Set this to inhibit the handler.
|
||||
bool no_winch_sighandler;
|
||||
} notcurses_options;
|
||||
|
||||
// Initialize a notcurses context, corresponding to a connected terminal.
|
||||
|
@ -3,9 +3,11 @@
|
||||
#include <term.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <libavformat/version.h>
|
||||
#include "notcurses.h"
|
||||
@ -46,24 +48,6 @@ typedef struct ncstats {
|
||||
int64_t render_min_ns; // min ns spent in successful notcurses_render()
|
||||
} ncstats;
|
||||
|
||||
static void
|
||||
update_render_stats(const struct timespec* time1, const struct timespec* time0,
|
||||
ncstats* stats){
|
||||
int64_t elapsed = timespec_subtract_ns(time1, time0);
|
||||
//fprintf(stderr, "Rendering took %ld.%03lds\n", elapsed / 1000000000,
|
||||
// (elapsed % 1000000000) / 1000000);
|
||||
if(elapsed > 0){ // don't count clearly incorrect information, egads
|
||||
++stats->renders;
|
||||
stats->renders_ns += elapsed;
|
||||
if(elapsed > stats->render_max_ns){
|
||||
stats->render_max_ns = elapsed;
|
||||
}
|
||||
if(elapsed < stats->render_min_ns || stats->render_min_ns == 0){
|
||||
stats->render_min_ns = elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct notcurses {
|
||||
int ttyfd; // file descriptor for controlling tty (takes stdin)
|
||||
int colors; // number of colors usable for this screen
|
||||
@ -93,6 +77,95 @@ typedef struct notcurses {
|
||||
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
|
||||
} notcurses;
|
||||
|
||||
// only one notcurses object can be the target of signal handlers, due to their
|
||||
// process-wide nature.
|
||||
static notcurses* _Atomic signal_nc; // ugh
|
||||
static void (*signal_sa_handler)(int); // stashed signal handler we replaced
|
||||
|
||||
static void
|
||||
sigwinch_handler(int signo __attribute__ ((unused))){
|
||||
// FIXME
|
||||
}
|
||||
|
||||
// this wildly unsafe handler will attempt to restore the screen upon
|
||||
// reception of a SIGINT or SIGQUIT. godspeed you, black emperor!
|
||||
static void
|
||||
fatal_handler(int signo){
|
||||
notcurses* nc = atomic_load(&signal_nc);
|
||||
if(nc){
|
||||
notcurses_stop(nc);
|
||||
if(signal_sa_handler){
|
||||
signal_sa_handler(signo);
|
||||
}
|
||||
raise(signo);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
setup_signals(notcurses* nc, bool no_quit_sigs, bool no_winch_sig){
|
||||
notcurses* expected = NULL;
|
||||
struct sigaction oldact;
|
||||
struct sigaction sa;
|
||||
|
||||
if(!atomic_compare_exchange_strong(&signal_nc, &expected, nc)){
|
||||
fprintf(stderr, "%p is already registered for signals\n", expected);
|
||||
return -1;
|
||||
}
|
||||
if(!no_winch_sig){
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = sigwinch_handler;
|
||||
if(sigaction(SIGWINCH, &sa, NULL)){
|
||||
atomic_store(&signal_nc, NULL);
|
||||
fprintf(stderr, "Error installing SIGWINCH handler (%s)\n",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(!no_quit_sigs){
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = fatal_handler;
|
||||
sigaddset(&sa.sa_mask, SIGINT);
|
||||
sigaddset(&sa.sa_mask, SIGQUIT);
|
||||
sa.sa_flags = SA_RESETHAND; // don't try twice
|
||||
if(sigaction(SIGINT, &sa, &oldact) || sigaction(SIGQUIT, &sa, &oldact)){
|
||||
atomic_store(&signal_nc, NULL);
|
||||
fprintf(stderr, "Error installing fatal signal handlers (%s)\n",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
signal_sa_handler = oldact.sa_handler;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
drop_signals(notcurses* nc){
|
||||
notcurses* old = nc;
|
||||
if(!atomic_compare_exchange_strong(&signal_nc, &old, NULL)){
|
||||
fprintf(stderr, "Can't drop signals: %p != %p\n", old, nc);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
update_render_stats(const struct timespec* time1, const struct timespec* time0,
|
||||
ncstats* stats){
|
||||
int64_t elapsed = timespec_subtract_ns(time1, time0);
|
||||
//fprintf(stderr, "Rendering took %ld.%03lds\n", elapsed / 1000000000,
|
||||
// (elapsed % 1000000000) / 1000000);
|
||||
if(elapsed > 0){ // don't count clearly incorrect information, egads
|
||||
++stats->renders;
|
||||
stats->renders_ns += elapsed;
|
||||
if(elapsed > stats->render_max_ns){
|
||||
stats->render_max_ns = elapsed;
|
||||
}
|
||||
if(elapsed < stats->render_min_ns || stats->render_min_ns == 0){
|
||||
stats->render_min_ns = elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char NOTCURSES_VERSION[] =
|
||||
notcurses_VERSION_MAJOR "."
|
||||
notcurses_VERSION_MINOR "."
|
||||
@ -159,7 +232,7 @@ free_plane(ncplane* p){
|
||||
}
|
||||
|
||||
static int
|
||||
term_emit(const char* seq, FILE* out){
|
||||
term_emit(const char* seq, FILE* out, bool flush){
|
||||
int ret = fprintf(out, "%s", seq);
|
||||
if(ret < 0){
|
||||
fprintf(stderr, "Error emitting %zub sequence (%s)\n", strlen(seq), strerror(errno));
|
||||
@ -169,6 +242,10 @@ term_emit(const char* seq, FILE* out){
|
||||
fprintf(stderr, "Short write (%db) for %zub sequence\n", ret, strlen(seq));
|
||||
return -1;
|
||||
}
|
||||
if(flush && fflush(out)){
|
||||
fprintf(stderr, "Error flushing after %db sequence (%s)\n", ret, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -266,7 +343,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
|
||||
return -1;
|
||||
}
|
||||
if(!opts->retain_cursor){
|
||||
if(term_emit(tiparm(cursor_invisible), stdout)){
|
||||
if(term_emit(tiparm(cursor_invisible), stdout, true)){
|
||||
return -1;
|
||||
}
|
||||
term_verify_seq(&nc->cnorm, "cnorm");
|
||||
@ -348,6 +425,9 @@ notcurses* notcurses_init(const notcurses_options* opts){
|
||||
ret->ttyfd, strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
if(setup_signals(ret, opts->no_quit_sighandlers, opts->no_winch_sighandler)){
|
||||
goto err;
|
||||
}
|
||||
int termerr;
|
||||
if(setupterm(opts->termtype, ret->ttyfd, &termerr) != OK){
|
||||
fprintf(stderr, "Terminfo error %d (see terminfo(3ncurses))\n", termerr);
|
||||
@ -362,7 +442,7 @@ notcurses* notcurses_init(const notcurses_options* opts){
|
||||
ret->top->z = NULL;
|
||||
ret->stdscr = ret->top;
|
||||
memset(&ret->stats, 0, sizeof(ret->stats));
|
||||
if(ret->smcup && term_emit(ret->smcup, stdout)){
|
||||
if(ret->smcup && term_emit(ret->smcup, stdout, true)){
|
||||
free_plane(ret->top);
|
||||
goto err;
|
||||
}
|
||||
@ -385,10 +465,11 @@ err:
|
||||
int notcurses_stop(notcurses* nc){
|
||||
int ret = 0;
|
||||
if(nc){
|
||||
if(nc->rmcup && term_emit(nc->rmcup, stdout)){
|
||||
drop_signals(nc);
|
||||
if(nc->rmcup && term_emit(nc->rmcup, stdout, true)){
|
||||
ret = -1;
|
||||
}
|
||||
if(nc->cnorm && term_emit(nc->cnorm, stdout)){
|
||||
if(nc->cnorm && term_emit(nc->cnorm, stdout, true)){
|
||||
return -1;
|
||||
}
|
||||
ret |= tcsetattr(nc->ttyfd, TCSANOW, &nc->tpreserved);
|
||||
@ -588,7 +669,7 @@ int notcurses_render(notcurses* nc){
|
||||
if(out == NULL){
|
||||
return -1;
|
||||
}
|
||||
term_emit(nc->clear, out);
|
||||
term_emit(nc->clear, out, false);
|
||||
for(y = 0 ; y < nc->stdscr->leny ; ++y){
|
||||
for(x = 0 ; x < nc->stdscr->lenx ; ++x){
|
||||
unsigned r, g, b, br, bg, bb;
|
||||
@ -599,7 +680,7 @@ int notcurses_render(notcurses* nc){
|
||||
// escapes ourselves, if either is set to default, we first send op, and
|
||||
// then a turnon for whichever aren't default.
|
||||
if(cell_fg_default_p(c) || cell_bg_default_p(c)){
|
||||
term_emit(nc->op, out);
|
||||
term_emit(nc->op, out, false);
|
||||
}
|
||||
if(!cell_fg_default_p(c)){
|
||||
cell_get_fg(c, &r, &g, &b);
|
||||
|
Loading…
x
Reference in New Issue
Block a user