diff --git a/README.md b/README.md index 87199a6..f37038d 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface. ```toml [dependencies] -openkal = "0.13.0" +openkal = "0.14.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.13.0" +openkal-linux = "0.14.0" ``` ## Why it does not use a C library diff --git a/mcpp.toml b/mcpp.toml index b2c1c20..28e1c87 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.13.0" +version = "0.14.0" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-linux" [dependencies] -openkal = "0.13.0" +openkal = "0.14.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/sys.h b/src/sys.h index 2f7ad0a..0c07fd8 100644 --- a/src/sys.h +++ b/src/sys.h @@ -506,9 +506,22 @@ enum : okl_long { tcgets = 0x5401, tcsets = 0x5402, tiocgwinsz = 0x5413, }; -// Positions within ktermios::lflag. Named here for the same reason the numbers -// above are: they belong to the kernel and not to any library. -enum : okl_u32 { t_icanon = 0000002u, t_echo = 0000010u }; +// Positions within ktermios::lflag and ktermios::iflag, and the two entries of +// ktermios::cc that decide how long a read waits. Named here for the same +// reason the numbers above are: they belong to the kernel and not to any +// library. +// +// `t_isig', `t_ixon' and `t_iexten' are the three mechanisms by which this +// kernel keeps a keystroke for itself --- the interrupt and its neighbours, the +// pair that stops and starts output, and the one that takes the next keystroke +// literally. KAL_TERM_PASS_CONTROL is the whole of them, because a program that +// asked for every keystroke and was given two mechanisms out of three would +// find one key missing and nothing to ask about it with. +enum : okl_u32 { + t_isig = 0000001u, t_icanon = 0000002u, t_echo = 0000010u, t_iexten = 0100000u, +}; +enum : okl_u32 { t_ixon = 0002000u }; +enum { v_time = 5, v_min = 6 }; // --- openkal.net and openkal.datagram -------------------------------------- // diff --git a/src/terminal.cpp b/src/terminal.cpp index 887caf9..dcedc7e 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -16,10 +16,21 @@ namespace { // positions in this interface because a program that wants a password prompt // turns off one and leaves the other, and a backend that conflated them would // make that program's intent inexpressible. +// +// KAL_TERM_PASS_CONTROL IS READ FROM THREE FLAGS AND NOT FROM ISIG. The +// position states that the environment reserves NO keystroke, so it is set only +// where every mechanism by which this kernel reserves one is off: ISIG for the +// interrupt and its neighbours, IXON for the pair that stops and starts output, +// IEXTEN for the one that takes the next keystroke literally. A terminal upon +// which some of them had been released reads as clear and is restored to the +// set this kernel ordinarily reserves, which is the cost the specification +// records beside the position. kal_uintptr mode_of(const okl::ktermios& t) { kal_uintptr m = 0; if ((t.lflag & okl::t_icanon) != 0) m |= KAL_TERM_LINE_EDIT; if ((t.lflag & okl::t_echo) != 0) m |= KAL_TERM_ECHO; + if ((t.lflag & (okl::t_isig | okl::t_iexten)) == 0 && + (t.iflag & okl::t_ixon) == 0) m |= KAL_TERM_PASS_CONTROL; return m; } @@ -59,12 +70,41 @@ int kal_terminal_set_mode(kal_stream s, kal_uintptr mode) { okl::ktermios t{}; const int rc = get_termios(s, t); if (rc != kal_ok) return rc; + const kal_uintptr in_effect = mode_of(t); if ((mode & KAL_TERM_LINE_EDIT) != 0) t.lflag |= okl::t_icanon; else t.lflag &= ~okl::t_icanon; if ((mode & KAL_TERM_ECHO) != 0) t.lflag |= okl::t_echo; else t.lflag &= ~okl::t_echo; + // A POSITION WHOSE REQUESTED VALUE IS THE ONE IN EFFECT IS NOT WRITTEN, and + // this position is why the specification states the rule: it stands for + // three of the kernel's flags, so establishing it again would settle two + // mechanisms the caller never asked about. A user who had released the + // keystroke that stops output keeps it released while a program turns the + // echo off and back on. + if (((mode ^ in_effect) & KAL_TERM_PASS_CONTROL) != 0) { + if ((mode & KAL_TERM_PASS_CONTROL) != 0) { + t.lflag &= ~(okl::t_isig | okl::t_iexten); + t.iflag &= ~okl::t_ixon; + } else { + t.lflag |= (okl::t_isig | okl::t_iexten); + t.iflag |= okl::t_ixon; + } + } + + // AND A MODE IS NOT A WAY TO END THE INPUT. With line assembly off, how + // long a read waits is decided by VMIN and VTIME rather than by a newline, + // and a terminal left at VMIN=0 by whatever ran before makes + // `kal_stream_read' report zero --- which clause 7.4 says denotes the end + // of the input. The least count is therefore established together with the + // mode that needs it; a caller that wants a read which gives up asks + // `kal_timeout_read' for one. + if ((mode & KAL_TERM_LINE_EDIT) == 0) { + t.cc[okl::v_min] = 1; + t.cc[okl::v_time] = 0; + } + // A position this implementation does not distinguish is ignored rather // than refused, which is what clause 6.2 requires of a word: a program // compiled against a later revision sets a position this build has never diff --git a/tests/conformance_v08.cpp b/tests/conformance_v08.cpp index 71af4e5..c5bc38e 100644 --- a/tests/conformance_v08.cpp +++ b/tests/conformance_v08.cpp @@ -56,6 +56,24 @@ void terminal_section() { check(m.e == kal_ok, "an interactive stream reports its mode"); check(kal::terminal::set_mode(out, m.m) == kal_ok, "the mode that was read can be set back"); + + // THE POSITION VERSION 0.14 ADDED, UPON A TERMINAL THAT EXISTS. This + // implementation distinguishes it, so the answer here is the stronger + // of the two clause 6.2 permits: what was asked for is what is read + // back. Whether the keystroke then arrives as a byte is a question for + // something that can type, which is the pty probe a C environment above + // this implementation runs; what is established here is that the + // position reaches the kernel and that the terminal survives it. + using kal::terminal::pass_control; + const auto wanted = m.m | pass_control; + check(kal::terminal::set_mode(out, wanted) == kal_ok, + "every keystroke can be asked for"); + const auto after = kal::terminal::get_mode(out); + check(after.e == kal_ok && after.m.has(pass_control), + "the position asked for is the position read back"); + check(kal::terminal::set_mode(out, m.m) == kal_ok && + kal::terminal::get_mode(out).m.bits == m.m.bits, + "the mode found is the mode left behind"); } else { check(m.e == kal_err_not_supported, "a stream that is not interactive refuses get_mode");