1
0
Fork 0
mirror of https://codeberg.org/noisytoot/notnotdnethack.git synced 2024-11-14 13:26:10 +00:00
notnotdnethack/win/curses/cursmisc.c
Ron Nazarov b5245a400c
Bugfix: curses command count was capped at LARGEST_INT (32767)
This made it impossible to repeat a single command more than 32767
times, or take out/put in a partial stack of more than 32767 items.
It's now capped at INT_MAX (2147483647 with 32-bit int) instead and
uses C23 stdckdint.h macros to avoid signed integer overflows (which
are undefined behaviour).  If stdckdint.h is unavailable (it was only
added in GCC 14), uses the GCC __builtin_*_overflow functions, which
are supported since GCC 5.  If these too are unavailable, falls back
to an implementation using C11 _Generic.  The fallback ckd_mul
implementation just casts to a long long, so won't work for anything
larger than a 32-bit integer (assuming 64-bit long long), which is
fine for now since nothing since it's only used on ints.  I'm not sure
the fallback implementations are even necessary, since we already
require either C23 or a GCC extension (__VA_OPT__) for macromagic.
2024-05-23 02:43:35 +01:00

876 lines
19 KiB
C

/* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "cursmisc.h"
#include "cursstat.h"
#include "func_tab.h"
#include "dlb.h"
#include <ctype.h>
#include <limits.h>
/* Misc. curses interface functions */
/* Private declarations */
static int curs_x = -1;
static int curs_y = -1;
static int parse_escape_sequence(void);
/* Macros for Control and Alt keys */
#ifndef M
# define M(c) ((c) - 128)
#endif
#ifndef C
# define C(c) (0x1f & (c))
#endif
/* Read a character of input from the user */
int
curses_read_char(void)
{
int ch, tmpch;
ch = getch();
tmpch = ch;
ch = curses_convert_keys(ch);
if (ch == 0) {
ch = DOESCAPE; /* map NUL to ESC since nethack doesn't expect NUL */
}
#if defined(ALT_0) && defined(ALT_9) /* PDCurses, maybe others */
if ((ch >= ALT_0) && (ch <= ALT_9)) {
tmpch = (ch - ALT_0) + '0';
ch = M(tmpch);
}
#endif
#if defined(ALT_A) && defined(ALT_Z) /* PDCurses, maybe others */
if ((ch >= ALT_A) && (ch <= ALT_Z)) {
tmpch = (ch - ALT_A) + 'a';
ch = M(tmpch);
}
#endif
#ifdef KEY_RESIZE
/* Handle resize events via get_nh_event, not this code */
if (ch == KEY_RESIZE) {
ch = DOESCAPE; /* NetHack doesn't know what to do with KEY_RESIZE */
}
#endif
if (counting && !isdigit(ch)) { /* Dismiss count window if necissary */
curses_count_window(NULL);
curses_refresh_nethack_windows();
}
return ch;
}
/* Turn on or off the specified color and / or attribute */
void
curses_toggle_color_attr(WINDOW * win, int color, int attr, int onoff)
{
int curses_color;
/* Map color disabled */
if ((!iflags.wc_color) && (win == mapwin)) {
return;
}
/* GUI color disabled */
if ((!iflags.wc2_guicolor) && (win != mapwin)) {
return;
}
if (color == 0) { /* make black fg visible */
# ifdef USE_DARKGRAY
if (iflags.wc2_darkgray) {
if (can_change_color() && (COLORS > 16)) {
/* colorpair for black is already darkgray */
} else { /* Use bold for a bright black */
wattron(win, A_BOLD);
}
} else
# endif/* USE_DARKGRAY */
color = CLR_BLUE;
}
curses_color = color + 1;
if (COLORS < 16) {
if (curses_color > 8 && curses_color < 17)
curses_color -= 8;
else if (curses_color > (17 + 16))
curses_color -= 16;
}
if (onoff == ON) { /* Turn on color/attributes */
if (color != NONE) {
if ((((color > 7) && (color < 17)) ||
(color > 17 + 17)) && (COLORS < 16)) {
wattron(win, A_BOLD);
}
wattron(win, COLOR_PAIR(curses_color));
}
if (attr != NONE) {
wattron(win, attr);
}
} else { /* Turn off color/attributes */
if (color != NONE) {
if ((color > 7) && (COLORS < 16)) {
wattroff(win, A_BOLD);
}
# ifdef USE_DARKGRAY
if ((color == 0) && (!can_change_color() || (COLORS <= 16))) {
wattroff(win, A_BOLD);
}
# else
if (iflags.use_inverse) {
wattroff(win, A_REVERSE);
}
# endif/* DARKGRAY */
wattroff(win, COLOR_PAIR(curses_color));
}
if (attr != NONE) {
wattroff(win, attr);
}
}
}
/* clean up and quit - taken from tty port */
void
curses_bail(const char *mesg)
{
clearlocks();
curses_exit_nhwindows(mesg);
terminate(EXIT_SUCCESS);
}
/* Return a winid for a new window of the given type */
winid
curses_get_wid(int type)
{
winid ret;
static winid menu_wid = 20; /* Always even */
static winid text_wid = 21; /* Always odd */
switch (type) {
case NHW_MESSAGE:
return MESSAGE_WIN;
case NHW_MAP:
return MAP_WIN;
case NHW_STATUS:
return STATUS_WIN;
case NHW_MENU:
ret = menu_wid;
break;
case NHW_TEXT:
ret = text_wid;
break;
default:
impossible("curses_get_wid: unsupported window type");
ret = -1;
}
while (curses_window_exists(ret)) {
ret += 2;
if ((ret + 2) > 10000) { /* Avoid "wid2k" problem */
ret -= 9900;
}
}
if (type == NHW_MENU) {
menu_wid += 2;
} else {
text_wid += 2;
}
return ret;
}
/*
* Allocate a copy of the given string. If null, return a string of
* zero length.
*
* This is taken from copy_of() in tty/wintty.c.
*/
char *
curses_copy_of(const char *s)
{
if (!s)
s = "";
return strcpy((char *) alloc((unsigned) (strlen(s) + 1)), s);
}
/* Determine the number of lines needed for a string for a dialog window
of the given width */
int
curses_num_lines(const char *str, int width)
{
int last_space, count;
int curline = 1;
char substr[BUFSZ];
char tmpstr[BUFSZ];
strncpy(substr, str, BUFSZ-1);
substr[BUFSZ-1] = '\0';
while (strlen(substr) > width) {
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ')
last_space = count;
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = (last_space + 1); count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
curline++;
}
return curline;
}
/* Break string into smaller lines to fit into a dialog window of the
given width */
char *
curses_break_str(const char *str, int width, int line_num)
{
int last_space, count;
char *retstr;
int curline = 0;
int strsize = strlen(str);
char substr[strsize+1];
char curstr[strsize+1];
char tmpstr[strsize+1];
strcpy(substr, str);
while (curline < line_num) {
if (strlen(substr) == 0) {
break;
}
curline++;
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ') {
last_space = count;
} else if (substr[count] == '\0') {
last_space = count;
break;
}
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = 0; count < last_space; count++) {
curstr[count] = substr[count];
}
curstr[count] = '\0';
if (substr[count] == '\0') {
break;
}
for (count = (last_space + 1); count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
}
if (curline < line_num) {
return NULL;
}
retstr = curses_copy_of(curstr);
return retstr;
}
/* Return the remaining portion of a string after hacking-off line_num lines */
char *
curses_str_remainder(const char *str, int width, int line_num)
{
int last_space, count;
char *retstr;
int curline = 0;
int strsize = strlen(str) + 1;
char substr[strsize];
char curstr[strsize];
char tmpstr[strsize];
strncpy(substr, str, strsize);
while (curline < line_num) {
if (strlen(substr) == 0) {
break;
}
curline++;
last_space = 0;
for (count = 0; count <= width; count++) {
if (substr[count] == ' ') {
last_space = count;
} else if (substr[count] == '\0') {
last_space = count;
break;
}
}
if (last_space == 0) { /* No spaces found */
last_space = count - 1;
}
for (count = 0; count < last_space; count++) {
curstr[count] = substr[count];
}
curstr[count] = '\0';
if (substr[count] == '\0') {
break;
}
for (count = (last_space + 1); count < strlen(substr); count++) {
tmpstr[count - (last_space + 1)] = substr[count];
}
tmpstr[count - (last_space + 1)] = '\0';
strcpy(substr, tmpstr);
}
if (curline < line_num) {
return NULL;
}
retstr = curses_copy_of(substr);
return retstr;
}
/* Determine if the given NetHack winid is a menu window */
boolean
curses_is_menu(winid wid)
{
if ((wid > 19) && !(wid % 2)) { /* Even number */
return TRUE;
} else {
return FALSE;
}
}
/* Determine if the given NetHack winid is a text window */
boolean
curses_is_text(winid wid)
{
if ((wid > 19) && (wid % 2)) { /* Odd number */
return TRUE;
} else {
return FALSE;
}
}
/* Replace certain characters with portable drawing characters if
cursesgraphics option is enabled */
int
curses_convert_glyph(int ch, int glyph)
{
int symbol;
#ifdef REINCARNATION
if (Is_rogue_level(&u.uz)) {
return ch;
}
#endif
/* Save some processing time by returning if the glyph represents
an object that we don't have custom characters for */
if (!glyph_is_cmap(glyph)) {
return ch;
}
symbol = glyph_to_cmap(glyph);
/* If user selected a custom character for this object, don't
override this. */
if (((glyph_is_cmap(glyph)) && (ch != showsyms[symbol]))) {
return ch;
}
switch (symbol) {
case S_vwall:
return ACS_VLINE;
case S_hwall:
return ACS_HLINE;
case S_tlcorn:
return ACS_ULCORNER;
case S_trcorn:
return ACS_URCORNER;
case S_blcorn:
return ACS_LLCORNER;
case S_brcorn:
return ACS_LRCORNER;
case S_crwall:
return ACS_PLUS;
case S_tuwall:
return ACS_BTEE;
case S_tdwall:
return ACS_TTEE;
case S_tlwall:
return ACS_RTEE;
case S_trwall:
return ACS_LTEE;
case S_tree:
return ACS_PLMINUS;
case S_deadtree:
return ACS_PLMINUS;
case S_corr:
return ACS_CKBOARD;
case S_litcorr:
return ACS_CKBOARD;
}
return ch;
}
/* Move text cursor to specified coordinates in the given NetHack window */
void
curses_move_cursor(winid wid, int x, int y)
{
int sx, sy, ex, ey;
int xadj = 0;
int yadj = 0;
#ifndef PDCURSES
WINDOW *win = curses_get_nhwin(MAP_WIN);
#endif
if (wid != MAP_WIN) {
return;
}
#ifdef PDCURSES
/* PDCurses seems to not handle wmove correctly, so we use move and
physical screen coordinates instead */
curses_get_window_xy(wid, &xadj, &yadj);
#endif
curs_x = x + xadj;
curs_y = y + yadj;
curses_map_borders(&sx, &sy, &ex, &ey, x, y);
if (curses_window_has_border(wid)) {
curs_x++;
curs_y++;
}
if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
curs_x -= sx;
curs_y -= sy;
#ifdef PDCURSES
move(curs_y, curs_x);
#else
wmove(win, curs_y, curs_x);
#endif
}
}
/* Perform actions that should be done every turn before nhgetch() */
void
curses_prehousekeeping(void)
{
#ifndef PDCURSES
WINDOW *win = curses_get_nhwin(MAP_WIN);
#endif /* PDCURSES */
if ((curs_x > -1) && (curs_y > -1)) {
curs_set(1);
#ifdef PDCURSES
/* PDCurses seems to not handle wmove correctly, so we use move
and physical screen coordinates instead */
move(curs_y, curs_x);
#else
wmove(win, curs_y, curs_x);
#endif /* PDCURSES */
curses_refresh_nhwin(MAP_WIN);
}
}
/* Perform actions that should be done every turn after nhgetch() */
void
curses_posthousekeeping(void)
{
curs_set(0);
curses_decrement_highlights(FALSE);
curses_clear_unhighlight_message_window();
}
void
curses_view_file(const char *filename, boolean must_exist)
{
winid wid;
anything identifier;
char buf[BUFSZ];
menu_item *selected = NULL;
dlb *fp = dlb_fopen(filename, "r");
if ((fp == NULL) && (must_exist)) {
pline("Cannot open %s for reading!", filename);
}
if (fp == NULL) {
return;
}
wid = curses_get_wid(NHW_MENU);
curses_create_nhmenu(wid);
identifier = zeroany;
while (dlb_fgets(buf, BUFSZ, fp) != NULL) {
curses_add_menu(wid, NO_GLYPH, &identifier, 0, 0, A_NORMAL, buf, FALSE);
}
dlb_fclose(fp);
curses_end_menu(wid, "");
curses_select_menu(wid, PICK_NONE, &selected);
}
void
curses_rtrim(char *str)
{
char *s;
for (s = str; *s != '\0'; ++s);
if (s == str)
return;
for (--s; isspace(*s) && s > str; --s);
if (s == str)
*s = '\0';
else
*(++s) = '\0';
}
/* Read numbers until non-digit is encountered, and return number
in int form. */
int
curses_get_count(int first_digit)
{
int current_count = first_digit;
int current_char;
current_char = curses_read_char();
while (isdigit(current_char)) {
if (ckd_mul(&current_count, current_count, 10)) {
current_count = INT_MAX;
} else if (ckd_add(&current_count, current_count, current_char - '0')) {
current_count = INT_MAX;
}
pline("Count: %d", current_count);
current_char = curses_read_char();
}
ungetch(current_char);
if (current_char == DOESCAPE) { /* Cancelled with escape */
current_count = -1;
}
return current_count;
}
/* Convert the given NetHack text attributes into the format curses
understands, and return that format mask. */
int
curses_convert_attr(int attr)
{
int curses_attr;
switch (attr) {
case ATR_NONE:
curses_attr = A_NORMAL;
break;
case ATR_ULINE:
curses_attr = A_UNDERLINE;
break;
case ATR_BOLD:
curses_attr = A_BOLD;
break;
case ATR_BLINK:
curses_attr = A_BLINK;
break;
case ATR_INVERSE:
curses_attr = A_REVERSE;
break;
default:
curses_attr = A_NORMAL;
}
return curses_attr;
}
/* Map letter attributes from a string to bitmask. Return mask on
success, or 0 if not found */
int
curses_read_attrs(char *attrs)
{
int retattr = 0;
if (strchr(attrs, 'b') || strchr(attrs, 'B')) {
retattr = retattr | A_BOLD;
}
if (strchr(attrs, 'i') || strchr(attrs, 'I')) {
retattr = retattr | A_REVERSE;
}
if (strchr(attrs, 'u') || strchr(attrs, 'U')) {
retattr = retattr | A_UNDERLINE;
}
if (strchr(attrs, 'k') || strchr(attrs, 'K')) {
retattr = retattr | A_BLINK;
}
#ifdef A_ITALIC
if (strchr(attrs, 't') || strchr(attrs, 'T')) {
retattr = retattr | A_ITALIC;
}
#endif
#ifdef A_RIGHTLINE
if (strchr(attrs, 'r') || strchr(attrs, 'R')) {
retattr = retattr | A_RIGHTLINE;
}
#endif
#ifdef A_LEFTLINE
if (strchr(attrs, 'l') || strchr(attrs, 'L')) {
retattr = retattr | A_LEFTLINE;
}
#endif
return retattr;
}
/* Convert special keys into values that NetHack can understand.
Currently this is limited to arrow keys, but this may be expanded. */
int
curses_convert_keys(int key)
{
int ret = key;
if (ret == '\033') {
ret = parse_escape_sequence();
}
/* Handle arrow keys */
switch (key) {
case KEY_LEFT:
if (iflags.num_pad) {
ret = '4';
} else {
ret = 'h';
}
break;
case KEY_RIGHT:
if (iflags.num_pad) {
ret = '6';
} else {
ret = 'l';
}
break;
case KEY_UP:
if (iflags.num_pad) {
ret = '8';
} else {
ret = 'k';
}
break;
case KEY_DOWN:
if (iflags.num_pad) {
ret = '2';
} else {
ret = 'j';
}
break;
#ifdef KEY_A1
case KEY_A1:
if (iflags.num_pad) {
ret = '7';
} else {
ret = 'y';
}
break;
#endif /* KEY_A1 */
#ifdef KEY_A3
case KEY_A3:
if (iflags.num_pad) {
ret = '9';
} else {
ret = 'u';
}
break;
#endif /* KEY_A3 */
#ifdef KEY_C1
case KEY_C1:
if (iflags.num_pad) {
ret = '1';
} else {
ret = 'b';
}
break;
#endif /* KEY_C1 */
#ifdef KEY_C3
case KEY_C3:
if (iflags.num_pad) {
ret = '3';
} else {
ret = 'n';
}
break;
#endif /* KEY_C3 */
#ifdef KEY_B2
case KEY_B2:
if (iflags.num_pad) {
ret = '5';
} else {
ret = 'g';
}
break;
#endif /* KEY_B2 */
}
return ret;
}
/* Process mouse events. Mouse movement is processed until no further
mouse movement events are available. Returns 0 for a mouse click
event, or the first non-mouse key event in the case of mouse
movement. */
int
curses_get_mouse(int *mousex, int *mousey, int *mod)
{
int key = '\033';
#ifdef NCURSES_MOUSE_VERSION
MEVENT event;
if (getmouse(&event) == OK) { /* When the user clicks left mouse button */
if (event.bstate & BUTTON1_CLICKED) {
/* See if coords are in map window & convert coords */
if (wmouse_trafo(mapwin, &event.y, &event.x, TRUE)) {
key = 0; /* Flag mouse click */
*mousex = event.x;
*mousey = event.y;
if (curses_window_has_border(MAP_WIN)) {
(*mousex)--;
(*mousey)--;
}
*mod = CLICK_1;
}
}
}
#endif /* NCURSES_MOUSE_VERSION */
return key;
}
static int
parse_escape_sequence(void)
{
#ifndef PDCURSES
int ret;
timeout(10);
ret = getch();
if (ret != ERR) { /* Likely an escape sequence */
if (((ret >= 'a') && (ret <= 'z')) || ((ret >= '0') && (ret <= '9'))) {
ret |= 0x80; /* Meta key support for most terminals */
} else if (ret == 'O') { /* Numeric keypad */
ret = getch();
if ((ret != ERR) && (ret >= 112) && (ret <= 121)) {
ret = ret - 112 + '0'; /* Convert to number */
} else {
ret = '\033'; /* Escape */
}
}
} else {
ret = '\033'; /* Just an escape character */
}
timeout(-1);
return ret;
#else
return '\033';
#endif /* !PDCURSES */
}
/* This is a kludge for the statuscolors patch which calls tty-specific
functions, which causes a compiler error if TTY_GRAPHICS is not
defined. Adding stub functions to avoid this. */
#ifndef TTY_GRAPHICS
extern void
term_start_color(int color)
{
}
extern void
term_start_attr(int attr)
{
}
extern void
term_end_color(void)
{
}
extern void
term_end_attr(int attr)
{
}
#endif /* !TTY_GRAPGICS */