Plan 9 bouncing ball demo

Where to start writing apps for Plan 9 from Bell Labs? Most of the documentation around focuses on the shell and the file system. The tutorial explains how to write a simple bouncing ball app for the Plan 9 UI.

First, install either the official Plan 9 distribution or the community fork 9front. If you are only interested in experimenting with the Plan 9 GUI, I would recommend 9front. However if you intend on understanding Plan 9 as an operating systems research project in the way that it was intended then start with the official distribution, set up the prerequisite file, CPU and auth servers and go from there.

Let’s create a simple bouncing ball demo with an exit menu to demonstrate drawing graphics directly and also use the builtin Plan 9 UI functions. Not as impressive as the canonical bouncing ball demo, but it’s a start.

In either Sam or Acme, create a new file named ball.c. Add these headers:

#include <u.h>

#include <libc.h>

#include <draw.h>

#include <event.h>

The first two are standard headers for writing in the Plan 9 dialect of C instead of ANSI C, which use a pragma directive to also provide the necessary parameters to the linker. The headers draw.h and event.h both include the libdraw and libevent. In the main method we will initiate drawing on the current window as well as register our interest in mouse events for the window:

/* Initiate graphics and mouse */
    
if(initdraw(nil, nil, "bouncing ball demo") < 0) {
  sysfatal("initdraw failed: %r");
}

einit(Emouse);

In Rio, the Plan 9 window manager, we do not create a new window but instead take over the terminal window that our program was spawned from. Just as the underlying shell forked, we take over the graphical context of the parent process.

Libevent requires that we implement a method called eresized to handle when the window has been resized or the window is newly created. If this is the first time the program is run then it will need to connect to the display using getwindow() and then clear the screen by painting it white.

void
eresized(int new)
{
  if(new && getwindow(display, Refnone) &lt; 0)
    sysfatal("can't reattach to window");

  /* Store new screen coordinates for collision detection */
  p = Pt(Dx(screen->r), Dy(screen->r));

  /* Draw the background DWhite */
  draw(screen, 
    insetrect(screen->r, borderWidth), 
    allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DWhite), 
    nil, ZP);
}

Call this method by adding eresized(0);. The windowing system Rio will call this function for subsequent window resizes.

So look at what we have done so far: we have created a new blank window by attaching to the current terminal window, specified a mouse handler and painted the terminal white. Now we can draw our ball. First set up a timer to move the ball every 5 milliseconds unless there is an Emouse event.

timer = etimer(0, 5);

Create a method initball() that allocates an image as a brush, and image to draw on and then an ellipse:

Image *brush;
brush=allocimage(display, Rect(0,0,1,1), CMAP8, 1, DRed);
ball=allocimage(display, (Rectangle){(Point){0,0},(Point){r*4,r*4}},
screen->chan, 0, DWhite);
fillellipse(ball, (Point){r*2,r*2}, r, r, brush, ZP);

Also, we will need a method moveball() to move the ball. Plan 9 has supported UTF-8 since the very beginning we can use symbols such as the greek letter delta to denote change.

static Point bp={6, 6}; /* Ball Position */
static double Δi=4, Δj=4;

/* Collision detection */
if(bp.x &gt; p.x - (r*3) || bp.x &lt; -r) Δi = Δi*-1;
if(bp.y &gt; p.y - (r*3) || bp.y &lt; -r) Δj = Δj*-1;

/* Increment ball position */
bp.x = bp.x + Δi;
bp.y = bp.y + Δj;

draw(screen, rectaddpt(screen->r, bp), ball, nil, ZP);

Now add an event loop that will either move the ball if there are no mouse events, or will handle the mouse event:

char *buttons[] = {"exit", 0};
Menu menu = { buttons };

for(;;)
{
  e = event(&ev);

  /* If there is a mouse event, the rightmost button
   * pressed and the first menu option selected
   * then exit.. */

 if( (e == Emouse) &&
    (ev.mouse.buttons & 4) && 
    (emenuhit(3, &ev.mouse, &menu) == 0)) exits(nil);
  else 
    if(e == timer)
      moveball();
}

Putting this all together gives the bouncing ball demo!

There are a number of ways this program can be improved:

Source: https://github.com/nspool/hello-plan9