https://github.com/akkartik/mu/blob/main/linux/304screen.subx
  1 # Primitives for screen control.
  2 # Require Linux and a modern terminal.
  3 
  4 == code
  5 
  6 enable-screen-grid-mode:
  7     # . prologue
  8     55/push-ebp
  9     89/<- %ebp 4/r32/esp
 10     #
 11     (flush Stdout)
 12     (flush Stderr)
 13     # switch to second screen buffer
 14     (write 1 Esc)
 15     (write 1 "[?1049h")
 16     #
 17     (clear-real-screen)
 18 $enable-screen-grid-mode:end:
 19     # . epilogue
 20     89/<- %esp 5/r32/ebp
 21     5d/pop-to-ebp
 22     c3/return
 23 
 24 enable-screen-type-mode:
 25     # . prologue
 26     55/push-ebp
 27     89/<- %ebp 4/r32/esp
 28     # switch to first screen buffer
 29     (write 1 Esc)
 30     (write 1 "[?1049l")
 31 $enable-screen-type-mode:end:
 32     # . epilogue
 33     89/<- %esp 5/r32/ebp
 34     5d/pop-to-ebp
 35     c3/return
 36 
 37 real-screen-size:  # -> nrows/eax: int, ncols/ecx: int
 38     # . prologue
 39     55/push-ebp
 40     89/<- %ebp 4/r32/esp
 41     # . save registers
 42     52/push-edx
 43     53/push-ebx
 44     56/push-esi
 45     57/push-edi
 46     #
 47     (_maybe-open-terminal)
 48     # var window-size-info/esi: (addr winsize)
 49     # winsize is a type from the Linux kernel. We don't care how large it is.
 50     81 5/subop/subtract %esp 0x40/imm32
 51     89/<- %esi 4/r32/esp
 52     # ioctl(*Terminal-file-descriptor, TIOCGWINSZ, window-size-info)
 53     89/<- %edx 6/r32/esi
 54     b9/copy-to-ecx 0x5413/imm32/TIOCGWINSZ
 55     8b/-> *Terminal-file-descriptor 3/r32/ebx
 56     e8/call syscall_ioctl/disp32
 57     # some bitworking to extract 2 16-bit shorts
 58     8b/-> *esi 0/r32/eax
 59     25/and-eax-with 0xffff/imm32
 60     8b/-> *esi 1/r32/ecx
 61     c1/shift 5/subop/logical-right %ecx 0x10/imm8
 62 $real-screen-size:end:
 63     # . reclaim locals
 64     81 0/subop/add %esp 0x40/imm32
 65     # . restore registers
 66     5f/pop-to-edi
 67     5e/pop-to-esi
 68     5b/pop-to-ebx
 69     5a/pop-to-edx
 70     # . epilogue
 71     89/<- %esp 5/r32/ebp
 72     5d/pop-to-ebp
 73     c3/return
 74 
 75 clear-real-screen:
 76     # . prologue
 77     55/push-ebp
 78     89/<- %ebp 4/r32/esp
 79     #
 80     (write 1 Esc)
 81     (write 1 "[H")
 82     (write 1 Esc)
 83     (write 1 "[2J")
 84 $clear-real-screen:end:
 85     # . epilogue
 86     89/<- %esp 5/r32/ebp
 87     5d/pop-to-ebp
 88     c3/return
 89 
 90 # row and col count from the top-left as (1, 1)
 91 move-cursor-on-real-screen:  # row: int, column: int
 92     # . prologue
 93     55/push-ebp
 94     89/<- %ebp 4/r32/esp
 95     # . save registers
 96     51/push-ecx
 97     # var buf/ecx: (stream byte 32)
 98     81 5/subop/subtract %esp 0x20/imm32
 99     68/push 0x20/imm32/size
100     68/push 0/imm32/read
101     68/push 0/imm32/write
102     89/<- %ecx 4/r32/esp
103     # construct directive in buf
104     (write %ecx Esc)
105     (write %ecx "[")
106     (write-int32-decimal %ecx *(ebp+8))
107     (write %ecx ";")
108     (write-int32-decimal %ecx *(ebp+0xc))
109     (write %ecx "H")
110     # flush
111     (write-stream 2 %ecx)
112 $move-cursor-on-real-screen:end:
113     # . reclaim locals
114     81 0/subop/add %esp 0x2c/imm32
115     # . restore registers
116     59/pop-to-ecx
117     # . epilogue
118     89/<- %esp 5/r32/ebp
119     5d/pop-to-ebp
120     c3/return
121 
122 print-string-to-real-screen:  # s: (addr array byte)
123     # . prologue
124     55/push-ebp
125     89/<- %ebp 4/r32/esp
126     #
127     (write 1 *(ebp+8))
128 $print-string-to-real-screen:end:
129     # . epilogue
130     89/<- %esp 5/r32/ebp
131     5d/pop-to-ebp
132     c3/return
133 
134 print-slice-to-real-screen:  # s: (addr slice)
135     # . prologue
136     55/push-ebp
137     89/<- %ebp 4/r32/esp
138     #
139     (write-slice-buffered Stdout *(ebp+8))
140     (flush Stdout)
141 $print-slice-to-real-screen:end:
142     # . epilogue
143     89/<- %esp 5/r32/ebp
144     5d/pop-to-ebp
145     c3/return
146 
147 print-stream-to-real-screen:  # s: (addr stream byte)
148     # . prologue
149     55/push-ebp
150     89/<- %ebp 4/r32/esp
151     #
152     (write-stream-data Stdout *(ebp+8))
153     (flush Stdout)
154 $print-stream-to-real-screen:end:
155     # . epilogue
156     89/<- %esp 5/r32/ebp
157     5d/pop-to-ebp
158     c3/return
159 
160 # print a code-point-utf8 in utf-8 (only up to 4 bytes so far)
161 print-code-point-utf8-to-real-screen:  # c: code-point-utf8
162     # . prologue
163     55/push-ebp
164     89/<- %ebp 4/r32/esp
165     # . save registers
166     50/push-eax
167     # var curr/eax: byte = 0
168     b8/copy-to-eax 0/imm32
169     # curr = *(ebp+8)
170     8a/byte-> *(ebp+8) 0/r32/al
171     # if (curr == 0) return
172     3d/compare-eax-and 0/imm32
173     74/jump-if-= $print-code-point-utf8-to-real-screen:end/disp8
174     #
175     (print-byte-to-real-screen %eax)
176     # curr = *(ebp+9)
177     8a/byte-> *(ebp+9) 0/r32/al
178     # if (curr == 0) return
179     3d/compare-eax-and 0/imm32
180     74/jump-if-= $print-code-point-utf8-to-real-screen:end/disp8
181     #
182     (print-byte-to-real-screen %eax)
183     # curr = *(ebp+10)
184     8a/byte-> *(ebp+0xa) 0/r32/al
185     # if (curr == 0) return
186     3d/compare-eax-and 0/imm32
187     74/jump-if-= $print-code-point-utf8-to-real-screen:end/disp8
188     #
189     (print-byte-to-real-screen %eax)
190     # curr = *(ebp+11)
191     8a/byte-> *(ebp+0xb) 0/r32/al
192     # if (curr == 0) return
193     3d/compare-eax-and 0/imm32
194     74/jump-if-= $print-code-point-utf8-to-real-screen:end/disp8
195     #
196     (print-byte-to-real-screen %eax)
197 $print-code-point-utf8-to-real-screen:end:
198     # . restore registers
199     58/pop-to-eax
200     # . epilogue
201     89/<- %esp 5/r32/ebp
202     5d/pop-to-ebp
203     c3/return
204 
205 print-byte-to-real-screen:  # c: byte
206     # . prologue
207     55/push-ebp
208     89/<- %ebp 4/r32/esp
209     # . save registers
210     51/push-ecx
211     # var s/ecx: (addr array byte)
212     ff 6/subop/push *(ebp+8)
213     68/push 1/imm32/size
214     89/<- %ecx 4/r32/esp
215     (write 1 %ecx)
216 $print-byte-to-real-screen:end:
217     # . reclaim locals
218     81 0/subop/add %esp 8/imm32
219     # . restore registers
220     59/pop-to-ecx
221     # . epilogue
222     89/<- %esp 5/r32/ebp
223     5d/pop-to-ebp
224     c3/return
225 
226 print-int32-hex-to-real-screen:  # n: int
227     # . prologue
228     55/push-ebp
229     89/<- %ebp 4/r32/esp
230     #
231     (write-int32-hex-buffered Stdout *(ebp+8))
232     (flush Stdout)
233 $print-int32-hex-to-real-screen:end:
234     # . epilogue
235     89/<- %esp 5/r32/ebp
236     5d/pop-to-ebp
237     c3/return
238 
239 print-int32-hex-bits-to-real-screen:  # n: int, bits: int
240     # . prologue
241     55/push-ebp
242     89/<- %ebp 4/r32/esp
243     #
244     (write-int32-hex-bits-buffered Stdout *(ebp+8) *(ebp+0xc) *(ebp+0x10))
245     (flush Stdout)
246 $print-int32-hex-bits-to-real-screen:end:
247     # . epilogue
248     89/<- %esp 5/r32/ebp
249     5d/pop-to-ebp
250     c3/return
251 
252 print-int32-decimal-to-real-screen:  # n: int
253     # . prologue
254     55/push-ebp
255     89/<- %ebp 4/r32/esp
256     #
257     (write-int32-decimal-buffered Stdout *(ebp+8))
258     (flush Stdout)
259 $print-int32-decimal-to-real-screen:end:
260     # . epilogue
261     89/<- %esp 5/r32/ebp
262     5d/pop-to-ebp
263     c3/return
264 
265 write-int32-decimal-buffered:  # f: (addr buffered-file), n: int
266     # . prologue
267     55/push-ebp
268     89/<- %ebp 4/r32/esp
269     # . save registers
270     51/push-ecx
271     # var ecx: (stream byte 16)
272     81 5/subop/subtract %esp 0x10/imm32
273     68/push 0x10/imm32/size
274     68/push 0/imm32/read
275     68/push 0/imm32/write
276     89/<- %ecx 4/r32/esp
277     (write-int32-decimal %ecx *(ebp+0xc))
278     (write-stream-data *(ebp+8) %ecx)
279 $write-int32-decimal-buffered:end:
280     # . reclaim locals
281     81 0/subop/add %esp 0x1c/imm32
282     # . restore registers
283     59/pop-to-ecx
284     # . epilogue
285     89/<- %esp 5/r32/ebp
286     5d/pop-to-ebp
287     c3/return
288 
289 reset-formatting-on-real-screen:
290     # . prologue
291     55/push-ebp
292     89/<- %ebp 4/r32/esp
293     #
294     (write 1 Esc)
295     (write 1 "(B")
296     (write 1 Esc)
297     (write 1 "[m")
298 $reset-formatting-on-real-screen:end:
299     # . epilogue
300     89/<- %esp 5/r32/ebp
301     5d/pop-to-ebp
302     c3/return
303 
304 start-color-on-real-screen:  # fg: int, bg: int
305     # . prologue
306     55/push-ebp
307     89/<- %ebp 4/r32/esp
308     # . save registers
309     51/push-ecx
310     # var buf/ecx: (stream byte 32)
311     81 5/subop/subtract %esp 0x20/imm32
312     68/push 0x20/imm32/size
313     68/push 0/imm32/read
314     68/push 0/imm32/write
315     89/<- %ecx 4/r32/esp
316     # construct directive in buf
317     # . set fg
318     (write %ecx Esc)
319     (write %ecx "[38;5;")
320     (write-int32-decimal %ecx *(ebp+8))
321     (write %ecx "m")
322     # . set bg
323     (write %ecx Esc)
324     (write %ecx "[48;5;")
325     (write-int32-decimal %ecx *(ebp+0xc))
326     (write %ecx "m")
327     # flush
328     (write-stream 2 %ecx)
329 $start-color-on-real-screen:end:
330     # . reclaim locals
331     81 0/subop/add %esp 0x2c/imm32
332     # . restore registers
333     59/pop-to-ecx
334     # . epilogue
335     89/<- %esp 5/r32/ebp
336     5d/pop-to-ebp
337     c3/return
338 
339 start-bold-on-real-screen:
340     # . prologue
341     55/push-ebp
342     89/<- %ebp 4/r32/esp
343     #
344     (write 1 Esc)
345     (write 1 "[1m")
346 $start-bold-on-real-screen:end:
347     # . epilogue
348     89/<- %esp 5/r32/ebp
349     5d/pop-to-ebp
350     c3/return
351 
352 start-underline-on-real-screen:
353     # . prologue
354     55/push-ebp
355     89/<- %ebp 4/r32/esp
356     #
357     (write 1 Esc)
358     (write 1 "[4m")
359 $start-underline-on-real-screen:end:
360     # . epilogue
361     89/<- %esp 5/r32/ebp
362     5d/pop-to-ebp
363     c3/return
364 
365 start-reverse-video-on-real-screen:
366     # . prologue
367     55/push-ebp
368     89/<- %ebp 4/r32/esp
369     #
370     (write 1 Esc)
371     (write 1 "[7m")
372 $start-reverse-video-on-real-screen:end:
373     # . epilogue
374     89/<- %esp 5/r32/ebp
375     5d/pop-to-ebp
376     c3/return
377 
378 # might require enabling blinking in your terminal program
379 start-blinking-on-real-screen:
380     # . prologue
381     55/push-ebp
382     89/<- %ebp 4/r32/esp
383     #
384     (write 1 Esc)
385     (write 1 "[5m")
386 $start-blinking-on-real-screen:end:
387     # . epilogue
388     89/<- %esp 5/r32/ebp
389     5d/pop-to-ebp
390     c3/return
391 
392 hide-cursor-on-real-screen:
393     # . prologue
394     55/push-ebp
395     89/<- %ebp 4/r32/esp
396     #
397     (write 1 Esc)
398     (write 1 "[?25l")
399 $hide-cursor-on-real-screen:end:
400     # . epilogue
401     89/<- %esp 5/r32/ebp
402     5d/pop-to-ebp
403     c3/return
404 
405 show-cursor-on-real-screen:
406     # . prologue
407     55/push-ebp
408     89/<- %ebp 4/r32/esp
409     #
410     (write 1 Esc)
411     (write 1 "[?12l")
412     (write 1 Esc)
413     (write 1 "[?25h")
414 $show-cursor-on-real-screen:end:
415     # . epilogue
416     89/<- %esp 5/r32/ebp
417     5d/pop-to-ebp
418     c3/return
419 
420 # This is a low-level detail; I don't think everything should be a file.
421 #
422 # Open "/dev/tty" if necessary and cache its file descriptor in Terminal-file-descriptor
423 # where later primitives can use it.
424 _maybe-open-terminal:
425     81 7/subop/compare *Terminal-file-descriptor -1/imm32
426     75/jump-if-!= $_maybe-open-terminal:epilogue/disp8
427     # . save registers
428     50/push-eax
429     51/push-ecx
430     53/push-ebx
431     # open("/dev/tty", O_RDWR)
432     bb/copy-to-ebx Terminal-filename/imm32
433     b9/copy-to-ecx 2/imm32/O_RDWR
434     e8/call syscall_open/disp32
435     89/<- *Terminal-file-descriptor 0/r32/eax
436 $_maybe-open-terminal:end:
437     # . restore registers
438     5b/pop-to-ebx
439     59/pop-to-ecx
440     58/pop-to-eax
441 $_maybe-open-terminal:epilogue:
442     c3/return
443 
444 == data
445 
446 Terminal-file-descriptor:  # (addr int)
447   -1/imm32
448 
449 Esc:  # (addr array byte)
450   # size
451   1/imm32
452   # data
453   0x1b
454 
455 Terminal-filename:  # (addr kernel-string)
456   # "/dev/tty"
457   2f/slash 64/d 65/e 76/v 2f/slash 74/t 74/t 79/y 0/nul
458   # on Linux console
459 #?   # "/dev/console"
460 #?   2f/slash 64/d 65/e 76/v 2f/slash 63/c 6f/o 6e/n 73/s 6f/o 6c/l 65/e 0/nul