Skip to main content

Ralsina.Me — Roberto Alsina's website

CobraPy: bits and pieces

As it hap­pens in ear­ly stages in fun prod­uct­s, progress in Co­braPy has been both faster and slow­er than ex­pect­ed.

In the past few days a num­ber of things hap­pened:

I made that terminal a whole lot nicer

I al­ready had a ter­mi­nal but I fixed a num­ber of things.

  • The key­board han­dling is much bet­ter, it now rec­og­nizes pret­ty much all keys, which is al­ways a nice thing.
  • It sor­ta sup­ports things like "á" via mod­e-switch (no dead­keys sup­port prob­a­bly ev­er)
  • It has 24-bit col­or sup­port! I did­n't know ter­mi­nals could sup­port that kind of thing!

I added a graphics protocol to it!

Ter­mi­nals with graph­ics sup­port have a very long tra­di­tion. This is a VT55, re­leased in 1977 dis­play­ing graph­ic­s:

How did it work? Well you can read the pro­gram­mer's man­u­al if you wan­t, but ba­si­cal­ly you sent a con­trol se­quence that put it in "graph­ics mod­e" and then sent com­mands de­scrib­ing what to dis­play.

Sim­i­lar ideas with dif­fer­ent pro­to­col de­tails were used in many dif­fer­ent fu­ture ter­mi­nal­s, in­clud­ing ReG­IS graph­ics and Tek­tron­ix vec­tor graph­ics and you could even trace this all the way to a cur­rent Lin­ux desk­top's X11 graph­ic­s.

So, what did I do? Not that, ex­act­ly. I am cre­at­ing a side-chan­nel as a sort-of-R­PC where you send se­ri­al­ized python method names and ar­gu­ments.

I wrote a Python REPL

I want­ed an in­ter­ac­tive mode that was slight­ly friend­lier than Python comes with, but not some­thing over­whelm­ing and pow­er­ful like IPython or BPython.

I did some re­search, and found pt­python which is pret­ty awe­some, but still a bit too much awe­some.

And then I start­ed on a much, much lamer ver­sion of it. Still em­bri­on­ic, but it does work. I have some plans for it.

I learned a lot more about Raylib

All the graph­ics and ba­si­cal­ly ev­ery­thing you see in this project is done us­ing the awe­some raylib and a home­grown CF­FI bind­ing for it. I was not us­ing it right, now I use it bet­ter, and things that took sev­er­al hun­dredth sec­onds now take a few dozen mi­crosec­ond­s.

I integrated the whole thing, sorta

So, I in­te­grat­ed it enough that you can start the ter­mi­nal, launch the RE­PL in it, and use the graph­ics pro­to­col to draw some­thing!

What next

Now comes a round of in­te­gra­tion, cleanup and op­ti­miza­tion.

  • Work­ing code needs to be re­or­ga­nized
  • The ter­mi­nal us­es 66% of a CPU core, which is not ac­cept­able, but there's tons of low hang­ing fruit there.
  • Graph­ics pro­to­col needs to be able to do more things so it's in­ter­est­ing

Af­ter that will come a new round of fea­ture work, and so on for the next ... 10 years? If it goes well?

Here, have a terminal

Co­braPy is in large part about rein­vent­ing wheel­s. We do, af­ter al­l, have per­fect­ly fine 80s-style de­vel­op­ment en­vi­ron­ments in the mil­lions of 80s com­put­ers float­ing around, as well as em­u­la­tors, things like the Maximite and so on.

How­ev­er, I on­ly want to rein­vent fun wheel­s, so I am not do­ing a text ed­i­tor. And prob­a­bly gonna hack a ter­mi­nal-based RE­PL. And there's no way I am do­ing my own wifi con­fig tool (again) so, I should have a reg­u­lar, or­di­nary ter­mi­nal.

But the things I want that ter­mi­nal to do are ... un­usu­al, like raster graph­ic­s, and sprites. So ... why not do my own ter­mi­nal for this?

It's not the first ter­mi­nal I write! I wrote a dumb ter­mi­nal em­u­la­tor around 1998 and I wrote a very ba­sic one us­ing python, pygame and pyte a few months ago. It's on video!

But this time I want­ed a bet­ter one, since I want this to look good. So I went and wrote one.

It us­es many 3rd par­ty things, be­cause life is short.

  • Pyte for all the nit­ty grit­ty of ter­mi­nal em­u­la­tion
  • raylib for all the graph­ics
  • mon­oid is the font
  • This page helped me with things like "what was the es­cape code for left­????"

And it looks this nice:

Yes, NICE, Stu­ar­t.

This is why you don't run random stuff.

Yes­ter­day I was try­ing to fig­ure ou­ut some ob­scure in­put things in Co­braPy such as "de­tect­ing the En­ter key" and ran in­to a love­ly pack­age in PyP­I: Pym­put

What does it do? It lets you in­ject and read in­put events.

And by in­put events I mean mouse and­key­board.

And it does so in your whole ses­sion.

And you would­n't no­tice if it was do­ing that.

And it does this in 20 lines of friend­ly python code. Here, have a key­board snif­fer:

from pynput import keyboard

def on_press(key):
    try:
        print('alphanumeric key {0} pressed'.format(
            key.char))
    except AttributeError:
        print('special key {0} pressed'.format(
            key))

def on_release(key):
    print('{0} released'.format(
        key))
    if key == keyboard.Key.esc:
        # Stop listener
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

This is one of the rea­sons why Way­land (or Mir, I re­mem­ber Mir!) needs to hap­pen. It's triv­ial for any desk­top app to mon­i­tor ev­ery­thing you do. Of course nowa­days you al­so will see soft­ware ad­ver­tis­ing this, as a "fea­ture" where it's used to "mon­i­tor em­ploy­ee pro­duc­tiv­i­ty".

Be­cause re­mem­ber, of­ten things are on­ly il­le­gal when in­di­vid­u­als do them, if you are a com­pa­ny and are charg­ing for it, then Bob's your un­cle.

Side Projects have Projects as Side Effects

I went and did a thing. But why, why?

When I start­ed play­ing with the idea of do­ing a sort-of-retro-80s-pro­gram­ming-en­vi­ron­ment called Co­braPy I sus­pect­ed one of the prob­lems would be find­ing a graph­ics li­brary that did what I want­ed and had good per­for­mance.

I have used PyGame a lit­tle in the past but nev­er liked it too much, so I tried Py­glet and it was work­ing great ... un­til I tried to add some­thing as sim­ple as a wid­get to en­ter tex­t. It does­n't re­al­ly do wid­get­s, for that you ap­par­ent­ly use glooey

But glooey is ... I don't like it. And then I ran into a bug where you can either clean the contents of an EditableLabel and set it to focused without it crashing deep in its bowels. I worked around it by using fake mouse events to focus. And then noticed you can't type a double quote in one of those widgets. So, I got pissed.

It lit­er­al­ly took me less time to find a nice C game li­brary, read a tu­to­ri­al on CF­FI, do min­i­mal ed­its to its head­er file, learn how to use in­voke and write my first pro­gram us­ing it than I wast­ed chas­ing that glooey bug.

And now, the ques­tion­s:

  • Does it work well? How could I know, I on­ly wrote the bind­ing, I have not used it yet!
  • Will I main­tain it? I don't think it re­quires much main­tain­ing.
  • Will I re­lease it? Maybe. It's here, mixed up with oth­er stuff

... aaaaand some­one else had al­ready done it. http­s://pyp­i.org/pro­jec­t/raylib/ But that's raylib 2.6, cur­rent is 3.0 :-)

CobraPy: baby steps

Some con­text: I am do­ing a sort of mod­ern retro-pro­gram­ming en­vi­ron­men­t-thingie.

So ... I caved. I caved and start­ed typ­ing.

It's still just ex­plorato­ry, fool­ing around, but I want­ed to see whether the vague ideas I had about how this could, maybe work were right, and hey, they are!

Text Mode

I want­ed a text mod­e. Some­thing with N rows and M col­umns where you could just say "put this text there". Done. In the most ba­sic pos­si­ble way that work­s.

Implemented a print_at function that takes row, column and text, and does what it says in the label.

Immediate Graphics Mode

In these old com­put­ers you could just draw in the graph­ics mem­o­ry. In 2020 ... not so much. So, I im­ple­ment­ed a sort of off­screen buf­fer. You draw there, and it gets blit­ted a lot to screen. Ends up work­ing like a flick­er-free dou­ble-buffered screen! Nice!

Implemented ellipse which ... draws an ellipse. I know, surprising.

Interpreter / Prompt

It would not be 80s-­like if you could not do those things in­ter­ac­tive­ly. So, yes, you can.

Now that was in­ter­est­ing. you see, nowa­days you are sup­posed to have an event loop. The com­put­er is there, idling and when some­thing hap­pen­s, then it does some­thing. So, you clicked? It re­act­s. 10 mil­lisec­onds passed? It does its thing.

In the 80s it was not like that. Your pro­gram start­ed and it owned the ma­chine. It went through your code, run­ning it as fast as it could with its lit­tle two-mega­herz-­heart un­til it ran out of code and then it was done.

So, how to make it act like that?

  • Start the event loop.
  • Start a sec­ond thread with a promp­t.
  • Have the us­er in­ter­act with the promp­t.
  • When the user presses Enter ... send the command to the event loop via a queue.
  • When the event ar­rives, do the thing.

And yes, it work­s! It's prob­a­bly hor­ri­ble for per­for­mance, but it does work just fine, and it's much, much eas­i­er to make a work­ing pro­gram fast than it is to make a fast pro­gram work.

So, what's nex­t?

The Future

I may try to in­te­grate the prompt and the win­dow where the text and such is dis­played (right now it's in a ter­mi­nal) ... maybe sprites.

Af­ter that:

  • Make the API nice and use­ful (a few weeks of work)
  • Make the thing run well (a few days of work)
  • Do the jan­i­to­ri­al work to make it us­able by some­one else (a cou­ple days of work)
  • Sup­port it, doc­u­ment it, etc (8 to 30 years of work)

The Nerdy Details

  • To do the graph­ics I am us­ing py­glet
  • To do off­screen draw­ing I am us­ing Pil­low
  • Tool­ing in­cludes the usu­al sus­pect­s: Python 3.8, po­et­ry, pylin­t, flake8
  • Code (all 50 lines or so!) in GitHub

Yes, this means this "80s" thing is ac­tu­al­ly mul­ti­thread­ed hard­ware-ac­cel­er­at­ed OpenGL graph­ic­s. I know, I know.


Contents © 2000-2023 Roberto Alsina