Sietch Hatz

Plan 9 is a fully-featured, clean, compact and uniform operating system built as a successor to Unix.

Plan 9 is not a product, it is an experimental investigation into a different way of computing. The developers started from several basic assumptions: that CPUs are very cheap but that we don't really know how to combine them effectively; that *good* networking is very important; that an intelligent user interface is a Right Decision; that existing systems are not the correct way to do things, and in particular that today's workstations are not the way to go.

This portal serves to introduce to Plan 9's ecosystem and application development. I currently use Plan 9 on an old Thinkpad x220, the flavor of the operating system that I presently run is 9front.

I've divided my notes into Rio(windows manager), Acme(text editor) and misc Plan9 C tutorials.

An argument for simplicity and clarity.
—Rob Pike
 

plan9 software

rio

Notes on the Plan9 interface and tools.

Rio is the name of the windowing system of the Plan9 operating system. To draw a new window on the screen you can right-click on an empty spot on the desktop and choose New You will see that the mouse cursor changes to a plus.

Moving windows around doesn't work like with other systems. Since there's no window title bar at the top, you can't left-click and drag it around. Instead, you can right-click and drag using the border of the window. The mouse cursor turns into a box shape in the process. Alternatively, there is a Move option when you right-click on the desktop that allows you to right-click and drag anywhere on a window to move it around.

 

acme

Notes on the Plan9 ACME development toolkit.

Acme is the classic text editor for the Plan9 operating system. The killer feature of Acme is how it integrates into surrounding system. Acme is not trying to be a complete environment by itself. Acme acts as a glue which links together other programs and tools. With Acme the OS becomes your IDE.

Middle clicking on a word or some selected text *executes* that command. Right-clicking on a file can potentially open up a new window, depending on which program is registered to handle the file type. You can close windows by right-clicking on an empty spot on the desktop, choose Delete and then right click on the window.

plan9 c

Notes on customizing and developing on the Plan 9 operating system.

Collected notes on writing GUI applications and modifying the look and feel of the Plan9 operating system. For a more complete example, see the Plan9 Clock. For more general notes on the C language, see ANSI C.

plan9 clock

Updated the look of the Plan9 clock.

I wasn't really into the whole baby blue clock, also in it not displaying seconds, so I've fixed it up a bit. It now displays each second on the clockface, and use hairline thin lines drawn through the Bresenham algorithm. The style is designed to align with the Plan9 Color Picker. This project is a good simple demonstration of Plan9 C.

Installation

Compile this source with the compiler for your platform, if you are using an ARM device:

5c clock.c && 5l -o clock clock.c

If you want to install globally, copy the source into /sys/src/cmd/clock.c and type mk install inside of /sys/src/cmd, to recompile all the applications found in the cmd directory.

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

Point
circlept(Point c, int r, int degrees)
{
	double rad = (double)degrees * PI / 180.0;

	c.x += cos(rad) * r;
	c.y -= sin(rad) * r;
	return c;
}

void
lineb(Image* dst, Point p0, Point p1, Image* src, Point sp)
{
	int dx = abs(p1.x - p0.x), sx = p0.x < p1.x ? 1 : -1;
	int dy = -abs(p1.y - p0.y), sy = p0.y < p1.y ? 1 : -1;
	int err = dx + dy, e2;

	for(;;) {
		draw(dst, Rect(p0.x, p0.y, p0.x + 1, p0.y + 1), src, nil, ZP);
		if(p0.x == p1.x && p0.y == p1.y)
			break;
		e2 = 2 * err;
		if(e2 >= dy) {
			err += dy;
			p0.x += sx;
		}
		if(e2 <= dx) {
			err += dx;
			p0.y += sy;
		}
	}
}

void
redraw(Image* dst)
{
	Point size = subpt(screen->r.max, screen->r.min);
	Point center = divpt(size, 2);
	Rectangle frame = (Rectangle){Pt(0, 0), size};
	int pad = 20;
	int rad = ((size.x < size.y ? size.x : size.y) / 2) - pad;
	int range = rad - pad;
	Image* view = allocimage(display, frame, screen->chan, 1, 0x000000FF);
	Image* secclr = allocimagemix(display, DRed, DRed);

	for(int i = 0; i < 60; i++) {
		int len = i % 15 == 0 ? range : i % 5 == 0 ? rad - pad / 2 : rad - pad / 3;
		lineb(view, circlept(center, len, i * 6), circlept(center, rad, i * 6), display->white, ZP);
	}

	Tm tms = *localtime(time(0));
	int anghr = 90 - (tms.hour * 5 + tms.min / 12) * 6;
	int angmin = 90 - tms.min * 6;
	int angsec = 90 - tms.sec * 6;
	int angsecrev = 270 - tms.sec * 6;

	fillellipse(view, center, rad - pad, rad - pad, display->black, ZP);
	lineb(view, center, circlept(center, range * 0.7, anghr), display->white, ZP);
	lineb(view, center, circlept(center, range - 2, angmin), display->white, ZP);
	lineb(view, center, circlept(center, range - 2, angsec), secclr, ZP);
	lineb(view, center, circlept(center, range * 0.1, angsecrev), secclr, ZP);
	fillellipse(view, center, 2, 2, secclr, ZP);

	/* collapse when horizontal window */
	if(size.y > size.x + 2 * pad) {
		/* time */
		char timestr[9];
		snprint(timestr, sizeof(timestr), "%02d:%02d:%02d", tms.hour, tms.min, tms.sec);
		Point timesize = stringsize(display->defaultfont, timestr);
		Point timept = Pt(pad, pad);
		/* date */
		char datestr[30];
		snprint(datestr, sizeof(datestr), "%s", ctime(time(0)));
		datestr[10] = '\0';
		Point datesize = stringsize(display->defaultfont, datestr);
		Point datept = Pt(size.x - datesize.x - pad, pad);
		/* draw */
		draw(view,
		     (Rectangle){timept, addpt(timept, Pt(size.x - pad * 2, 0))},
		     display->black, nil, ZP);
		string(view, timept, display->white, ZP, display->defaultfont, timestr);
		if(timesize.x + datesize.x < size.x - pad - pad)
			string(view, datept, display->white, ZP, display->defaultfont, datestr);
	}

	draw(dst, screen->r, view, nil, ZP);
	flushimage(display, 1);
	freeimage(secclr);
	freeimage(view);
}

void
eresized(int new)
{
	if(new&& getwindow(display, Refnone) < 0)
		fprint(2, "can't reattach to window");
	redraw(screen);
}

void
main(int argc, char* argv[])
{
	USED(argc, argv);
	
	Event e;
	Mouse m;
	Menu menu;
	char* mstr[] = {"exit", 0};
	int key, timer, t = 1000;

	if(initdraw(0, 0, "clock") < 0)
		sysfatal("initdraw failed");

	eresized(0);
	einit(Emouse);
	timer = etimer(0, t);
	menu.item = mstr;
	menu.lasthit = 0;

	for(;;) {
		key = event(&e);
		if(key == Emouse) {
			m = e.mouse;
			if(m.buttons & 4) {
				if(emenuhit(3, &m, &menu) == 0)
					exits(0);
			}
		} else if(key == timer) {
			redraw(screen);
		}
	}
}
— Submit an edit to clock9.c.txt(136 lines)

plan9 color

A simple HSV color picker for Plan9.

Just a simple color picker utility written in Plan9 C, following the style of the Plan9 Clock. Alternatively, you can use Sigrid's Color Picker if you want to use something made by someone who knows what they're doing.

Installation

Compile this source with the compiler for your platform, if you are using an ARM device:

5c color.c && 5l -o color color.c
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <event.h>

/* Conversion */

typedef struct RgbColor {
	unsigned char r, g, b;
} RgbColor;

typedef struct HsvColor {
	unsigned char h, s, v;
} HsvColor;

static HsvColor selection;

double
ptangle(Point a, Point b)
{
	return atan2(b.y - a.y, b.x - a.x);
}

double
ptdistance(Point a, Point b)
{
	int x = a.x - b.x;
	int y = a.y - b.y;

	return sqrt(x * x + y * y);
}

Point
circlept(Point c, int r, int degrees)
{
	double rad = (double)degrees * PI / 180.0;

	c.x += cos(rad) * r;
	c.y -= sin(rad) * r;
	return c;
}

Point
getcenter(Rectangle r)
{
	return divpt(addpt(r.min, r.max), 2);
}

int
within(Point p, Rectangle r)
{
	return p.x > r.min.x && p.x < r.max.x && p.y > r.min.y && p.y < r.max.y;
}

RgbColor
hsv2rgb(HsvColor hsv)
{
	RgbColor rgb;
	unsigned char region, remainder, p, q, t;

	if(hsv.s == 0) {
		return ((RgbColor){hsv.v, hsv.v, hsv.v});
	}
	region = hsv.h / 43;
	remainder = (hsv.h - (region * 43)) * 6;
	p = (hsv.v * (255 - hsv.s)) >> 8;
	q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8;
	t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8;
	switch(region) {
	case 0:
		rgb.r = hsv.v;
		rgb.g = t;
		rgb.b = p;
		break;
	case 1:
		rgb.r = q;
		rgb.g = hsv.v;
		rgb.b = p;
		break;
	case 2:
		rgb.r = p;
		rgb.g = hsv.v;
		rgb.b = t;
		break;
	case 3:
		rgb.r = p;
		rgb.g = q;
		rgb.b = hsv.v;
		break;
	case 4:
		rgb.r = t;
		rgb.g = p;
		rgb.b = hsv.v;
		break;
	default:
		rgb.r = hsv.v;
		rgb.g = p;
		rgb.b = q;
		break;
	}
	return rgb;
}

HsvColor
rgb2hsv(RgbColor rgb)
{
	HsvColor hsv;
	unsigned char rgbMin, rgbMax;

	rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
	rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);
	hsv.v = rgbMax;
	if(hsv.v == 0) {
		hsv.h = 0;
		hsv.s = 0;
		return hsv;
	}
	hsv.s = 255 * (double)(rgbMax - rgbMin) / hsv.v;
	if(hsv.s == 0) {
		hsv.h = 0;
		return hsv;
	}
	if(rgbMax == rgb.r)
		hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
	else if(rgbMax == rgb.g)
		hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
	else
		hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);
	return hsv;
}

unsigned int
rgb2hex(RgbColor clr)
{
	return ((clr.r & 0xFF) << 24) + ((clr.g & 0xFF) << 16) + ((clr.b & 0xFF) << 8) + (255 & 0xFF);
}

unsigned int
hsv2hex(HsvColor hsvclr)
{
	return rgb2hex(hsv2rgb(hsvclr));
}

/* Defaults */

void
lineb(Image* dst, Point p0, Point p1, Image* src, Point sp)
{
	int dx = abs(p1.x - p0.x), sx = p0.x < p1.x ? 1 : -1;
	int dy = -abs(p1.y - p0.y), sy = p0.y < p1.y ? 1 : -1;
	int err = dx + dy, e2;

	for(;;) {
		draw(dst, Rect(p0.x, p0.y, p0.x + 1, p0.y + 1), src, nil, sp);
		if(p0.x == p1.x && p0.y == p1.y)
			break;
		e2 = 2 * err;
		if(e2 >= dy) {
			err += dy;
			p0.x += sx;
		}
		if(e2 <= dx) {
			err += dx;
			p0.y += sy;
		}
	}
}

unsigned int
gradeint(int a, int b, double ratio)
{
	return (a * ratio) + (b * (1 - ratio));
}

Point
gradept(Point a, Point b, double ratio)
{
	return Pt(
	    gradeint(a.x, b.x, ratio),
	    gradeint(a.y, b.y, ratio));
}

unsigned int
gradecolor(HsvColor a, HsvColor b, double ratio)
{
	HsvColor clr = (HsvColor){
	    gradeint(a.h, b.h, ratio),
	    gradeint(a.s, b.s, ratio),
	    gradeint(a.v, b.v, ratio)};

	return hsv2hex(clr);
}

void
gradeline(Image* dst, Point p0, Point p1, HsvColor clr0, HsvColor clr1, int segs, Point sp)
{
	for(int i = 0; i < segs; i++) {
		double ratio = (double)i / segs;
		Image* clrimg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1,
		                           gradecolor(clr0, clr1, ratio));
		lineb(dst,
		      gradept(p0, p1, ratio),
		      gradept(p0, p1, (double)(i + 1) / segs), clrimg, ZP);
		freeimage(clrimg);
	}
}

void
redraw(Image* dst)
{
	Point size = subpt(screen->r.max, screen->r.min);
	Point center = divpt(size, 2);
	Rectangle frame = (Rectangle){Pt(0, 0), size};
	int pad = 20;
	int rad = ((size.x < size.y ? size.x : size.y) / 2) - pad;
	Image* view = allocimage(display, frame, RGBA32, 1, 0x000000FF);

	/* draw ring */
	for(int i = 0; i < 180; i++) {
		Point p0 = circlept(center, rad, i * 2);
		Point p1 = circlept(center, rad, (i + 1) * 2);
		unsigned int angle = ptangle(center, p0) / PI / 2 * 255;
		int hexclr = hsv2hex((HsvColor){angle, 255, 255});
		Image* imgclr = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, hexclr);
		lineb(view, p0, p1, imgclr, ZP);
		freeimage(imgclr);
	}

	/* draw selection */
	RgbColor selrgb = hsv2rgb(selection);
	HsvColor selhue = (HsvColor){selection.h, 255, 255};
	unsigned int selhex = rgb2hex(selrgb);
	Image* selclr = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, selhex);
	Image* selhueclr = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, hsv2hex(selhue));

	/* draw hue */
	double angle = (selection.h / 255.0) * -360.0;
	Point huepos = circlept(center, rad, angle);
	fillellipse(view, huepos, 2, 2, selhueclr, ZP);

	/* draw sat */
	double distance = (selection.s / 255.0) * rad;
	Point satpos = circlept(center, distance, angle);
	ellipse(view, center, distance, distance, 0, selclr, ZP);
	fillellipse(view, satpos, 2, 2, selclr, ZP);
	gradeline(view, huepos, satpos, selhue, selection, 8, ZP);

	/* collapse if window is horizontal */
	if(size.y > size.x + 2 * pad) {
		Rectangle sliderect = Rect(0, size.y - pad * 2, size.x, size.y - pad);
		draw(view, sliderect, display->black, nil, ZP);
		gradeline(view,
		          addpt(sliderect.min, Pt(pad, pad / 2)),
		          addpt(sliderect.max, Pt(-pad, -pad / 2)),
		          (HsvColor){selection.h, selection.s, 0},
		          (HsvColor){selection.h, selection.s, 255}, 16, ZP);
		Point valpos = addpt(sliderect.min, Pt((selection.v / 255.0) * (size.x - 2 * pad) + pad, pad / 2));
		fillellipse(view, valpos, 2, 2, selclr, ZP);
	}

	/* header */
	char hexstr[16];
	char rgbstr[12];
	snprint(hexstr, sizeof(hexstr), "#%02ux%02ux%02ux",
	        (selhex >> 24) & 0xFF,
	        (selhex >> 16) & 0xFF,
	        (selhex >> 8) & 0xFF);
	snprint(rgbstr, sizeof(rgbstr), "%ud,%ud,%ud",
	        selrgb.r,
	        selrgb.g,
	        selrgb.b);
	Point hexstrsize = stringsize(display->defaultfont, hexstr);
	Point rgbstrsize = stringsize(display->defaultfont, rgbstr);

	/* collapse if window is horizontal */
	if(size.y > size.x + 2 * pad) {
		Rectangle clearrect = Rect(pad, pad, size.x - pad, 2 * pad);
		draw(view, clearrect, display->black, nil, ZP);
		string(view,
		       Pt(pad, pad),
		       display->white, ZP, display->defaultfont, hexstr);
		if(hexstrsize.x + rgbstrsize.x < size.x)
			string(view,
			       Pt(size.x - pad - rgbstrsize.x, pad),
			       selclr, ZP, display->defaultfont, rgbstr);
	}

	draw(dst, screen->r, view, nil, ZP);
	flushimage(display, 1);
	freeimage(selclr);
	freeimage(selhueclr);
	freeimage(view);
}

void
select(HsvColor clr)
{
	selection.h = clr.h;
	selection.s = clr.s;
	selection.v = clr.v;
	redraw(screen);
}

void
touch(Point m)
{
	Rectangle r = screen->r;
	Point center = getcenter(r);
	int pad = 20;
	int radius = ((Dx(r) < Dy(r) ? Dx(r) : Dy(r)) / 2) - pad;
	HsvColor newsel = selection;

	if(ptdistance(center, m) > radius) {
		int width = (r.max.x - r.min.x) - pad * 2;
		Rectangle sliderect = Rect(r.min.x, r.max.y - pad * 2, r.max.x, r.max.y);
		if(within(m, sliderect)) {
			int touchx = m.x - screen->r.min.x - pad;
			double ratio = touchx / (double)width;
			newsel.v = ratio > 1 ? 255.0 : ratio < 0 ? 0 : ratio * 255.0;
		} else {
			double angle = ptangle(center, m);
			newsel.h = (int)(angle / PI / 2 * 255) % 255;
		}
	} else {
		double distance = ptdistance(center, m);
		newsel.s = (distance / radius) * 255.0;
	}
	select(newsel);
}

int
hex2int(char a, char b)
{
	a = (a <= '9') ? a - '0' : (a & 0x7) + 9;
	b = (b <= '9') ? b - '0' : (b & 0x7) + 9;
	return (a << 4) + b;
}

void
dopaste(void)
{
	char* p;
	int f;
	if((f = open("/dev/snarf", OREAD)) >= 0) {
		char body[8];
		read(f, body, 8);
		if(body[0] == '#') {
			RgbColor rgbclr = (RgbColor){
			    hex2int(body[1], body[2]),
			    hex2int(body[3], body[4]),
			    hex2int(body[5], body[6])};
			select(rgb2hsv(rgbclr));
		}
		close(f);
	}
}

void
dosnarf(void)
{
	int f;
	if((f = open("/dev/snarf", OWRITE)) >= 0) {
		unsigned int selhex = rgb2hex(hsv2rgb(selection));
		char hexstr[16];
		snprint(hexstr, sizeof(hexstr), "#%02ux%02ux%02ux",
		        (selhex >> 24) & 0xFF,
		        (selhex >> 16) & 0xFF,
		        (selhex >> 8) & 0xFF);
		write(f, hexstr, strlen(hexstr));
		close(f);
	}
}
void
eresized(int new)
{
	if(new&& getwindow(display, Refnone) < 0)
		fprint(2, "can't reattach to window");
	draw(screen, screen->r, display->black, nil, ZP);
	redraw(screen);
}

void
main(int argc, char* argv[])
{
	USED(argc, argv);
	
	Event e;
	Mouse m;
	Menu menu;
	char* mstr[] = {"Snarf", "Paste", "Exit", 0};
	int key;

	if(initdraw(0, 0, "Color") < 0)
		sysfatal("initdraw failed");

	/* initial color */
	selection.h = 110;
	selection.s = 120;
	selection.v = 220;

	eresized(0);
	einit(Emouse);
	menu.item = mstr;
	menu.lasthit = 0;

	redraw(screen);

	/* Break on mouse3 */
	for(;;) {
		key = event(&e);
		if(key == Emouse) {
			m = e.mouse;
			if(m.buttons & 4) {
				if(emenuhit(3, &m, &menu) == 0)
					dosnarf();
				if(emenuhit(3, &m, &menu) == 1)
					dopaste();
				if(emenuhit(3, &m, &menu) == 2)
					exits(0);
			} else if(m.buttons & 1) {
				touch(m.xy);
			}
		}
	}
}
— Submit an edit to color9.c.txt(425 lines)

incoming(6): about ansi c rio acme plan9 c muaddib