Вот код от /usr/src/bin/stty/key.c
:
f_cbreak(struct info *ip)
{
if (ip->off)
f_sane(ip);
else {
ip->t.c_iflag |= BRKINT|IXON|IMAXBEL;
ip->t.c_oflag |= OPOST;
ip->t.c_lflag |= ISIG|IEXTEN;
ip->t.c_lflag &= ~ICANON;
ip->set = 1;
}
}
Как минимум, вы должны выйти из режима ICANON
, прежде чем ваш select(2)
системный вызов или FIONREAD ioctl
сработает.
У меня есть древняя 20-летняя программа perl4, которая таким образом очищает режимы CBREAK и ECHO. Он делает вещи с проклятиями, не прибегая к библиотеке проклятий:
sub BSD_cbreak {
local($on) = shift;
local(@sb);
local($sgttyb);
# global $sbttyb_t
$sgttyb_t = &sgttyb'typedef() unless defined $sgttyb_t;
# native BSD stuff by author (tsc)
ioctl(TTY,&TIOCGETP,$sgttyb)
|| die "Can't ioctl TIOCGETP: $!";
@sb = unpack($sgttyb_t,$sgttyb);
if ($on) {
$sb[&sgttyb'sg_flags] |= &CBREAK;
$sb[&sgttyb'sg_flags] &= ~&ECHO;
} else {
$sb[&sgttyb'sg_flags] &= ~&CBREAK;
$sb[&sgttyb'sg_flags] |= &ECHO;
}
$sgttyb = pack($sgttyb_t,@sb);
ioctl(TTY,&TIOCSETN,$sgttyb)
|| die "Can't ioctl TIOCSETN: $!";
}
sub SYSV_cbreak {
# SysV code contributed by Jeff Okamoto <okamoto@hpcc25.corp.hp.com>
local($on) = shift;
local($termio,@termio);
# global termio_t ???
$termio_t = &termio'typedef() unless defined $termio_t;
ioctl(TTY,&TCGETA,$termio)
|| die "Can't ioctl TCGETA: $!";
@termio = unpack($termio_t, $termio);
if ($on) {
$termio[&termio'c_lflag] &= ~(&ECHO | &ICANON);
$termio[&termio'c_cc + &VMIN] = 1;
$termio[&termio'c_cc + &VTIME] = 1;
} else {
$termio[&termio'c_lflag] |= (&ECHO | &ICANON);
# In HP-UX, it appears that turning ECHO and ICANON back on is
# sufficient to re-enable cooked mode. Therefore I'm not bothering
# to reset VMIN and VTIME (VEOF and VEOL above). This might be a
# problem on other SysV variants.
}
$termio = pack($termio_t, @termio);
ioctl(TTY, &TCSETA, $termio)
|| die "Can't ioctl TCSETA: $!";
}
sub POSIX_cbreak {
local($on) = shift;
local(@termios, $termios, $bitmask);
# "file statics" for package cbreak:
# $savebits, $save_vtime, $save_vmin, $is_on
$termios_t = &termios'typedef() unless defined $termios_t;
$termios = pack($termios_t, ()); # for Sun SysVr4, which dies w/o this
ioctl(TTY,&$GETIOCTL,$termios)
|| die "Can't ioctl GETIOCTL ($GETIOCTL): $!";
@termios = unpack($termios_t,$termios);
$bitmask = &ICANON | &IEXTEN | &ECHO;
if ($on && $cbreak'ison == 0) {
$cbreak'ison = 1;
$cbreak'savebits = $termios[&termios'c_lflag] & $bitmask;
$termios[&termios'c_lflag] &= ~$bitmask;
$cbreak'save_vtime = $termios[&termios'c_cc + &VTIME];
$termios[&termios'c_cc + &VTIME] = 0;
$cbreak'save_vmin = $termios[&termios'c_cc + &VMIN];
$termios[&termios'c_cc + &VMIN] = 1;
} elsif ( !$on && $cbreak'ison == 1 ) {
$cbreak'ison = 0;
$termios[&termios'c_lflag] |= $cbreak'savebits;
$termios[&termios'c_cc + &VTIME] = $cbreak'save_vtime;
$termios[&termios'c_cc + &VMIN] = $cbreak'save_vmin;
} else {
return 1;
}
$termios = pack($termios_t,@termios);
ioctl(TTY,&$SETIOCTL,$termios)
|| die "Can't ioctl SETIOCTL ($SETIOCTL): $!";
}
sub DUMB_cbreak {
local($on) = shift;
if ($on) {
system("stty cbreak -echo");
} else {
system("stty -cbreak echo");
}
}
А в другом месте сказано, что для POSIX,
($GETIOCTL, $SETIOCTL) = (TIOCGETA, TIOCSETA);
Повторный перевод обратно в исходный C оставлен в качестве упражнения для читателя, потому что я не могу вспомнить, откуда 20-летней давности я его поймал. (
После того как вы вышли из режима ICANON на tty, теперь ваш системный вызов select(2)
снова работает правильно. Когда маска чтения select
возвращает, что этот дескриптор готов, тогда вы делаете FIONREAD ioctl
, чтобы точно узнать, сколько байтов ожидает вас в этом дескрипторе файла. Получив это, вы можете сделать системный вызов read(2)
только для такого количества байтов, предпочтительно для дескриптора O_NONBLOCK
, хотя к настоящему времени это больше не требуется.
Хм, вот примечание предчувствия в /usr/src/usr.bin/vi/cl/README.signal
:
Run in cbreak mode. There are two problems in this area. First, the
current curses implementations (both System V and Berkeley) don't give
you clean cbreak modes. For example, the IEXTEN bit is left on, turning
on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with
the exception that flow control and signals are turned on, and curses
cbreak mode doesn't give you this.
We can either set raw mode and twiddle the tty, or cbreak mode and
twiddle the tty. I chose to use raw mode, on the grounds that raw
mode is better defined and I'm less likely to be surprised by a curses
implementation down the road. The twiddling consists of setting ISIG,
IXON/IXOFF, and disabling some of the interrupt characters (see the
comments in cl_init.c). This is all found in historic System V (SVID
3) and POSIX 1003.1-1992, so it should be fairly portable.
Если вы делаете рекурсивный grep
для \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b
для неядерных частей /usr/src/
, вы должны найти материал, который вы можете использовать. Например:
% grep -Pr '\b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b' /usr/src/{{s,}bin,usr.{s,}bin,etc,gnu}
Я бы посмотрел на /usr/src/usr.bin/less/screen.c
, вниз в функции raw_mode()
. Загаданный ifdef
s, хотя он находится в поиске переносимости, это выглядит как самый чистый код для того, что вы хотите сделать. В GNU также есть что-то скрытое.
О, МОЙ , посмотрите /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl
! Это должен быть мой старый код, который я разместил выше. Интересно, что это применимо ко всем системам src в мире. Страшно тоже, потому что - двадцать лет назад. Черт возьми, странно видеть отголоски молодого себя. Действительно трудно вспомнить такие подробности 20 лет назад.
Я также вижу в /usr/src/lib/libcurses/term.h
эту строку:
#define tcgetattr(fd, arg) ioctl(fd, TCGETA, arg)
в связке ifdef
с, которые пытаются определить termio
или termios
доступность.
Этого должно быть достаточно, чтобы начать работу.