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     81 4/subop/and %eax 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 grapheme in utf-8 (only up to 4 bytes so far)
    161 print-grapheme-to-real-screen:  # c: grapheme
    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-grapheme-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-grapheme-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-grapheme-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-grapheme-to-real-screen:end/disp8
    195     #
    196     (print-byte-to-real-screen %eax)
    197 $print-grapheme-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