/* 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" -*-*/ /* NetHack 3.7 cursmisc.c */ /* Copyright (c) Karl Garrison, 2010. */ /* NetHack may be freely redistributed. See license for details. */ #include "curses.h" #include "hack.h" #include "wincurs.h" #include "cursmisc.h" #include "func_tab.h" #include "dlb.h" /* Misc. curses interface functions */ /* Private declarations */ static int curs_x = -1; static int curs_y = -1; #if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT) static unsigned long last_getch_modifiers = 0L; static boolean modifiers_available = TRUE; #else static boolean modifiers_available = FALSE; #endif static int modified(int ch); static void update_modifiers(void); static int parse_escape_sequence(int, boolean *); #define SS3 M(C('O')) /* 8-bit escape sequence initiator for VT number pad */ int curses_getch(void) { int ch; if (iflags.debug_fuzzer) ch = randomkey(); else ch = getch(); return ch; } /* Read a character of input from the user */ int curses_read_char(void) { int ch; /* cancel message suppression; all messages have had a chance to be read */ curses_got_input(); ch = curses_getch(); ch = curses_convert_keys(ch); if (ch == 0) { ch = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */ } if (counting && !isdigit(ch)) { /* dismiss count window if necessary */ 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) { if (color == NO_COLOR) color = NONE; int curses_color; boolean use_bold = FALSE; /* if color is disabled, just show attribute */ if ((win == mapwin) ? !iflags.wc_color /* statuswin is for #if STATUS_HILITES but doesn't need to be conditional */ : !(iflags.wc2_guicolor || win == statuswin)) { if (attr != NONE) { if (onoff == ON) wattron(win, attr); else wattroff(win, attr); } return; } if (color == CLR_BLACK) { /* make black fg visible */ # ifdef USE_DARKGRAY if (iflags.wc2_darkgray) { if (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; } if (COLORS < 16) { /* convert NetHack's 16 colors to 8 colors + BOLD */ int fg = color % 16; int bg = color / 16; if (fg > 7) use_bold = TRUE; curses_color = (8 * (bg % 8)) + (fg % 8) + 1; } else { curses_color = color + 1; } if (onoff == ON) { /* Turn on color/attributes */ if (color != NONE) { if (use_bold) { 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 (use_bold) { wattroff(win, A_BOLD); } # ifdef USE_DARKGRAY if ((color == 0) && (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); } } } /* call curses_toggle_color_attr() with 'menucolors' instead of 'guicolor' as the control flag */ void curses_menu_color_attr(WINDOW *win, int color, int attr, int onoff) { boolean save_guicolor = iflags.wc2_guicolor; /* curses_toggle_color_attr() uses 'guicolor' to decide whether to honor specified color, but menu windows have their own more-specific control, 'menucolors', so override with that here */ /* iflags.wc2_guicolor = iflags.use_menu_color; */ iflags.wc2_guicolor = (color != NONE); curses_toggle_color_attr(win, color, attr, onoff); iflags.wc2_guicolor = save_guicolor; } /* clean up and quit - taken from tty port */ void curses_bail(const char *mesg) { clearlocks(); curses_exit_nhwindows(mesg); nh_terminate(EXIT_SUCCESS); } /* Return a winid for a new window of the given type */ winid curses_get_wid(int type) { static winid menu_wid = 20; /* Always even */ static winid text_wid = 21; /* Always odd */ winid ret; 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 dupstr(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) > (size_t) 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 < (int) 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 = (int) strlen(str) + 1; #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) && !defined(_MSC_VER) char substr[strsize]; char curstr[strsize]; char tmpstr[strsize]; strcpy(substr, str); #else #ifndef BUFSZ #define BUFSZ 256 #endif char substr[BUFSZ * 2]; char curstr[BUFSZ * 2]; char tmpstr[BUFSZ * 2]; if (strsize > (BUFSZ * 2) - 1) { paniclog("curses", "curses_break_str() string too long."); strncpy(substr, str, (BUFSZ * 2) - 2); substr[(BUFSZ * 2) - 1] = '\0'; } else strcpy(substr, str); #endif 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 < (int) strlen(substr); count++) { tmpstr[count - (last_space + 1)] = substr[count]; } tmpstr[count - (last_space + 1)] = '\0'; strcpy(substr, tmpstr); } if (curline < line_num) { #if 0 return NULL; #else /* callers aren't prepared to handle NULL return */ Strcpy(curstr, ""); #endif } 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; #if (__STDC_VERSION__ >= 199901L) && !defined(_MSC_VER) char substr[strsize]; char tmpstr[strsize]; strcpy(substr, str); #else #ifndef BUFSZ #define BUFSZ 256 #endif char substr[BUFSZ * 2]; char tmpstr[BUFSZ * 2]; if (strsize > (BUFSZ * 2) - 1) { paniclog("curses", "curses_str_remainder() string too long."); strncpy(substr, str, (BUFSZ * 2) - 2); substr[(BUFSZ * 2) - 1] = '\0'; } else strcpy(substr, str); #endif 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; } assert(IndexOk(last_space, substr)); if (substr[last_space] == '\0') { break; } for (count = (last_space + 1); count < (int) 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; } } /* convert nethack's DECgraphics encoding into curses' ACS encoding */ int curses_convert_glyph(int ch, int glyph) { /* The DEC line drawing characters use 0x5f through 0x7e instead of the much more straightforward 0x60 through 0x7f, possibly because 0x7f is effectively a control character (Rubout); nethack ORs 0x80 to flag line drawing--that's stripped below */ static int decchars[33]; /* for chars 0x5f through 0x7f (95..127) */ ch &= 0xff; /* 0..255 only */ if (!(ch & 0x80)) return ch; /* no conversion needed */ /* this conversion routine is only called for SYMHANDLING(H_DEC) and we decline to support special graphics symbols on the rogue level */ if (Is_rogue_level(&u.uz)) { /* attempting to use line drawing characters will end up being rendered as lowercase gibberish */ ch &= ~0x80; return ch; } /* * Curses has complete access to all characters that DECgraphics uses. * However, their character value isn't consistent between terminals * and implementations. For actual DEC terminals and faithful emulators, * line-drawing characters are specified as lowercase letters (mostly) * and a control code is sent to the terminal telling it to switch * character sets (that's how the tty interface handles them). * Curses remaps the characters instead. */ /* one-time initialization; some ACS_x aren't compile-time constant */ if (!decchars[0]) { /* [0] is non-breakable space; irrelevant to nethack */ decchars[0x5f - 0x5f] = ' '; /* NBSP */ decchars[0x60 - 0x5f] = ACS_DIAMOND; /* [1] solid diamond */ decchars[0x61 - 0x5f] = ACS_CKBOARD; /* [2] checkerboard */ /* several "line drawing" characters are two-letter glyphs which could be substituted for invisible control codes; nethack's DECgraphics doesn't use any of them so we're satisfied with conversion to a simple letter; [3] "HT" as one char, with small raised upper case H over and/or preceding small lowered upper case T */ decchars[0x62 - 0x5f] = 'H'; /* "HT" (horizontal tab) */ decchars[0x63 - 0x5f] = 'F'; /* "FF" as one char (form feed) */ decchars[0x64 - 0x5f] = 'C'; /* "CR" as one (carriage return) */ decchars[0x65 - 0x5f] = 'L'; /* [6] "LF" as one (line feed) */ decchars[0x66 - 0x5f] = ACS_DEGREE; /* small raised circle */ /* [8] plus or minus sign, '+' with horizontal line below */ decchars[0x67 - 0x5f] = ACS_PLMINUS; decchars[0x68 - 0x5f] = 'N'; /* [9] "NL" as one char (new line) */ decchars[0x69 - 0x5f] = 'V'; /* [10] "VT" as one (vertical tab) */ decchars[0x6a - 0x5f] = ACS_LRCORNER; /* lower right corner */ decchars[0x6b - 0x5f] = ACS_URCORNER; /* upper right corner, 7-ish */ decchars[0x6c - 0x5f] = ACS_ULCORNER; /* upper left corner */ decchars[0x6d - 0x5f] = ACS_LLCORNER; /* lower left corner, 'L' */ /* [15] center cross, like big '+' sign */ decchars[0x6e - 0x5f] = ACS_PLUS; decchars[0x6f - 0x5f] = ACS_S1; /* very high horizontal line */ decchars[0x70 - 0x5f] = ACS_S3; /* medium high horizontal line */ decchars[0x71 - 0x5f] = ACS_HLINE; /* centered horizontal line */ decchars[0x72 - 0x5f] = ACS_S7; /* medium low horizontal line */ decchars[0x73 - 0x5f] = ACS_S9; /* very low horizontal line */ /* [21] left tee, 'H' with right-hand vertical stroke removed; note on left vs right: the ACS name (also DEC's terminal documentation) refers to vertical bar rather than cross stroke, nethack's left/right refers to direction of the cross stroke */ decchars[0x74 - 0x5f] = ACS_LTEE; /* ACS left tee, NH right tee */ /* [22] right tee, 'H' with left-hand vertical stroke removed */ decchars[0x75 - 0x5f] = ACS_RTEE; /* ACS right tee, NH left tee */ /* [23] bottom tee, '+' with lower half of vertical stroke removed and remaining stroke pointed up (unside-down 'T'); nethack is inconsistent here--unlike with left/right, its bottom/top directions agree with ACS */ decchars[0x76 - 0x5f] = ACS_BTEE; /* bottom tee, stroke up */ /* [24] top tee, '+' with upper half of vertical stroke removed */ decchars[0x77 - 0x5f] = ACS_TTEE; /* top tee, stroke down, 'T' */ decchars[0x78 - 0x5f] = ACS_VLINE; /* centered vertical line */ decchars[0x79 - 0x5f] = ACS_LEQUAL; /* less than or equal to */ /* [27] greater than or equal to, '>' with underscore */ decchars[0x7a - 0x5f] = ACS_GEQUAL; /* [28] Greek pi ('n'-like; case is ambiguous: small size suggests lower case but flat top suggests upper case) */ decchars[0x7b - 0x5f] = ACS_PI; /* [29] not equal sign, combination of '=' and '/' */ decchars[0x7c - 0x5f] = ACS_NEQUAL; /* [30] British pound sign (curly 'L' with embellishments) */ decchars[0x7d - 0x5f] = ACS_STERLING; decchars[0x7e - 0x5f] = ACS_BULLET; /* [31] centered dot */ /* [32] is not used for DEC line drawing but is a potential value for someone who assumes that 0x60..0x7f is the valid range, so we're prepared to accept--and sanitize--it */ decchars[0x7f - 0x5f] = '?'; } /* high bit set means special handling */ if (ch & 0x80) { int convindx, symbol; ch &= ~0x80; /* force plain ASCII for last resort */ convindx = ch - 0x5f; /* if it's in the lower case block of ASCII (which includes a few punctuation characters), use the conversion table */ if (convindx >= 0 && convindx < SIZE(decchars)) { ch = decchars[convindx]; /* in case ACS_foo maps to 0 when current terminal is unable to handle a particular character; if so, revert to default rather than using DECgr value with high bit stripped */ if (!ch) { symbol = glyph_to_cmap(glyph); ch = (int) defsyms[symbol].sym; } } } 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) { /* map column #0 isn't used; shift column #1 to first screen column */ curs_x -= (sx + 1); curs_y -= sy; #ifdef PDCURSES move(curs_y, curs_x); #else wmove(win, curs_y, curs_x); #endif } } /* update the ncurses stdscr cursor to where the cursor in our map is */ void curses_update_stdscr_cursor(void) { #ifndef PDCURSES int xadj = 0, yadj = 0; curses_get_window_xy(MAP_WIN, &xadj, &yadj); move(curs_y + yadj, curs_x + xadj); #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 Id; char buf[BUFSZ]; menu_item *selected = NULL; dlb *fp = dlb_fopen(filename, "r"); int clr = NO_COLOR; if (fp == NULL) { if (must_exist) pline("Cannot open \"%s\" for reading!", filename); return; } wid = curses_get_wid(NHW_MENU); curses_create_nhmenu(wid, 0UL); Id = cg.zeroany; while (dlb_fgets(buf, BUFSZ, fp) != NULL) { curses_add_menu(wid, &nul_glyphinfo, &Id, 0, 0, A_NORMAL, clr, buf, FALSE); } dlb_fclose(fp); curses_end_menu(wid, ""); curses_select_menu(wid, PICK_NONE, &selected); curses_del_wid(wid); } void curses_rtrim(char *str) { char *s; for (s = str; *s != '\0'; ++s); if (s > str) 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. */ long curses_get_count(int first_digit) { int current_char; long current_count = 0L; /* use core's count routine; we have the first digit; if any more are typed, get_count() will send "Count:123" to the message window; curses's message window will display that in count window instead */ current_char = get_count(NULL, (char) first_digit, /* 0L => no limit on value unless it wraps * to negative */ 0L, ¤t_count, /* don't put into message history, echo full * number rather than waiting until 2nd digit */ GC_ECHOFIRST); ungetch(current_char); if (current_char == '\033') { /* 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. */ attr_t curses_convert_attr(int attr) { attr_t curses_attr; /* first, strip off control flags masked onto the display attributes (caller should have already done this...) */ attr &= ~(ATR_URGENT | ATR_NOHISTORY); 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_DIM: curses_attr = A_DIM; break; case ATR_BLINK: curses_attr = A_BLINK; break; case ATR_INVERSE: curses_attr = A_REVERSE; break; case ATR_ITALIC: curses_attr = A_ITALIC; break; default: curses_attr = A_NORMAL; } return curses_attr; } /* 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) { boolean reject = (program_state.input_state == otherInp), as_is = FALSE, numpad_esc = FALSE; int ret = key; if (modifiers_available) update_modifiers(); /* Handle arrow and keypad keys, but only when getting a command (or a command-like keystroke for getpos() or getdir()). */ switch (key) { case SS3: /* M-^O, 8-bit version of ESC 'O' c for keypad key */ case '\033': /* ESC or ^[ */ /* changes ESC c to M-c or number pad key to corresponding digit (but we only get here via key==ESC if curses' getch() didn't change the latter to KEY_xyz) */ ret = parse_escape_sequence(key, &numpad_esc); reject = ((uchar) ret < 1 || ret > 255); as_is = !numpad_esc; /* don't perform phonepad inversion */ break; case KEY_BACKSPACE: /* we can't distinguish between a separate backspace key and explicit Ctrl+H intended to rush to the left; without this, a value for ^H greater than 255 is passed back to core's readchar() and stripping the value down to 0..255 yields ^G! */ ret = C('H'); FALLTHROUGH; /*FALLTHRU*/ default: if (modifiers_available) ret = modified(ret); #if defined(ALT_A) && defined(ALT_Z) /* for PDcurses, but doesn't handle Alt+X for upper case X; ncurses doesn't have ALT_x definitions so we achieve a similar effect via parse_escape_sequence(), and that works for upper case and other non-letter, non-digit keys */ if (ret >= ALT_A && ret <= ALT_Z) { ret = (ret - ALT_A) + 'a'; ret = M(ret); } #endif #if defined(ALT_0) && defined(ALT_9) if (ret >= ALT_0 && ret <= ALT_9) { ret = (ret - ALT_0) + '0'; ret = M(ret); } #endif /* use key as-is unless it's out of normal char range */ reject = ((uchar) ret < 1 || ret > 255); as_is = TRUE; break; #ifdef KEY_B1 case KEY_B1: #endif case KEY_LEFT: ret = iflags.num_pad ? '4' : 'h'; break; #ifdef KEY_B3 case KEY_B3: #endif case KEY_RIGHT: ret = iflags.num_pad ? '6' : 'l'; break; #ifdef KEY_A2 case KEY_A2: #endif case KEY_UP: ret = iflags.num_pad ? '8' : 'k'; break; #ifdef KEY_C2 case KEY_C2: #endif case KEY_DOWN: ret = iflags.num_pad ? '2' : 'j'; break; #ifdef KEY_A1 case KEY_A1: #endif case KEY_HOME: ret = iflags.num_pad ? '7' : (!gc.Cmd.swap_yz ? 'y' : 'z'); break; #ifdef KEY_A3 case KEY_A3: #endif case KEY_PPAGE: ret = iflags.num_pad ? '9' : 'u'; break; #ifdef KEY_C1 case KEY_C1: #endif case KEY_END: ret = iflags.num_pad ? '1' : 'b'; break; #ifdef KEY_C3 case KEY_C3: #endif case KEY_NPAGE: ret = iflags.num_pad ? '3' : 'n'; break; #ifdef KEY_B2 case KEY_B2: ret = iflags.num_pad ? '5' : 'g'; break; #endif /* KEY_B2 */ #ifdef KEY_RESIZE case KEY_RESIZE: /* actual resize is handled elsewhere; just avoid beep/bell here */ ret = '\033'; /* was C('R'); -- nethack's redraw command */ reject = FALSE; break; #endif } /* phone layout is inverted, 123 on top and 789 on bottom; if player has set num_pad to deal with that, we need to invert here too but only when some key has been converted into a digit, not for actual digit */ if (iflags.num_pad && (iflags.num_pad_mode & 2) != 0 && !as_is) { if (ret >= '1' && ret <= '3') ret += 6; /* 1,2,3 -> 7,8,9 */ else if (ret >= '7' && ret <= '9') ret -= 6; /* 7,8,9 -> 1,2,3 */ } if (reject) { /* an arrow or function key has been pressed during text entry */ curses_nhbell(); /* calls beep() which might cause unwanted screen * refresh if terminal is set for 'visible bell' */ ret = '\033'; /* ESC */ } return ret; } /* * We treat buttons 2 and 3 as equivalent so that it doesn't matter which * one is for right-click and which for middle-click. The core uses CLICK_2 * for right-click ("not left"-click) even though 2 might be middle button. * * BUTTON_CTRL was enabled at one point but was not working as intended. * Ctrl+left_click was generating pairs of duplicated events with Ctrl and * Report_mouse_position bits set (even though Report_mouse_position wasn't * enabled) but no button click bit set. (It sort of worked because Ctrl+ * Report_mouse_position wasn't a left click so passed along CLICK_2, but * the duplication made that too annoying to use. Attempting to immediately * drain the second one wasn't working as intended either.) */ #define MOUSEBUTTONS (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED) /* 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(coordxy *mousex, coordxy *mousey, int *mod) { int key = '\033'; #ifdef NCURSES_MOUSE_VERSION MEVENT event; if (getmouse(&event) == OK) { /* True if user has clicked */ if ((event.bstate & MOUSEBUTTONS) != 0) { /* * The ncurses man page documents wmouse_trafo() incorrectly. * It says that last argument 'TRUE' translates from screen * to window and 'FALSE' translates from window to screen, * but those are backwards. The mouse_trafo() macro calls * last argument 'to_screen', suggesting that the backwards * implementation is the intended behavior and the man page * is describing it wrong. */ /* See if coords are in map window & convert coords */ if (wmouse_trafo(mapwin, &event.y, &event.x, FALSE)) { key = '\0'; /* core uses this to detect a mouse click */ *mousex = event.x + 1; /* +1: screen 0..78 is map 1..79 */ *mousey = event.y; if (curses_window_has_border(MAP_WIN)) { (*mousex)--; (*mousey)--; } *mod = ((event.bstate & (BUTTON1_CLICKED | BUTTON_CTRL)) == BUTTON1_CLICKED) ? CLICK_1 : CLICK_2; } } } #else nhUse(mousex); nhUse(mousey); nhUse(mod); #endif /* NCURSES_MOUSE_VERSION */ return key; } void curses_mouse_support(int mode) /* 0: off, 1: on, 2: alternate on */ { #ifdef NCURSES_MOUSE_VERSION mmask_t result, oldmask, newmask; if (!mode) newmask = 0; else newmask = MOUSEBUTTONS; /* buttons 1, 2, and 3 */ result = mousemask(newmask, &oldmask); nhUse(result); #else nhUse(mode); #endif } /* caller just got an input character of ESC or M-^O; note: curses converts a lot of escape sequences to single values greater than 255 and those won't look like ESC to caller so won't get here */ static int parse_escape_sequence(int key, boolean *keypadnum) { #ifndef PDCURSES int ret, keypadother = 0; *keypadnum = FALSE; timeout(10); ret = getch(); if (ret == 'O' || key == SS3) { /* handle numeric keypad */ /* * ESC O or M-^O . * * For the former, we don't have the next char yet so get it now. * If there isn't one, treat ESC O as if user typed M-O (which * is probably the case, via alt+shift+O combo sending two char * "ESC O"). * * For the latter, it there wasn't another char then 'ret' will * be ERR and we'll treat the result as M-^O. However, if there * is another char and it is O meant as two characters "M-^O O" * we'll be fooled, but that's not a valid escape sequence so * don't worry about those two characters arriving together. */ if (key == '\033') ret = getch(); if (ret == ERR) { iflags.term_gone = 1; /* there was no additional char; treat as M-O or M-^O below */ ret = (key == '\033') ? 'O' : C('O'); } else if (ret >= 112 && ret <= 121) { /* 'p'..'y' */ *keypadnum = TRUE; /* convert 'p'..'y' to '0'..'9' below */ } else if (ret >= 108 && ret <= 110) { /* 'l'..'n' */ keypadother = 1; /* convert 'l','m','n' to ',','.','-' below */ } else if (ret == 'M') { keypadother = 2; /* convert "ESC O M" or "SS3 M" to ^M */ } } timeout(-1); /* reset to 'wait unlimited time for next input' */ if (*keypadnum) { /* 'p' -> '0', ..., 'y' -> '9' */ ret -= ('p' - '0'); /* Convert c from 'ESC O c' to digit */ } else if (keypadother > 0) { /* conversion for VT keypad keys (no plus; ignore PF1 through PF4) [typical PC keyboard has period and plus, no comma or minus] */ if (keypadother == 1) ret -= ('l' - ','); /* keypad comma, period, or minus */ else ret = C('M'); /* keypad */ } else if (ret != ERR && ret <= 255) { /* ESC ; effectively 'altmeta' behind player's back */ ret = M(ret); /* Meta key support for most terminals */ } else { ret = '\033'; /* Just an escape character */ } return ret; #else nhUse(key); nhUse(keypadnum); return '\033'; #endif /* !PDCURSES */ } #undef SS3 /* update_modifiers() and modified() will never be called if modifiers_available is FALSE */ static void update_modifiers(void) { #if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT) last_getch_modifiers = PDC_get_key_modifiers(); #endif } static int modified(int ch) { int ret_ch = ch; #if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT) /* PDCurses key modifier masks: * PDC_KEY_MODIFIER_SHIFT = 1 * PDC_KEY_MODIFIER_CONTROL = 2 * PDC_KEY_MODIFIER_ALT = 4 * PDC_KEY_MODIFIER_NUMLOCK = 8 * PDC_KEY_MODIFIER_REPEAT = 16 * ALT + 'a' through ALT + 'z' returns ALT_A through ALT_Z * and those are out of the normal character range and * code in curses_convert_keys() handles those. * ALT + 'A' through ALT + 'Z' return normal 'A' through 'Z' * so we check the modifier here. */ if (((last_getch_modifiers & PDC_KEY_MODIFIER_ALT) == PDC_KEY_MODIFIER_ALT) && (ch >= 'A' && ch <= 'Z')) ret_ch = M(ch); #endif return ret_ch; } /*cursmisc.c*/