Although rio is fairly simple, the job of a window manager rectangle multiplexor1 involves quite a few independent parts. Let's start with something simpler!
These are all the data structures, functions and variables we are going to implement during this first iteration:
void resized(void);
void mousethread(void*);
void initstyle(void);
void usage(void);
void threadmain(int, char*[]);
int abortonerror;
Channel* exitchan;
Mousectl* mousectl;
Screen* wscreen;
Image* riaback;
int basecolhex;
I promise we will cover all of them — eventually.
Here you have some helper functions which we will be using:
void
error(char err[])
{
fprint(2, "%s: %s: %r\n", argv0, err);
if(abortonerror)
abort();
threadexitsall("error");
}
Image*
eallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong col)
{
Image *i;
i = allocimage(d, r, chan, repl, col);
if(i == nil)
error("can't allocate image");
return i;
}
Let's start with defining our main function. ria will make use of libthread (more on that later), so instead of calling it main
we have to call it threadmain
. We will also define an usage
function, to be called if the parameters passed are incorrect. Nothing fancy yet:
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
enum
{
STACK = 8192,
};
int abortonerror;
Mousectl* mousectl;
Channel* exitchan;
void
usage(void)
{
fprint(2, "usage: %s\n", argv0);
exits("usage");
}
void
threadmain(int argc, char *argv[])
{
ARGBEGIN{
default:
usage();
}ARGEND
if(argc > 0)
usage();
}
The first step is to setup libdraw and the mouse.
if(geninitdraw(nil, nil, nil, argv0, nil, Refnone) < 0)
error("can't open display");
mousectl = initmouse(nil, screen);
if(mousectl == nil)
error("can't open mouse");
Usually, you would use the initdraw
function instead of the more explicit geninitdraw
. However, rio uses the latter (presumably to set the refresh function to Refnone
.
The next step is initializing all the resources needed to style ria. In this example, all of them are plain colors (1×1 images with the repl
bit set), but as far as libdraw is concerned, they are tiled bitmaps.
Image* riaback;
int basecolhex;
void
initstyle(void)
{
Rectangle r₁;
r₁ = Rect(0, 0, 1, 1);
basecolhex = 0xFFFFFFFF;
riaback = allocimage(display, r₁, CMAP8, 1, basecolhex);
}
Just call it from threadmain
:
initstyle();
From this point onward, any error must abort the program (so you can attach a debugger to it later). This behavior of error
is controlled by the abortonerror
flag. Let's set it now:
abortonerror = 1;
After any change in the screen rectangle, we have to adjust our internal state and redraw everything again (so everything stays consistent). We still have little to draw, but we will come back later to add more stuff.
This simple implementation will just make libdraw update its internal state:
Image* wscreen;
void
resized(void)
{
if(getwindow(display, Refnone) < 0)
error("cannot get display");
}
Now it's time to recreate something called the screen. The screen (not to be confused with the screen
variable, which is our destination image) is an structure which can be used to manipulate libdraw windows. We will call that screen wscreen
.
In order to set it up, we need a fill image to be displayed below the windows. Let's add some code for freeing the old stuff and recreating it:
Image *fill;
if(wscreen != nil)
freeimage(wscreen->fill);
freescreen(wscreen);
fill = eallocimage(display, screen->r, screen->chan, 1, basecolhex);
wscreen = allocscreen(screen, fill, 0);
if(wscreen == nil)
error("cannot allocate screen");
You could think that passing the background
image directly would be enough. And you would be right, but only if the background is a replicated pattern and you don't care about its origin being misaligned. This intermediate image makes positioning possible.
Now it's time to transfer riaback
to fill
, and fill
to screen
. Let's just insert the appropiate draw
calls:
if(wscreen != nil)
freeimage(wscreen->fill);
freescreen(wscreen);
fill = eallocimage(display, screen->r, screen->chan, 1, basecolhex);
draw(fill, screen->r, riaback, nil, riaback->r.min);
wscreen = allocscreen(screen, fill, 0);
if(wscreen == nil)
error("cannot allocate screen");
draw(screen, screen->r, fill, nil, fill->r.min);
Finally, as we have drawn stuff to the screen, we must flush the display:
flushimage(display, 1);
Now our resize
function is ready to be called from threadmain
. Stick a call right where we left before:
abortonerror = 1;
resized();
rio is multithreaded, and makes use of communication channels called, ugh, Channel*
. These allow different threads to exchange information safely. We're not going to get into much detail now (as this first iteration doesn't make heavy use of threads), but we'll start using these two primitives:
None of those are really needed right now, but rio has them and for good reason. They will be useful once we add more threads and channels to the mix.
exitchan = chancreate(sizeof(int), 0);
threadcreate(mousethread, nil, STACK);
recv(exitchan, nil);
threadexitsall(nil);
And that's all! The exit channel will be created, the mouse thread will be spawned and the main thread will wait until someone uses exitchan
to gracefully exit from all threads.
Let's start with a simple thread:
void
mousethread(void*)
{
threadsetname("mousethread");
for(;;);
}
It just sits there. Looping forever. Doing nothing. Not good, right? Let's make it handle mouse events!
Remember the mousectl
structure we initialized before? It interacts with /dev/mouse and /dev/mousectl in the background, providing an easy-to-use interface using channels. Fortunately, libthread provides a nice way of handling messages coming from multiple channels:
enum
{
Reshapec,
Mousec,
};
void
mousethread(void*)
{
Alt alts[] = {
{mousectl->resizec, nil, CHANRCV}, /* Reshapec */
{mousectl->c, &mousectl->Mouse, CHANRCV}, /* Mousec */
{nil, nil, CHANEND},
};
threadsetname("mousethread");
for(;;) switch(alt(alts)){
case Reshapec:
resized();
break;
case Mousec:
break;
}
}
It loops forever, handling incoming data from the mousectl->resizec
and mousectl->c
channels. The first one emits reshape events, and the second one emits regular mouse events.
Finally, let's make it exit after pressing the left mouse button:
case Mousec:
if(mousectl->buttons & 1)
send(exitchan, nil);
break;
That's all, we have finished! Although it doesn't do much, now we have a foundation to build upon.