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 } } }