html lang="en" {
head { HEAD }
body {
header {
div .head {
h1 {-Disabling Terminal Transmission Codes}
}
figure .quote {
blockquote {
p {=
UNIX was not designed to stop its users from doing stupid
things, as that would also stop them from doing clever
things.
}
}
figcaption {-Doug Gwyn}
}
}
main {
h2 #intro {-Introduction}
p {=
As part of my university studies which I have recently
restarted@sup{=MKREF(uni)}, I had to implement the popular dice
game @em{-Yatzy}@sup{=MKREF(yz)}. We were only expected to
implement a basic @code{-print()}/@code{-readline()} interface
since it’s an intro class and everyone is just learning to code
for the first time.
}
p {=
Being the already well-experienced programmer I am, I decided to
take this opportunity to learn something new and decided to
implement a more fully-featured game with a
@a
href="https://en.wikipedia.org/wiki/Curses_%28programming_library%29"
target="_blank"
{-curses UI}
and various keyboard shortcuts for more efficient navigation of
the various game screens.
}
p {=
This post is going to cover how I handled the keyboard shortcuts
@kbd{-Ctrl+Q} and @kbd{-Ctrl+S}, and how it isn’t as trivial as
you might first expect. For the sake of brevity I will be
refering to keychords of the form @kbd{-Ctrl+K} as @kbd{-^K} for
the remainder of this article.
}
hr{}
aside data-ref="REF(uni)" {=
I dropped out of university 3 years ago after loosing interest in
the course I was taking at the time. I’ve made the decision to
continue again so that I can obtain a degree while the
opportunity still presents itself.
}
aside data-ref="REF(yz)" {=
If you’re familiar with the game @em{-Yahtzee} then you might
think this is a typo — it’s not. Yatzy is a very similar game
to Yahtzee that is more commonly played in the nordic countries
(including my current country of residence, Sweden).
}
h2 #hello-world {-A Simple Test}
p {=
In order to better describe the issue at hand, allow me to
provide you with a basic test program in C using the
aforementioned curses API. The following program@sup{=MKREF(cc)}
implements a basic loop where you can enter a key on your
keyboard and the corresponding character is displayed on the
terminal. Since we are interested in keyboard shortcuts
involving the control key we need a way to actually visualize
control characters. Luckily the
@a
href="https://man.openbsd.org/vis.3"
target="_blank"
{-@code{-vis(3)}}@sup{=MKREF(vis)}
family of functions exists to do just that.
}
figure { FMT_CODE(test.c) }
p {=
If you were to run the above example program you would get for
the most part, expected results. Pressing @kbd{-a} renders ‘a’,
and pressing @kbd{-^X} renders ‘\\^X’. There’s an issue though:
try to enter @kbd{-^S} and notice that what happens is …nothing!
Not only does nothing happen but the entire terminal freezes up.
The only way to @em{-unfreeze} the terminal is to hit @kbd{-^Q}.
}
p {=
This is @em{-really} unfortunate because @kbd{-^Q} and @kbd{-^S}
are really useful mnemonic shortcuts: just think of ‘quit’,
‘save’, and other common actions an application might want
shortcuts for.
}
hr{}
aside data-ref="REF(cc)" {=
If you’re interested in building it for yourself, you will
probably need to pass @code{--lcurses} to your compiler to link
with curses.
}
aside data-ref="REF(vis)" {=
Likewise here if you’re on a BSD-like system (including MacOS)
then you should be fine, but Linux users will need to pass the
output of @code{-pkg-config libbsd-overlay} to their compiler,
with both the @code{---cflags} and @code{---libs} flags.
}
h2 #explanation {-What’s Going On?}
p {=
So what’s going on here is in simple terms, a historical relic of
times gone by. It just so happens that the @kbd{-^S} and
@kbd{-^Q} keys correspond to the XOFF and XON terminal
transmission codes, which stand for ‘transmit off’ and ‘transmit
on’. There may still be a modern usecase for these two
transmission codes, but from my surface-level understanding they
were mostly useful in the earlier years of computing where
devices were slower and had to tell input devices to stop sending
data to avoid getting overwhelmed. As a user of a terminal
emulator on a modern personal computer, this is totally useless
functionality.
}
p {=
So now that we know what the problem is, we need to figure out
how to solve it. Luckily for us this is actually a lot easier
than you might expect thanks to two simple functions:
@a
href="https://linux.die.net/man/3/tcgetattr"
target="_blank"
{-@code{-tcgetattr(3)}}
and
@a
href="https://linux.die.net/man/3/tcsetattr"
target="_blank"
{-@code{-tcsetattr(3)}}.
}
p {=
Here’s how we can disable the XOFF/XON codes completely, and
allow the user to input @kbd{-^S} and @kbd{-^Q}:
}
figure { FMT_CODE(disable-xoff-xon.diff) }
p {=
What’s going on here is actually really simple. We first get the
current terminal attributes for the standard input using
@code{-tcgetattr} and store them in a @code{-struct termios}.
This structure (amongst other things) has various bitmasks
describing the terminal modes including input-, output-,
control-, and local-modes. What most of those are is out of the
scope of this article but what we are interested in are the
@em{-input modes}.
}
p {=
The input modes are stored in the @code{-c_iflag} member of the
@code{-struct termios} structure and there are various flags here
that we can toggle which are documented in the
@code{-tcgetattr(3)} manual. The three we’re interested in are
@code{-IXANY}, @code{-IXON}, and @code{-IXOFF}. These flags are
described as such by the aforementioned manual:
}
dl {
dt { code {-IXANY} }
dd {-Typing any character will restart stopped output.}
dt { code {-IXON} }
dd {-Enable XON/XOFF flow control on output.}
dt { code {-IXOFF} }
dd {-Enable XON/XOFF flow control on input.}
}
p {=
In other words… this is all stuff we want to turn off. Since we
are working with bitmasks we can unset the relevant bits by ORing
all the bits together, negating the mask, and doing a bitwise
AND; it’s all standard bitwise operations.
}
p {=
The last thing we need to do is to take our modified attributes
and tell the terminal to use them. This is where
@code{-tcsetattr()} comes into play; we just give it our
attributes and file descriptor and we can now use @kbd{-^S}
without freezing our terminal! The only other thing to note is
the @code{-TCSANOW} flag — it just means that we want our
changes to take effect immediately.
}
h2 #finish {-Finishing Touches}
p {=
What we’ve got here is almost all that we need, but we’re missing
one crucial part: when the user exits our program these settings
will still persist! Chances are that most users will never
realize this, but that doesn’t mean that we shouldn’t be good
engineers and clean up after ourselves. Luckily to do this we
don’t actually need to do anything new, we just need to keep
track of the original attributes when we first queried them:
}
figure { FMT_CODE(enable-xoff-xon.diff) }
p {=
With these simple changes, we can now use @kbd{-^S} and @kbd{-^Q}
as expected in our application but once we exit the application
with @kbd{-^Q} the XON/XOFF functionality of our terminal is
restored and behaves as expected.
}
}
footer { FOOT }
}
}