From 860ceeb6f53e82110036ea23477dff5a200e3ee7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 20 Sep 2026 16:34:58 +0800 Subject: [PATCH] 0.14.0 --- the keystrokes this kernel keeps for itself are a position now openkal 0.14 adds KAL_TERM_PASS_CONTROL, and this implementation reads and writes it. * THE POSITION IS THREE FLAGS AND NOT ISIG. It states that the environment reserves NO keystroke, so it is reported 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. An implementation mapping ISIG alone would leave one mode word meaning two things on two kernels --- the keystroke that stops output would still be swallowed here and not on a console. * A POSITION WHOSE REQUESTED VALUE IS THE ONE IN EFFECT IS NOT WRITTEN. The specification states the rule beside set_mode and this position is why: it stands for three 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. * 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; a terminal left at VMIN=0 by whatever ran before made kal_stream_read report zero, which clause 7.4 says denotes the end of the input. The least count is established with the mode that needs it. Measured: the conformance suite of openkal-0.14, run upon a pseudo-terminal, reports 199 held and 0 did not hold; this package's own tests pass, and the terminal section observes the position round-tripping where it used to have nothing to observe. --- README.md | 4 ++-- mcpp.toml | 4 ++-- src/sys.h | 19 ++++++++++++++++--- src/terminal.cpp | 40 +++++++++++++++++++++++++++++++++++++++ tests/conformance_v08.cpp | 18 ++++++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) 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");