merge dorne specific cursetree files into main repo

This commit is contained in:
Emile Clark-Boman 2025-09-27 20:47:20 +10:00
parent 9b0e752b8a
commit 8767e4ca6e
20 changed files with 1369 additions and 0 deletions

6
cursetree/_ncurses.h Normal file
View file

@ -0,0 +1,6 @@
/* This file exists to isolate the ncurses instance being used
* ie ncurses, ncursesw, ncursest, ncursestw
*/
/* libncurses with wide-character support. */
#include <ncursesw/ncurses.h>

128
cursetree/cursetree.c Normal file
View file

@ -0,0 +1,128 @@
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "_ncurses.h"
#include "ncrswrap.h"
#include "ncurses.h"
#include "node.h"
#include "tree.h"
volatile sig_atomic_t got_winch = 0;
void handle_SIGWINCH(int sig) {
// endwin();
// Needs to be called after an endwin() so ncurses will initialize
// itself with the new terminal dimensions.
got_winch = 1;
}
/*
* If ncurses is configured to supply its own SIGWINCH handler,
* - on receipt of a SIGWINCH, the handler sets a flag
* - which is tested in wgetch(3X), doupdate(3X) and restartterm(3X),
* - in turn, calling the resizeterm function,
* - which ungetch's a KEY_RESIZE which will be read on the next call to
* wgetch. REF: `man resizeterm(3x)`
*/
static void bind_SIGWINCH(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_SIGWINCH;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* Restart functions if
interrupted by handler */
if (sigaction(SIGWINCH, &sa, NULL) == -1) {
perror("bind_SIGWINCH");
exit(2);
}
}
int ct_init(struct ct_tree **const tree) {
// bind_SIGWINCH();
/* Initialise NCurses Library & Root Node */
init_ncurses();
init_tree(tree);
return EXIT_SUCCESS;
}
void ct_redraw(void) {
/* TODO: hide doupdate() behind some other method */
/* flush ncurses virtual screen -> physical screen */
doupdate();
}
/* Recursively search the tree for update requests.
* Returns:
* 0 - success (no action required)
* 1 - success (request: redraw the screen)
*/
static int __update_rec(struct ct_node *const node) {
int result = 0;
struct ct_dims *dims;
if (node->flags & NFLAG_RESIZE) {
/* TODO: the child has requested a resizing, but resize_node()
* TODO: wastes CPU time resizing itself!
*/
dims = IS_ROOT_NODE(node) ? termdims() : dup_dims(node->surface->dims);
resize_node(node, dims);
node->flags &= ~NFLAG_RESIZE;
result = 1;
}
if (node->surface->updatereq) {
// TODO: is this necessary or does mvresize_win do this for us?
wnoutrefresh(node->surface->win);
node->surface->updatereq = 0;
}
for (int i = 0; i < node->cindex; i++) {
result |= __update_rec(node->child[i]);
}
return result;
}
void ct_update(struct ct_tree *const tree) {
// WARNING: update_rec does not flush the screen, wgetch will do that for us
if (__update_rec(tree->root)) {
doupdate();
// redrawwin(curscr);
// wrefresh(curscr);
}
}
void ct_process(struct ct_tree *const tree) {
const int key = wgetch(curscr);
// int key = -1;
/* ncurses binds a SIGWINCH handler if SIGWINCH has SIG_DFL disposition
* when initscr(3x) is called. This handler emits KEY_RESIZE (decimal 410) to
* stdin. REF: manpages -> resizeterm(3x) initscr(3x) wgetch(3x)
*/
switch (key) {
case -1:
// wclear(tree->root->surface->win);
// mvwprintw(tree->root->surface->win, 0, 0, " \r-1\n");
// wrefresh(tree->root->surface->win);
break;
case KEY_RESIZE:
// got_winch = 1;
tree->root->flags |= NFLAG_RESIZE;
break;
default:
// wclear(tree->root->surface->win);
// mvwprintw(tree->root->surface->win, 0, 0, " \r%d\n", key);
// wrefresh(tree->root->surface->win);
break;
}
// if (got_winch == 1) {
// tree->root->flags |= NFLAG_RESIZE;
// got_winch = 0;
// }
// if (__update_rec(tree->root)) {
// ct_redraw();
// }
}

11
cursetree/cursetree.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef CURSETREE_CURSETREE_H
#define CURSETREE_CURSETREE_H
#include "tree.h"
/* === External Interface === */
int ct_init(struct ct_tree **const tree);
void ct_update(struct ct_tree *const tree);
void ct_process(struct ct_tree *const tree);
#endif /* CURSETREE_CURSETREE_H */

88
cursetree/dims.c Normal file
View file

@ -0,0 +1,88 @@
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "dims.h"
#include "node.h"
#include "util.h"
struct ct_dims *new_dims(const int x, const int y, const int width, const int height) {
struct ct_dims *dims;
dims = (struct ct_dims *)malloc(sizeof(struct ct_dims));
*dims = (struct ct_dims){
.x = x,
.y = y,
.width = width,
.height = height,
};
return dims;
}
struct ct_dims *dup_dims(const struct ct_dims *const dims) {
struct ct_dims *dup;
dup = (struct ct_dims *)malloc(sizeof(struct ct_dims));
memcpy(dup, dims, sizeof(struct ct_dims));
return dup;
}
static struct ct_bounds *__bounds(const enum ct_boundtype type, const int wmin,
const int wmax, const int hmin,
const int hmax) {
struct ct_bounds *bounds;
bounds = (struct ct_bounds *)malloc(sizeof(struct ct_bounds));
*bounds = (struct ct_bounds){
.type = type,
.wmin = wmin,
.wmax = wmax,
.hmin = hmin,
.hmax = hmax,
};
return bounds;
}
static inline void __clamp(int *const val, const int min, const int max,
const int do_ceiling) {
if (*val == __BOUND_UNLIMITED)
*val = do_ceiling ? max : min;
else
*val = clampi(*val, min, max);
}
#define CLAMP_ABS(val, is_max) \
(__clamp(&val, __BOUND_ABS_MIN, __BOUND_ABS_MAX, is_max))
#define CLAMP_REL(val, is_max) \
(__clamp(&val, __BOUND_REL_MIN, __BOUND_REL_MAX, is_max))
struct ct_bounds *bounds_none(void) {
return __bounds(BOUND_NONE, __BOUND_ABS_MIN, __BOUND_ABS_MAX, __BOUND_ABS_MIN,
__BOUND_ABS_MAX);
}
struct ct_bounds *bounds_absolute(int wmin, int wmax,
int hmin, int hmax) {
CLAMP_ABS(wmin, false);
CLAMP_ABS(wmax, true);
CLAMP_ABS(hmin, false);
CLAMP_ABS(hmax, true);
return __bounds(BOUND_ABSOLUTE, wmin, wmax, hmin, hmax);
}
struct ct_bounds *bounds_relative(int wmin, int wmax,
int hmin, int hmax) {
CLAMP_REL(wmin, false);
CLAMP_REL(wmax, true);
CLAMP_REL(hmin, false);
CLAMP_REL(hmax, true);
return __bounds(BOUND_RELATIVE, wmin, wmax, hmin, hmax);
}
struct ct_bounds *dup_bounds(const struct ct_bounds *const bounds) {
struct ct_bounds *dup;
dup = (struct ct_bounds *)malloc(sizeof(struct ct_bounds));
memcpy(dup, bounds, sizeof(struct ct_bounds));
return dup;
}

41
cursetree/dims.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef CURSETREE_DIMS_H
#define CURSETREE_DIMS_H
#define __BOUND_UNLIMITED (-1)
#define __BOUND_ABS_MIN (1)
#define __BOUND_ABS_MAX (INT_MAX / CINDEX_MAX - 1)
#define __BOUND_REL_MIN ((float)0)
#define __BOUND_REL_MAX ((float)1)
enum ct_axis {
AXIS_X,
AXIS_Y,
};
enum ct_boundtype {
BOUND_NONE,
BOUND_ABSOLUTE,
BOUND_RELATIVE,
};
/* Stores a node's starting x,y coordinates, width, & height.
* NOTE: Intended for interfunction communication.
*/
struct ct_dims {
int x, y, width, height;
};
struct ct_bounds {
enum ct_boundtype type;
int wmin, wmax, hmin, hmax;
};
struct ct_dims *new_dims(const int x, const int y, const int width, const int height);
struct ct_dims *dup_dims(const struct ct_dims *const dims);
struct ct_bounds *bounds_none(void);
struct ct_bounds *bounds_absolute(int wmin, int wmax, int hmin, int hmax);
struct ct_bounds *bounds_relative(int wmin, int wmax, int hmin, int hmax);
struct ct_bounds *dup_bounds(const struct ct_bounds *const bounds);
#endif /* CURSETREE_DIMS_H */

114
cursetree/ncrswrap.c Normal file
View file

@ -0,0 +1,114 @@
#include <locale.h>
#include <unistd.h>
#include "_ncurses.h"
#include "ncrswrap.h"
#define CRS_LOCALE "en_US.UTF-8"
/* Set ncurses terminal mode (buffering, character processing,
* Key->SIG handling, and other termios(3) functionality).
*/
int termmode(const enum crs_termmode mode) {
switch (mode) {
case TMODE_CBREAK:
return cbreak();
case TMODE_RAW:
return raw();
case TMODE_NOCBREAK:
return nocbreak();
case TMODE_NORAW:
return noraw();
default:
/* defaulting is not possible. */
return 1;
}
}
void termsize(int *const width, int *const height) {
*width = COLS;
*height = LINES;
}
struct ct_dims *termdims(void) {
struct ct_dims *dims = new_dims(0, 0, 0, 0);
termsize(&dims->width, &dims->height);
return dims;
}
/* Apply a default IO configuration for an ncurses WINDOW.
*/
static inline void __conf_window(WINDOW *const win) {
nodelay(win, TRUE); // getch(3x) nonblocking IO
keypad(win, TRUE); // allow function keys
// intrflush(3x) - flush terminal input buffer on interrupt key
intrflush(win, FALSE);
}
/* Initialise ncurses for our usecase.
* WARNING: This function should only be called once.
*/
void init_ncurses(void) {
// ncurses expects a locale for consistent behaviour
setlocale(LC_ALL, CRS_LOCALE);
/* NCurses Initialisation */
initscr();
/* WARNING: no you shouldn't delwin(stdscr) it breaks everything... */
__conf_window(stdscr);
start_color();
/* Screen Configuration */
termmode(TMODE_CBREAK);
noecho(); // manually echo from getch(3x)
curs_set(0); // hide cursor
}
void end_ncurses(void) {
endwin();
}
/* Initialise (with default IO configuration) a new ncurses WINDOW.
*/
WINDOW *new_window(const int x, const int y, const int width,
const int height) {
WINDOW *win = newwin(height, width, y, x);
__conf_window(win);
return win;
}
/* Initialise (with default IO configuration) a fullscreen ncurses WINDOW.
*/
WINDOW *new_window_fs(void) {
WINDOW *rootwin = new_window(0, 0, COLS, LINES);
__conf_window(rootwin);
return rootwin;
}
// void destroy_window(WINDOW *) __attribute__((alias("delwin")));
// int winposx(WINDOW *) __attribute__((alias("getbegy")));
// int winposy(WINDOW *) __attribute__((alias("getbegy")));
// int winwidth(WINDOW *) __attribute__((alias("getmaxx")));
// int winheight(WINDOW *) __attribute__((alias("getmaxy")));
// struct ct_dims *windims(WINDOW *win) {
// int x, y, width, height;
// winpos(win, x, y);
// winsize(win, width, height);
// return new_dims(x, y, width, height);
// }
/* Resize and move (if resized successfully) an ncurses WINDOW.
* Returns ERR (1) on fail, and OK (0) on success.
* NOTE: Failure occurs if width or height <= 0,
* NOTE: or if the x,y coordinate is outside the screen bounds.
*/
int resizemv_window(WINDOW *const win, const int x, const int y,
const int width, const int height) {
return wresize(win, height, width) || mvwin(win, y, x);
}

35
cursetree/ncrswrap.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef CURSETREE_NCRSWRAP_H
#define CURSETREE_NCRSWRAP_H
#include "dims.h"
#ifndef __NCURSES_H
typedef struct _win_st WINDOW;
#endif /* __NCURSES_H */
enum crs_termmode {
/* tty cbreak mode */
TMODE_CBREAK,
/* tty raw mode */
TMODE_RAW,
/* tty cooked mode (ISIG & IXON not modified)*/
TMODE_NOCBREAK,
/* tty cooked mode (ISIG & IXON set) */
TMODE_NORAW,
};
int termmode(const enum crs_termmode mode);
void termsize(int *const width, int *const height);
struct ct_dims *termdims(void);
void init_ncurses(void);
void end_ncurses(void);
WINDOW *new_window(const int x, const int y, const int width, const int height);
WINDOW *new_window_fs(void);
void destroy_window(WINDOW *);
int resizemv_window(WINDOW *const win, const int x, const int y,
const int width, const int height);
#endif /* CURSETREE_NCRSWRAP_H */

359
cursetree/node.c Normal file
View file

@ -0,0 +1,359 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dims.h"
#include "node.h"
#include "surface.h"
#include "util.h"
/* Internal allocator method for ct_node structures.
*/
static inline struct ct_node *__alloc_node(void) {
struct ct_node *node = (struct ct_node *)malloc(sizeof(struct ct_node));
return node;
}
/* Returns NULL if memory allocation failed.
* TODO: should dims be given as a parameter, or lazily computed in ct_update?
*/
struct ct_node *__node(struct ct_dims *const dims,
struct ct_bounds *const bounds,
struct ct_node *const parent) {
struct ct_node *node = __alloc_node();
if (node != NULL) {
*node = (struct ct_node){
.surface = new_surface(dims, bounds),
.flags = NFLAG_EMPTY,
.parent = parent,
.child = (struct ct_node **)malloc(NODE_INIT_CHILDREN *
sizeof(struct ct_node *)),
.csize = NODE_INIT_CHILDREN,
.cindex = 0,
.axis = AXIS_X,
};
}
return node;
}
struct ct_node *new_node(struct ct_bounds *const bounds,
struct ct_node *const parent) {
/* copy the parent's dimensions for now and request
* cursetree resize this node appropriately afterwards
* WARNING: new_node doesn't set the NFLAG_RESIZE request flag
* WARNING: that should be done by a function calling new_node
*/
return __node(dup_dims(parent->surface->dims), bounds, parent);
}
/* WARNING: Do NOT use __destroy_node() to destroy a node's children!
* WARNING: Use the destroy_child_node() function instead.
*/
void __destroy_node(struct ct_node *const node) {
if (node == NULL)
return;
// destroy children first
for (int j = 0; j < node->cindex; j++) {
__destroy_node(node->child[j]);
}
destroy_surface(node->surface);
free(node);
}
/* Surface Partition Dimensions */
/* TODO: can I use unsigned short instead of int? */
struct ct_spdims {
// Main Axis & Orthogonal Axis Sizes
int axm_size;
bool fixed;
int min, max;
};
/*
*/
int resize_node(struct ct_node *const node, struct ct_dims *dims) {
resize_surface(node->surface, dims);
dims = node->surface->dims;
if (node->surface->bounds->wmin > dims->width ||
node->surface->bounds->hmin > dims->height) {
node->flags |= NFLAG_SMALL;
return 1;
} else if (node->surface->bounds->wmax < dims->width ||
node->surface->bounds->hmax < dims->height) {
node->flags |= NFLAG_LARGE;
return 1;
}
if (!IS_PARENT_NODE(node))
return 0;
if (node->cbounds.wmin_abs + node->cbounds.wmin_rel * dims->width >
dims->width ||
node->cbounds.hmin_abs + node->cbounds.hmin_rel * dims->height >
dims->height) {
node->flags |= NFLAG_SMALLCHILD;
return 1;
}
int axm_size, axm_free;
size_t bounds_axm_min_offset, bounds_axm_max_offset;
size_t dims_axm_pos_offset, dims_axm_size_offset;
if (node->axis == AXIS_X) {
axm_free = dims->width;
axm_size = dims->width;
dims_axm_pos_offset = offsetof(struct ct_dims, x);
dims_axm_size_offset = offsetof(struct ct_dims, width);
bounds_axm_min_offset = offsetof(struct ct_bounds, wmin);
bounds_axm_max_offset = offsetof(struct ct_bounds, wmax);
} else {
assert(node->axis == AXIS_Y);
axm_free = dims->height;
axm_size = dims->height;
dims_axm_pos_offset = offsetof(struct ct_dims, y);
dims_axm_size_offset = offsetof(struct ct_dims, height);
bounds_axm_min_offset = offsetof(struct ct_bounds, hmin);
bounds_axm_max_offset = offsetof(struct ct_bounds, hmax);
}
struct ct_spdims cspdims[node->cindex];
cindex parts_n = node->cindex;
memset(cspdims, 0, sizeof(struct ct_spdims) * node->cindex);
for (int i = 0; i < node->cindex; i++) {
cspdims[i].min = *(int *)((char *)node->child[i]->surface->bounds +
bounds_axm_min_offset);
cspdims[i].max = *(int *)((char *)node->child[i]->surface->bounds +
bounds_axm_max_offset);
if (node->child[i]->surface->bounds->type == BOUND_RELATIVE) {
cspdims[i].min *= axm_size;
cspdims[i].max *= axm_size;
}
cspdims[i].axm_size = 0;
}
int split, new_size;
while (axm_free && parts_n) {
split = (axm_free > parts_n) ? (axm_free / parts_n) : 1;
for (int i = 0; i < node->cindex; i++) {
if (cspdims[i].fixed)
continue;
new_size =
clampi(cspdims[i].axm_size + split, cspdims[i].min, cspdims[i].max);
if (new_size == cspdims[i].axm_size) {
cspdims[i].fixed = true;
parts_n--;
continue;
}
axm_free -= (new_size - cspdims[i].axm_size);
cspdims[i].axm_size = new_size;
if (axm_free == 0)
break;
}
}
struct ct_dims *cdims = dup_dims(dims);
/* NOTE: the statements below are done implicitly by dup_dims */
// *(int*)(cdims + dims_axm_pos_offset) = *(int*)(dims +
// dims_axm_pos_offset);
// *(int*)(cdims + dims_axo_pos_offset) = *(int*)(dims +
// dims_axo_pos_offset);
// *(int*)(cdims + dims_axo_size_offset) = axo_size;
for (int i = 0; i < node->cindex; i++) {
*(int *)((char *)cdims + dims_axm_size_offset) = cspdims[i].axm_size;
resize_node(node->child[i], dup_dims(cdims));
*(int *)((char *)cdims + dims_axm_pos_offset) += cspdims[i].axm_size;
}
free(cdims);
return 0;
}
static int __set_cbounds(struct ct_node *const parent,
const struct ct_node *const child,
const bool additive) {
/* child and parent w/h min aliases */
int c_wmin, c_hmin;
int p_wmin_rel, p_hmin_rel;
c_wmin = child->surface->bounds->wmin;
c_hmin = child->surface->bounds->hmin;
if (!additive) {
c_wmin *= -1;
c_hmin *= -1;
}
if (child->surface->bounds->type == BOUND_RELATIVE) {
parent->cbounds.wmin_abs++;
parent->cbounds.hmin_abs++;
p_wmin_rel = parent->cbounds.wmin_rel + c_wmin;
p_hmin_rel = parent->cbounds.hmin_rel + c_wmin;
if (p_wmin_rel > __BOUND_REL_MAX || p_hmin_rel >= __BOUND_REL_MAX)
return 1;
parent->cbounds.wmin_rel = p_wmin_rel;
parent->cbounds.hmin_rel = p_hmin_rel;
assert(parent->cbounds.wmin_rel >= __BOUND_REL_MIN);
assert(parent->cbounds.hmin_rel >= __BOUND_REL_MIN);
} else {
parent->cbounds.wmin_abs += c_wmin;
parent->cbounds.hmin_abs += c_hmin;
assert(parent->cbounds.wmin_abs >= __BOUND_ABS_MIN);
assert(parent->cbounds.hmin_abs >= __BOUND_ABS_MIN);
}
return 0;
}
/* Returns:
* 0 -> success
* 1 -> failed (max child limit reached)
* 2 -> failed (cumulative relative bounds surpasses 100%)
*/
int insert_child_node(struct ct_node *const parent, struct ct_node *const child,
const cindex i) {
if (parent->cindex == CINDEX_MAX)
return 1;
else if (__set_cbounds(parent, child, true))
return 2;
else if (parent->cindex == parent->csize) {
// grow child array size and clamp maximum
cindex new_csize = parent->csize * NODE_CHILDREN_GROWTH;
// check overflow
parent->csize = (parent->csize <= new_csize) ? new_csize : CINDEX_MAX;
parent->child =
reallocarray(parent->child, parent->csize, sizeof(struct ct_node *));
}
// shift all children up for insertion
for (int j = parent->cindex; j > i; j--) {
parent->child[j] = parent->child[j - 1];
}
// do insertion
parent->child[i] = child;
parent->cindex++;
// request cursetree recompute dimensions recursively from parent
parent->flags |= NFLAG_RESIZE;
return EXIT_SUCCESS;
}
/* Returns:
* 0 -> success
* 1 -> failed (max child limit reached)
* 2 -> failed (cumulative relative bounds surpasses 100%)
*/
int append_child_node(struct ct_node *const parent,
struct ct_node *const child) {
return insert_child_node(parent, child, parent->cindex);
}
/* Remove a child node from a parent node by index.
* Returns NULL if an invalid index was provided, otherwise returns
* a pointer to the child node removed.
* NOTE: This does NOT destroy the child node and should be
* NOTE: used when moving the child somewhere else.
* NOTE: Otherwise use destroy_child_node() instead.
*/
struct ct_node *remove_child_node(struct ct_node *const parent,
const cindex i) {
struct ct_node *child;
if (i >= parent->cindex)
return NULL;
else if (parent->cindex <= parent->csize / NODE_CHILDREN_GROWTH) {
// shrink child array to avoid memory bloat
parent->csize /= NODE_CHILDREN_GROWTH;
parent->child =
reallocarray(parent->child, parent->csize, sizeof(struct ct_node *));
}
child = &parent[i];
// shift all children down to fill removal
for (int j = i; j < parent->cindex; j++) {
parent->child[j] = parent->child[j + 1];
}
parent->cindex--;
__set_cbounds(parent, child, false);
// request cursetree recompute dimensions recursively from parent
parent->flags |= NFLAG_RESIZE;
return child;
}
/* Remove and destroy a child node by index.
* Returns 1 on failure (invalid index provided), and 0 on success.
* NOTE: Use remove_child_node() instead if the child should live.
*/
int destroy_child_node(struct ct_node *const parent, const cindex i) {
struct ct_node *child = remove_child_node(parent, i);
__destroy_node(child);
return (child == NULL);
}
/*
* Returns:
* 0 -> success
* 1 -> failed (node has no parent)
* 2 -> failed (parent has no reference to node, CRITICAL)
*/
static int __index_as_child(const struct ct_node *const node,
int *const index) {
if (node->parent == NULL)
return 1;
for (int i = 0; i < node->parent->cindex; i++) {
if (node->parent->child[i] == node) {
*index = i;
return 0;
}
}
return 2;
}
/* If preserve_bounds is set then upon collapse the new node maintains the
* original node's bounds struct. Otherwise the bounds of the child collapsed
* onto are used.
*/
void collapse_node(struct ct_node **const node, const int i,
const bool preserve_bounds) {
assert(0 <= i && i < (*node)->cindex);
int parent_index;
struct ct_node *const parent = (*node)->parent;
struct ct_node *collapse_target = remove_child_node(*node, i);
struct ct_dims *dims = dup_dims((*node)->surface->dims);
struct ct_bounds *bounds;
if (preserve_bounds) {
bounds = dup_bounds((*node)->surface->bounds);
rebind_surface(collapse_target->surface, bounds);
}
/* Destroy original node and point to the collapse_target */
if (__index_as_child(*node, &parent_index)) {
/* __index_as_child fails (typically) because
* the *node is cursetree's root node. */
__destroy_node(*node);
*node = collapse_target;
} else {
destroy_child_node(parent, parent_index);
insert_child_node(parent, collapse_target, parent_index);
}
resize_node(collapse_target, dims);
}

66
cursetree/node.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef CURSETREE_NODE_H
#define CURSETREE_NODE_H
#include "dims.h"
#include "limits.h"
#include "surface.h"
#ifndef __NCURSES_H
typedef struct _win_st WINDOW;
#endif /* __NCURSES_H */
#define NODE_INIT_CHILDREN 4
#define NODE_CHILDREN_GROWTH 1.5
#define CINDEX_MAX (UCHAR_MAX)
#define NFLAG_EMPTY (0)
#define NFLAG_RESIZE (1 << 0)
#define NFLAG_SMALL (1 << 1)
#define NFLAG_SMALLCHILD (1 << 2)
#define NFLAG_LARGE (1 << 3)
/* Child Index */
typedef unsigned char cindex;
struct ct_node {
struct ct_surface *surface;
unsigned char flags;
struct ct_node *parent;
enum ct_axis axis;
struct ct_node **child;
cindex csize, cindex;
/* child imposed minimum bounds */
struct {
int wmin_abs;
int hmin_abs;
int wmin_rel;
int hmin_rel;
} cbounds;
};
/* === External Interface === */
#define IS_ROOT_NODE(node) (node->parent == NULL)
#define IS_PARENT_NODE(node) (node->cindex != 0)
struct ct_node *__node(struct ct_dims *const dims,
struct ct_bounds *const bounds,
struct ct_node *const parent);
struct ct_node *new_node(struct ct_bounds *const bounds,
struct ct_node *const parent);
void __destroy_node(struct ct_node *const node);
int resize_node(struct ct_node *const node, struct ct_dims *dims);
int insert_child_node(struct ct_node *const parent, struct ct_node *const child,
const cindex i);
int append_child_node(struct ct_node *const parent,
struct ct_node *const child);
struct ct_node *remove_child_node(struct ct_node *const parent,
const cindex i);
int destroy_child_node(struct ct_node *const parent, const cindex i);
void collapse_node(struct ct_node **const node, const int i,
const bool preserve_bounds);
#endif /* CURSETREE_NODE_H */

177
cursetree/pty/_pty.c Normal file
View file

@ -0,0 +1,177 @@
/* WARNING: The mkpty(), forkmkpty(), & bindpty() functions are based on
* WARNING: the glibc openpty(), forkpty(), & login_tty() functions respectively.
* WARNING:
* WARNING: The GNU C Library's COPYING & COPYING.LIB licenses are available at
* WARNING: LICENSES/GLIBC-COPYING & LICENSES/GLIBC-COPYING.LIB in this repo.
* WARNING:
* WARNING: login_tty() maintains the original University of California license
* WARNING: available at LICENSES/UC-LICENSE in this repo.
*/
/* _XOPEN_SOURCE unlocks <stdlib.h> pty/ptmx/pts declarations. */
#define _XOPEN_SOURCE 600
/* _GNU_SOURCE unlocks the ptsname_r declaration*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <asm/termbits.h> /* TIOC* constants */
#include <sys/ioctl.h>
#include "_pty.h"
/* Allocate PTY master and slave file descriptors.
* errno will have been set if newpty() fails.
*
* NOTE: This function is my alternative to GLibC's
* NOTE: openpty() function. It exists as a learning resource.
* REF: https://sourceware.org/git/glibc.git -> ./login/openpty.c
*/
int mkpty(int *fdmx, int *fds) {
int _fdmx = -1, _fds = -1;
char sname[TTYNAME_MAX];
// Configure PTY master (file descriptor)
_fdmx = posix_openpt(O_RDWR | O_NOCTTY);
if (_fdmx == -1)
return EXIT_FAILURE;
if (grantpt(_fdmx))
goto fail;
if (unlockpt(_fdmx))
goto fail;
#ifdef TIOCGPTPEER
/* Try to allocate slave fd solely based on PTMX fd first. */
_fds = ioctl(_fdmx, TIOCGPTPEER, O_RDWR | O_NOCTTY);
#endif
if (_fds == -1) {
/* Fallback to path-based slave fd allocation
* (if the kernel doesn't support TIOCGPTPEER, ie Linux <4.13) */
if(ptsname_r(_fdmx, sname, sizeof(sname)))
goto fail;
_fds = open(sname, O_RDWR | O_NOCTTY);
if (_fds == -1)
goto fail;
}
/* Propagate file descriptors via parameters */
*fdmx = _fdmx;
*fds = _fds;
return EXIT_SUCCESS;
fail:
if (_fdmx == -1) {
close(_fdmx);
if (_fds == -1)
close(_fds);
}
return EXIT_FAILURE;
}
/* Set fdty as the controlling terminal for the calling process.
* Returns 0 on success, and 1 on failure.
* NOTE: This function is my alternative to GLibC's
* NOTE: login_tty() function. It exists as a learning resource.
* REF: https://sourceware.org/git/glibc.git -> ./login/login_tty.c
*/
static inline int setctty(const int fdty) {
/* We assume any kernel compiling this defines TIOCSCTTY,
* otherwise this implementation won't exactly work... */
return (ioctl(fdty, TIOCSCTTY, 0) != -1);
}
/* Bind fdty (terminal fd) to stdin/stdout/stderr for the calling process.
* This functions blocks until the EBUSY (see `man dup2`) race condition lifts.
* NOTE: This function is my alternative to GLibC's
* NOTE: login_tty() function. It exists as a learning resource.
* WARNING: This function maintains the original University of California
* WARNING: LICENSE (1990-1993) as per glibc.git:/login/login_tty.c
* WARNING: available at LICENSES/UC-LICENSE in this repo.
* REF: https://sourceware.org/git/glibc.git -> ./login/login_tty.c
*/
void bindpty(const int fdty) {
/* Adjust stdin/stdout/stderr to refer to fd*/
BIND(fdty, STDIN_FILENO);
BIND(fdty, STDOUT_FILENO);
BIND(fdty, STDERR_FILENO);
if (fdty > 2)
close(fdty);
}
/* Allocate a PTY and fork, giving ptmx (master) to the parent
* and binding the child's stdin/stdout/stderr to pts (slave).
* Return value is indentical to fork(2).
* NOTE: This function is my alternative to GLibC's
* NOTE: forkpty() function. It exists as a learning resource.
* REF: https://sourceware.org/git/glibc.git -> ./login/forkpty.c
*/
pid_t forkmkpty(int *fdmx) {
int _fdmx, fds;
pid_t pid;
if (mkpty(&_fdmx, &fds))
return EXIT_FAILURE;
switch (pid = fork()) {
case -1:
close(_fdmx);
close(fds);
return -1;
case 0:
/* Child Process */
close(_fdmx);
setctty(fds);
bindpty(fds);
break;
default:
/* Parent Process */
close(fds);
// propagate ptmx (master) fd
*fdmx = _fdmx;
break;
}
/* Both Processes */
return pid;
}
/* Set pseudoterminal slave's window size.
* Returns 0 on success, and fails with -1 if the kernel doesn't
* implement this, or 1 for general errors (errno will be set).
* NOTE: Typically this is part of a glibc openpty() call.
*/
int setptsxy(const unsigned short rows, const unsigned short cols, const int fds) {
#ifndef TIOCSWINSZ
/* Fail if kernel doesn't support TIOCSWINSZ. */
return -1;
#else
struct winsize win = {
.ws_row = rows,
.ws_col = cols,
};
if (ioctl(fds, TIOCSWINSZ, &win) == -1)
return EXIT_FAILURE;
return EXIT_SUCCESS;
#endif /* TIOCSWINSZ */
}
/* Get pseudoterminal slave's window size.
* Returns 0 on success, and fails with -1 if the kernel doesn't
* implement this, or 1 for general errors (errno will be set).
*/
int getptsxy(unsigned short *rows, unsigned short *cols, const int fds) {
#ifndef TIOCGWINSZ
/* Fail if kernel doesn't support TIOCGWINSZ. */
return -1;
#else
struct winsize win = (struct winsize){ 0 };
if (ioctl(fds, TIOCGWINSZ, &win) == -1)
return EXIT_FAILURE;
*rows = win.ws_row;
*cols = win.ws_col;
return EXIT_SUCCESS;
#endif /* TIOCGWINSZ */
}

30
cursetree/pty/_pty.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef CURSETREE_PTY_H
#define CURSETREE_PTY_H
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef PATH_MAX
#define TTYNAME_MAX PATH_MAX
#else
#define TTYNAME_MAX 512
#endif /* PATH_MAX */
#define BIND(fdsrc, fddst) \
while (dup2(fdsrc, fddst) == -1 && errno == EBUSY) \
;
/* Custom implementation of glibc::openpty() */
int mkpty(int *fdmx, int *fds);
/* Custom implementation of glibc::login_tty() */
void bindpty(const int fdty);
/* Custom implementation of glibc::forkpty() */
pid_t forkmkpty(int *fdmx);
int setptsxy(const unsigned short rows, const unsigned short cols,
const int fds);
int getptsxy(unsigned short *rows, unsigned short *cols, const int fds);
#endif /* CURSETREE_PTY_H */

63
cursetree/pty/child.c Normal file
View file

@ -0,0 +1,63 @@
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include "child.h"
#include "epty.h"
/* SIGTERM grace period before a child is sent SIGKILL.
* Value is in microseconds (us), 1000us = 1ms */
#define CHILD_GRACE_US 100000
/* Check if a child is still alive by pid
* by kill(3p) sending a null signal (0).
*/
static int is_alive(struct d_child *child) {
return (kill(child->pid, 0) == 0);
}
/* Force a child to die, first by sending SIGTERM, otherwise SIGKILL.
* NOTE: killchild() ASSUMES the child is LIVING at the start.
* NOTE: Keep this in mind when threading!
*/
void killchild(struct d_child *child) {
int stat;
/* Request Death */
kill(child->pid, SIGTERM);
usleep(CHILD_GRACE_US);
/* Force Death */
if (is_alive(child))
kill(child->pid, SIGKILL);
/* Reap */
waitpid(child->pid, &stat, 0);
}
/* Fork to spawn a child process running bin/shfx in an "epty".
*/
int spawnchild(struct d_child *child) {
*child = (struct d_child){ 0 };
/* fork(2) and allocate an "error-piped pseudoterminal" (epty) */
switch (child->pid = forkepty(&child->fdmx, &child->fderr)) {
case -1:
perror("forkepty");
return EXIT_FAILURE;
case 0:
char *args[] = {"shfx", NULL};
execvp("shfx", args);
perror("execvp");
exit(1);
default:
usleep(1000);
char bufout[10];
bufout[9] = '\0';
read(child->fdmx, bufout, 9);
printf("child stdout: \"%s\"\n", bufout);
break;
}
return EXIT_SUCCESS;
}

14
cursetree/pty/child.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef CURSETREE_CHILD_H
#define CURSETREE_CHILD_H
#include <unistd.h>
struct d_child {
pid_t pid;
int fdmx; /* PTY master fd (read/write) */
int fderr; /* child's stderr (readonly) */
} __attribute__((packed, aligned(4)));
int spawnchild(struct d_child *child);
#endif /* CURSETREE_CHILD_H */

45
cursetree/pty/epty.c Normal file
View file

@ -0,0 +1,45 @@
#include <stdlib.h>
#include "_pty.h"
#define PIPE_READ 0
#define PIPE_WRITE 1
/* Allocate a PTY and fork, giving fdmx (master) to the parent
* and binding the child's stdin/stdout to fds (slave).
* Return value is indentical to fork(2).
* NOTE: This function is my alternative to GLibC's
* NOTE: forkpty() function. It exists as a learning resource.
* REF: https://sourceware.org/git/glibc.git -> ./login/forkpty.c
*/
pid_t forkepty(int *fdmx, int *fderr) {
// master/slave, and stderr pipe fds
int epipe[2];
pid_t pid;
if (pipe(epipe) == -1)
return EXIT_FAILURE;
switch (pid = forkmkpty(fdmx)) {
case -1:
/* forkmkpty() will close fdmx/fds for us */
close(epipe[PIPE_READ]);
close(epipe[PIPE_WRITE]);
return -1;
case 0:
/* Child Process */
/* forkmkpty() will close fdmx for us */
close(epipe[PIPE_READ]);
BIND(epipe[PIPE_WRITE], STDERR_FILENO);
break;
default:
/* Parent Process */
/* forkmkpty() will close fds for us */
close(epipe[PIPE_WRITE]);
*fderr = epipe[PIPE_READ];
break;
}
/* Both Processes */
return pid;
}

8
cursetree/pty/epty.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef CURSETREE_EPTY_H
#define CURSETREE_EPTY_H
#include <sys/types.h>
pid_t forkepty(int *fdmx, int *fderr);
#endif /* CURSETREE_EPTY_H */

81
cursetree/surface.c Normal file
View file

@ -0,0 +1,81 @@
#include <stdlib.h>
#include "ncrswrap.h"
#include "surface.h"
#include "_ncurses.h"
#include "ncurses.h"
static inline struct ct_surface *__surface(struct ct_dims *const dims,
struct ct_bounds *const bounds,
WINDOW *const win) {
struct ct_surface *surface;
surface = (struct ct_surface *)malloc(sizeof(struct ct_surface));
*surface = (struct ct_surface){
.dims = dims,
.bounds = bounds,
.win = win,
.updatereq = true,
};
return surface;
}
struct ct_surface *new_surface(struct ct_dims *const dims,
struct ct_bounds *const bounds) {
WINDOW *const win = new_window(dims->x, dims->y, dims->width, dims->height);
return __surface(dims, bounds, win);
}
void destroy_surface(const struct ct_surface *const surface) {
delwin(surface->win);
free(surface->dims);
}
void resize_surface(struct ct_surface *const surface,
struct ct_dims *const dims) {
free(surface->dims);
surface->dims = dims;
surface->updatereq = true;
resizemv_window(surface->win, dims->x, dims->y, dims->width, dims->height);
}
void rebind_surface(struct ct_surface *const surface,
struct ct_bounds *const bounds) {
free(surface->bounds);
surface->bounds = bounds;
}
int sfwidth(const struct ct_surface *const surface) {
return getmaxx(surface->win);
}
int sfheight(const struct ct_surface *const surface) {
return getmaxy(surface->win);
}
int sfposx(const struct ct_surface *const surface) {
return getbegx(surface->win);
}
int sfposy(const struct ct_surface *const surface) {
return getbegy(surface->win);
}
struct ct_dims *sfdims(const struct ct_surface *const surface) {
int x, y, width, height;
sfpos(surface, x, y);
sfsize(surface, width, height);
return new_dims(x, y, width, height);
}
void sfclear(struct ct_surface *const surface) {
wclear(surface->win);
surface->updatereq = true;
}
void sfflush(struct ct_surface *const surface) {
wnoutrefresh(surface->win);
surface->updatereq = false;
}

38
cursetree/surface.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef CURSETREE_SURFACE_H
#define CURSETREE_SURFACE_H
#include "dims.h"
#ifndef __NCURSES_H
typedef struct _win_st WINDOW;
#endif /* __NCURSES_H */
struct ct_surface {
struct ct_dims *dims;
struct ct_bounds *bounds;
WINDOW *win;
unsigned char updatereq;
};
struct ct_surface *new_surface(struct ct_dims *const dims,
struct ct_bounds *const bounds);
void destroy_surface(const struct ct_surface *const surface);
void resize_surface(struct ct_surface *const surface,
struct ct_dims *const dims);
void rebind_surface(struct ct_surface *const surface,
struct ct_bounds *const bounds);
#define sfpos(surface, x, y) (x = sfposx(surface), y = sfposy(surface))
#define sfsize(surface, width, height) (width = sfwidth(surface), height = sfheight(surface))
int sfwidth(const struct ct_surface *const surface);
int sfheight(const struct ct_surface *const surface);
int sfposx(const struct ct_surface *const surface);
int sfposy(const struct ct_surface *const surface);
struct ct_dims *sfdims(const struct ct_surface *const surface);
void sfclear(struct ct_surface *const surface);
void sfflush(struct ct_surface *const surface);
#endif /* CURSETREE_SURFACE_H */

38
cursetree/tree.c Normal file
View file

@ -0,0 +1,38 @@
#include <stdlib.h>
#include "ncrswrap.h"
#include "tree.h"
/*
*/
static inline struct ct_node *__root_node(void) {
return __node(termdims(), bounds_none(), NULL);
}
int init_tree(struct ct_tree **const tree) {
*tree = (struct ct_tree *)malloc(sizeof(struct ct_tree));
(*tree)->root = __root_node();
return EXIT_SUCCESS;
}
void destroy_tree(struct ct_tree *const tree) {
__destroy_node(tree->root);
end_ncurses();
free(tree);
}
void resize_tree(struct ct_tree *const tree, struct ct_dims *const dims) {
resize_node(tree->root, dims);
}
void switch_nodes(struct ct_node **const node0, struct ct_node **const node1) {
struct ct_node *const node0ptr = *node0;
struct ct_dims *const node0dims = dup_dims((*node0)->surface->dims);
struct ct_dims *const node1dims = dup_dims((*node1)->surface->dims);
*node0 = *node1;
resize_node(*node0, node1dims);
*node1 = node0ptr;
resize_node(*node1, node0dims);
}

15
cursetree/tree.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef CURSETREE_TREE_H
#define CURSETREE_TREE_H
#include "node.h"
struct ct_tree {
struct ct_node *root;
};
/* === External Interface === */
int init_tree(struct ct_tree **const tree);
void destroy_tree(struct ct_tree *const tree);
void resize_tree(struct ct_tree *const tree, struct ct_dims *const dims);
#endif /* CURSETREE_TREE_H */

12
cursetree/util.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef CURSETREE_UTIL_H
#define CURSETREE_UTIL_H
static inline int clampi(int val, int min, int max) {
if (val > max)
return max;
else if (val < min)
return min;
return val;
}
#endif /* CURSETREE_UTIL_H */