mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
* put stdin into nonblocking mode, retry on short write to stdout #78 * wrap getc_blocking() around a poll #78 * get CCCflag from terminfo. stop clearing the screen in render/startup * implement ncplane_at_cursor() #76 * ncplane_at_cursor() unit test for simples #76 * PlaneAtCursorComplex unit test #76 * PlaneAtCursorInsane() unit test #76 * nplane_at_cursor: return number of bytes, not just 0/-1 * uniblock-demo: add a bunch of pages from Unicode 12 * demo: make -d delay multiplier a float * egcpool: check offset against poolsize in check_validity() * notcurses_init(): set smkx/rmkx to NULL with pass_through_esc * PlaneAtCursorAttrs unit test #76 * add ncplane_styles() accessor
This commit is contained in:
parent
0cb1c24622
commit
a7d50b557d
@ -48,7 +48,7 @@ target_compile_options(notcurses
|
||||
)
|
||||
target_compile_definitions(notcurses
|
||||
PUBLIC
|
||||
_DEFAULT_SOURCE _XOPEN_SOURCE=700
|
||||
_DEFAULT_SOURCE _XOPEN_SOURCE=700 _GNU_SOURCE
|
||||
)
|
||||
|
||||
file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c)
|
||||
|
@ -216,9 +216,9 @@ API int ncplane_move_below(struct ncplane* RESTRICT n, struct ncplane* RESTRICT
|
||||
// Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'.
|
||||
API int ncplane_move_above(struct ncplane* RESTRICT n, struct ncplane* RESTRICT above);
|
||||
|
||||
// Retrieve the topmost cell at the cursor location on the specified plane,
|
||||
// returning it in 'c'. This copy is safe to use until the ncplane is destroyed.
|
||||
API void ncplane_at_cursor(const struct ncplane* n, cell* c);
|
||||
// Retrieve the cell at the cursor location on the specified plane, returning
|
||||
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
||||
API int ncplane_at_cursor(struct ncplane* n, cell* c);
|
||||
|
||||
// Manipulate the opaque user pointer associated with this plane.
|
||||
// ncplane_set_userptr() returns the previous userptr after replacing
|
||||
@ -321,6 +321,9 @@ API void ncplane_styles_on(struct ncplane* n, unsigned stylebits);
|
||||
// Remove the specified styles from the ncplane's existing spec.
|
||||
API void ncplane_styles_off(struct ncplane* n, unsigned stylebits);
|
||||
|
||||
// Return the current styling for this ncplane.
|
||||
API unsigned ncplane_styles(const struct ncplane* n);
|
||||
|
||||
// Fine details about terminal
|
||||
|
||||
// Returns a 16-bit bitmask in the LSBs of supported curses-style attributes
|
||||
@ -377,7 +380,7 @@ cell_styles_set(cell* c, unsigned stylebits){
|
||||
|
||||
// Get the style bits, shifted over into the LSBs.
|
||||
static inline unsigned
|
||||
cell_get_style(const cell* c){
|
||||
cell_styles(const cell* c){
|
||||
return (c->attrword & CELL_STYLE_MASK) >> 16u;
|
||||
}
|
||||
|
||||
@ -531,6 +534,10 @@ cell_egc_idx(const cell* c){
|
||||
return c->gcluster - 0x80;
|
||||
}
|
||||
|
||||
// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer
|
||||
// is invalidated by any further operation on the plane 'n', so...watch out!
|
||||
API const char* cell_extended_gcluster(const struct ncplane* n, const cell* c);
|
||||
|
||||
// load up six cells with the EGCs necessary to draw a box. returns 0 on
|
||||
// success, -1 on error. on error, any cells this function might
|
||||
// have loaded before the error are cell_release()d. There must be at least
|
||||
@ -592,6 +599,142 @@ API struct AVFrame* ncvisual_decode(struct ncvisual* nc);
|
||||
// appropriate size.
|
||||
API int ncvisual_render(struct ncvisual* ncv, int ystop, int xstop);
|
||||
|
||||
// A panelreel is an notcurses region devoted to displaying zero or more
|
||||
// line-oriented, contained panels between which the user may navigate. If at
|
||||
// least one panel exists, there is an active panel. As much of the active
|
||||
// panel as is possible is always displayed. If there is space left over, other
|
||||
// panels are included in the display. Panels can come and go at any time, and
|
||||
// can grow or shrink at any time.
|
||||
//
|
||||
// This structure is amenable to line- and page-based navigation via keystrokes,
|
||||
// scrolling gestures, trackballs, scrollwheels, touchpads, and verbal commands.
|
||||
|
||||
enum bordermaskbits {
|
||||
BORDERMASK_TOP = 0x1,
|
||||
BORDERMASK_RIGHT = 0x2,
|
||||
BORDERMASK_BOTTOM = 0x4,
|
||||
BORDERMASK_LEFT = 0x8,
|
||||
};
|
||||
|
||||
typedef struct panelreel_options {
|
||||
// require this many rows and columns (including borders). otherwise, a
|
||||
// message will be displayed stating that a larger terminal is necessary, and
|
||||
// input will be queued. if 0, no minimum will be enforced. may not be
|
||||
// negative. note that panelreel_create() does not return error if given a
|
||||
// WINDOW smaller than these minima; it instead patiently waits for the
|
||||
// screen to get bigger.
|
||||
int min_supported_cols;
|
||||
int min_supported_rows;
|
||||
|
||||
// use no more than this many rows and columns (including borders). may not be
|
||||
// less than the corresponding minimum. 0 means no maximum.
|
||||
int max_supported_cols;
|
||||
int max_supported_rows;
|
||||
|
||||
// desired offsets within the surrounding WINDOW (top right bottom left) upon
|
||||
// creation / resize. a panelreel_move() operation updates these.
|
||||
int toff, roff, boff, loff;
|
||||
// is scrolling infinite (can one move down or up forever, or is an end
|
||||
// reached?). if true, 'circular' specifies how to handle the special case of
|
||||
// an incompletely-filled reel.
|
||||
bool infinitescroll;
|
||||
// is navigation circular (does moving down from the last panel move to the
|
||||
// first, and vice versa)? only meaningful when infinitescroll is true. if
|
||||
// infinitescroll is false, this must be false.
|
||||
bool circular;
|
||||
// outcurses can draw a border around the panelreel, and also around the
|
||||
// component tablets. inhibit borders by setting all valid bits in the masks.
|
||||
// partially inhibit borders by setting individual bits in the masks. the
|
||||
// appropriate attr and pair values will be used to style the borders.
|
||||
// focused and non-focused tablets can have different styles. you can instead
|
||||
// draw your own borders, or forgo borders entirely.
|
||||
unsigned bordermask; // bitfield; 1s will not be drawn (see bordermaskbits)
|
||||
uint32_t borderattr; // attributes used for panelreel border, no color!
|
||||
int borderpair; // extended color pair for panelreel border
|
||||
unsigned tabletmask; // bitfield; same as bordermask but for tablet borders
|
||||
uint32_t tabletattr; // attributes used for tablet borders, no color!
|
||||
int tabletpair; // extended color pair for tablet borders
|
||||
uint32_t focusedattr;// attributes used for focused tablet borders, no color!
|
||||
int focusedpair; // extended color pair for focused tablet borders
|
||||
} panelreel_options;
|
||||
|
||||
struct tablet;
|
||||
struct panelreel;
|
||||
|
||||
// Create a panelreel according to the provided specifications. Returns NULL on
|
||||
// failure. w must be a valid WINDOW*, to which offsets are relative. Note that
|
||||
// there might not be enough room for the specified offsets, in which case the
|
||||
// panelreel will be clipped on the bottom and right. A minimum number of rows
|
||||
// and columns can be enforced via popts. efd, if non-negative, is an eventfd
|
||||
// that ought be written to whenever panelreel_touch() updates a tablet (this
|
||||
// is useful in the case of nonblocking input).
|
||||
struct panelreel* panelreel_create(struct ncplane* nc,
|
||||
const panelreel_options* popts,
|
||||
int efd);
|
||||
|
||||
// Tablet draw callback, provided a ncplane the first column that may be used,
|
||||
// the first row that may be used, the first column that may not be used, the
|
||||
// first row that may not be used, and a bool indicating whether output ought
|
||||
// be clipped at the top (true) or bottom (false). Rows and columns are
|
||||
// zero-indexed, and both are relative to the panel.
|
||||
//
|
||||
// Regarding clipping: it is possible that the tablet is only partially
|
||||
// displayed on the screen. If so, it is either partially present on the top of
|
||||
// the screen, or partially present at the bottom. In the former case, the top
|
||||
// is clipped (cliptop will be true), and output ought start from the end. In
|
||||
// the latter case, cliptop is false, and output ought start from the beginning.
|
||||
//
|
||||
// Returns the number of lines of output, which ought be less than or equal to
|
||||
// maxy - begy, and non-negative (negative values might be used in the future).
|
||||
typedef int (*tabletcb)(struct ncplane* p, int begx, int begy, int maxx,
|
||||
int maxy, bool cliptop);
|
||||
|
||||
// Add a new tablet to the provided panelreel, having the callback object
|
||||
// opaque. Neither, either, or both of after and before may be specified. If
|
||||
// neither is specified, the new tablet can be added anywhere on the reel. If
|
||||
// one or the other is specified, the tablet will be added before or after the
|
||||
// specified tablet. If both are specifid, the tablet will be added to the
|
||||
// resulting location, assuming it is valid (after->next == before->prev); if
|
||||
// it is not valid, or there is any other error, NULL will be returned.
|
||||
struct tablet* panelreel_add(struct panelreel* pr, struct tablet* after,
|
||||
struct tablet *before, tabletcb cb, void* opaque);
|
||||
|
||||
// Return the number of tablets.
|
||||
int panelreel_tabletcount(const struct panelreel* pr);
|
||||
|
||||
// Indicate that the specified tablet has been updated in a way that would
|
||||
// change its display. This will trigger some non-negative number of callbacks
|
||||
// (though not in the caller's context).
|
||||
int panelreel_touch(struct panelreel* pr, struct tablet* t);
|
||||
|
||||
// Delete the tablet specified by t from the panelreel specified by pr. Returns
|
||||
// -1 if the tablet cannot be found.
|
||||
int panelreel_del(struct panelreel* pr, struct tablet* t);
|
||||
|
||||
// Delete the active tablet. Returns -1 if there are no tablets.
|
||||
int panelreel_del_focused(struct panelreel* pr);
|
||||
|
||||
// Move to the specified location within the containing WINDOW.
|
||||
int panelreel_move(struct panelreel* pr, int x, int y);
|
||||
|
||||
// Redraw the panelreel in its entirety, for instance after
|
||||
// clearing the screen due to external corruption, or a SIGWINCH.
|
||||
int panelreel_redraw(struct panelreel* pr);
|
||||
|
||||
// Return the focused tablet, if any tablets are present. This is not a copy;
|
||||
// be careful to use it only for the duration of a critical section.
|
||||
struct tablet* panelreel_focused(struct panelreel* pr);
|
||||
|
||||
// Change focus to the next tablet, if one exists
|
||||
struct tablet* panelreel_next(struct panelreel* pr);
|
||||
|
||||
// Change focus to the previous tablet, if one exists
|
||||
struct tablet* panelreel_prev(struct panelreel* pr);
|
||||
|
||||
// Destroy a panelreel allocated with panelreel_create(). Does not destroy the
|
||||
// underlying WINDOW. Returns non-zero on failure.
|
||||
int panelreel_destroy(struct panelreel* pr);
|
||||
|
||||
#undef API
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <notcurses.h>
|
||||
#include "demo.h"
|
||||
|
||||
static const unsigned long GIG = 1000000000;
|
||||
|
||||
struct timespec demodelay = {
|
||||
.tv_sec = 1,
|
||||
.tv_nsec = 0,
|
||||
@ -19,7 +21,7 @@ usage(const char* exe, int status){
|
||||
fprintf(out, "usage: %s [ -h ] [ -k ] [ -d ns ] [ -f renderfile ] demospec\n", exe);
|
||||
fprintf(out, " -h: this message\n");
|
||||
fprintf(out, " -k: keep screen; do not switch to alternate\n");
|
||||
fprintf(out, " -d: delay in nanoseconds between demos\n");
|
||||
fprintf(out, " -d: delay multiplier (float)\n");
|
||||
fprintf(out, " -f: render to file in addition to stdout\n");
|
||||
fprintf(out, "all demos are run if no specification is provided\n");
|
||||
fprintf(out, " i: run intro\n");
|
||||
@ -173,13 +175,15 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
|
||||
}
|
||||
break;
|
||||
case 'd':{
|
||||
char* eptr;
|
||||
unsigned long ns = strtoul(optarg, &eptr, 0);
|
||||
if(*eptr){
|
||||
float f;
|
||||
if(sscanf(optarg, "%f", &f) != 1){
|
||||
fprintf(stderr, "Couldn't get a float from %s\n", optarg);
|
||||
usage(*argv, EXIT_FAILURE);
|
||||
}
|
||||
demodelay.tv_sec = ns / 1000000000;
|
||||
demodelay.tv_nsec = ns % 1000000000;
|
||||
uint64_t ns = f * GIG;
|
||||
printf("F: %f NS: %lu\n", f, ns);
|
||||
demodelay.tv_sec = ns / GIG;
|
||||
demodelay.tv_nsec = ns % GIG;
|
||||
break;
|
||||
}default:
|
||||
usage(*argv, EXIT_FAILURE);
|
||||
|
@ -45,9 +45,39 @@ int unicodeblocks_demo(struct notcurses* nc){
|
||||
{ .name = "Supplemental Mathematical Operators", .start = 0x2a00, },
|
||||
{ .name = "Glagolitic, Georgian Supplement, Tifinagh", .start = 0x2c00, },
|
||||
{ .name = "Supplemental Punctuation, CJK Radicals", .start = 0x2e00, },
|
||||
{ .name = "CJK Symbols and Punctuation", .start = 0x3000, },
|
||||
{ .name = "Enclosed CJK Letters and Months", .start = 0x3200, },
|
||||
{ .name = "CJK Unified Ideographs Extension A", .start = 0x3400, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3600, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3800, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3a00, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3c00, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3e00, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4000, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4200, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4400, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4600, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4800, },
|
||||
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4a00, },
|
||||
{ .name = "CJK Unified Ideographs Extension A, Yijang Hexagram", .start = 0x4c00, },
|
||||
{ .name = "CJK Unified Ideographs", .start = 0x4e00, },
|
||||
{ .name = "Yi Syllables", .start = 0xa000, },
|
||||
{ .name = "Yi Syllables", .start = 0xa200, },
|
||||
{ .name = "Yi Syllables, Yi Radicals, Lisu, Vai", .start = 0xa400, },
|
||||
{ .name = "Vai, Cyrillic Extended-B, Bamum, Tone Letters, Latin Extended-D", .start = 0xa600, },
|
||||
{ .name = "Halfwidth and Fullwidth Forms", .start = 0xff00, },
|
||||
{ .name = "Linear B Syllabary, Linear B Ideograms, Aegean Numbers, Phaistos Disc", .start = 0x10000, },
|
||||
{ .name = "Lycian, Carian, Coptic Epact Numbers, Old Italic, Gothic, Old Permic", .start = 0x10200, },
|
||||
{ .name = "Cuneiform", .start = 0x12000, },
|
||||
{ .name = "Cuneiform (cont.)", .start = 0x12200, },
|
||||
{ .name = "Byzantine Musical Symbols, Musical Symbols", .start = 0x1d000, },
|
||||
{ .name = "Ancient Greek Musical Notation, Mayan Numerals, Tai Xuan Jing, Counting Rods", .start = 0x1d200, },
|
||||
{ .name = "Mathematical Alphanumeric Symbols", .start = 0x1d400, },
|
||||
{ .name = "Mathematical Alphanumeric Symbols (cont.)", .start = 0x1d600, },
|
||||
{ .name = "Sutton SignWriting", .start = 0x1d800, },
|
||||
{ .name = "Glagolitic Supplement, Nyiakeng Puachue Hmong", .start = 0x1e000, },
|
||||
{ .name = "Ottoman Siyaq Numbers", .start = 0x1ed00, },
|
||||
{ .name = "Arabic Mathematical Alphabetic Symbols", .start = 0x1ee00, },
|
||||
{ .name = "Mahjong Tiles, Domino Tiles, Playing Cards", .start = 0x1f000, },
|
||||
{ .name = "Enclosed Ideographic Supplement, Miscellaneous Symbols", .start = 0x1f200, },
|
||||
{ .name = "Miscellaneous Symbols and Pictographs (cont.)", .start = 0x1f400, },
|
||||
@ -72,7 +102,6 @@ int unicodeblocks_demo(struct notcurses* nc){
|
||||
nstotal /= 10;
|
||||
subdelay.tv_sec = nstotal / 1000000000;
|
||||
subdelay.tv_nsec = nstotal % 1000000000;
|
||||
fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
|
||||
for(sindex = 0 ; sindex < sizeof(blocks) / sizeof(*blocks) ; ++sindex){
|
||||
uint32_t blockstart = blocks[sindex].start;
|
||||
const char* description = blocks[sindex].name;
|
||||
@ -105,7 +134,6 @@ fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
wchar_t w = blockstart + chunk * CHUNKSIZE + z;
|
||||
char utf8arr[MB_CUR_MAX + 1];
|
||||
// FIXME we can print wide ones here, just add an extra line
|
||||
if(wcwidth(w) >= 1 && iswprint(w)){
|
||||
int bwc = wcrtomb(utf8arr, w, &ps);
|
||||
if(bwc < 0){
|
||||
@ -122,8 +150,6 @@ fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
|
||||
if(cell_load(n, &c, utf8arr) < 0){ // FIXME check full len was eaten?
|
||||
return -1;;
|
||||
}
|
||||
/*ncplane_fg_rgb8(n, 0xad + z, 0xd8, 0xe6);
|
||||
ncplane_bg_rgb8(n, 0x20, 0x20, 0x20);*/
|
||||
cell_set_fg(&c, 0xad + z * 2, 0xd8, 0xe6 - z * 2);
|
||||
cell_set_bg(&c, 8 * chunk, 8 * chunk + z, 8 * chunk);
|
||||
if(ncplane_putc(n, &c) < 0){
|
||||
|
@ -179,6 +179,10 @@ egcpool_stash(egcpool* pool, const char* egc, size_t ulen){
|
||||
// Run a consistency check on the offset; ensure it's a valid, non-empty EGC.
|
||||
static inline bool
|
||||
egcpool_check_validity(const egcpool* pool, int offset){
|
||||
if(offset >= pool->poolsize){
|
||||
fprintf(stderr, "Offset (%d) greater than size (%d)\n", offset, pool->poolsize);
|
||||
return false;
|
||||
}
|
||||
const char* egc = pool->pool + offset;
|
||||
if(*egc == '\0'){
|
||||
fprintf(stderr, "Bad offset (%d): empty\n", offset);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
|
||||
#include <time.h>
|
||||
#include <term.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
@ -8,6 +9,7 @@
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <sys/poll.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <libavutil/version.h>
|
||||
@ -94,6 +96,7 @@ typedef struct notcurses {
|
||||
|
||||
struct termios tpreserved; // terminal state upon entry
|
||||
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
|
||||
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
|
||||
ncplane* top; // the contents of our topmost plane (initially entire screen)
|
||||
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
|
||||
FILE* renderfp; // debugging FILE* to which renderings are written
|
||||
@ -217,6 +220,26 @@ const void* ncplane_userptr_const(const ncplane* n){
|
||||
return n->userptr;
|
||||
}
|
||||
|
||||
// is the cursor in an invalid position? it never should be, but it's probably
|
||||
// better to make sure (it's cheap) than to read from/write to random crap.
|
||||
static bool
|
||||
cursor_invalid_p(const ncplane* n){
|
||||
if(n->y >= n->leny || n->x >= n->lenx){
|
||||
return true;
|
||||
}
|
||||
if(n->y < 0 || n->x < 0){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ncplane_at_cursor(ncplane* n, cell* c){
|
||||
if(cursor_invalid_p(n)){
|
||||
return -1;
|
||||
}
|
||||
return cell_duplicate(n, c, &n->fb[fbcellidx(n, n->y, n->x)]);
|
||||
}
|
||||
|
||||
void ncplane_dim_yx(const ncplane* n, int* rows, int* cols){
|
||||
if(rows){
|
||||
*rows = n->leny;
|
||||
@ -472,6 +495,8 @@ int ncplane_resize(ncplane* n, int keepy, int keepx, int keepleny,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// find the pointer on the z-index referencing the specified plane. writing to
|
||||
// this pointer will remove the plane (and everything below it) from the stack.
|
||||
static ncplane**
|
||||
find_above_ncplane(ncplane* n){
|
||||
notcurses* nc = n->nc;
|
||||
@ -507,6 +532,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
|
||||
char* longname_term = longname();
|
||||
fprintf(stderr, "Term: %s\n", longname_term ? longname_term : "?");
|
||||
nc->RGBflag = tigetflag("RGB") == 1;
|
||||
nc->CCCflag = tigetflag("ccc") == 1;
|
||||
if((nc->colors = tigetnum("colors")) <= 0){
|
||||
fprintf(stderr, "This terminal doesn't appear to support colors\n");
|
||||
nc->colors = 1;
|
||||
@ -578,6 +604,8 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
|
||||
if(!opts->pass_through_esc){
|
||||
term_verify_seq(&nc->smkx, "smkx");
|
||||
term_verify_seq(&nc->rmkx, "rmkx");
|
||||
}else{
|
||||
nc->smkx = nc->rmkx = NULL;
|
||||
}
|
||||
// Neither of these is supported on e.g. the "linux" virtual console.
|
||||
if(!opts->inhibit_alternate_screen){
|
||||
@ -590,6 +618,19 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
make_nonblocking(FILE* fp){
|
||||
int fd = fileno(fp);
|
||||
if(fd < 0){
|
||||
return -1;
|
||||
}
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if(flags < 0){
|
||||
return -1;
|
||||
}
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
notcurses* notcurses_init(const notcurses_options* opts){
|
||||
struct termios modtermios;
|
||||
notcurses* ret = malloc(sizeof(*ret));
|
||||
@ -599,6 +640,10 @@ notcurses* notcurses_init(const notcurses_options* opts){
|
||||
ret->ttyfp = opts->outfp;
|
||||
ret->renderfp = opts->renderfp;
|
||||
ret->ttyinfp = stdin; // FIXME
|
||||
if(make_nonblocking(ret->ttyinfp)){
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
||||
if((ret->ttyfd = fileno(ret->ttyfp)) < 0){
|
||||
fprintf(stderr, "No file descriptor was available in opts->outfp\n");
|
||||
free(ret);
|
||||
@ -644,7 +689,7 @@ notcurses* notcurses_init(const notcurses_options* opts){
|
||||
free_plane(ret->top);
|
||||
goto err;
|
||||
}
|
||||
term_emit(ret->clear, ret->ttyfp, false);
|
||||
// term_emit(ret->clear, ret->ttyfp, false);
|
||||
fprintf(ret->ttyfp, "\n"
|
||||
" notcurses %s by nick black\n"
|
||||
" terminfo from %s\n"
|
||||
@ -806,6 +851,10 @@ extended_gcluster(const ncplane* n, const cell* c){
|
||||
return n->pool.pool + idx;
|
||||
}
|
||||
|
||||
const char* cell_extended_gcluster(const struct ncplane* n, const cell* c){
|
||||
return extended_gcluster(n, c);
|
||||
}
|
||||
|
||||
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
|
||||
// number of columns occupied by this EGC (only an approximation; it's actually
|
||||
// a property of the font being used).
|
||||
@ -835,18 +884,12 @@ term_putc(FILE* out, const ncplane* n, const cell* c){
|
||||
|
||||
static void
|
||||
advance_cursor(ncplane* n, int cols){
|
||||
if(n->y >= n->leny){
|
||||
if(n->x >= n->lenx){
|
||||
return; // stuck!
|
||||
}
|
||||
if(cursor_invalid_p(n)){
|
||||
return; // stuck!
|
||||
}
|
||||
if((n->x += cols) >= n->lenx){
|
||||
if(n->y >= n->leny){
|
||||
n->x = n->lenx;
|
||||
}else{
|
||||
n->x -= n->lenx;
|
||||
++n->y;
|
||||
}
|
||||
++n->y;
|
||||
n->x -= n->lenx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,7 +925,7 @@ term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c)
|
||||
if(cell_inherits_style(c)){
|
||||
return 0; // change nothing
|
||||
}
|
||||
uint32_t cellattr = cell_get_style(c);
|
||||
uint32_t cellattr = cell_styles(c);
|
||||
if(cellattr == *curattr){
|
||||
return 0; // happy agreement, change nothing
|
||||
}
|
||||
@ -973,6 +1016,36 @@ int ncplane_move_bottom(ncplane* n){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
blocking_write(int fd, const char* buf, size_t buflen){
|
||||
size_t written = 0;
|
||||
do{
|
||||
ssize_t w = write(fd, buf + written, buflen - written);
|
||||
if(w < 0){
|
||||
if(errno != EAGAIN && errno != EWOULDBLOCK){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
written += w;
|
||||
}
|
||||
}while(written < buflen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// determine the best palette for the current frame, and write the necessary
|
||||
// escape sequences to 'out'.
|
||||
static int
|
||||
prep_optimized_palette(notcurses* nc, FILE* out){
|
||||
if(nc->RGBflag){
|
||||
return 0; // DirectColor, no need to write palette
|
||||
}
|
||||
if(!nc->CCCflag){
|
||||
return 0; // can't change palette
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIXME this needs to keep an invalidation bitmap, rather than blitting the
|
||||
// world every time
|
||||
int notcurses_render(notcurses* nc){
|
||||
@ -986,8 +1059,11 @@ int notcurses_render(notcurses* nc){
|
||||
if(out == NULL){
|
||||
return -1;
|
||||
}
|
||||
prep_optimized_palette(nc, out); // FIXME do what on failure?
|
||||
uint32_t curattr = 0; // current attributes set (does not include colors)
|
||||
term_emit(nc->clear, out, false);
|
||||
// no need to write a clearscreen, since we update everything that's been
|
||||
// changed. just move the physical cursor to the upper left corner.
|
||||
term_emit(tiparm(nc->cup, 0, 0), out, false);
|
||||
unsigned lastr, lastg, lastb;
|
||||
unsigned lastbr, lastbg, lastbb;
|
||||
// we can elide a color escape iff the color has not changed between the two
|
||||
@ -1060,8 +1136,8 @@ int notcurses_render(notcurses* nc){
|
||||
}
|
||||
ret |= fclose(out);
|
||||
fflush(nc->ttyfp);
|
||||
ssize_t w = write(nc->ttyfd, buf, buflen);
|
||||
if(w < 0 || (size_t)w != buflen){
|
||||
fprintf(nc->ttyfp, "%s", buf);
|
||||
if(blocking_write(nc->ttyfd, buf, buflen)){
|
||||
ret = -1;
|
||||
}
|
||||
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
|
||||
@ -1119,7 +1195,7 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){
|
||||
}
|
||||
|
||||
int ncplane_putc(ncplane* n, const cell* c){
|
||||
if(n->y >= n->leny || n->x >= n->lenx){
|
||||
if(cursor_invalid_p(n)){
|
||||
return -1;
|
||||
}
|
||||
cell* targ = &n->fb[fbcellidx(n, n->y, n->x)];
|
||||
@ -1230,6 +1306,10 @@ void ncplane_styles_set(ncplane* n, unsigned stylebits){
|
||||
((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
unsigned ncplane_styles(const ncplane* n){
|
||||
return (n->attrword & CELL_STYLE_MASK) >> 16u;
|
||||
}
|
||||
|
||||
int ncplane_printf(ncplane* n, const char* format, ...){
|
||||
int ret;
|
||||
va_list va;
|
||||
@ -1365,6 +1445,7 @@ handle_getc(const notcurses* nc __attribute__ ((unused)), cell* c, int kpress,
|
||||
if(kpress == 0x04){ // ctrl-d
|
||||
return -1;
|
||||
}
|
||||
// FIXME look for keypad
|
||||
if(kpress < 0x80){
|
||||
c->gcluster = kpress;
|
||||
}else{
|
||||
@ -1387,18 +1468,31 @@ int notcurses_getc(const notcurses* nc, cell* c, ncspecial_key* special){
|
||||
return handle_getc(nc, c, r, special);
|
||||
}
|
||||
|
||||
// we set our infd to non-blocking on entry, so to do a blocking call (without
|
||||
// burning cpu) we'll need to set up a poll().
|
||||
int notcurses_getc_blocking(const notcurses* nc, cell* c, ncspecial_key* special){
|
||||
int r = getc(nc->ttyinfp);
|
||||
if(r < 0){
|
||||
if(errno == EINTR){
|
||||
if(resize_seen){
|
||||
resize_seen = 0;
|
||||
c->gcluster = 0;
|
||||
*special = NCKEY_RESIZE;
|
||||
return 1;
|
||||
}
|
||||
struct pollfd pfd = {
|
||||
.fd = fileno(nc->ttyinfp),
|
||||
.events = POLLIN | POLLRDHUP,
|
||||
.revents = 0,
|
||||
};
|
||||
int pret;
|
||||
while((pret = poll(&pfd, 1, -1)) >= 0){
|
||||
if(pret == 0){
|
||||
continue;
|
||||
}
|
||||
int r = getc(nc->ttyinfp);
|
||||
if(r < 0){
|
||||
if(errno == EINTR){
|
||||
if(resize_seen){
|
||||
resize_seen = 0;
|
||||
c->gcluster = 0;
|
||||
*special = NCKEY_RESIZE;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return handle_getc(nc, c, r, special);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
return handle_getc(nc, c, r, special);
|
||||
return -1;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <cstdlib>
|
||||
#include <notcurses.h>
|
||||
#include "main.h"
|
||||
|
||||
@ -394,3 +395,148 @@ TEST_F(NcplaneTest, GrowPlane) {
|
||||
// FIXME check dims, pos
|
||||
ASSERT_EQ(0, ncplane_destroy(newp));
|
||||
}
|
||||
|
||||
// we ought be able to see what we're about to render, or have just rendered, or
|
||||
// in any case whatever's in the virtual framebuffer for a plane
|
||||
TEST_F(NcplaneTest, PlaneAtCursorSimples){
|
||||
const char STR1[] = "Jackdaws love my big sphinx of quartz";
|
||||
const char STR2[] = "Cwm fjord bank glyphs vext quiz";
|
||||
const char STR3[] = "Pack my box with five dozen liquor jugs";
|
||||
ncplane_styles_set(n_, 0);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR1));
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n_, &dimy, &dimx);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - strlen(STR2)));
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR2));
|
||||
int y, x;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
ASSERT_EQ(2, y);
|
||||
ASSERT_EQ(0, x);
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR3));
|
||||
cell testcell = CELL_TRIVIAL_INITIALIZER;
|
||||
ASSERT_EQ(0, ncplane_at_cursor(n_, &testcell)); // want nothing at the cursor
|
||||
EXPECT_EQ(0, testcell.gcluster);
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
|
||||
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR1
|
||||
EXPECT_EQ(STR1[0], testcell.gcluster);
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - 1));
|
||||
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want last char of STR2
|
||||
EXPECT_EQ(STR2[strlen(STR2) - 1], testcell.gcluster);
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
// FIXME maybe check all cells?
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
}
|
||||
|
||||
// ensure we read back what's expected for latinesque complex characters
|
||||
TEST_F(NcplaneTest, PlaneAtCursorComplex){
|
||||
const char STR1[] = "Σιβυλλα τι θελεις; respondebat illa:";
|
||||
const char STR2[] = "αποθανειν θελω";
|
||||
const char STR3[] = "Война и мир"; // just thrown in to complicate things
|
||||
ncplane_styles_set(n_, 0);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR1));
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n_, &dimy, &dimx);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - mbstowcs(NULL, STR2, 0)));
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR2));
|
||||
int y, x;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
ASSERT_EQ(2, y);
|
||||
ASSERT_EQ(0, x);
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR3));
|
||||
cell testcell = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_cursor(n_, &testcell); // should be nothing at the cursor
|
||||
EXPECT_EQ(0, testcell.gcluster);
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
|
||||
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR1
|
||||
EXPECT_STREQ("Σ", cell_extended_gcluster(n_, &testcell));
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - mbstowcs(NULL, STR2, 0)));
|
||||
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR2
|
||||
EXPECT_STREQ("α", cell_extended_gcluster(n_, &testcell));
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
// FIXME maybe check all cells?
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
}
|
||||
|
||||
// half-width, double-width, huge-yet-narrow, all that crap
|
||||
TEST_F(NcplaneTest, PlaneAtCursorInsane){
|
||||
const char EGC0[] = "\uffe0"; // fullwidth cent sign ¢
|
||||
const char EGC1[] = "\u00c5"; // neutral A with ring above Å
|
||||
const char EGC2[] = "\u20a9"; // half-width won ₩
|
||||
const char EGC3[] = "\u212b"; // ambiguous angstrom Å
|
||||
const char EGC4[] = "\ufdfd"; // neutral yet huge bismillah ﷽
|
||||
std::array<cell, 5> tcells;
|
||||
for(auto i = 0u ; i < tcells.size() ; ++i){
|
||||
cell_init(&tcells[i]);
|
||||
}
|
||||
ASSERT_LT(1, cell_load(n_, &tcells[0], EGC0));
|
||||
ASSERT_LT(1, cell_load(n_, &tcells[1], EGC1));
|
||||
ASSERT_LT(1, cell_load(n_, &tcells[2], EGC2));
|
||||
ASSERT_LT(1, cell_load(n_, &tcells[3], EGC3));
|
||||
ASSERT_LT(1, cell_load(n_, &tcells[4], EGC4));
|
||||
for(auto i = 0u ; i < tcells.size() ; ++i){
|
||||
ASSERT_LT(1, ncplane_putc(n_, &tcells[i]));
|
||||
}
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
int x = 0;
|
||||
for(auto i = 0u ; i < tcells.size() ; ++i){
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, 0, x));
|
||||
cell testcell = CELL_TRIVIAL_INITIALIZER;
|
||||
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell));
|
||||
EXPECT_STREQ(cell_extended_gcluster(n_, &tcells[i]),
|
||||
cell_extended_gcluster(n_, &testcell));
|
||||
EXPECT_EQ(0, testcell.attrword);
|
||||
wchar_t w;
|
||||
ASSERT_LT(0, mbtowc(&w, cell_extended_gcluster(n_, &tcells[i]), MB_CUR_MAX));
|
||||
if(wcwidth(w) == 2){
|
||||
EXPECT_EQ(CELL_WIDEASIAN_MASK, testcell.channels);
|
||||
++x;
|
||||
}else{
|
||||
EXPECT_EQ(0, testcell.channels);
|
||||
}
|
||||
++x;
|
||||
}
|
||||
}
|
||||
|
||||
// test that we read back correct attrs/colors despite changing defaults
|
||||
TEST_F(NcplaneTest, PlaneAtCursorAttrs){
|
||||
const char STR1[] = "this has been a world destroyer production";
|
||||
const char STR2[] = "not to mention dank";
|
||||
const char STR3[] = "da chronic lives";
|
||||
ncplane_styles_set(n_, CELL_STYLE_BOLD);
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR1));
|
||||
int y, x;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y + 1, x - strlen(STR2)));
|
||||
ncplane_styles_on(n_, CELL_STYLE_ITALIC);
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR2));
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y + 2, x - strlen(STR3)));
|
||||
ncplane_styles_off(n_, CELL_STYLE_BOLD);
|
||||
ASSERT_LT(0, ncplane_putstr(n_, STR3));
|
||||
ncplane_styles_off(n_, CELL_STYLE_ITALIC);
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
int newx;
|
||||
ncplane_cursor_yx(n_, &y, &newx);
|
||||
EXPECT_EQ(newx, x);
|
||||
cell testcell = CELL_TRIVIAL_INITIALIZER;
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y - 2, x - 1));
|
||||
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
|
||||
EXPECT_EQ(testcell.gcluster, STR1[strlen(STR1) - 1]);
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y - 1, x - 1));
|
||||
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
|
||||
EXPECT_EQ(testcell.gcluster, STR2[strlen(STR2) - 1]);
|
||||
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y, x - 1));
|
||||
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
|
||||
EXPECT_EQ(testcell.gcluster, STR3[strlen(STR3) - 1]);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user