mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-09-10 12:40:58 +02:00
1236 lines
25 KiB
C
1236 lines
25 KiB
C
/*
|
|
* curses.c - curses windowing module for zsh
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 2007 Clint Adams
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Clint Adams or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Clint Adams and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Clint Adams and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Clint Adams and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#define _XOPEN_SOURCE_EXTENDED 1
|
|
|
|
#include "curses.mdh"
|
|
#include "curses.pro"
|
|
|
|
#ifdef HAVE_NCURSES_H
|
|
# include <ncurses.h>
|
|
#else
|
|
# ifdef HAVE_CURSES_H
|
|
# include <curses.h>
|
|
# endif
|
|
#endif
|
|
|
|
#ifndef MULTIBYTE_SUPPORT
|
|
# undef HAVE_SETCCHAR
|
|
# undef HAVE_WADDWSTR
|
|
# undef HAVE_WGET_WCH
|
|
#endif
|
|
|
|
#ifdef HAVE_SETCCHAR
|
|
# include <wchar.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
enum zc_win_flags {
|
|
/* Window is permanent (probably "stdscr") */
|
|
ZCWF_PERMANENT = 0x0001,
|
|
/* Scrolling enabled */
|
|
ZCWF_SCROLL = 0x0002
|
|
};
|
|
|
|
typedef struct zc_win *ZCWin;
|
|
|
|
struct zc_win {
|
|
WINDOW *win;
|
|
char *name;
|
|
int flags;
|
|
LinkList children;
|
|
ZCWin parent;
|
|
};
|
|
|
|
struct zcurses_namenumberpair {
|
|
char *name;
|
|
int number;
|
|
};
|
|
|
|
struct colorpairnode {
|
|
struct hashnode node;
|
|
short colorpair;
|
|
};
|
|
typedef struct colorpairnode *Colorpairnode;
|
|
|
|
typedef int (*zccmd_t)(const char *nam, char **args);
|
|
struct zcurses_subcommand {
|
|
const char *name;
|
|
zccmd_t cmd;
|
|
int minargs;
|
|
int maxargs;
|
|
};
|
|
|
|
static struct ttyinfo saved_tty_state;
|
|
static struct ttyinfo curses_tty_state;
|
|
static LinkList zcurses_windows;
|
|
static HashTable zcurses_colorpairs = NULL;
|
|
|
|
#define ZCURSES_EINVALID 1
|
|
#define ZCURSES_EDEFINED 2
|
|
#define ZCURSES_EUNDEFINED 3
|
|
|
|
#define ZCURSES_UNUSED 1
|
|
#define ZCURSES_USED 2
|
|
|
|
#define ZCURSES_ATTRON 1
|
|
#define ZCURSES_ATTROFF 2
|
|
|
|
static int zc_errno, zc_color_phase=0;
|
|
static short next_cp=0;
|
|
|
|
static const struct zcurses_namenumberpair zcurses_attributes[] = {
|
|
{"blink", A_BLINK},
|
|
{"bold", A_BOLD},
|
|
{"dim", A_DIM},
|
|
{"reverse", A_REVERSE},
|
|
{"standout", A_STANDOUT},
|
|
{"underline", A_UNDERLINE},
|
|
{NULL, 0}
|
|
};
|
|
|
|
static const struct zcurses_namenumberpair zcurses_colors[] = {
|
|
{"black", COLOR_BLACK},
|
|
{"red", COLOR_RED},
|
|
{"green", COLOR_GREEN},
|
|
{"yellow", COLOR_YELLOW},
|
|
{"blue", COLOR_BLUE},
|
|
{"magenta", COLOR_MAGENTA},
|
|
{"cyan", COLOR_CYAN},
|
|
{"white", COLOR_WHITE},
|
|
#ifdef HAVE_USE_DEFAULT_COLORS
|
|
{"default", -1},
|
|
#endif
|
|
{NULL, 0}
|
|
};
|
|
|
|
/* Autogenerated keypad string/number mapping*/
|
|
#include "curses_keys.h"
|
|
|
|
static char **
|
|
zcurses_pairs_to_array(const struct zcurses_namenumberpair *nnps)
|
|
{
|
|
char **arr, **arrptr;
|
|
int count;
|
|
const struct zcurses_namenumberpair *nnptr;
|
|
|
|
for (nnptr = nnps; nnptr->name; nnptr++)
|
|
;
|
|
count = nnptr - nnps;
|
|
|
|
arrptr = arr = (char **)zhalloc((count+1) * sizeof(char *));
|
|
|
|
for (nnptr = nnps; nnptr->name; nnptr++)
|
|
*arrptr++ = dupstring(nnptr->name);
|
|
*arrptr = NULL;
|
|
|
|
return arr;
|
|
}
|
|
|
|
static const char *
|
|
zcurses_strerror(int err)
|
|
{
|
|
static const char *errs[] = {
|
|
"unknown error",
|
|
"window name invalid",
|
|
"window already defined",
|
|
"window undefined",
|
|
NULL };
|
|
|
|
return errs[(err < 1 || err > 3) ? 0 : err];
|
|
}
|
|
|
|
static LinkNode
|
|
zcurses_getwindowbyname(const char *name)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
for (node = firstnode(zcurses_windows); node; incnode(node))
|
|
if (w = (ZCWin)getdata(node), !strcmp(w->name, name))
|
|
return node;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static LinkNode
|
|
zcurses_validate_window(char *win, int criteria)
|
|
{
|
|
LinkNode target;
|
|
|
|
if (win==NULL || strlen(win) < 1) {
|
|
zc_errno = ZCURSES_EINVALID;
|
|
return NULL;
|
|
}
|
|
|
|
target = zcurses_getwindowbyname(win);
|
|
|
|
if (target && (criteria & ZCURSES_UNUSED)) {
|
|
zc_errno = ZCURSES_EDEFINED;
|
|
return NULL;
|
|
}
|
|
|
|
if (!target && (criteria & ZCURSES_USED)) {
|
|
zc_errno = ZCURSES_EUNDEFINED;
|
|
return NULL;
|
|
}
|
|
|
|
zc_errno = 0;
|
|
return target;
|
|
}
|
|
|
|
static int
|
|
zcurses_free_window(ZCWin w)
|
|
{
|
|
if (!(w->flags & ZCWF_PERMANENT) && delwin(w->win)!=OK)
|
|
return 1;
|
|
|
|
if (w->name)
|
|
zsfree(w->name);
|
|
|
|
if (w->children)
|
|
freelinklist(w->children, (FreeFunc)NULL);
|
|
|
|
zfree(w, sizeof(struct zc_win));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
zcurses_attribute(WINDOW *w, char *attr, int op)
|
|
{
|
|
struct zcurses_namenumberpair *zca;
|
|
|
|
if (!attr)
|
|
return 1;
|
|
|
|
for(zca=(struct zcurses_namenumberpair *)zcurses_attributes;zca->name;zca++)
|
|
if (!strcmp(attr, zca->name)) {
|
|
switch(op) {
|
|
case ZCURSES_ATTRON:
|
|
wattron(w, zca->number);
|
|
break;
|
|
case ZCURSES_ATTROFF:
|
|
wattroff(w, zca->number);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static short
|
|
zcurses_color(const char *color)
|
|
{
|
|
struct zcurses_namenumberpair *zc;
|
|
|
|
for(zc=(struct zcurses_namenumberpair *)zcurses_colors;zc->name;zc++)
|
|
if (!strcmp(color, zc->name)) {
|
|
return (short)zc->number;
|
|
}
|
|
|
|
return (short)-1;
|
|
}
|
|
|
|
static int
|
|
zcurses_colorset(const char *nam, WINDOW *w, char *colorpair)
|
|
{
|
|
char *bg, *cp;
|
|
short f, b;
|
|
Colorpairnode cpn;
|
|
|
|
/* zcurses_colorpairs is only initialised if color is supported */
|
|
if (!zcurses_colorpairs)
|
|
return 1;
|
|
|
|
if (zc_color_phase==1 ||
|
|
!(cpn = (Colorpairnode) gethashnode(zcurses_colorpairs, colorpair))) {
|
|
zc_color_phase = 2;
|
|
cp = ztrdup(colorpair);
|
|
|
|
bg = strchr(cp, '/');
|
|
if (bg==NULL) {
|
|
zsfree(cp);
|
|
return 1;
|
|
}
|
|
|
|
*bg = '\0';
|
|
f = zcurses_color(cp);
|
|
b = zcurses_color(bg+1);
|
|
|
|
if (f==-1 || b==-1) {
|
|
if (f == -1)
|
|
zwarnnam(nam, "foreground color `%s' not known", cp);
|
|
if (b == -1)
|
|
zwarnnam(nam, "background color `%s' not known", bg+1);
|
|
*bg = '/';
|
|
zsfree(cp);
|
|
return 1;
|
|
}
|
|
*bg = '/';
|
|
|
|
++next_cp;
|
|
if (next_cp >= COLOR_PAIRS || init_pair(next_cp, f, b) == ERR) {
|
|
zsfree(cp);
|
|
return 1;
|
|
}
|
|
|
|
cpn = (Colorpairnode)zalloc(sizeof(struct colorpairnode));
|
|
|
|
if (!cpn) {
|
|
zsfree(cp);
|
|
return 1;
|
|
}
|
|
|
|
cpn->colorpair = next_cp;
|
|
addhashnode(zcurses_colorpairs, cp, (void *)cpn);
|
|
}
|
|
|
|
return (wcolor_set(w, cpn->colorpair, NULL) == ERR);
|
|
}
|
|
|
|
static void
|
|
freecolorpairnode(HashNode hn)
|
|
{
|
|
zsfree(hn->nam);
|
|
zfree(hn, sizeof(struct colorpairnode));
|
|
}
|
|
|
|
|
|
/*************
|
|
* Subcommands
|
|
*************/
|
|
|
|
static int
|
|
zccmd_init(const char *nam, char **args)
|
|
{
|
|
LinkNode stdscr_win = zcurses_getwindowbyname("stdscr");
|
|
|
|
if (!stdscr_win) {
|
|
ZCWin w = (ZCWin)zshcalloc(sizeof(struct zc_win));
|
|
if (!w)
|
|
return 1;
|
|
|
|
gettyinfo(&saved_tty_state);
|
|
w->name = ztrdup("stdscr");
|
|
w->win = initscr();
|
|
if (w->win == NULL) {
|
|
zsfree(w->name);
|
|
zfree(w, sizeof(struct zc_win));
|
|
return 1;
|
|
}
|
|
w->flags = ZCWF_PERMANENT;
|
|
zinsertlinknode(zcurses_windows, lastnode(zcurses_windows), (void *)w);
|
|
if (start_color() != ERR) {
|
|
Colorpairnode cpn;
|
|
|
|
if(!zc_color_phase)
|
|
zc_color_phase = 1;
|
|
zcurses_colorpairs = newhashtable(8, "zc_colorpairs", NULL);
|
|
|
|
zcurses_colorpairs->hash = hasher;
|
|
zcurses_colorpairs->emptytable = emptyhashtable;
|
|
zcurses_colorpairs->filltable = NULL;
|
|
zcurses_colorpairs->cmpnodes = strcmp;
|
|
zcurses_colorpairs->addnode = addhashnode;
|
|
zcurses_colorpairs->getnode = gethashnode2;
|
|
zcurses_colorpairs->getnode2 = gethashnode2;
|
|
zcurses_colorpairs->removenode = removehashnode;
|
|
zcurses_colorpairs->disablenode = NULL;
|
|
zcurses_colorpairs->enablenode = NULL;
|
|
zcurses_colorpairs->freenode = freecolorpairnode;
|
|
zcurses_colorpairs->printnode = NULL;
|
|
|
|
#ifdef HAVE_USE_DEFAULT_COLORS
|
|
use_default_colors();
|
|
#endif
|
|
/* Initialise the default color pair, always 0 */
|
|
cpn = (Colorpairnode)zalloc(sizeof(struct colorpairnode));
|
|
if (cpn) {
|
|
cpn->colorpair = 0;
|
|
addhashnode(zcurses_colorpairs,
|
|
ztrdup("default/default"), (void *)cpn);
|
|
}
|
|
}
|
|
/*
|
|
* We use cbreak mode because we don't want line buffering
|
|
* on input since we'd just need to loop over characters.
|
|
* We use noecho since the manual says that's the right
|
|
* thing to do with cbreak.
|
|
*
|
|
* Turn these on immediately to catch typeahead.
|
|
*/
|
|
cbreak();
|
|
noecho();
|
|
gettyinfo(&curses_tty_state);
|
|
} else {
|
|
settyinfo(&curses_tty_state);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_addwin(const char *nam, char **args)
|
|
{
|
|
int nlines, ncols, begin_y, begin_x;
|
|
ZCWin w;
|
|
|
|
if (zcurses_validate_window(args[0], ZCURSES_UNUSED) == NULL &&
|
|
zc_errno) {
|
|
zerrnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0], 0);
|
|
return 1;
|
|
}
|
|
|
|
nlines = atoi(args[1]);
|
|
ncols = atoi(args[2]);
|
|
begin_y = atoi(args[3]);
|
|
begin_x = atoi(args[4]);
|
|
|
|
w = (ZCWin)zshcalloc(sizeof(struct zc_win));
|
|
if (!w)
|
|
return 1;
|
|
|
|
w->name = ztrdup(args[0]);
|
|
if (args[5]) {
|
|
LinkNode node;
|
|
ZCWin worig;
|
|
|
|
node = zcurses_validate_window(args[5], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0],
|
|
0);
|
|
zsfree(w->name);
|
|
zfree(w, sizeof(struct zc_win));
|
|
return 1;
|
|
}
|
|
|
|
worig = (ZCWin)getdata(node);
|
|
|
|
w->win = subwin(worig->win, nlines, ncols, begin_y, begin_x);
|
|
if (w->win) {
|
|
w->parent = worig;
|
|
if (!worig->children)
|
|
worig->children = znewlinklist();
|
|
zinsertlinknode(worig->children, lastnode(worig->children),
|
|
(void *)w);
|
|
}
|
|
} else {
|
|
w->win = newwin(nlines, ncols, begin_y, begin_x);
|
|
}
|
|
|
|
if (w->win == NULL) {
|
|
zwarnnam(nam, "failed to create window `%s'", w->name);
|
|
zsfree(w->name);
|
|
zfree(w, sizeof(struct zc_win));
|
|
return 1;
|
|
}
|
|
|
|
zinsertlinknode(zcurses_windows, lastnode(zcurses_windows), (void *)w);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
zccmd_delwin(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
int ret = 0;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (w == NULL) {
|
|
zwarnnam(nam, "record for window `%s' is corrupt", args[0]);
|
|
return 1;
|
|
}
|
|
if (w->flags & ZCWF_PERMANENT) {
|
|
zwarnnam(nam, "window `%s' can't be deleted", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (w->children && firstnode(w->children)) {
|
|
zwarnnam(nam, "window `%s' has subwindows, delete those first",
|
|
w->name);
|
|
return 1;
|
|
}
|
|
|
|
if (delwin(w->win)!=OK) {
|
|
/*
|
|
* Not sure what to do here, but we are probably stuffed,
|
|
* so delete the window locally anyway.
|
|
*/
|
|
ret = 1;
|
|
}
|
|
|
|
if (w->parent) {
|
|
/* Remove from parent's list of children */
|
|
LinkList wpc = w->parent->children;
|
|
LinkNode pcnode;
|
|
for (pcnode = firstnode(wpc); pcnode; incnode(pcnode)) {
|
|
ZCWin child = (ZCWin)getdata(pcnode);
|
|
if (child == w) {
|
|
remnode(wpc, pcnode);
|
|
break;
|
|
}
|
|
}
|
|
DPUTS(pcnode == NULL, "BUG: child node not found in parent's children");
|
|
/*
|
|
* We need to touch the parent to get the parent to refresh
|
|
* properly.
|
|
*/
|
|
touchwin(w->parent->win);
|
|
}
|
|
else
|
|
touchwin(stdscr);
|
|
|
|
if (w->name)
|
|
zsfree(w->name);
|
|
|
|
zfree((ZCWin)remnode(zcurses_windows, node), sizeof(struct zc_win));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_refresh(const char *nam, char **args)
|
|
{
|
|
WINDOW *win;
|
|
int ret = 0;
|
|
|
|
if (args[0]) {
|
|
for (; *args; args++) {
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0],
|
|
0);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (w->parent) {
|
|
/* This is what the manual says you have to do. */
|
|
touchwin(w->parent->win);
|
|
}
|
|
win = w->win;
|
|
if (wnoutrefresh(win) != OK)
|
|
ret = 1;
|
|
}
|
|
return (doupdate() != OK || ret);
|
|
}
|
|
else
|
|
{
|
|
return (wrefresh(stdscr) != OK) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_move(const char *nam, char **args)
|
|
{
|
|
int y, x;
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
y = atoi(args[1]);
|
|
x = atoi(args[2]);
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (wmove(w->win, y, x)!=OK)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_clear(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (!args[1]) {
|
|
return werase(w->win) != OK;
|
|
} else if (!strcmp(args[1], "redraw")) {
|
|
return wclear(w->win) != OK;
|
|
} else if (!strcmp(args[1], "eol")) {
|
|
return wclrtoeol(w->win) != OK;
|
|
} else if (!strcmp(args[1], "bot")) {
|
|
return wclrtobot(w->win) != OK;
|
|
} else {
|
|
zwarnnam(nam, "`clear' expects `redraw', `eol' or `bot'");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_char(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
#ifdef HAVE_SETCCHAR
|
|
wchar_t c;
|
|
cchar_t cc;
|
|
#endif
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
#ifdef HAVE_SETCCHAR
|
|
if (mbrtowc(&c, args[1], MB_CUR_MAX, NULL) < 1)
|
|
return 1;
|
|
|
|
if (setcchar(&cc, &c, A_NORMAL, 0, NULL)==ERR)
|
|
return 1;
|
|
|
|
if (wadd_wch(w->win, &cc)!=OK)
|
|
return 1;
|
|
#else
|
|
if (waddch(w->win, (chtype)args[1][0])!=OK)
|
|
return 1;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_string(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
#ifdef HAVE_WADDWSTR
|
|
int clen;
|
|
wint_t wc;
|
|
wchar_t *wstr, *wptr;
|
|
char *str = args[1];
|
|
#endif
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
#ifdef HAVE_WADDWSTR
|
|
mb_metacharinit();
|
|
wptr = wstr = zhalloc((strlen(str)+1) * sizeof(wchar_t));
|
|
|
|
while (*str && (clen = mb_metacharlenconv(str, &wc))) {
|
|
str += clen;
|
|
if (wc == WEOF) /* TODO: replace with space? nicen? */
|
|
continue;
|
|
*wptr++ = wc;
|
|
}
|
|
*wptr++ = L'\0';
|
|
if (waddwstr(w->win, wstr)!=OK) {
|
|
return 1;
|
|
}
|
|
#else
|
|
if (waddstr(w->win, args[1])!=OK)
|
|
return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_border(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (wborder(w->win, 0, 0, 0, 0, 0, 0, 0, 0)!=OK)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_endwin(const char *nam, char **args)
|
|
{
|
|
LinkNode stdscr_win = zcurses_getwindowbyname("stdscr");
|
|
|
|
if (stdscr_win) {
|
|
endwin();
|
|
/* Restore TTY as it was before zcurses -i */
|
|
settyinfo(&saved_tty_state);
|
|
/*
|
|
* TODO: should I need the following? Without it
|
|
* the screen stays messed up. Presumably we are
|
|
* doing stuff with shttyinfo when we shouldn't really be.
|
|
*/
|
|
gettyinfo(&shttyinfo);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_attr(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
char **attrs;
|
|
int ret = 0;
|
|
|
|
if (!args[0])
|
|
return 1;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
for(attrs = args+1; *attrs; attrs++) {
|
|
if (strchr(*attrs, '/')) {
|
|
if (zcurses_colorset(nam, w->win, *attrs))
|
|
ret = 1;
|
|
} else {
|
|
char *ptr;
|
|
int onoff;
|
|
|
|
switch(*attrs[0]) {
|
|
case '-':
|
|
onoff = ZCURSES_ATTROFF;
|
|
ptr = (*attrs) + 1;
|
|
break;
|
|
case '+':
|
|
onoff = ZCURSES_ATTRON;
|
|
ptr = (*attrs) + 1;
|
|
break;
|
|
default:
|
|
onoff = ZCURSES_ATTRON;
|
|
ptr = *attrs;
|
|
break;
|
|
}
|
|
if (zcurses_attribute(w->win, ptr, onoff)) {
|
|
zwarnnam(nam, "attribute `%s' not known", ptr);
|
|
ret = 1;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_scroll(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
int ret = 0;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (!strcmp(args[1], "on")) {
|
|
if (scrollok(w->win, TRUE) == ERR)
|
|
return 1;
|
|
w->flags |= ZCWF_SCROLL;
|
|
} else if (!strcmp(args[1], "off")) {
|
|
if (scrollok(w->win, FALSE) == ERR)
|
|
return 1;
|
|
w->flags &= ~ZCWF_SCROLL;
|
|
} else {
|
|
char *endptr;
|
|
zlong sl = zstrtol(args[1], &endptr, 10);
|
|
if (*endptr) {
|
|
zwarnnam(nam, "scroll requires `on', `off' or integer: %s",
|
|
args[1]);
|
|
return 1;
|
|
}
|
|
if (!(w->flags & ZCWF_SCROLL))
|
|
scrollok(w->win, TRUE);
|
|
if (wscrl(w->win, (int)sl) == ERR)
|
|
ret = 1;
|
|
if (!(w->flags & ZCWF_SCROLL))
|
|
scrollok(w->win, FALSE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_input(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
char *var;
|
|
int keypadnum = -1;
|
|
#ifdef HAVE_WGET_WCH
|
|
int ret;
|
|
wint_t wi;
|
|
VARARR(char, instr, 2*MB_CUR_MAX+1);
|
|
#else
|
|
int ci;
|
|
char instr[3];
|
|
#endif
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
if (args[1] && args[2]) {
|
|
keypad(w->win, TRUE);
|
|
} else {
|
|
keypad(w->win, FALSE);
|
|
}
|
|
|
|
#ifdef HAVE_WGET_WCH
|
|
switch (wget_wch(w->win, &wi)) {
|
|
case OK:
|
|
ret = wctomb(instr, (wchar_t)wi);
|
|
if (ret == 0) {
|
|
instr[0] = Meta;
|
|
instr[1] = '\0' ^ 32;
|
|
instr[2] = '\0';
|
|
} else {
|
|
(void)metafy(instr, ret, META_NOALLOC);
|
|
}
|
|
break;
|
|
|
|
case KEY_CODE_YES:
|
|
*instr = '\0';
|
|
keypadnum = (int)wi;
|
|
break;
|
|
|
|
case ERR:
|
|
default:
|
|
return 1;
|
|
}
|
|
#else
|
|
ci = wgetch(w->win);
|
|
if (ci == ERR)
|
|
return 1;
|
|
if (ci >= 256) {
|
|
keypadnum = ci;
|
|
*instr = '\0';
|
|
} else {
|
|
if (imeta(ci)) {
|
|
instr[0] = Meta;
|
|
instr[1] = (char)ci ^ 32;
|
|
instr[2] = '\0';
|
|
} else {
|
|
instr[0] = (char)ci;
|
|
instr[1] = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
if (args[1])
|
|
var = args[1];
|
|
else
|
|
var = "REPLY";
|
|
if (!setsparam(var, ztrdup(instr)))
|
|
return 1;
|
|
if (args[1] && args[2]) {
|
|
if (keypadnum > 0) {
|
|
const struct zcurses_namenumberpair *nnptr;
|
|
char fbuf[DIGBUFSIZE+1];
|
|
|
|
for (nnptr = keypad_names; nnptr->name; nnptr++) {
|
|
if (keypadnum == nnptr->number) {
|
|
if (!setsparam(args[2], ztrdup(nnptr->name)))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
if (keypadnum > KEY_F0) {
|
|
/* assume it's a function key */
|
|
sprintf(fbuf, "F%d", keypadnum - KEY_F0);
|
|
} else {
|
|
/* print raw number */
|
|
sprintf(fbuf, "%d", keypadnum);
|
|
}
|
|
if (!setsparam(args[2], ztrdup(fbuf)))
|
|
return 1;
|
|
} else {
|
|
if (!setsparam(args[2], ztrdup("")))
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_timeout(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
int to;
|
|
char *eptr;
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
to = (int)zstrtol(args[1], &eptr, 10);
|
|
if (*eptr) {
|
|
zwarnnam(nam, "timeout requires an integer: %s", args[1]);
|
|
return 1;
|
|
}
|
|
|
|
wtimeout(w->win, to);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_position(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
int i, intarr[6];
|
|
char **array, dbuf[DIGBUFSIZE];
|
|
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
|
|
/* Look no pointers: these are macros. */
|
|
if (getyx(w->win, intarr[0], intarr[1]) == ERR ||
|
|
getbegyx(w->win, intarr[2], intarr[3]) == ERR ||
|
|
getmaxyx(w->win, intarr[4], intarr[5]) == ERR)
|
|
return 1;
|
|
|
|
array = (char **)zalloc(7*sizeof(char *));
|
|
for (i = 0; i < 6; i++) {
|
|
sprintf(dbuf, "%d", intarr[i]);
|
|
array[i] = ztrdup(dbuf);
|
|
}
|
|
array[6] = NULL;
|
|
|
|
setaparam(args[1], array);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
zccmd_touch(const char *nam, char **args)
|
|
{
|
|
LinkNode node;
|
|
ZCWin w;
|
|
int ret = 0;
|
|
|
|
for (; *args; args++) {
|
|
node = zcurses_validate_window(args[0], ZCURSES_USED);
|
|
if (node == NULL) {
|
|
zwarnnam(nam, "%s: %s", zcurses_strerror(zc_errno), args[0]);
|
|
return 1;
|
|
}
|
|
|
|
w = (ZCWin)getdata(node);
|
|
if (touchwin(w->win) != OK)
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*********************
|
|
Main builtin handler
|
|
*********************/
|
|
|
|
/**/
|
|
static int
|
|
bin_zcurses(char *nam, char **args, Options ops, UNUSED(int func))
|
|
{
|
|
char **saargs;
|
|
struct zcurses_subcommand *zcsc;
|
|
int num_args;
|
|
|
|
struct zcurses_subcommand scs[] = {
|
|
{"init", zccmd_init, 0, 0},
|
|
{"addwin", zccmd_addwin, 5, 6},
|
|
{"delwin", zccmd_delwin, 1, 1},
|
|
{"refresh", zccmd_refresh, 0, -1},
|
|
{"move", zccmd_move, 3, 3},
|
|
{"clear", zccmd_clear, 1, 2},
|
|
{"position", zccmd_position, 2, 2},
|
|
{"char", zccmd_char, 2, 2},
|
|
{"string", zccmd_string, 2, 2},
|
|
{"border", zccmd_border, 1, 1},
|
|
{"end", zccmd_endwin, 0, 0},
|
|
{"attr", zccmd_attr, 2, -1},
|
|
{"scroll", zccmd_scroll, 2, 2},
|
|
{"input", zccmd_input, 1, 3},
|
|
{"timeout", zccmd_timeout, 2, 2},
|
|
{"touch", zccmd_touch, 1, -1},
|
|
{NULL, (zccmd_t)0, 0, 0}
|
|
};
|
|
|
|
for(zcsc = scs; zcsc->name; zcsc++) {
|
|
if(!strcmp(args[0], zcsc->name))
|
|
break;
|
|
}
|
|
|
|
if (zcsc->name == NULL) {
|
|
zwarnnam(nam, "unknown subcommand: %s", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
saargs = args;
|
|
while (*saargs++);
|
|
num_args = saargs - (args + 2);
|
|
|
|
if (num_args < zcsc->minargs) {
|
|
zwarnnam(nam, "too few arguments for subcommand: %s", args[0]);
|
|
return 1;
|
|
} else if (zcsc->maxargs >= 0 && num_args > zcsc->maxargs) {
|
|
zwarnnam(nam, "too may arguments for subcommand: %s", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (zcsc->cmd != zccmd_init && zcsc->cmd != zccmd_endwin &&
|
|
!zcurses_getwindowbyname("stdscr")) {
|
|
zwarnnam(nam, "command `%s' can't be used before `zcurses init'",
|
|
zcsc->name);
|
|
return 1;
|
|
}
|
|
|
|
return zcsc->cmd(nam, args+1);
|
|
}
|
|
|
|
|
|
static struct builtin bintab[] = {
|
|
BUILTIN("zcurses", 0, bin_zcurses, 1, -1, 0, "", NULL),
|
|
};
|
|
|
|
|
|
/*******************
|
|
* Special variables
|
|
*******************/
|
|
|
|
static char **
|
|
zcurses_colorsarrgetfn(UNUSED(Param pm))
|
|
{
|
|
return zcurses_pairs_to_array(zcurses_colors);
|
|
}
|
|
|
|
static const struct gsu_array zcurses_colorsarr_gsu =
|
|
{ zcurses_colorsarrgetfn, arrsetfn, stdunsetfn };
|
|
|
|
|
|
static char **
|
|
zcurses_attrgetfn(UNUSED(Param pm))
|
|
{
|
|
return zcurses_pairs_to_array(zcurses_attributes);
|
|
}
|
|
|
|
static const struct gsu_array zcurses_attrs_gsu =
|
|
{ zcurses_attrgetfn, arrsetfn, stdunsetfn };
|
|
|
|
|
|
static char **
|
|
zcurses_windowsgetfn(UNUSED(Param pm))
|
|
{
|
|
LinkNode node;
|
|
char **arr, **arrptr;
|
|
int count = countlinknodes(zcurses_windows);
|
|
|
|
arrptr = arr = (char **)zhalloc((count+1) * sizeof(char *));
|
|
|
|
for (node = firstnode(zcurses_windows); node; incnode(node))
|
|
*arrptr++ = dupstring(((ZCWin)getdata(node))->name);
|
|
*arrptr = NULL;
|
|
|
|
return arr;
|
|
}
|
|
|
|
static const struct gsu_array zcurses_windows_gsu =
|
|
{ zcurses_windowsgetfn, arrsetfn, stdunsetfn };
|
|
|
|
|
|
static zlong
|
|
zcurses_colorsintgetfn(UNUSED(Param pm))
|
|
{
|
|
return COLORS;
|
|
}
|
|
|
|
static const struct gsu_integer zcurses_colorsint_gsu =
|
|
{ zcurses_colorsintgetfn, nullintsetfn, stdunsetfn };
|
|
|
|
|
|
static zlong
|
|
zcurses_colorpairsintgetfn(UNUSED(Param pm))
|
|
{
|
|
return COLOR_PAIRS;
|
|
}
|
|
|
|
static const struct gsu_integer zcurses_colorpairsint_gsu =
|
|
{ zcurses_colorpairsintgetfn, nullintsetfn, stdunsetfn };
|
|
|
|
|
|
static struct paramdef partab[] = {
|
|
SPECIALPMDEF("zcurses_colors", PM_ARRAY|PM_READONLY,
|
|
&zcurses_colorsarr_gsu, NULL, NULL),
|
|
SPECIALPMDEF("zcurses_attrs", PM_ARRAY|PM_READONLY,
|
|
&zcurses_attrs_gsu, NULL, NULL),
|
|
SPECIALPMDEF("zcurses_windows", PM_ARRAY|PM_READONLY,
|
|
&zcurses_windows_gsu, NULL, NULL),
|
|
SPECIALPMDEF("ZCURSES_COLORS", PM_INTEGER|PM_READONLY,
|
|
&zcurses_colorsint_gsu, NULL, NULL),
|
|
SPECIALPMDEF("ZCURSES_COLOR_PAIRS", PM_INTEGER|PM_READONLY,
|
|
&zcurses_colorpairsint_gsu, NULL, NULL)
|
|
};
|
|
|
|
/***************************
|
|
* Standard module interface
|
|
***************************/
|
|
|
|
|
|
/*
|
|
* boot_ is executed when the module is loaded.
|
|
*/
|
|
|
|
static struct features module_features = {
|
|
bintab, sizeof(bintab)/sizeof(*bintab),
|
|
NULL, 0,
|
|
NULL, 0,
|
|
partab, sizeof(partab)/sizeof(*partab),
|
|
0
|
|
};
|
|
|
|
/**/
|
|
int
|
|
setup_(UNUSED(Module m))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
features_(Module m, char ***features)
|
|
{
|
|
*features = featuresarray(m, &module_features);
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
enables_(Module m, int **enables)
|
|
{
|
|
return handlefeatures(m, &module_features, enables);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
boot_(Module m)
|
|
{
|
|
zcurses_windows = znewlinklist();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
cleanup_(Module m)
|
|
{
|
|
freelinklist(zcurses_windows, (FreeFunc) zcurses_free_window);
|
|
if (zcurses_colorpairs)
|
|
deletehashtable(zcurses_colorpairs);
|
|
return setfeatureenables(m, &module_features, NULL);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
finish_(UNUSED(Module m))
|
|
{
|
|
return 0;
|
|
}
|