magmax / python-readchar

Python library to read characters and key strokes
MIT License
143 stars 45 forks source link

ctrl+c and ctrl+z (Linux) are intercepted #40

Closed altendky closed 2 years ago

altendky commented 5 years ago

I suspect that while some people want to intercept ctrl+c and ctrl+z, many also do not want to. I am no expert in this area so there may be significant issues with this but I figured I'd document the issue and share what I put together (along with another #python user). This was written as a thing to use readchar but presumably could be translated into a feature for readchar proper. No promises right now that I will get around to that.

https://repl.it/@altendky/readchar-getch-6

import os
import signal
import sys

import readchar

signals = {
    '\x03': getattr(signal, 'SIGINT', getattr(signal, 'CTRL_C_EVENT', None)),
    '\x1a': getattr(signal, 'SIGTSTP', None),
}

kill = getattr(os, 'killpg', getattr(os, 'kill'))
pid = (
    os.tcgetpgrp(sys.stdin.fileno())
    if hasattr(os, 'tcgetpgrp')
    else os.getpid()
)

def prompt(text, options):
    prompt = '{} ({}): '.format(text, '/'.join(options))

    while True:
        sys.stdout.write(prompt)
        sys.stdout.flush()

        while True:
            answer = readchar.readchar()
            sig = signals.get(answer)

            if sig is not None:
                kill(pid, sig)
                continue

            break

        sys.stdout.write(answer + '\n')
        sys.stdout.flush()

        if answer in options:
            return answer

sys.stdout.write('answer: {}\n'.format(prompt('prompt', 'ynm')))
rogerdahl commented 4 years ago

This looks like it would work, but all that's really required is to change tty.setraw() to tty.setcbreak() in the library readchar() function. Since it's only a few lines, you can make a local copy of it:

def readchar():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        # tty.setraw(sys.stdin.fileno())
        tty.setcbreak(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch
shasheene commented 2 years ago

I hit this same issue. Here's my workaround that may help people:

# Read a single character using readchar(). This is a blocking call.
character = readchar.readchar()
# Note: readchar() currently intercepts CTRL-C etc [1]. The easiest solution is to detect CTRL-C and simply
# emit the relevant signal [2]
# [1] https://github.com/magmax/python-readchar/issues/40
# [2] https://github.com/magmax/python-readchar/issues/40#issuecomment-934018349
if character == '\x03':
    # CTRL-C
    os.kill(os.getpid(), signal.SIGINT)
if character == '\x1a':
    # CTRL-Z
    os.kill(os.getpid(), signal.SIGTSTP)

You can find readchar's list of character codes here.

I agree the best solution is to modify the library and add an option. But this workaround is sufficient for my purposes right now.