Merge branch 'master' of github.com:dankamongmen/notcurses

pull/217/head
nick black 5 years ago
commit 30a3cc0a4e

@ -212,7 +212,7 @@ install(FILES
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
file(GLOB TESTDATA CONFIGURE_DEPENDS tests/*.png tests/*.jpg tests/*.mkv tests/*.bmp)
file(GLOB TESTDATA CONFIGURE_DEPENDS data/*)
install(FILES
${TESTDATA}
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/notcurses

@ -14,6 +14,7 @@ for more information, see [my wiki](https://nick-black.com/dankwiki/index.php/No
* [Introduction](#introduction)
* [Requirements](#requirements)
* [Building](#building)
* [Use](#use)
* [Input](#input)
* [Planes](#planes) ([Plane Channels API](#plane-channels-api), [Wide chars](#wide-chars))
@ -105,6 +106,20 @@ that fine library.
* From NCURSES: terminfo 6.1+
* From FFMpeg: libswscale 5.0+, libavformat 57.0+, libavutil 56.0+
### Building
* Create a subdirectory, traditionally `build`. Enter the directory.
* `cmake ..`. You might want to set e.g. `CMAKE_BUILD_TYPE`.
* `make`
* `make test`
If you have unit test failures, *please* file a bug including the output of
`./notcurses-tester > log 2>&1` (`make test` also runs `notcurses-tester`, but
hides important output).
To watch the bitchin' demo, run `./notcurses-demo -p ../data`. More details can
be found on the `notcurses-demo(1)` man page.
## Use
A program wishing to use notcurses will need to link it, ideally using the
@ -158,6 +173,8 @@ typedef struct notcurses_options {
// Notcurses typically prints version info in notcurses_init() and
// performance info in notcurses_stop(). This inhibits that output.
bool suppress_bannner;
// Notcurses does not clear the screen on startup unless thus requested to.
bool clear_screen_start;
// If non-NULL, notcurses_render() will write each rendered frame to this
// FILE* in addition to outfp. This is used primarily for debugging.
FILE* renderfp;
@ -286,12 +303,6 @@ must be readable without delay for it to be interpreted as such.
// returned to indicate that no input was available, but only by
// notcurses_getc(). Otherwise (including on EOF) (char32_t)-1 is returned.
// is this wide character a Supplementary Private Use Area-B codepoint?
static inline bool
wchar_supppuab_p(char32_t w){
return w >= 0x100000 && w <= 0x10fffd;
}
#define suppuabize(w) ((w) + 0x100000)
// Special composed key defintions. These values are added to 0x100000.
@ -319,6 +330,26 @@ wchar_supppuab_p(char32_t w){
#define NCKEY_F08 suppuabize(28)
#define NCKEY_F09 suppuabize(29)
#define NCKEY_F10 suppuabize(30)
#define NCKEY_F11 suppuabize(31)
#define NCKEY_F12 suppuabize(32)
#define NCKEY_F13 suppuabize(33)
#define NCKEY_F14 suppuabize(34)
#define NCKEY_F15 suppuabize(35)
#define NCKEY_F16 suppuabize(36)
#define NCKEY_F17 suppuabize(37)
#define NCKEY_F18 suppuabize(38)
#define NCKEY_F19 suppuabize(39)
#define NCKEY_F20 suppuabize(40)
#define NCKEY_F21 suppuabize(41)
#define NCKEY_F22 suppuabize(42)
#define NCKEY_F23 suppuabize(43)
#define NCKEY_F24 suppuabize(44)
#define NCKEY_F25 suppuabize(45)
#define NCKEY_F26 suppuabize(46)
#define NCKEY_F27 suppuabize(47)
#define NCKEY_F28 suppuabize(48)
#define NCKEY_F29 suppuabize(49)
#define NCKEY_F30 suppuabize(50)
// ... leave room for up to 100 function keys, egads
#define NCKEY_ENTER suppuabize(121)
#define NCKEY_CLS suppuabize(122) // "clear-screen or erase"
@ -335,31 +366,102 @@ wchar_supppuab_p(char32_t w){
#define NCKEY_EXIT suppuabize(133)
#define NCKEY_PRINT suppuabize(134)
#define NCKEY_REFRESH suppuabize(135)
// Mouse events. We try to encode some details into the char32_t (i.e. which
// button was pressed), but some is embedded in the ncinput event. The release
// event is generic across buttons; callers must maintain state, if they care.
#define NCKEY_BUTTON1 suppuabize(201)
#define NCKEY_BUTTON2 suppuabize(202)
#define NCKEY_BUTTON3 suppuabize(203)
#define NCKEY_BUTTON4 suppuabize(204)
#define NCKEY_BUTTON5 suppuabize(205)
#define NCKEY_BUTTON6 suppuabize(206)
#define NCKEY_BUTTON7 suppuabize(207)
#define NCKEY_BUTTON8 suppuabize(208)
#define NCKEY_BUTTON9 suppuabize(209)
#define NCKEY_BUTTON10 suppuabize(210)
#define NCKEY_BUTTON11 suppuabize(211)
#define NCKEY_RELEASE suppuabize(212)
// Is this char32_t a Supplementary Private Use Area-B codepoint?
static inline bool
wchar_supppuab_p(char32_t w){
return w >= 0x100000 && w <= 0x10fffd;
}
// Is the event a synthesized mouse event?
static inline bool
nckey_mouse_p(char32_t r){
return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE;
}
// An input event. Cell coordinates are currently defined only for mouse events.
typedef struct ncinput {
char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event
int y; // y cell coordinate of event, -1 for undefined
int x; // x cell coordinate of event, -1 for undefined
// FIXME modifiers (alt, etc?)
} ncinput;
// See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, a 'ts'
// of 0 for non-blocking operation, and otherwise a timespec to bound blocking.
// Signals in sigmask (less several we handle internally) will be atomically
// masked and unmasked per ppoll(2). It should generally contain all signals.
// Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may
// be NULL.
char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, sigset_t* sigmask);
// be NULL. Returns 0 on a timeout. If an event is processed, the return value
// is the 'id' field from that event. 'ni' may be NULL.
API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts,
sigset_t* sigmask, ncinput* ni);
// 'ni' may be NULL if the caller is uninterested in event details. If no event
// is ready, returns 0.
static inline char32_t
notcurses_getc_nblock(struct notcurses* n){
notcurses_getc_nblock(struct notcurses* n, ncinput* ni){
sigset_t sigmask;
sigfillset(&sigmask);
struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 };
return notcurses_getc(n, &ts, &sigmask);
return notcurses_getc(n, &ts, &sigmask, ni);
}
// 'ni' may be NULL if the caller is uninterested in event details. Blocks
// until an event is processed or a signal is received.
static inline char32_t
notcurses_getc_blocking(struct notcurses* n){
notcurses_getc_blocking(struct notcurses* n, ncinput* ni){
sigset_t sigmask;
sigemptyset(&sigmask);
return notcurses_getc(n, NULL, &sigmask);
return notcurses_getc(n, NULL, &sigmask, ni);
}
// Enable the mouse in "button-event tracking" mode with focus detection and
// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0
// is returned, and mouse events will be published to notcurses_getc().
API int notcurses_mouse_enable(struct notcurses* n);
// Disable mouse events. Any events in the input queue can still be delivered.
API int notcurses_mouse_disable(struct notcurses* n);
```
### Mice
notcurses supports mice, though only through brokers such as X or
[GPM](https://www.nico.schottelius.org/software/gpm/). It does not speak
directly to hardware. Mouse events must be explicitly enabled with a
successful call to `notcurses_mouse_enable()`, and can later be disabled.
```c
// Enable the mouse in "button-event tracking" mode with focus detection and
// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0
// is returned, and mouse events will be published to notcurses_getc().
int notcurses_mouse_enable(struct notcurses* n);
// Disable mouse events. Any events in the input queue can still be delivered.
int notcurses_mouse_disable(struct notcurses* n);
```
"Button-event tracking mode" implies the ability to detect mouse button
presses, and also mouse movement while holding down a mouse button (i.e. to
effect drag-and-drop). Mouse events are returned via the `NCKEY_MOUSE*` values,
with coordinate information in the `ncinput` struct.
### Planes
Fundamental to notcurses is a z-buffer of rectilinear virtual screens, known
@ -2178,6 +2280,7 @@ up someday **FIXME**.
* Linux: [ioctl_tty(2)](http://man7.org/linux/man-pages/man2/ioctl_tty.2.html)
* Linux: [ioctl_console(2)](http://man7.org/linux/man-pages/man2/ioctl_console.2.html)
* Portable: [terminfo(5)](http://man7.org/linux/man-pages/man5/terminfo.5.html)
* Portable: [user_caps(5)](http://man7.org/linux/man-pages/man5/user_caps.5.html)
### Other TUI libraries of note

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before

Width:  |  Height:  |  Size: 980 KiB

After

Width:  |  Height:  |  Size: 980 KiB

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

7
debian/changelog vendored

@ -1,3 +1,10 @@
notcurses (1.0.0) UNRELEASED; urgency=medium
* notcurses-bin depends on ncurses-term
* libnotcurses0 recommends ncurses-term
-- Nick Black <dankamongmen@gmail.com> Mon, 23 Dec 2019 00:57:22 -0500
notcurses (0.9.2-1) unstable; urgency=medium
* New upstream

3
debian/control vendored

@ -22,6 +22,7 @@ Description: Development files for notcurses
Package: libnotcurses0
Architecture: any
Multi-Arch: same
Recommends: ncurses-term (>= 6.1)
Depends:${shlibs:Depends}, ${misc:Depends}
Description: Shared libraries for notcurses TUI
notcurses facilitates the creation of modern TUI programs,
@ -31,7 +32,7 @@ Description: Shared libraries for notcurses TUI
Package: notcurses-bin
Architecture: any
Multi-Arch: foreign
Depends:${shlibs:Depends}, ${misc:Depends}
Depends:${shlibs:Depends}, ${misc:Depends}, ncurses-term (>= 6.1)
Description: Binaries from libnotcurses
notcurses facilitates the creation of modern TUI programs,
making full use of Unicode and 24-bit direct color. It presents

@ -121,6 +121,8 @@ typedef struct notcurses_options {
// Notcurses typically prints version info in notcurses_init() and performance
// info in notcurses_stop(). This inhibits that output.
bool suppress_bannner;
// Notcurses does not clear the screen on startup unless thus requested to.
bool clear_screen_start;
// If non-NULL, notcurses_render() will write each rendered frame to this
// FILE* in addition to outfp. This is used primarily for debugging.
FILE* renderfp;
@ -159,12 +161,6 @@ API struct ncplane* notcurses_top(struct notcurses* n);
#define suppuabize(w) ((w) + 0x100000)
// is this wide character a Supplementary Private Use Area-B codepoint?
static inline bool
wchar_supppuab_p(char32_t w){
return w >= 0x100000 && w <= 0x10fffd;
}
// Special composed key defintions. These values are added to 0x100000.
#define NCKEY_INVALID suppuabize(0)
#define NCKEY_RESIZE suppuabize(1) // generated interally in response to SIGWINCH
@ -226,30 +222,79 @@ wchar_supppuab_p(char32_t w){
#define NCKEY_EXIT suppuabize(133)
#define NCKEY_PRINT suppuabize(134)
#define NCKEY_REFRESH suppuabize(135)
// Mouse events. We try to encode some details into the char32_t (i.e. which
// button was pressed), but some is embedded in the ncinput event. The release
// event is generic across buttons; callers must maintain state, if they care.
#define NCKEY_BUTTON1 suppuabize(201)
#define NCKEY_BUTTON2 suppuabize(202)
#define NCKEY_BUTTON3 suppuabize(203)
#define NCKEY_BUTTON4 suppuabize(204)
#define NCKEY_BUTTON5 suppuabize(205)
#define NCKEY_BUTTON6 suppuabize(206)
#define NCKEY_BUTTON7 suppuabize(207)
#define NCKEY_BUTTON8 suppuabize(208)
#define NCKEY_BUTTON9 suppuabize(209)
#define NCKEY_BUTTON10 suppuabize(210)
#define NCKEY_BUTTON11 suppuabize(211)
#define NCKEY_RELEASE suppuabize(212)
// Is this char32_t a Supplementary Private Use Area-B codepoint?
static inline bool
wchar_supppuab_p(char32_t w){
return w >= 0x100000 && w <= 0x10fffd;
}
// Is the event a synthesized mouse event?
static inline bool
nckey_mouse_p(char32_t r){
return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE;
}
// An input event. Cell coordinates are currently defined only for mouse events.
typedef struct ncinput {
char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event
int y; // y cell coordinate of event, -1 for undefined
int x; // x cell coordinate of event, -1 for undefined
// FIXME modifiers (alt, etc?)
} ncinput;
// See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, a 'ts'
// of 0 for non-blocking operation, and otherwise a timespec to bound blocking.
// Signals in sigmask (less several we handle internally) will be atomically
// masked and unmasked per ppoll(2). It should generally contain all signals.
// Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may
// be NULL.
API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, sigset_t* sigmask);
// be NULL. Returns 0 on a timeout. If an event is processed, the return value
// is the 'id' field from that event. 'ni' may be NULL.
API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts,
sigset_t* sigmask, ncinput* ni);
// 'ni' may be NULL if the caller is uninterested in event details. If no event
// is ready, returns 0.
static inline char32_t
notcurses_getc_nblock(struct notcurses* n){
notcurses_getc_nblock(struct notcurses* n, ncinput* ni){
sigset_t sigmask;
sigfillset(&sigmask);
struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 };
return notcurses_getc(n, &ts, &sigmask);
return notcurses_getc(n, &ts, &sigmask, ni);
}
// 'ni' may be NULL if the caller is uninterested in event details. Blocks
// until an event is processed or a signal is received.
static inline char32_t
notcurses_getc_blocking(struct notcurses* n){
notcurses_getc_blocking(struct notcurses* n, ncinput* ni){
sigset_t sigmask;
sigemptyset(&sigmask);
return notcurses_getc(n, NULL, &sigmask);
return notcurses_getc(n, NULL, &sigmask, ni);
}
// Enable the mouse in "button-event tracking" mode with focus detection and
// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0
// is returned, and mouse events will be published to notcurses_getc().
API int notcurses_mouse_enable(struct notcurses* n);
// Disable mouse events. Any events in the input queue can still be delivered.
API int notcurses_mouse_disable(struct notcurses* n);
// Refresh our idea of the terminal's dimensions, reshaping the standard plane
// if necessary. Without a call to this function following a terminal resize
// (as signaled via SIGWINCH), notcurses_render() might not function properly.

@ -231,7 +231,7 @@ handle_input(struct notcurses* nc, struct panelreel* pr, int efd,
return (wchar_t)-1;
}else{
if(fds[0].revents & POLLIN){
key = notcurses_getc_blocking(nc);
key = notcurses_getc_blocking(nc, NULL);
if(key < 0){
return -1;
}

@ -5,7 +5,7 @@ static int
watch_for_keystroke(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused))){
wchar_t w;
// we don't want a keypress, but should handle NCKEY_RESIZE
if((w = notcurses_getc_nblock(nc)) != (wchar_t)-1){
if((w = notcurses_getc_nblock(nc, NULL)) != (wchar_t)-1){
if(w == NCKEY_RESIZE){
// FIXME resize that sumbitch
}else{

@ -673,7 +673,7 @@ int witherworm_demo(struct notcurses* nc){
struct timespec left, cur;
clock_gettime(CLOCK_MONOTONIC, &cur);
timespec_subtract(&left, &screenend, &cur);
key = notcurses_getc(nc, &left, NULL);
key = notcurses_getc(nc, &left, NULL, NULL);
clock_gettime(CLOCK_MONOTONIC, &cur);
int64_t ns = timespec_subtract_ns(&cur, &screenend);
if(ns > 0){

@ -4,13 +4,14 @@
#include <cstdlib>
#include <clocale>
#include <iostream>
#include <termios.h>
#include <notcurses.h>
static int dimy, dimx;
static struct notcurses* nc;
// return the string version of a special composed key
const char* nckeystr(wchar_t spkey){
const char* nckeystr(char32_t spkey){
switch(spkey){ // FIXME
case NCKEY_RESIZE:
notcurses_resize(nc, &dimy, &dimx);
@ -73,23 +74,73 @@ const char* nckeystr(wchar_t spkey){
case NCKEY_EXIT: return "exit";
case NCKEY_PRINT: return "print";
case NCKEY_REFRESH: return "refresh";
case NCKEY_BUTTON1: return "mouse (button 1 pressed)";
case NCKEY_BUTTON2: return "mouse (button 2 pressed)";
case NCKEY_BUTTON3: return "mouse (button 3 pressed)";
case NCKEY_BUTTON4: return "mouse (button 4 pressed)";
case NCKEY_BUTTON5: return "mouse (button 5 pressed)";
case NCKEY_BUTTON6: return "mouse (button 6 pressed)";
case NCKEY_BUTTON7: return "mouse (button 7 pressed)";
case NCKEY_BUTTON8: return "mouse (button 8 pressed)";
case NCKEY_BUTTON9: return "mouse (button 9 pressed)";
case NCKEY_BUTTON10: return "mouse (button 10 pressed)";
case NCKEY_BUTTON11: return "mouse (button 11 pressed)";
case NCKEY_RELEASE: return "mouse (button released)";
default: return "unknown";
}
}
// print the utf8 Control Pictures for otherwise unprintable chars
wchar_t printutf8(wchar_t kp){
if(kp <= 27 && kp >= 0){
// Print the utf8 Control Pictures for otherwise unprintable ASCII
char32_t printutf8(char32_t kp){
if(kp <= 27){
return 0x2400 + kp;
}
return kp;
}
// Dim all text on the plane by the same amount. This will stack for
// older text, and thus clearly indicate the current output.
static int
dim_rows(struct ncplane* n){
int y, x;
cell c = CELL_TRIVIAL_INITIALIZER;
for(y = 2 ; y < dimy ; ++y){
for(x = 0 ; x < dimx ; ++x){
if(ncplane_at_yx(n, y, x, &c) < 0){
cell_release(n, &c);
return -1;
}
unsigned r, g, b;
cell_get_fg_rgb(&c, &r, &g, &b);
r -= r / 32;
g -= g / 32;
b -= b / 32;
if(r > 247){ r = 0; }
if(g > 247){ g = 0; }
if(b > 247){ b = 0; }
if(cell_set_fg_rgb(&c, r, g, b)){
cell_release(n, &c);
return -1;
}
if(ncplane_putc_yx(n, y, x, &c) < 0){
cell_release(n, &c);
return -1;
}
if(cell_double_wide_p(&c)){
++x;
}
}
}
cell_release(n, &c);
return 0;
}
int main(void){
if(setlocale(LC_ALL, "") == nullptr){
return EXIT_FAILURE;
}
notcurses_options opts{};
opts.clear_screen_start = true;
if((nc = notcurses_init(&opts, stdout)) == nullptr){
return EXIT_FAILURE;;
}
@ -97,48 +148,64 @@ int main(void){
notcurses_term_dim_yx(nc, &dimy, &dimx);
ncplane_set_fg(n, 0);
ncplane_set_bg(n, 0xbb64bb);
ncplane_styles_set(n, CELL_STYLE_UNDERLINE);
if(ncplane_putstr_aligned(n, 0, "mash some keys, yo", NCALIGN_CENTER) <= 0){
ncplane_styles_on(n, CELL_STYLE_UNDERLINE);
if(ncplane_putstr_aligned(n, 0, "mash keys, yo. give that mouse some waggle! ctrl+d exits.", NCALIGN_CENTER) <= 0){
notcurses_stop(nc);
return EXIT_FAILURE;
}
ncplane_styles_off(n, CELL_STYLE_UNDERLINE);
ncplane_styles_set(n, 0);
ncplane_set_bg_default(n);
notcurses_render(nc);
int y = 1;
int y = 2;
std::deque<wchar_t> cells;
wchar_t r;
while(errno = 0, (r = notcurses_getc_blocking(nc)) >= 0){
char32_t r;
if(notcurses_mouse_enable(nc)){
notcurses_stop(nc);
return EXIT_FAILURE;
}
ncinput ni;
while(errno = 0, (r = notcurses_getc_blocking(nc, &ni)) != (char32_t)-1){
if(r == 0){ // interrupted by signal
continue;
}
if(r == CEOT){
notcurses_stop(nc);
return EXIT_SUCCESS;
}
if(ncplane_cursor_move_yx(n, y, 0)){
break;
}
if(r < 0x80){
ncplane_set_fg_rgb(n, 128, 250, 64);
if(ncplane_printf(n, "Got ASCII: [0x%02x (%03d)] '%lc'\n",
if(ncplane_printf(n, "Got ASCII: [0x%02x (%03d)] '%lc'",
r, r, iswprint(r) ? r : printutf8(r)) < 0){
break;
}
}else{
if(wchar_supppuab_p(r)){
ncplane_set_fg_rgb(n, 250, 64, 128);
if(ncplane_printf(n, "Got special key: [0x%02x (%02d)] '%s'\n",
if(ncplane_printf(n, "Got special key: [0x%02x (%02d)] '%s'",
r, r, nckeystr(r)) < 0){
break;
}
if(nckey_mouse_p(r)){
if(ncplane_printf(n, " x: %d y: %d", ni.x, ni.y) < 0){
break;
}
}
}else{
ncplane_set_fg_rgb(n, 64, 128, 250);
ncplane_printf(n, "Got UTF-8: [0x%08x] '%lc'\n", r, r);
ncplane_printf(n, "Got UTF-8: [0x%08x] '%lc'", r, r);
}
}
// FIXME reprint all lines, fading older ones
if(dim_rows(n)){
break;
}
if(notcurses_render(nc)){
break;
}
if(++y >= dimy - 2){ // leave a blank line at the bottom
y = 1; // and at the top
y = 2; // and at the top
}
while(cells.size() >= dimy - 3u){
cells.pop_back();
@ -147,7 +214,7 @@ int main(void){
}
int e = errno;
notcurses_stop(nc);
if(r < 0 && e){
if(r == (char32_t)-1 && e){
std::cerr << "Error reading from terminal (" << strerror(e) << "?)\n";
}
return EXIT_FAILURE;

@ -1,8 +1,15 @@
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
#include <term.h>
#include <ctype.h>
#include <sys/poll.h>
#include "internal.h"
// CSI (Control Sequence Indicators) originate in the terminal itself, and are
// not reported in their bare form to the user. For our purposes, these usually
// indicate a mouse event.
#define CSIPREFIX "\x1b[<"
static const char32_t NCKEY_CSI = 1;
static const unsigned char ESC = 0x1b; // 27
sig_atomic_t resize_seen = 0;
@ -30,7 +37,7 @@ unpop_keypress(notcurses* nc, int kpress){
// we assumed escapes can only be composed of 7-bit chars
typedef struct esctrie {
int special; // composed key terminating here
char32_t special; // composed key terminating here
struct esctrie** trie; // if non-NULL, next level of radix-128 trie
} esctrie;
@ -66,7 +73,7 @@ notcurses_add_input_escape(notcurses* nc, const char* esc, char32_t special){
fprintf(stderr, "Not an escape: %s (0x%x)\n", esc, special);
return -1;
}
if(!wchar_supppuab_p(special)){
if(!wchar_supppuab_p(special) && special != NCKEY_CSI){
fprintf(stderr, "Not a supplementary-b PUA char: %lc (0x%x)\n", special, special);
return -1;
}
@ -102,21 +109,98 @@ notcurses_add_input_escape(notcurses* nc, const char* esc, char32_t special){
return 0;
}
// We received the CSI prefix. Extract the data payload.
static char32_t
handle_csi(notcurses* nc, ncinput* ni){
enum {
PARAM1, // reading first param (button + modifiers) plus delimiter
PARAM2, // reading second param (x coordinate) plus delimiter
PARAM3, // reading third param (y coordinate) plus terminator
} state = PARAM1;
int param = 0; // numeric translation of param thus far
char32_t id = (char32_t)-1;
while(nc->inputbuf_occupied){
int candidate = pop_input_keypress(nc);
if(state == PARAM1){
if(candidate == ';'){
state = PARAM2;
// modifiers: 32 (motion) 16 (control) 8 (alt) 4 (shift)
// buttons 4, 5, 6, 7: adds 64
// buttons 8, 9, 10, 11: adds 128
if(param >= 0 && param < 64){
if(param % 4 == 3){
id = NCKEY_RELEASE;
}else{
id = NCKEY_BUTTON1 + (param % 4);
}
}else if(param >= 64 && param < 128){
id = NCKEY_BUTTON4 + (param % 4);
}else if(param >= 128 && param < 192){
id = NCKEY_BUTTON8 + (param % 4);
}else{
break;
}
param = 0;
}else if(isdigit(candidate)){
param *= 10;
param += candidate - '0';
}else{
break;
}
}else if(state == PARAM2){
if(candidate == ';'){
state = PARAM3;
if(param == 0){
break;
}
if(ni){
ni->x = param - 1;
}
param = 0;
}else if(isdigit(candidate)){
param *= 10;
param += candidate - '0';
}else{
break;
}
}else if(state == PARAM3){
if(candidate == 'm' || candidate == 'M'){
if(candidate == 'm'){
id = NCKEY_RELEASE;
}
if(param == 0){
break;
}
if(ni){
ni->y = param - 1;
ni->id = id;
}
return id;
}else if(isdigit(candidate)){
param *= 10;
param += candidate - '0';
}else{
break;
}
}
}
// FIXME ungetc on failure! walk trie backwards or something
return (char32_t)-1;
}
// add the keypress we just read to our input queue (assuming there is room).
// if there is a full UTF8 codepoint or keystroke (composed or otherwise),
// return it, and pop it from the queue.
static char32_t
handle_getc(notcurses* nc, int kpress){
handle_getc(notcurses* nc, int kpress, ncinput* ni){
// fprintf(stderr, "KEYPRESS: %d\n", kpress);
if(kpress < 0){
return -1;
}
if(kpress == ESC){
// FIXME delay a little waiting for more?
const esctrie* esc = nc->inputescapes;
while(esc && esc->special == NCKEY_INVALID && nc->inputbuf_occupied){
int candidate = pop_input_keypress(nc);
//fprintf(stderr, "CANDIDATE: %c\n", candidate);
if(esc->trie == NULL){
esc = NULL;
}else if(candidate >= 0x80 || candidate < 0){
@ -125,8 +209,10 @@ handle_getc(notcurses* nc, int kpress){
esc = esc->trie[candidate];
}
}
//fprintf(stderr, "esc? %c special: %d\n", esc ? 'y' : 'n', esc ? esc->special : NCKEY_INVALID);
if(esc && esc->special != NCKEY_INVALID){
if(esc->special == NCKEY_CSI){
return handle_csi(nc, ni);
}
return esc->special;
}
// FIXME ungetc on failure! walk trie backwards or something
@ -184,7 +270,7 @@ input_queue_full(const notcurses* nc){
}
static char32_t
handle_input(notcurses* nc){
handle_input(notcurses* nc, ncinput* ni){
int r;
// getc() returns unsigned chars cast to ints
while(!input_queue_full(nc) && (r = getc(nc->ttyinfp)) >= 0){
@ -205,17 +291,18 @@ handle_input(notcurses* nc){
return -1;
}
r = pop_input_keypress(nc);
return handle_getc(nc, r);
return handle_getc(nc, r, ni);
}
// infp has always been set non-blocking
char32_t notcurses_getc(notcurses* nc, const struct timespec *ts, sigset_t* sigmask){
// infp has already been set non-blocking
char32_t notcurses_getc(notcurses* nc, const struct timespec *ts,
sigset_t* sigmask, ncinput* ni){
errno = 0;
char32_t r = handle_input(nc);
char32_t r = handle_input(nc, ni);
if(r == (char32_t)-1){
if(errno == EAGAIN || errno == EWOULDBLOCK){
block_on_input(nc->ttyinfp, ts, sigmask);
return handle_input(nc);
return handle_input(nc, ni);
}
return r;
}
@ -302,5 +389,9 @@ int prep_special_keys(notcurses* nc){
return -1;
}
}
if(notcurses_add_input_escape(nc, CSIPREFIX, NCKEY_CSI)){
fprintf(stderr, "Couldn't add support for %s\n", k->tinfo);
return -1;
}
return 0;
}

@ -23,6 +23,8 @@
#include "version.h"
#include "egcpool.h"
#define ESC "\x1b"
// only one notcurses object can be the target of signal handlers, due to their
// process-wide nature.
static notcurses* _Atomic signal_nc = ATOMIC_VAR_INIT(NULL); // ugh
@ -575,9 +577,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
// support for the style in that case.
int nocolor_stylemask = tigetnum("ncv");
if(nocolor_stylemask > 0){
// FIXME this doesn't work if we're using sgr, which we are at the moment!
// ncv is defined in terms of curses style bits, which differ from ours
if(nocolor_stylemask & WA_STANDOUT){
if(nocolor_stylemask & WA_STANDOUT){ // ncv is composed of terminfo bits, not ours
nc->standout = NULL;
}
if(nocolor_stylemask & WA_UNDERLINE){
@ -599,6 +599,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
nc->italics = NULL;
}
}
term_verify_seq(&nc->getm, "getm"); // get mouse events
// Not all terminals support setting the fore/background independently
term_verify_seq(&nc->setaf, "setaf");
term_verify_seq(&nc->setab, "setab");
@ -716,6 +717,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
free(ret);
return NULL;
}
notcurses_mouse_disable(ret);
if(tcgetattr(ret->ttyfd, &ret->tpreserved)){
fprintf(stderr, "Couldn't preserve terminal state for %d (%s)\n",
ret->ttyfd, strerror(errno));
@ -766,7 +768,6 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
free_plane(ret->top);
goto err;
}
// term_emit("clear", ret->clear, ret->ttyfp, false);
ret->suppress_banner = opts->suppress_bannner;
if(!opts->suppress_bannner){
char prefixbuf[BPREFIXSTRLEN + 1];
@ -796,6 +797,9 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
fprintf(ret->ttyfp, "Are you specifying a proper DirectColor TERM?\n");
}
}
if(opts->clear_screen_start){
term_emit("clear", ret->clearscr, ret->ttyfp, false);
}
return ret;
err:
@ -823,6 +827,7 @@ int notcurses_stop(notcurses* nc){
if(nc->sgr0 && term_emit("sgr0", nc->sgr0, nc->ttyfp, true)){
ret = -1;
}
ret |= notcurses_mouse_disable(nc);
ret |= tcsetattr(nc->ttyfd, TCSANOW, &nc->tpreserved);
while(nc->top){
ncplane* p = nc->top;
@ -1547,3 +1552,20 @@ ncplane* notcurses_top(notcurses* n){
ncplane* ncplane_below(ncplane* n){
return n->z;
}
#define SET_BTN_EVENT_MOUSE "1002"
#define SET_FOCUS_EVENT_MOUSE "1004"
#define SET_SGR_MODE_MOUSE "1006"
int notcurses_mouse_enable(notcurses* n){
return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";"
SET_FOCUS_EVENT_MOUSE ";" SET_SGR_MODE_MOUSE "h",
n->ttyfp, true);
}
// this seems to work (note difference in suffix, 'l' vs 'h'), but what about
// the sequences 1000 etc?
int notcurses_mouse_disable(notcurses* n){
return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";"
SET_FOCUS_EVENT_MOUSE ";" SET_SGR_MODE_MOUSE "l",
n->ttyfp, true);
}

@ -64,7 +64,7 @@ int main(void){
}
PR = pr; // FIXME eliminate
char32_t key;
while((key = notcurses_getc_blocking(nc)) != (char32_t)-1){
while((key = notcurses_getc_blocking(nc, nullptr)) != (char32_t)-1){
switch(key){
case 'q':
return notcurses_stop(nc) ? EXIT_FAILURE : EXIT_SUCCESS;

@ -37,7 +37,7 @@ int ncview(struct notcurses* nc, struct ncvisual* ncv, int* averr){
.tv_sec = start.tv_sec + (long)(ns / 1000000000),
.tv_nsec = start.tv_nsec + (long)(ns % 1000000000),
};
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &interval, NULL);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &interval, nullptr);
}
if(*averr == AVERROR_EOF){
return 0;
@ -78,7 +78,7 @@ int main(int argc, char** argv){
std::cerr << "Error decoding " << argv[i] << ": " << errbuf.data() << std::endl;
return EXIT_FAILURE;
}
notcurses_getc_blocking(nc);
notcurses_getc_blocking(nc, nullptr);
ncvisual_destroy(ncv);
}
if(notcurses_stop(nc)){

@ -0,0 +1,38 @@
#include "main.h"
class InputTest : public :: testing::Test {
protected:
void SetUp() override {
setlocale(LC_ALL, "");
if(getenv("TERM") == nullptr){
GTEST_SKIP();
}
notcurses_options nopts{};
nopts.inhibit_alternate_screen = true;
nopts.suppress_bannner = true;
outfp_ = fopen("/dev/tty", "wb");
ASSERT_NE(nullptr, outfp_);
nc_ = notcurses_init(&nopts, outfp_);
ASSERT_NE(nullptr, nc_);
}
void TearDown() override {
if(nc_){
EXPECT_EQ(0, notcurses_stop(nc_));
}
if(outfp_){
fclose(outfp_);
}
}
struct notcurses* nc_{};
FILE* outfp_{};
};
TEST_F(InputTest, TestMouseOn){
ASSERT_EQ(0, notcurses_mouse_enable(nc_));
}
TEST_F(InputTest, TestMouseOff){
ASSERT_EQ(0, notcurses_mouse_disable(nc_));
}

@ -37,7 +37,7 @@ TEST_F(LibavTest, LoadImage) {
int averr;
int dimy, dimx;
ncplane_dim_yx(ncp_, &dimy, &dimx);
auto ncv = ncplane_visual_open(ncp_, "../tests/dsscaw-purp.png", &averr);
auto ncv = ncplane_visual_open(ncp_, "../data/dsscaw-purp.png", &averr);
ASSERT_NE(nullptr, ncv);
ASSERT_EQ(0, averr);
auto frame = ncvisual_decode(ncv, &averr);
@ -58,7 +58,7 @@ TEST_F(LibavTest, LoadVideo) {
int averr;
int dimy, dimx;
ncplane_dim_yx(ncp_, &dimy, &dimx);
auto ncv = ncplane_visual_open(ncp_, "../tests/fm6.mkv", &averr);
auto ncv = ncplane_visual_open(ncp_, "../data/fm6.mkv", &averr);
ASSERT_NE(nullptr, ncv);
EXPECT_EQ(0, averr);
auto frame = ncvisual_decode(ncv, &averr);
@ -75,7 +75,7 @@ TEST_F(LibavTest, LoadVideoCreatePlane) {
int averr;
int dimy, dimx;
ncplane_dim_yx(ncp_, &dimy, &dimx);
auto ncv = ncvisual_open_plane(nc_, "../tests/fm6.mkv", &averr, 0, 0, NCSCALE_STRETCH);
auto ncv = ncvisual_open_plane(nc_, "../data/fm6.mkv", &averr, 0, 0, NCSCALE_STRETCH);
ASSERT_NE(nullptr, ncv);
EXPECT_EQ(0, averr);
auto frame = ncvisual_decode(ncv, &averr);

Loading…
Cancel
Save