commit 66a4bea481b042f54617d7c43d731e5e0b0cde28
Author: emma <emma@potato.my.domain>
Date: Sun, 7 Jun 2026 21:52:00 -0500
Initial work on window positioning and controls
Diffstat:
18 files changed, 706 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,23 @@
+#include "config.mk"
+
+all: twn
+
+twn: config.mk config.h main.c term.o subterm.o window.o
+ $(CC) main.c term.o window.o -o twn
+
+window.o: config.h window.c window.h
+ $(CC) window.c -c -o window.o
+
+subterm.o: config.h subterm.c subterm.h
+ $(CC) subterm.c -c -o subterm.o
+
+term.o: config.h term.c term.h
+ $(CC) term.c -c -o term.o
+
+clean:
+ rm -f window.o term.o twn
+
+install: twn
+ cp -f twn $(PREFIX)/bin
+
+PHONEY: install
diff --git a/README b/README
@@ -0,0 +1,39 @@
+ - ########### -
+ * - # # - *
+ O - # TWN # - O
+ . * - # # - * .
+ o - ########### - o
+
+ TWN is a Bare-Bones Terminal Multiplexer, like tmux or mtm. It has
+ distinct goals from either of those projects though. Its goals are:
+
+ (1) Be as "transparent" as possible. That is, it should delegate as
+ much of its behavior as possible to the terminal it's running in.
+ (2) Be configurable. It should be easy to change "normal" behaviors, to
+ support different base terminals or to customize appearance/controls.
+ (3) Have maximal code quality, with minimal total LoC. This includes
+ dependencies!
+
+ So, it doesn't use the curses library as this would harm goals (1) and (3).
+ Goal (1) allows it to ignore things like terminfo, csets, and anything else
+ that the base terminal implements.
+
+ BUILD
+
+ To build this project, first update config.mk to reflect your system's
+ configuration, then run "make". If you wish to install TWN, you can then
+ run "make install" with root priveleges.
+
+ CONFIGURATION
+
+ TWN can be configured by editing config.h. Feel free to edit the code,
+ and send in patches if you think you've made changes that might benefit
+ others.
+
+ CONTRIBUTING
+
+ Send patches to git at y1.nz, if you have an improvement.
+ Be cool please&thx u <3
+
+ With Love,
+ Emma <3
diff --git a/TODO b/TODO
@@ -0,0 +1,5 @@
+TODO on this / ROADMAP of what to do in what order.
+
+[x] draw subdividing windows
+[ ] correctly render a shell in a subwindow (just one is fine for now)
+[ ] run shells in multiple subwindows w/ proper switching
diff --git a/codes.h b/codes.h
@@ -0,0 +1,213 @@
+/* list of all codes I need to worry about, with descriptions */
+/* (that is, if they do window-specific things? or...) */
+/* this is psuedocode, but will later be converted to c. */
+
+/* BECAUSE I'M LAZY: these are based on the alpine linux console_codes man page. */
+/* Those are just the codes my system supports. Feel free to edit these */
+/* to support your system as well. If you send me a patch, and a description */
+/* of your system (so I can test/integrate your changes correctly), I will */
+/* see if it is possible to add support for it. */
+
+#define ESC (char) 0x1B
+#define CSI (char) 0x9B
+
+/**********************/
+/* CONTROL CHARACTERS */
+/**********************/
+
+/* backspace one column (but not past beginning of line) */
+#define BS (char) 0x08
+
+/* goes to next tab stop, or to EOL */
+#define HT (char) 0x09
+
+/* linefeeds (plus carriage return if LF/NL is set) */
+#define LF (char) 0x0A
+#define VT (char) 0x0B
+#define FF (char) 0x0C
+
+/* carriage return */
+#define CR (char) 0x0D
+
+/**********************************/
+/* ESCAPE (but Not CSI) SEQUENCES */
+/**********************************/
+
+/* "reset" (what attributes, exactly?) */
+#define RIS 'c'
+
+/* linefeed */
+#define IND 'D'
+
+/* newline */
+#define NEL 'E'
+
+/* set tab stop at current column */
+#define HTS 'H'
+
+/* reverse linefeed */
+#define RI 'M'
+
+/* DEC private identification (return "ESC[?6c") */
+#define DECID 'Z'
+// TODO or should this be passed to parent? hm...
+
+/************************/
+/* ESC[ or CSI SEQUENCES */
+/************************/
+/* the numerical arguments are referred to by $1, $2, $3, etc. */
+/* ALSO (I think?) all row/col numbers are 1-INDEXED!!! */
+
+/* insert $1 blank characters */
+#define ICH '@'
+
+/* Move cursor up $1 steps */
+#define CUU 'A'
+
+/* Move cursor down $1 steps */
+#define CUD 'B'
+
+/* Move cursor right $1 steps */
+#define CUF 'C'
+
+/* Move cursor left $1 steps */
+#define CUB 'D'
+
+/* Move cursor down $1 of rows, and to column 1 */
+#define CNL 'E'
+
+/* Move cursor up $1 rows and to column 1 */
+#define CPL 'F'
+
+/* Move cursor to column $1 in current row */
+#define CHA 'G'
+
+/* Move cursor to row $1 and column $2 */
+#define CUP 'H'
+
+/* Erase display, based on $1: */
+/* 1 > (1, 1) to cursor */
+/* 2 > whole display */
+/* 3 > whole display and scrollback buffer */
+/* else > cursor to (max, max) */
+#define ED 'J'
+
+/* Erase line, based on $1: */
+/* 1 > from column 1 to cursor */
+/* 2 > whole line */
+/* else > from cursor to end of line */
+#define EL 'K'
+
+/* Insert $1 blank lines */
+#define IL 'L'
+
+/* Delete $1 lines */
+#define DL 'M'
+
+/* Delete $1 chars on current line */
+#define DCH 'P'
+
+/* Erase $1 chars on current line */
+#define ECH 'X'
+
+/* Move cursor right $1 columns */
+#define HPR 'a'
+
+/* Move cursor to row $1, current column */
+#define VPA 'd'
+
+/* Move cursor down $1 rows */
+#define VPR 'e'
+
+/* Move cursor to row $1, column $2 */
+#define HVP 'f'
+
+/* Clear tab stop(s) based on $1: */
+/* empty > only at current position */
+/* 3 > clear all tab stops */
+#define TBC 'g'
+
+/* Save cursor location */
+#define SCOSC 's'
+
+/* Restore cursor location */
+#define SCORC 'u'
+
+/* Move cursor to column $1 in current row */
+#define HPA '`'
+
+/***************************************************/
+/* ESC [ $1 ; $2 ; ... m: select graphic rendition */
+/***************************************************/
+/* we need to manage these too, so subterms have independent coloring. */
+/* the values defined here correspond to $1. */
+
+#define ATTR_RESET 0
+#define ATTR_SET_BOLD 1
+#define ATTR_SET_HALF_BRIGHT 2
+#define ATTR_SET_ITALIC 3
+#define ATTR_SET_UNDERSCORE 4
+#define ATTR_SET_BLINK 5
+#define ATTR_SET_REVERSE 6
+
+#define ATTR_SET_UNDERLINE 21
+#define ATTR_SET_NORMAL_INTENSITY 22
+#define ATTR_SET_NO_ITALIC 23
+#define ATTR_SET_NO_UNDERLINE 24
+#define ATTR_SET_NO_BLINK 25
+#define ATTR_SET_NO_REVERSE 27
+
+#define ATTR_SET_FG_BLACK 30
+#define ATTR_SET_FG_RED 31
+#define ATTR_SET_FG_GREEN 32
+#define ATTR_SET_FG_BROWN 33
+#define ATTR_SET_FG_BLUE 34
+#define ATTR_SET_FG_MAGENTA 35
+#define ATTR_SET_FG_CYAN 36
+#define ATTR_SET_FG_WHITE 37
+#define ATTR_SET_FG_CUSTOM 38 /* takes additional parameter */
+#define ATTR_SET_DEFAULT_FG 39 /* takes additional parameter */
+
+#define ATTR_SET_BG_BLACK 40
+#define ATTR_SET_BG_RED 41
+#define ATTR_SET_BG_GREEN 42
+#define ATTR_SET_BG_BROWN 43
+#define ATTR_SET_BG_BLUE 44
+#define ATTR_SET_BG_MAGENTA 45
+#define ATTR_SET_BG_CYAN 46
+#define ATTR_SET_BG_WHITE 47
+#define ATTR_SET_BG_CUSTOM 48 /* takes additional parameter */
+#define ATTR_SET_DEFAULT_BG 49 /* takes additional parameter */
+
+#define ATTR_SET_FG_BLACK_BRIGHT 100
+#define ATTR_SET_FG_RED_BRIGHT 101
+#define ATTR_SET_FG_GREEN_BRIGHT 102
+#define ATTR_SET_FG_BROWN_BRIGHT 103
+#define ATTR_SET_FG_BLUE_BRIGHT 104
+#define ATTR_SET_FG_MAGENTA_BRIGHT 105
+#define ATTR_SET_FG_CYAN_BRIGHT 106
+#define ATTR_SET_FG_WHITE_BRIGHT 107
+
+#define ATTR_SET_BG_BLACK_BRIGHT 100
+#define ATTR_SET_BG_RED_BRIGHT 101
+#define ATTR_SET_BG_GREEN_BRIGHT 102
+#define ATTR_SET_BG_BROWN_BRIGHT 103
+#define ATTR_SET_BG_BLUE_BRIGHT 104
+#define ATTR_SET_BG_MAGENTA_BRIGHT 105
+#define ATTR_SET_BG_CYAN_BRIGHT 106
+#define ATTR_SET_BG_WHITE_BRIGHT 107
+
+/****************************************/
+/* ESC [ $1 ; $2 ; ... h: MODE SWITCHES */ (some of which are positional)
+/****************************************/
+/* the values here correspond to $1. */
+
+#define DECCRM 3 /* display control chars. do we need to handle this? */
+#define DECIM 4 /* set insert mode. again, not sure what this does... */
+#define LFNL 20 /* set LF/NL - echo CR after all LF, VT, and FF */
+
+/*****************************************/
+/* ESC [ $1 ; $2 ; ... n: STATUS REPORTS */
+/*****************************************/
+
+#define CPR 6 /* report cursor position. Anwers with 'ESC [ y ; x R' */
diff --git a/config.h b/config.h
@@ -0,0 +1,19 @@
+
+#define DIVIDER L'#'
+
+/* padding of the root window */
+#define ROOT_LEFT_PAD 0
+#define ROOT_RIGHT_PAD 1
+#define ROOT_UP_PAD 0
+#define ROOT_DOWN_PAD 1
+
+/* padding between splits */
+#define SPLIT_LEFT_PAD 0
+#define SPLIT_RIGHT_PAD 1
+#define SPLIT_UP_PAD 0
+#define SPLIT_DOWN_PAD 1
+
+/* visual style of pad */
+#define H_PAD_CHAR '#'
+#define V_PAD_CHAR '#'
+#define CORNER_PAD_CHAR '#'
diff --git a/config.mk b/config.mk
@@ -0,0 +1,4 @@
+
+PREFIX=/usr/local
+
+CC=cc -lc -std=c99 #...
diff --git a/direction.h b/direction.h
@@ -0,0 +1,6 @@
+
+#define left 0
+#define right 1
+#define up 2
+#define down 3
+
diff --git a/main.c b/main.c
@@ -0,0 +1,84 @@
+#include <stdio.h> /* for like everything. */
+#include <signal.h> /* for signal, SIGCHLD, and SIG_IGN */
+
+#include "window.h"
+#include "config.h"
+#include "direction.h"
+#include "term.h"
+
+int
+init(Window *root)
+{
+ int w, h;
+
+ /* automatically reap children */
+ signal(SIGCHLD, SIG_IGN);
+
+ /* CSI ED: clear whole screen */
+ fprintf(stdout, "\033[2J");
+
+ /* determine base terminal's bounds */
+ if (!get_root_size(&w, &h)) {
+ fprintf(stderr, "Fatal: failed to measure base terminal :(" "\n");
+ return 1;
+ }
+
+ /* init root window */
+ root->l = 1; root->u = 1; root->r = w; root->d = h;
+ root->pl = ROOT_LEFT_PAD; root->pu = ROOT_UP_PAD;
+ root->pr = ROOT_RIGHT_PAD; root->pd = ROOT_DOWN_PAD;
+ root->child1 = NULL;
+ root->child2 = NULL;
+ root->parent = NULL;
+ root->t = NULL;
+
+ render(root, root);
+}
+
+void
+clean_up()
+{
+ /* move to the end of the screen */
+ printf("\033[9999;9999H");
+
+ /* reset bg */
+ printf("\033[49m");
+
+ /* print a cute message! */
+ printf("\n" "ALLL done :3" "\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ Window _root; Window *root = &_root;
+ int result;
+
+ result = init(root);
+ if (result > 0) return result;
+
+ Window *focus = root;
+ Window *next;
+ char ch;
+ for (;;) {
+ ch = fgetc(stdin);
+ switch (ch) {
+ case 'h': next = find_next_window(root, focus, left); focus = next == NULL ? focus : next; break;
+ case 'k': next = find_next_window(root, focus, up); focus = next == NULL ? focus : next; break;
+ case 'l': next = find_next_window(root, focus, right); focus = next == NULL ? focus : next; break;
+ case 'j': next = find_next_window(root, focus, down); focus = next == NULL ? focus : next; break;
+ case 'H': focus = split_window(focus, left); break;
+ case 'K': focus = split_window(focus, up); break;
+ case 'L': focus = split_window(focus, right); break;
+ case 'J': focus = split_window(focus, down); break;
+ case 'q': focus = close_window(root, focus); break;
+ default: goto end;
+ }
+ if (focus == NULL) goto end;
+ render(root, focus);
+ }
+
+end:
+ clean_up();
+ printf("Terminating char was %d (%c)", (int) ch, ch);
+}
diff --git a/subterm.c b/subterm.c
@@ -0,0 +1 @@
+
diff --git a/subterm.h b/subterm.h
@@ -0,0 +1,11 @@
+
+typedef struct _subterm {
+ /* relative cursor position (is this 0 or 1 indexed?) */
+ int cx, cy;
+
+ // TODO shell, in/out streams
+
+ // TODO color, modes, blah blah blah
+} Subterm;
+
+
diff --git a/subterm.o b/subterm.o
Binary files differ.
diff --git a/term.c b/term.c
@@ -0,0 +1,49 @@
+
+#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define RESPONSE_SIZE 10
+
+int
+get_root_size(int *x, int *y)
+{
+ struct termios original, changed;
+ char response[RESPONSE_SIZE] = "";
+ int index = 0;
+ int ch = 0;
+ int result;
+
+ tcgetattr(STDIN_FILENO, &original);
+ changed = original;
+ changed.c_lflag &= ~(ICANON | ECHO);
+ changed.c_cc[VMIN] = 1;
+ changed.c_cc[VTIME] = 0;
+ tcsetattr(STDIN_FILENO, TCSANOW, &changed);
+
+ /* move down/right until end of screen */
+ printf("\033[9999;9999H");
+ fflush(stdout);
+
+ /* query cursor position */
+ printf("\033[6n");
+ fflush(stdout);
+
+ for (;;) {
+ ch = getchar();
+ if (ch == 'R' || ch == EOF || index >= RESPONSE_SIZE) break;
+ if (!isprint(ch)) continue;
+ response[index++] = ch;
+ }
+ response[index] = '\0';
+
+ result = sscanf(response, "[%d;%d", y, x);
+
+ /* move cursor to top-left of screen */
+ printf("\033[1;1H");
+
+ // tcsetattr(STDIN_FILENO, TCSANOW, &original);
+
+ return result == 2;
+}
diff --git a/term.h b/term.h
@@ -0,0 +1 @@
+int get_root_size(int *x, int *y);
diff --git a/term.o b/term.o
Binary files differ.
diff --git a/twn b/twn
Binary files differ.
diff --git a/window.c b/window.c
@@ -0,0 +1,214 @@
+#include <stdio.h> /* for stdin/out, fprintf, etc */
+#include <stdlib.h> /* for malloc, free */
+
+#include "config.h"
+#include "window.h"
+#include "direction.h"
+
+/* interior bounds, inclusive. */
+/* min and max cursor positions in the text area of the given subterm */
+#define IN_L(win_ptr) ((win_ptr)->l + (win_ptr)->pl)
+#define IN_U(win_ptr) ((win_ptr)->u + (win_ptr)->pu)
+#define IN_R(win_ptr) ((win_ptr)->r - (win_ptr)->pr)
+#define IN_D(win_ptr) ((win_ptr)->d - (win_ptr)->pd)
+
+/* collision check */
+#define IN_WINDOW(w, x, y) (w->l <= x && x <= w->r && w->u <= y && y <= w->d)
+
+/* get cursor position within window, in base-terminal coordinates */
+// TODO use actual cursor instead of top-left corner
+#define ABS_CURS_X(w) (w)->l
+#define ABS_CURS_Y(w) (w)->u
+
+
+/* recursive helper for close_window */
+void
+resize_others(Window *w, Window *other)
+{
+ if (other->l == w->r+1) other->l = w->l;
+ if (other->r == w->l-1) other->r = w->r;
+ if (other->u == w->d+1) other->u = w->u;
+ if (other->d == w->u-1) other->d = w->d;
+ if (other->child1 != NULL) {
+ resize_others(w, other->child1);
+ resize_others(w, other->child2);
+ }
+}
+
+/* FOCUS MUST BE A LEAF */
+/* returns the new focus */
+Window *
+close_window(Window *root, Window *focus) {
+ Window *parent = focus->parent;
+ Window *sibling;
+ Window *new_focus;
+
+ if (parent == NULL) return NULL;
+
+ sibling = focus == parent->child1 ?
+ parent->child2 : parent->child1;
+
+ /* merge sibling and parent */
+ parent->child1 = sibling->child1;
+ parent->child2 = sibling->child2;
+ if (parent->child1 != NULL) {
+ parent->child1->parent = parent;
+ parent->child2->parent = parent;
+ }
+ // TODO set parent's subterm, cursor, etc to sibling's
+ resize_others(focus, parent);
+ new_focus = find_window(root, ABS_CURS_X(focus), ABS_CURS_Y(focus));
+ free(focus);
+ free(sibling);
+
+ return new_focus;
+}
+
+Window *
+clone_window(Window *a)
+{
+ Window *b = (Window *) malloc(sizeof(Window));
+ b->l = a->l; b->u = a->u;
+ b->r = a->r; b->d = a->d;
+ b->pl = a->pl; b->pu = a->pu;
+ b->pr = a->pr; b->pd = a->pd;
+ b->parent = NULL;
+ b->child1 = NULL;
+ b->child2 = NULL;
+ b->t = NULL;
+ return b;
+}
+
+/* Shrink the given window and create a new one. */
+/* Returns the pointer to the new focused window. */
+Window *
+split_window(Window *parent, int direction)
+{
+ Window *c1 = clone_window(parent), *c2 = clone_window(parent);
+
+ /* calculate the new windows' geometries */
+ int xmid = (parent->l + parent->r) / 2;
+ int ymid = (parent->u + parent->d) / 2;
+ switch (direction) {
+ case down:
+ c1->d = ymid; c1->pd = SPLIT_DOWN_PAD;
+ c2->u = ymid+1; c2->pu = SPLIT_UP_PAD;
+ break;
+ case up:
+ c1->u = ymid; c1->pu = SPLIT_UP_PAD;
+ c2->d = ymid-1; c2->pd = SPLIT_DOWN_PAD;
+ break;
+ case right:
+ c1->r = xmid; c1->pr = SPLIT_RIGHT_PAD;
+ c2->l = xmid+1; c2->pl = SPLIT_LEFT_PAD;
+ break;
+ case left:
+ c1->l = xmid; c1->pl = SPLIT_LEFT_PAD;
+ c2->r = xmid-1; c2->pr = SPLIT_RIGHT_PAD;
+ break;
+ }
+
+ /* update the window tree */
+ parent->t = NULL;
+ c1->parent = parent; c2->parent = parent;
+ parent->child1 = c1; parent->child2 = c2;
+
+ // TODO open a new subterm for c2
+ // c2->t = ...
+
+ return c2;
+}
+
+/* Find the leaf window containing the given point, or else NULL */
+/* Recursing helper method for find_window_in_direction */
+Window *
+find_window(Window *w, int x, int y)
+{
+ // TODO may be unnecessary: only check root window?
+ if (!IN_WINDOW(w, x, y)) return NULL;
+
+ /* no children */
+ if (w->child1 == NULL) return w;
+
+ return find_window(IN_WINDOW(w->child1, x, y) ? w->child1 : w->child2, x, y);
+}
+
+/* Find the next window in a given direction */
+Window *
+find_next_window(Window *root, Window *focus, int direction)
+{
+ int x, y; /* target position */
+ switch(direction) {
+ case left: x = focus->l - 1; y = ABS_CURS_Y(focus); break;
+ case up: y = focus->u - 1; x = ABS_CURS_X(focus); break;
+ case right: x = focus->r + 1; y = ABS_CURS_Y(focus); break;
+ case down: y = focus->d + 1; x = ABS_CURS_X(focus); break;
+ }
+ return find_window(root, x, y);
+}
+
+void
+fill_rect(int l, int u, int r, int d, char c)
+{
+ int x = l, y = u;
+ for (;;) {
+ /* CSI sequence HVP: move cursor to row,col */
+ printf("\033[%d;%df", y, x);
+
+ putchar(c);
+
+ x++;
+ if (x > r) {
+ x = l;
+ y++;
+ }
+ if (y > d) break;
+ }
+}
+
+void
+fill_window_pad(Window *w)
+{
+ if (w->pu > 0) fill_rect(IN_L(w), w->u, IN_R(w), IN_U(w) - 1, V_PAD_CHAR);
+ if (w->pd > 0) fill_rect(IN_L(w), IN_D(w) + 1, IN_R(w), w->d, V_PAD_CHAR);
+
+ if (w->pl > 0) fill_rect(w->l, IN_U(w), IN_L(w) - 1, IN_D(w), H_PAD_CHAR);
+ if (w->pr > 0) fill_rect(IN_R(w) + 1, IN_U(w), w->r, IN_D(w), H_PAD_CHAR);
+
+ if (w->pl > 0 && w->pu > 0) fill_rect(w->l, w->u, IN_L(w) - 1, IN_U(w) - 1, CORNER_PAD_CHAR);
+ if (w->pl > 0 && w->pd > 0) fill_rect(w->l, IN_D(w)+1, IN_L(w) - 1, w->d, CORNER_PAD_CHAR);
+ if (w->pr > 0 && w->pu > 0) fill_rect(IN_R(w) + 1, w->u, w->r, IN_U(w) - 1, CORNER_PAD_CHAR);
+ if (w->pr > 0 && w->pd > 0) fill_rect(IN_R(w) + 1, IN_D(w)+1, w->r, w->d, CORNER_PAD_CHAR);
+ // TODO draw corners...
+}
+
+void
+fill_window(Window *w, char c)
+{
+ fill_rect(IN_L(w), IN_U(w), IN_R(w), IN_D(w), c);
+}
+
+void
+render(Window *w, Window *focus)
+{
+ int r;
+ if (w == NULL) return;
+
+ if (w->child1 != NULL) {
+ render(w->child1, focus);
+ render(w->child2, focus);
+ return;
+ }
+
+ /* reset bg */
+ fprintf(stdout, "\033[49m");
+
+ fill_window(w, ' ');
+
+ /* show border in a color... */
+ if (w == focus) fprintf(stdout, "\033[41m");
+
+ fill_window_pad(w);
+ fflush(stdout);
+}
+
diff --git a/window.h b/window.h
@@ -0,0 +1,37 @@
+
+#include "subterm.h"
+
+typedef struct _window {
+ /* inclusive bounds: left/up/right/down. (always order as LURD when possible!) */
+ int l, u, r, d;
+
+ /* padding by direction, cutting into the l/u/r/d bounds */
+ int pl, pu, pr, pd;
+
+ /* terminal in this window. has IO + colors + modes + etc */
+ /* NULL if there are children */
+ struct Subterm *t;
+
+ /* NULL iff root window */
+ struct _window *parent;
+
+ /* always either both defined, or both NULL. */
+ struct _window *child1, *child2;
+} Window;
+
+/* Close the current window and fix the tree around it. */
+Window *close_window(Window *root, Window *focus);
+
+/* Shrink the given window and create a new one. */
+Window *split_window(Window *parent, int direction);
+
+/* find the leaf that contains (x, y) */
+Window *find_window(Window *root, int x, int y);
+
+/* find the next window, from the focus in the given direction */
+Window *find_next_window(Window *root, Window *focus, int direction);
+
+/* drawing helpers */
+void fill_window(Window *a, char c);
+void fill_window_pad(Window *a);
+void render(Window *w, Window *focus);
diff --git a/window.o b/window.o
Binary files differ.