summaryrefslogtreecommitdiffhomepage
path: root/src/blog/termios/index.gsp
blob: d1f4e48662ce6f41ec5d8f100328c3d2029a1bdd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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 }
	}
}