Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand Down
19 changes: 16 additions & 3 deletions src/sys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 --------------------------------------
//
Expand Down
40 changes: 40 additions & 0 deletions src/terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions tests/conformance_v08.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading