// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer 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.
//
// Klipspringer 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 Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <sys/errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include "Library.h"
#include "net_avadeaux_klipspringer_Terminal.h" // generated by javac -h

#define SIG_INTERRUPT_GETCHAR SIGHUP
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_Terminal_ ## name

static struct termios orig_termios;

static struct sigaction orig_sigact;
static pthread_t getchar_thread;
static bool getchar_active = false, getchar_interrupted;
static pthread_mutex_t getchar_mutex = PTHREAD_MUTEX_INITIALIZER;

JNIEXPORT jboolean
METHOD(inTty) (JNIEnv *env,
               jclass jthisClass)
{
    return isatty(STDIN_FILENO);
}

JNIEXPORT jboolean
METHOD(outTty) (JNIEnv *env,
                jclass jthisClass)
{
    return isatty(STDOUT_FILENO);
}

JNIEXPORT jboolean
METHOD(errTty) (JNIEnv *env,
                jclass jthisClass)
{
    return isatty(STDERR_FILENO);
}

JNIEXPORT void
METHOD(setupNonCanonical) (JNIEnv *env,
                           jclass jthisClass)
{
    struct termios ios;
    if (tcgetattr(STDIN_FILENO, &ios) < 0) {
        raiseErrorCodeException(env, errno, strerror(errno));
        return;
    }
    orig_termios = ios;

    ios.c_lflag &= ~ICANON & ~ECHO;
    ios.c_cc[VMIN] = 1;
    ios.c_cc[VTIME] = 0;
    if (tcsetattr(STDIN_FILENO, TCSANOW, &ios) < 0) {
        raiseErrorCodeException(env, errno, strerror(errno));
    }
}

JNIEXPORT void
METHOD(cancelNonCanonical) (JNIEnv *env,
                            jclass jthisClass)
{
    if (tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios) < 0) {
        raiseErrorCodeException(env, errno, strerror(errno));
    }
}

static int lock(JNIEnv *env, pthread_mutex_t *mutex) {
    int err = pthread_mutex_lock(mutex);
    if (err == 0) { return 0; }                 // lock ok
    raiseErrorCodeException(env, err, strerror(err));
    return -1;
}

static int unlock(JNIEnv *env, pthread_mutex_t *mutex) {
    int err = pthread_mutex_unlock(mutex);
    if (err == 0) { return 0; }                 // unlock ok
    raiseErrorCodeException(env, err, strerror(err));
    return -1;
}

static void handle_sig(int signum) {
    int err = pthread_mutex_lock(&getchar_mutex);
    if (err) {
        fprintf(stderr, "failed locking mutex in signal handler: %s\n", strerror(err));
        exit(1);
    }
    getchar_interrupted = true;
    err = pthread_mutex_unlock(&getchar_mutex);
    if (err) {
        fprintf(stderr, "failed unlocking mutex in signal handler: %s\n", strerror(err));
        exit(1);
    }
}

JNIEXPORT void
METHOD(setupSigaction) (JNIEnv *env,
                        jclass jthisClass)
{
    struct sigaction sh;

    sh.sa_handler = handle_sig;
    sigemptyset(&sh.sa_mask);
    sh.sa_flags = 0;
    if (sigaction(SIG_INTERRUPT_GETCHAR, &sh, &orig_sigact) < 0) {
        raiseErrorCodeException(env, errno, strerror(errno));
    }
}

JNIEXPORT void
METHOD(cancelSigaction) (JNIEnv *env,
                         jclass jthisClass)
{
    if (sigaction(SIG_INTERRUPT_GETCHAR, &orig_sigact, NULL) < 0) {
        raiseErrorCodeException(env, errno, strerror(errno));
    }
}

JNIEXPORT jint
METHOD(getChar) (JNIEnv *env,
                 jclass jthisClass)
{
    pthread_t me = pthread_self();
    bool success = false;

    if (lock(env, &getchar_mutex) < 0) { return -1; }
    if (!getchar_active) {
        getchar_thread = me;
        getchar_active = true;
        getchar_interrupted = false;
        success = true;
    }
    if (unlock(env, &getchar_mutex) < 0) { return -1; }
    if (!success) {
        raiseIOException(env, "getChar already in progress");
        return -1;
    }

    int ch = getchar();

    if (lock(env, &getchar_mutex) < 0) { return -1; }
    getchar_active = false;
    bool interrupted = getchar_interrupted;     // happened while waiting for mutex?
    if (unlock(env, &getchar_mutex) < 0 || interrupted) { return -1; }

    return ch;
}

JNIEXPORT void
METHOD(sigThread) (JNIEnv *env,
                   jclass jthisClass)
{
    pthread_t it;
    bool success = false;

    if (lock(env, &getchar_mutex) < 0) { return; }
    success = getchar_active;
    it = getchar_thread;
    if (unlock(env, &getchar_mutex) < 0 || !success) { return; }

    int err = pthread_kill(it, SIG_INTERRUPT_GETCHAR);
    if (err) {
        raiseErrorCodeException(env, err, strerror(err));
    }
}

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home