Reading the keyboard
An OS that reads what you type, a line at a time, and says it back. Type off and it turns the
computer off. This is where your OS starts talking with the hardware both ways: it waits for a
device, and calls one.
What you need
- Your OS disk from Booting your own code, and the mcos disk to write files with.
- A screen on the computer. Right-click its glass to open it: what you type then goes to the computer.
The whole thing
Replace /init.lua on your OS disk with this:
-- /init.lua: an OS that reads a line from the keyboard and says it back, forever. Typing "off"
-- turns the computer off. It shows the two calls every OS is built on: waiting for a device
-- (board.waitForInterrupt) and calling one of its methods (board.invoke).
-- Writes text to every screen. A terminal moves down a row on \n, but only \r goes back to the
-- left, so each \n is sent as \r\n.
local function write(text)
board.invoke("console", "write", (text:gsub("\n", "\r\n")))
end
-- Reads a line: shows what's typed, takes Backspace, ends at Enter.
local function readline(prompt)
write(prompt)
local line = ""
local escape = false -- in the middle of a key's escape sequence (an arrow key sends ESC [ A)
while true do
-- Sleep until a key is pressed: the console's interrupt goes up when it has keys.
board.waitForInterrupt(nil, "console")
-- Take what was typed: a string of bytes, or nil if someone else took it first.
local ok, keys = board.invoke("console", "read")
if ok and keys then
for i = 1, #keys do
local key = keys:sub(i, i)
local byte = key:byte()
if escape then
escape = not key:match("[%a~]") -- a sequence ends at a letter or ~
elseif byte == 27 then
escape = true -- ESC: a key this OS doesn't use (arrows, F-keys): skipped
elseif key == "\r" then
write("\n")
return line
elseif byte == 127 or byte == 8 then
if #line > 0 then
line = line:sub(1, -2)
write("\b \b") -- back, blank it out, back again
end
elseif byte >= 32 then
line = line .. key
write(key) -- the screen shows nothing by itself: the OS echoes what you type
end
end
end
end
end
write("Type something. Type off to turn the computer off.\n")
while true do
local line = readline("> ")
if line == "off" then
board.poweroff()
end
write("you typed: " .. line .. "\n")
end
Boot it (your disk first in the bus order) and type:
Type something. Type off to turn the computer off.
> hello
you typed: hello
> off
The computer turns off. Typing mistakes can be rubbed out with Backspace; the arrow keys do nothing.
How it works
The screen shows nothing by itself
A terminal is two devices in one, like a real VT100 on a serial line: a screen
that shows what’s written to it, and a keyboard whose keys wait in the terminal until a program reads
them. When you press a key, nothing appears. The letter appears only because the OS writes it back
(write(key)). That’s called echo, and every OS does it: in mcos, the kernel’s terminal driver
does.
Sleeping until a key comes
board.waitForInterrupt(nil, "console")
The computer stops here and draws only its waiting power, until the console has keys. nil means
no time limit. This is an interrupt: a device with news raises its line,
and the waiting computer wakes. Interrupts are level-triggered: the line stays up while the keys
are waiting, so the call returns at once if you typed while the OS was busy. Nothing is lost.
Taking the keys
local ok, keys = board.invoke("console", "read")
Every call to a device answers first with whether it worked. Then read gives what was typed since
the last read, as one string of bytes (several keys, if you typed fast), or nil when there’s
nothing. The loop handles each byte:
| You press | The byte(s) | The OS |
|---|---|---|
| a letter, a digit, space… | the character | adds it to the line, and echoes it |
| Enter | "\r" (13) |
ends the line |
| Backspace | 127 | removes the last character, and writes "\b \b": back a column, a space over the letter, back again |
| an arrow key, F1… | ESC (27), then [ and more, ending in a letter or ~ |
skips the whole sequence |
So readline builds the line in a string, and returns it at Enter. The main loop at the bottom reads
a line, turns the computer off if it’s off, and otherwise writes it back. It never ends: an OS
that returns makes the CPU abort.
write and new lines
write turns every \n into \r\n, so the rest of the code can write "\n" as usual. A terminal
moves down a row on \n but goes back to the left only on \r.
When it goes wrong
- Nothing appears when you type: check that you’re typing into the screen (its glass right-clicked open), and
that the OS reached its prompt (
>). ABRT: init.lua:...: an error while it ran: the message says the line. A typo in a name (board.invok) shows here, not at boot, because Lua finds it only when that line runs.
Next: A command shell.