You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notcurses/src/demo/input.c

185 lines
4.8 KiB
C

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include "demo.h"
typedef struct nciqueue {
ncinput ni;
struct nciqueue *next;
} nciqueue;
// a pipe on which we write upon receipt of input, so that demos
// can reliably multiplex against other fds. osx doesn't have
// eventfd, alas (freebsd added it in 13.0).
static int input_pipefds[2] = {-1, -1};
static pthread_t tid;
static nciqueue* queue;
static nciqueue** enqueue = &queue;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond; // use pthread_condmonotonic_init()
static int
handle_mouse(const ncinput* ni){
if(ni->id != NCKEY_BUTTON1){
return 0;
}
int ret;
if(ni->evtype == NCTYPE_RELEASE){
ret = hud_release();
if(ret < 0){
ret = fpsplot_release();
}
}else{
ret = hud_grab(ni->y, ni->x);
if(ret < 0){
ret = fpsplot_grab(ni->y);
}
}
// do not render here. the demos, if coded properly, will be regularly
// rendering (if via demo_nanosleep() if nothing else). rendering based off
// HUD movements can cause disruptions due to the main thread being unready.
return ret;
}
// incoming timespec is relative (or even NULL, for blocking), but we need
// absolute deadline, so convert it up.
uint32_t demo_getc(struct notcurses* nc, const struct timespec* ts, ncinput* ni){
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t ns;
// abstime shouldn't be further out than our maximum sleep time -- this can
// lead to 0 frames output during the wait
if(ts){
ns = timespec_to_ns(ts);
}else{
ns = MAXSLEEP;
}
if(ns > MAXSLEEP){
ns = MAXSLEEP;
}
struct timespec abstime;
ns_to_timespec(ns + timespec_to_ns(&now), &abstime);
bool handoff = false; // does the input go back to the user?
uint32_t id;
do{
pthread_mutex_lock(&lock);
while(!queue){
clock_gettime(CLOCK_MONOTONIC, &now);
if(timespec_to_ns(&now) > timespec_to_ns(&abstime)){
pthread_mutex_unlock(&lock);
return 0;
}
pthread_cond_timedwait(&cond, &lock, &abstime);
}
nciqueue* q = queue;
queue = queue->next;
if(queue == NULL){
enqueue = &queue;
}
pthread_mutex_unlock(&lock);
id = q->ni.id;
// if this was about the menu or HUD, pass to them, and continue
if(!menu_or_hud_key(nc, &q->ni)){
if(nckey_mouse_p(q->ni.id)){
if(!handle_mouse(&q->ni)){
handoff = true;
}
}else{
handoff = true;
}
}
if(handoff && ni){
memcpy(ni, &q->ni, sizeof(*ni));
}
free(q);
}while(!handoff);
return id;
}
static int
pass_along(const ncinput* ni){
pthread_mutex_lock(&lock);
nciqueue *nq = malloc(sizeof(*nq));
memcpy(&nq->ni, ni, sizeof(*ni));
nq->next = NULL;
*enqueue = nq;
enqueue = &nq->next;
pthread_mutex_unlock(&lock);
const uint64_t eventcount = 1;
int ret = 0;
if(write(input_pipefds[1], &eventcount, sizeof(eventcount)) < 0){
ret = -1;
}
pthread_cond_signal(&cond);
return ret;
}
static void *
ultramegaok_demo(void* vnc){
ncinput ni;
struct notcurses* nc = vnc;
uint32_t id;
while((id = notcurses_get_blocking(nc, &ni)) != (uint32_t)-1){
if(id == 0){
continue;
}
// go ahead and pass keyboard through to demo, even if it was a 'q' (this
// might cause the demo to exit immediately, as is desired). we can't just
// mess with the menu/HUD in our own context, as the demo thread(s) might
// be rendering, or otherwise fucking with things we musn't fuck with
// concurrentwise (z-axis manipulations, etc.).
pass_along(&ni);
}
return NULL;
}
int demo_input_fd(void){
return input_pipefds[1];
}
// listens for events, handling mouse events directly and making other ones
// available to demos. returns -1 if already spawned or resource failures.
int input_dispatcher(struct notcurses* nc){
if(input_pipefds[0] >= 0){
return -1;
}
if(pthread_condmonotonic_init(&cond)){
fprintf(stderr, "error creating monotonic condvar\n");
return -1;
}
// freebsd doesn't have eventfd :/ and apple doesn't even have pipe2() =[ =[
// omg windows doesn't have pipe() fml FIXME
#ifndef __MINGW32__
#if defined(__APPLE__)
if(pipe(input_pipefds)){
#else
if(pipe2(input_pipefds, O_CLOEXEC | O_NONBLOCK)){
#endif
fprintf(stderr, "Error creating pipe (%s)\n", strerror(errno));
return -1;
}
#endif
if(pthread_create(&tid, NULL, ultramegaok_demo, nc)){
close(input_pipefds[0]);
close(input_pipefds[1]);
input_pipefds[0] = input_pipefds[1] = -1;
return -1;
}
return 0;
}
int stop_input(void){
int ret = 0;
if(input_pipefds[0] >= 0){
ret |= pthread_cancel(tid);
ret |= pthread_join(tid, NULL);
ret |= close(input_pipefds[0]);
ret |= close(input_pipefds[1]);
input_pipefds[0] = input_pipefds[1] = -1;
ret |= pthread_cond_destroy(&cond);
}
return ret;
}