ncneofetch rewritten in CLI mode

ncneofetch was previously direct mode followed by rendered mode with
margins, a Frankenstein application if one ever existed. Rewrite it
using CLI mode, extending the latter as necessary to accomplish this
task. We now have one fewer dependency on direct mode, we have better
proven out CLI mode, and we get a ~30% reduction in ncneofetch runtime.
Good stuff! Closes #2030.

Add ncplane_scrollup() and ncplane_scrollup_child()
Cleans up ncport.h
Eliminates some inconsequential memory leaks in ncneofetch
Add SPRIXEL_UNSEEN to avoid invalid moves on
 not-yet-displayed sprixels
This commit is contained in:
nick black 2021-08-12 02:36:36 -04:00 committed by nick black
parent 47fa86f90d
commit 4d6526a61d
15 changed files with 141 additions and 51 deletions

View File

@ -2,6 +2,10 @@ This document attempts to list user-visible changes and any major internal
rearrangements of Notcurses.
* 2.3.14 (not yet released)
* `ncneofetch` has been changed to use "CLI mode" instead of Direct Mode,
as a proof of concept. It is very likely that Direct Mode will be
deprecated for ABI3. New code ought not be written using it.
* Added `ncplane_scrollup()` and `ncplane_scrollup_child()`.
* Fixed grotesque errors in `ncplane_set_*_palindex()`.
* 2.3.13 (2021-08-04)

View File

@ -883,6 +883,15 @@ scrolling is enabled).
// controlled with ncplane_set_scrolling(). Returns true if scrolling was
// previously enabled, or false if it was disabled.
bool ncplane_set_scrolling(struct ncplane* n, bool scrollp);
// Effect |r| scroll events on the plane |n|. Returns an error if |n| is not
// a scrolling plane, and otherwise returns the number of lines scrolled.
int ncplane_scrollup(struct ncplane* n, int r);
// Scroll |n| up until |child| is no longer hidden beneath it. Returns an
// error if |child| is not a child of |n|, or |n| is not scrolling, or |child|
// is fixed. Returns the number of scrolling events otherwise (might be 0).
int ncplane_scrollup_child(struct ncplane* n, const struct ncplane* child);
```
Planes can be freely resized, though they must retain a positive size in

View File

@ -209,6 +209,10 @@ typedef struct ncplane_options {
**bool ncplane_scrolling_p(const struct ncplane* ***n***);**
**int ncplane_scrollup(struct ncplane* ***n***, int ***r***);**
**int ncplane_scrollup_child(struct ncplane* ***n***, const struct ncplane* ***child***);**
**int ncplane_rotate_cw(struct ncplane* ***n***);**
**int ncplane_rotate_ccw(struct ncplane* ***n***);**

View File

@ -5,37 +5,30 @@
extern "C" {
#endif
// take host byte order and turn it into network (reverse on LE, no-op on BE),
// then reverse that, guaranteeing LE. htole(x) == ltohe(x).
#if defined(__linux__) || defined(__gnu_hurd__)
#include <poll.h>
#include <termios.h>
#include <byteswap.h>
#include <netinet/in.h>
#define htole(x) (__bswap_32(htonl(x)))
#elif defined(__APPLE__)
#include <poll.h>
#include <termios.h>
#include <netinet/in.h>
#include <libkern/OSByteOrder.h>
#define htole(x) (OSSwapInt32(htonl(x)))
#elif defined(__MINGW64__)
#define htole(x) (x) // FIXME
// Platform-dependent preprocessor material (includes and definitions) needed
// to compile against Notcurses. A critical definition is htole(), which forces
// 32-bit values to little-endian (as used in the nccell gcluster field). This
// ought be defined so that it's a a no-op on little-endian builds.
#if defined(__MINGW64__) // Windows
// FIXME placeholders, need real solutions here
#define wcwidth(w) 1
#define wcswidth(w, s) (s)
#define sigset_t int
#define sigemptyset(x)
#define O_CLOEXEC O_NOINHERIT
#define O_NONBLOCK 0
#define O_DIRECTORY 0
#define S_IFLNK 0
#else // bsd
#define htole(x) (x) // FIXME are all windows installs LE?
#else // Non-Windows, UNIX-common
#include <poll.h>
#include <netinet/in.h>
#include <termios.h>
#if defined(__linux__) || defined(__gnu_hurd__) // Linux/Hurd
#include <byteswap.h>
#define htole(x) (__bswap_32(htonl(x)))
#elif defined(__APPLE__) // macOS
#include <libkern/OSByteOrder.h>
#define htole(x) (OSSwapInt32(htonl(x)))
#else // BSD
#include <sys/endian.h>
#define htole(x) (bswap32(htonl(x)))
#endif
#endif
#ifdef __cplusplus
} // extern "C"

View File

@ -1570,8 +1570,23 @@ API int ncplane_move_below(struct ncplane* RESTRICT n,
struct ncplane* RESTRICT below);
// Return the plane below this one, or NULL if this is at the bottom.
API struct ncplane* ncplane_below(struct ncplane* n);
API struct ncplane* ncplane_above(struct ncplane* n);
API struct ncplane* ncplane_below(struct ncplane* n)
__attribute__ ((nonnull (1)));
// Return the plane above this one, or NULL if this is at the top.
API struct ncplane* ncplane_above(struct ncplane* n)
__attribute__ ((nonnull (1)));
// Effect |r| scroll events on the plane |n|. Returns an error if |n| is not
// a scrolling plane, and otherwise returns the number of lines scrolled.
API int ncplane_scrollup(struct ncplane* n, int r)
__attribute__ ((nonnull (1)));
// Scroll |n| up until |child| is no longer hidden beneath it. Returns an
// error if |child| is not a child of |n|, or |n| is not scrolling, or |child|
// is fixed. Returns the number of scrolling events otherwise (might be 0).
API int ncplane_scrollup_child(struct ncplane* n, const struct ncplane* child)
__attribute__ ((nonnull (1, 2)));
// Rotate the plane π/2 radians clockwise or counterclockwise. This cannot
// be performed on arbitrary planes, because glyphs cannot be arbitrarily

View File

@ -17,6 +17,7 @@ extern "C" {
#ifdef __MINGW64__
#include <Lmcons.h>
#define wcswidth(w, s) (s)
#define tcgetattr(x, y) -1
#define tcsetattr(x, y, z) -1
#define ECHO 0
@ -27,6 +28,10 @@ extern "C" {
#define TCSAFLUSH 0
#define TCSANOW 0
#define O_NOCTTY 0
#define O_CLOEXEC O_NOINHERIT
#define O_NONBLOCK 0
#define O_DIRECTORY 0
#define S_IFLNK 0
#define SA_SIGINFO 0
#define SA_RESETHAND 0
#define SIGQUIT 0

View File

@ -200,8 +200,8 @@ int unicodeblocks_demo(struct notcurses* nc){
{ .name = "CJK Compatibility Ideographs, Alphabetic Presentation Forms", .start = 0xfa00, },
{ .name = "Arabic Presentation Forms-A", .start = 0xfc00, },
{ .name = "Halfwidth and Fullwidth Forms", .start = 0xfe00, },
{ .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 = "Linear B Syllabary, Linear B Ideograms, Aegean, Phaistos Disc", .start = 0x10000, },
{ .name = "Lycian, Carian, Coptic Epact, Old Italic, Gothic, Old Permic", .start = 0x10200, },
{ .name = "Cuneiform", .start = 0x12000, },
{ .name = "Cuneiform (cont.)", .start = 0x12200, },
{ .name = "Byzantine Musical Symbols, Musical Symbols", .start = 0x1d000, },
@ -278,10 +278,10 @@ int unicodeblocks_demo(struct notcurses* nc){
if(ncplane_set_fg_rgb8(n, 0x40, 0xc0, 0x40)){
return -1;
}
if(ncplane_cursor_move_yx(n, 6 + BLOCKSIZE / CHUNKSIZE, 2)){
if(ncplane_cursor_move_yx(n, 6 + BLOCKSIZE / CHUNKSIZE, 4)){
return -1;
}
if(ncplane_printf(n, "%*.*s", maxx - 4, maxx - 4, "") <= 0){
if(ncplane_printf(n, "%*.*s", maxx - 8, maxx - 8, "") <= 0){
return -1;
}
if(ncplane_printf_aligned(n, 6 + BLOCKSIZE / CHUNKSIZE, NCALIGN_CENTER, "%s", description) <= 0){

View File

@ -48,7 +48,12 @@ typedef struct fetched_info {
static void
free_fetched_info(fetched_info* fi){
free(fi->cpu_model);
free(fi->hostname);
free(fi->username);
free(fi->kernel);
free(fi->kernver);
free(fi->distro_pretty);
free(fi->term);
}
static int
@ -337,7 +342,7 @@ xnu_ncneofetch(fetched_info* fi){
.logofile = "/System/Library/PrivateFrameworks/LoginUIKit.framework/Versions/A/Frameworks/LoginUICore.framework/Versions/A/Resources/apple@2x.png",
};
fi->neologo = get_neofetch_art("Darwin");
fi->distro_pretty = "OS X 11.4 (Big Sur)"; // FIXME
fi->distro_pretty = strdup("OS X 11.4 (Big Sur)"); // FIXME
return &fbsd;
}
@ -380,9 +385,11 @@ static int
infoplane_notcurses(struct notcurses* nc, const fetched_info* fi, int planeheight){
const int planewidth = 72;
int dimy;
int y;
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, NULL);
ncplane_cursor_yx(std, &y, NULL);
struct ncplane_options nopts = {
.y = dimy - planeheight,
.y = y,
.x = NCALIGN_CENTER,
.rows = planeheight,
.cols = planewidth,
@ -428,7 +435,6 @@ infoplane_notcurses(struct notcurses* nc, const fetched_info* fi, int planeheigh
}else{
ncplane_printf_aligned(infop, 4, NCALIGN_LEFT, " TERM: %s", fi->term);
}
free(fi->term);
ncplane_printf_aligned(infop, 4, NCALIGN_RIGHT, "Screen0: %dx%d ", fi->dimx, fi->dimy);
ncplane_printf_aligned(infop, 5, NCALIGN_LEFT, " LANG: %s", fi->lang);
#ifndef __MINGW64__
@ -469,6 +475,7 @@ infoplane_notcurses(struct notcurses* nc, const fetched_info* fi, int planeheigh
ncchannels_set_fg_rgb8(&channels, 0, 0, 0);
ncchannels_set_bg_rgb8(&channels, 0x50, 0x50, 0x50);
ncplane_set_base(infop, " ", 0, channels);
ncplane_scrollup_child(std, infop);
if(notcurses_render(nc)){
return -1;
}
@ -522,7 +529,7 @@ neologo_present(struct notcurses* nc, const char* nlogo){
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
const int leftpad = (dimx - maxlinelen) / 2;
for(int i = 0 ; i < linecount ; ++i){
printf("%*.*s%s", leftpad, leftpad, "", lines[i]);
ncplane_printf(n, "%*.*s%s", leftpad, leftpad, "", lines[i]);
free(lines[i]);
}
free(lines);
@ -542,6 +549,8 @@ display_thread(void* vmarshal){
struct marshal* m = vmarshal;
drawpalette(m->nc);
notcurses_render(m->nc);
ncplane_set_bg_default(notcurses_stdplane(m->nc));
ncplane_set_fg_default(notcurses_stdplane(m->nc));
// we've just rendered, so any necessary scrolling has been performed. draw
// our image wherever the palette ended, and then scroll as necessary to
// make that new plane visible.
@ -553,6 +562,8 @@ display_thread(void* vmarshal){
ncv = ncvisual_from_file(m->dinfo->logofile);
}
if(ncv){
int y;
ncplane_cursor_yx(notcurses_stdplane_const(m->nc), &y, NULL);
struct ncvisual_options vopts = {
.x = NCALIGN_CENTER,
.blitter = NCBLIT_PIXEL,
@ -561,7 +572,14 @@ display_thread(void* vmarshal){
};
struct ncplane* iplane = ncvisual_render(m->nc, ncv, &vopts);
ncvisual_destroy(ncv);
if(iplane){
ncplane_move_yx(iplane, y, 0);
ncplane_scrollup_child(notcurses_stdplane(m->nc), iplane);
notcurses_render(m->nc);
ncplane_cursor_move_yx(notcurses_stdplane(m->nc),
ncplane_abs_y(iplane) + ncplane_dim_y(iplane), 0);
return NULL;
}
}
}
if(m->neologo){
@ -627,12 +645,10 @@ ncneofetch(struct notcurses* nc){
}
int main(void){
if(setlocale(LC_ALL, "") == NULL){
fprintf(stderr, "Warning: couldn't set locale based off LANG\n");
}
struct notcurses_options opts = {
.flags = NCOPTION_SUPPRESS_BANNERS | NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN | NCOPTION_NO_CLEAR_BITMAPS
.flags = NCOPTION_SUPPRESS_BANNERS
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_NO_CLEAR_BITMAPS
| NCOPTION_PRESERVE_CURSOR,
};
struct notcurses* nc = notcurses_init(&opts, NULL);

View File

@ -1145,10 +1145,8 @@ int kitty_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
}
if(animated){
fbuf_free(&s->glyph);
s->invalidated = SPRIXEL_LOADED;
}else{
s->invalidated = SPRIXEL_LOADED;
}
s->invalidated = SPRIXEL_LOADED;
return ret;
}

View File

@ -1544,6 +1544,43 @@ void scroll_down(ncplane* n){
}
}
int ncplane_scrollup(ncplane* n, int r){
if(!ncplane_scrolling_p(n)){
logerror("can't scroll %d on non-scrolling plane\n", r);
return -1;
}
if(r < 0){
logerror("can't scroll %d lines\n", r);
return -1;
}
while(r-- > 0){
scroll_down(n);
}
return 0;
}
// Scroll |n| up until |child| is no longer hidden beneath it. Returns an
// error if |child| is not a child of |n|, or |n| is not scrolling, or |child|
// is fixed. Returns the number of scrolling events otherwise (might be 0).
int ncplane_scrollup_child(ncplane* n, const ncplane* child){
if(ncplane_parent_const(child) != n){
logerror("not a child of specified plane\n");
return -1;
}
if(child->fixedbound){
logerror("child plane is fixed\n");
return -1;
}
int parend = ncplane_abs_y(n) + ncplane_dim_y(n); // where parent ends
int chend = ncplane_abs_y(child) + ncplane_dim_y(child); // where child ends
if(chend <= parend){
return 0;
}
int r = chend - parend; // how many rows we need scroll parent
int ret = ncplane_scrollup(n, r);
return ret;
}
int nccell_width(const ncplane* n __attribute__ ((unused)), const nccell* c){
return nccell_cols(c);
}

View File

@ -840,7 +840,7 @@ clean_sprixels(notcurses* nc, ncpile* p, fbuf* f){
}
continue; // don't account as an elision
}
if(s->invalidated == SPRIXEL_MOVED || s->invalidated == SPRIXEL_INVALIDATED){
if(s->invalidated == SPRIXEL_MOVED || s->invalidated == SPRIXEL_INVALIDATED || s->invalidated == SPRIXEL_UNSEEN){
int y, x;
ncplane_yx(s->n, &y, &x);
//fprintf(stderr, "1 MOVING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n);
@ -929,6 +929,7 @@ rasterize_scrolls(ncpile* p, fbuf* f){
if(goto_location(p->nc, f, p->dimy, 0)){
return -1;
}
// FIXME if bce is set, we need reset background color
while(p->scrolls){
if(fbuf_putc(f, '\n') < 0){
return -1;
@ -955,9 +956,9 @@ rasterize_sprixels(notcurses* nc, ncpile* p, fbuf* f){
while( (s = *parent) ){
//fprintf(stderr, "YARR HARR HARR SPIRXLE %u STATE %d\n", s->id, s->invalidated);
if(s->invalidated == SPRIXEL_INVALIDATED){
//fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n);
int y,x;
int y, x;
ncplane_yx(s->n, &y, &x);
//fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n);
int r = sprite_draw(&nc->tcache, p, s, f, y + nc->margin_t, x + nc->margin_l);
if(r < 0){
return -1;
@ -966,7 +967,7 @@ rasterize_sprixels(notcurses* nc, ncpile* p, fbuf* f){
nc->rstate.hardcursorpos = true;
}else if(s->invalidated == SPRIXEL_LOADED){
if(nc->tcache.pixel_commit){
int y,x;
int y, x;
ncplane_yx(s->n, &y, &x);
if(goto_location(nc, f, y + nc->margin_t, x + nc->margin_l)){
return -1;

View File

@ -933,7 +933,13 @@ int sixel_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
}
if(s->invalidated == SPRIXEL_MOVED){
for(int yy = s->movedfromy ; yy < s->movedfromy + s->dimy && yy < p->dimy ; ++yy){
if(yy < 0){
continue;
}
for(int xx = s->movedfromx ; xx < s->movedfromx + s->dimx && xx < p->dimx ; ++xx){
if(xx < 0){
continue;
}
struct crender *r = &p->crender[yy * p->dimx + xx];
if(!r->sprixel || sprixel_state(r->sprixel, yy, xx) != SPRIXCELL_OPAQUE_SIXEL){
r->s.damaged = 1;

View File

@ -67,7 +67,7 @@ sprixel* sprixel_recycle(ncplane* n){
// store the original (absolute) coordinates from which we moved, so that
// we can invalidate them in sprite_draw().
void sprixel_movefrom(sprixel* s, int y, int x){
if(s->invalidated != SPRIXEL_HIDE){
if(s->invalidated != SPRIXEL_HIDE && s->invalidated != SPRIXEL_UNSEEN){
if(s->invalidated != SPRIXEL_MOVED){
// FIXME if we're Sixel, we need to effect any wipes that were run
// (we normally don't because redisplaying sixel doesn't change
@ -168,7 +168,7 @@ int sprixel_load(sprixel* spx, fbuf* f, int pixy, int pixx,
fbuf_free(&spx->glyph);
memcpy(&spx->glyph, f, sizeof(*f));
}
spx->invalidated = SPRIXEL_INVALIDATED;
spx->invalidated = SPRIXEL_UNSEEN;
spx->pixx = pixx;
spx->pixy = pixy;
spx->parse_start = parse_start;

View File

@ -17,8 +17,9 @@ struct blitterargs;
typedef enum {
SPRIXEL_QUIESCENT, // up-to-date and visible at the proper place
SPRIXEL_UNSEEN, // not yet loaded, invisible, but wants loading
SPRIXEL_LOADED, // loaded, but not yet made visible (kitty-only)
SPRIXEL_INVALIDATED, // not up-to-date, need reload, trumps MOVED
SPRIXEL_INVALIDATED, // not up-to-date, need reload
SPRIXEL_HIDE, // queued for destruction
SPRIXEL_MOVED, // visible, up-to-date, but in the wrong place
} sprixel_e;

View File

@ -4,6 +4,7 @@
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <compat/compat.h>
#include <notcurses/direct.h>
static int