| | 1 | /* FrobTadsApplicationCurses class implementation. |
| | 2 | */ |
| | 3 | #include "common.h" |
| | 4 | |
| | 5 | #include <string.h> |
| | 6 | #if HAVE_TIOCGWINSZ || HAVE_TIOCGSIZE |
| | 7 | # if (!defined(GWINSZ_IN_SYS_IOCTL) || HAVE_TIOCGSIZE) && HAVE_TERMIOS_H |
| | 8 | # include <termios.h> |
| | 9 | # endif |
| | 10 | # if HAVE_SYS_IOCTL_H |
| | 11 | # include <sys/ioctl.h> |
| | 12 | # endif |
| | 13 | #endif |
| | 14 | |
| | 15 | #include "frobtadsappcurses.h" |
| | 16 | #include "colors.h" |
| | 17 | |
| | 18 | |
| | 19 | /* Service routine. Gets the terminal's size. |
| | 20 | * On success, stores the terminal dimensions in 'width' and |
| | 21 | * 'height' and returns true. On failure, returns false and |
| | 22 | * 'width' nor 'height' are modified. |
| | 23 | */ |
| | 24 | static bool |
| | 25 | getTermSize( unsigned& width, unsigned& height ) |
| | 26 | { |
| | 27 | // If the system has support for the TIOCGWINSZ or TIOCGSIZE |
| | 28 | // ioctl, use it to obtain the terminal's dimensions. |
| | 29 | int res = -1; |
| | 30 | unsigned w = 0; |
| | 31 | unsigned h = 0; |
| | 32 | #if HAVE_TIOCGWINSZ |
| | 33 | winsize size; |
| | 34 | size.ws_col = size.ws_row = 0; |
| | 35 | res = ioctl(0, TIOCGWINSZ, &size); |
| | 36 | w = size.ws_col; |
| | 37 | h = size.ws_row; |
| | 38 | #elif HAVE_TIOCGSIZE |
| | 39 | ttysize size; |
| | 40 | size.ts_cols = size.ts_lines = 0; |
| | 41 | res = ioctl(0, TIOCGSIZE, &size); |
| | 42 | w = size.ts_cols; |
| | 43 | h = size.ts_lines; |
| | 44 | #endif |
| | 45 | if (res < 0 or w == 0 or h == 0) { |
| | 46 | // The call failed or the system doesn't seem to know |
| | 47 | // the terminal's size. |
| | 48 | return false; |
| | 49 | } |
| | 50 | width = w; |
| | 51 | height = h; |
| | 52 | return true; |
| | 53 | } |
| | 54 | |
| | 55 | |
| | 56 | FrobTadsApplicationCurses::FrobTadsApplicationCurses( const FrobOptions& opts ) |
| | 57 | : FrobTadsApplication(opts), fGameWindow(0), fDispBuf(0) |
| | 58 | { |
| | 59 | // Before starting curses, we set the LINES and COLUMNS env. |
| | 60 | // variables to a "maximum" value. This is needed in case we're |
| | 61 | // running on a system with an old/broken curses implementation |
| | 62 | // (Solaris curses, for example). This forces curses to |
| | 63 | // allocate enough internal memory for the windows in case the |
| | 64 | // terminal gets resized. Note that we only do this if |
| | 65 | // detection of the terminal size is possible at all. |
| | 66 | #if HAVE_PUTENV |
| | 67 | unsigned bogusX, bogusY; // Dummies. |
| | 68 | if (getTermSize(bogusX, bogusY)) { |
| | 69 | // The strings become part of the environment; they have to be |
| | 70 | // static. |
| | 71 | static char lines[] = "LINES=225"; |
| | 72 | static char columns[] = "COLUMNS=300"; |
| | 73 | putenv(lines); |
| | 74 | putenv(columns); |
| | 75 | } |
| | 76 | #endif |
| | 77 | |
| | 78 | // Fire-up curses. |
| | 79 | initscr(); |
| | 80 | |
| | 81 | // Make characters typed by the user immediately available to |
| | 82 | // the program. |
| | 83 | cbreak(); |
| | 84 | |
| | 85 | // Tell courses not to echo user-input (TADS does the echoing). |
| | 86 | noecho(); |
| | 87 | |
| | 88 | // Hide the cursor. This avoids the "crazy cursor jumping" |
| | 89 | // problem on *really* slow terminals, as TADS constantly moves |
| | 90 | // the cursor all over the place. |
| | 91 | curs_set(0); |
| | 92 | |
| | 93 | // Tell courses not to try to translate the RETURN/ENTER key on |
| | 94 | // input (or else getch() and friends would never report that |
| | 95 | // key), and the '\n' character on output (since the osgen layer |
| | 96 | // handles newlines). |
| | 97 | nonl(); |
| | 98 | |
| | 99 | // Initialize colors if we want color support and the terminal supports it. |
| | 100 | if ((opts.useColors and has_colors()) or opts.forceColors) { |
| | 101 | this->fColorsEnabled = true; |
| | 102 | start_color(); |
| | 103 | // Assign terminal default foreground/background colors |
| | 104 | // to color number -1 / color pair 0. |
| | 105 | #ifdef HAVE_USE_DEFAULT_COLORS |
| | 106 | if (opts.defColors) { |
| | 107 | use_default_colors(); |
| | 108 | } |
| | 109 | #endif |
| | 110 | // Initialize the curses color-pairs we use. |
| | 111 | // |
| | 112 | // This is paranoia, but better to make sure that curses |
| | 113 | // will use the right colors. The paranoid part of this |
| | 114 | // is that we assume that there might be some curses |
| | 115 | // version out there that does not use monotonically |
| | 116 | // increasing values for the COLOR_* symbols. |
| | 117 | init_pair(makeColorPair(FROB_BLACK, FROB_BLACK), COLOR_BLACK, COLOR_BLACK); |
| | 118 | init_pair(makeColorPair(FROB_RED, FROB_BLACK), COLOR_RED, COLOR_BLACK); |
| | 119 | init_pair(makeColorPair(FROB_GREEN, FROB_BLACK), COLOR_GREEN, COLOR_BLACK); |
| | 120 | init_pair(makeColorPair(FROB_YELLOW, FROB_BLACK), COLOR_YELLOW, COLOR_BLACK); |
| | 121 | init_pair(makeColorPair(FROB_BLUE, FROB_BLACK), COLOR_BLUE, COLOR_BLACK); |
| | 122 | init_pair(makeColorPair(FROB_MAGENTA, FROB_BLACK), COLOR_MAGENTA, COLOR_BLACK); |
| | 123 | init_pair(makeColorPair(FROB_CYAN, FROB_BLACK), COLOR_CYAN, COLOR_BLACK); |
| | 124 | // White on black is wired to color pair 0 by |
| | 125 | // definition; we can't and don't need to initialize it. |
| | 126 | //init_pair(makeColorPair(FROB_WHITE, FROB_BLACK), COLOR_WHITE, COLOR_BLACK); |
| | 127 | |
| | 128 | init_pair(makeColorPair(FROB_BLACK, FROB_RED), COLOR_BLACK, COLOR_RED); |
| | 129 | init_pair(makeColorPair(FROB_RED, FROB_RED), COLOR_RED, COLOR_RED); |
| | 130 | init_pair(makeColorPair(FROB_GREEN, FROB_RED), COLOR_GREEN, COLOR_RED); |
| | 131 | init_pair(makeColorPair(FROB_YELLOW, FROB_RED), COLOR_YELLOW, COLOR_RED); |
| | 132 | init_pair(makeColorPair(FROB_BLUE, FROB_RED), COLOR_BLUE, COLOR_RED); |
| | 133 | init_pair(makeColorPair(FROB_MAGENTA, FROB_RED), COLOR_MAGENTA, COLOR_RED); |
| | 134 | init_pair(makeColorPair(FROB_CYAN, FROB_RED), COLOR_CYAN, COLOR_RED); |
| | 135 | init_pair(makeColorPair(FROB_WHITE, FROB_RED), COLOR_WHITE, COLOR_RED); |
| | 136 | |
| | 137 | init_pair(makeColorPair(FROB_BLACK, FROB_GREEN), COLOR_BLACK, COLOR_GREEN); |
| | 138 | init_pair(makeColorPair(FROB_RED, FROB_GREEN), COLOR_RED, COLOR_GREEN); |
| | 139 | init_pair(makeColorPair(FROB_GREEN, FROB_GREEN), COLOR_GREEN, COLOR_GREEN); |
| | 140 | init_pair(makeColorPair(FROB_YELLOW, FROB_GREEN), COLOR_YELLOW, COLOR_GREEN); |
| | 141 | init_pair(makeColorPair(FROB_BLUE, FROB_GREEN), COLOR_BLUE, COLOR_GREEN); |
| | 142 | init_pair(makeColorPair(FROB_MAGENTA, FROB_GREEN), COLOR_MAGENTA, COLOR_GREEN); |
| | 143 | init_pair(makeColorPair(FROB_CYAN, FROB_GREEN), COLOR_CYAN, COLOR_GREEN); |
| | 144 | init_pair(makeColorPair(FROB_WHITE, FROB_GREEN), COLOR_WHITE, COLOR_GREEN); |
| | 145 | |
| | 146 | init_pair(makeColorPair(FROB_BLACK, FROB_YELLOW), COLOR_BLACK, COLOR_YELLOW); |
| | 147 | init_pair(makeColorPair(FROB_RED, FROB_YELLOW), COLOR_RED, COLOR_YELLOW); |
| | 148 | init_pair(makeColorPair(FROB_GREEN, FROB_YELLOW), COLOR_GREEN, COLOR_YELLOW); |
| | 149 | init_pair(makeColorPair(FROB_YELLOW, FROB_YELLOW), COLOR_YELLOW, COLOR_YELLOW); |
| | 150 | init_pair(makeColorPair(FROB_BLUE, FROB_YELLOW), COLOR_BLUE, COLOR_YELLOW); |
| | 151 | init_pair(makeColorPair(FROB_MAGENTA, FROB_YELLOW), COLOR_MAGENTA, COLOR_YELLOW); |
| | 152 | init_pair(makeColorPair(FROB_CYAN, FROB_YELLOW), COLOR_CYAN, COLOR_YELLOW); |
| | 153 | init_pair(makeColorPair(FROB_WHITE, FROB_YELLOW), COLOR_WHITE, COLOR_YELLOW); |
| | 154 | |
| | 155 | init_pair(makeColorPair(FROB_BLACK, FROB_BLUE), COLOR_BLACK, COLOR_BLUE); |
| | 156 | init_pair(makeColorPair(FROB_RED, FROB_BLUE), COLOR_RED, COLOR_BLUE); |
| | 157 | init_pair(makeColorPair(FROB_GREEN, FROB_BLUE), COLOR_GREEN, COLOR_BLUE); |
| | 158 | init_pair(makeColorPair(FROB_YELLOW, FROB_BLUE), COLOR_YELLOW, COLOR_BLUE); |
| | 159 | init_pair(makeColorPair(FROB_BLUE, FROB_BLUE), COLOR_BLUE, COLOR_BLUE); |
| | 160 | init_pair(makeColorPair(FROB_MAGENTA, FROB_BLUE), COLOR_MAGENTA, COLOR_BLUE); |
| | 161 | init_pair(makeColorPair(FROB_CYAN, FROB_BLUE), COLOR_CYAN, COLOR_BLUE); |
| | 162 | init_pair(makeColorPair(FROB_WHITE, FROB_BLUE), COLOR_WHITE, COLOR_BLUE); |
| | 163 | |
| | 164 | init_pair(makeColorPair(FROB_BLACK, FROB_MAGENTA), COLOR_BLACK, COLOR_MAGENTA); |
| | 165 | init_pair(makeColorPair(FROB_RED, FROB_MAGENTA), COLOR_RED, COLOR_MAGENTA); |
| | 166 | init_pair(makeColorPair(FROB_GREEN, FROB_MAGENTA), COLOR_GREEN, COLOR_MAGENTA); |
| | 167 | init_pair(makeColorPair(FROB_YELLOW, FROB_MAGENTA), COLOR_YELLOW, COLOR_MAGENTA); |
| | 168 | init_pair(makeColorPair(FROB_BLUE, FROB_MAGENTA), COLOR_BLUE, COLOR_MAGENTA); |
| | 169 | init_pair(makeColorPair(FROB_MAGENTA, FROB_MAGENTA), COLOR_MAGENTA, COLOR_MAGENTA); |
| | 170 | init_pair(makeColorPair(FROB_CYAN, FROB_MAGENTA), COLOR_CYAN, COLOR_MAGENTA); |
| | 171 | init_pair(makeColorPair(FROB_WHITE, FROB_MAGENTA), COLOR_WHITE, COLOR_MAGENTA); |
| | 172 | |
| | 173 | init_pair(makeColorPair(FROB_BLACK, FROB_CYAN), COLOR_BLACK, COLOR_CYAN); |
| | 174 | init_pair(makeColorPair(FROB_RED, FROB_CYAN), COLOR_RED, COLOR_CYAN); |
| | 175 | init_pair(makeColorPair(FROB_GREEN, FROB_CYAN), COLOR_GREEN, COLOR_CYAN); |
| | 176 | init_pair(makeColorPair(FROB_YELLOW, FROB_CYAN), COLOR_YELLOW, COLOR_CYAN); |
| | 177 | init_pair(makeColorPair(FROB_BLUE, FROB_CYAN), COLOR_BLUE, COLOR_CYAN); |
| | 178 | init_pair(makeColorPair(FROB_MAGENTA, FROB_CYAN), COLOR_MAGENTA, COLOR_CYAN); |
| | 179 | init_pair(makeColorPair(FROB_CYAN, FROB_CYAN), COLOR_CYAN, COLOR_CYAN); |
| | 180 | init_pair(makeColorPair(FROB_WHITE, FROB_CYAN), COLOR_WHITE, COLOR_CYAN); |
| | 181 | |
| | 182 | init_pair(makeColorPair(FROB_BLACK, FROB_WHITE), COLOR_BLACK, COLOR_WHITE); |
| | 183 | init_pair(makeColorPair(FROB_RED, FROB_WHITE), COLOR_RED, COLOR_WHITE); |
| | 184 | init_pair(makeColorPair(FROB_GREEN, FROB_WHITE), COLOR_GREEN, COLOR_WHITE); |
| | 185 | init_pair(makeColorPair(FROB_YELLOW, FROB_WHITE), COLOR_YELLOW, COLOR_WHITE); |
| | 186 | init_pair(makeColorPair(FROB_BLUE, FROB_WHITE), COLOR_BLUE, COLOR_WHITE); |
| | 187 | init_pair(makeColorPair(FROB_MAGENTA, FROB_WHITE), COLOR_MAGENTA, COLOR_WHITE); |
| | 188 | init_pair(makeColorPair(FROB_CYAN, FROB_WHITE), COLOR_CYAN, COLOR_WHITE); |
| | 189 | init_pair(makeColorPair(FROB_WHITE, FROB_WHITE), COLOR_WHITE, COLOR_WHITE); |
| | 190 | } |
| | 191 | } |
| | 192 | |
| | 193 | |
| | 194 | FrobTadsApplicationCurses::~FrobTadsApplicationCurses() |
| | 195 | { |
| | 196 | // Enable the cursor. |
| | 197 | curs_set(0); |
| | 198 | |
| | 199 | // Flush any pending output so we won't lose any "thanks for |
| | 200 | // playing" messages when "pausing prior to exit" is disabled. |
| | 201 | if (not this->options.exitPause) this->fGameWindow->flush(); |
| | 202 | |
| | 203 | // Delete our windows, if we have any. |
| | 204 | if (this->fGameWindow != 0) delete this->fGameWindow; |
| | 205 | |
| | 206 | // Shut down curses. |
| | 207 | endwin(); |
| | 208 | |
| | 209 | // Delete our optimization buffer. |
| | 210 | delete[] this->fDispBuf; |
| | 211 | } |
| | 212 | |
| | 213 | |
| | 214 | void |
| | 215 | FrobTadsApplicationCurses::init() |
| | 216 | { |
| | 217 | // Delete the current window, if we have one. |
| | 218 | if (this->fGameWindow != 0) delete this->fGameWindow; |
| | 219 | |
| | 220 | // Create the main window. Make it fill the whole terminal. We |
| | 221 | // initialize the dimensions to 0 in case getTermSize() is |
| | 222 | // unable to detect the actual size, since in curses/ncurses 0 |
| | 223 | // for width and height actually means "fullscreen". |
| | 224 | unsigned width = 0; |
| | 225 | unsigned height = 0; |
| | 226 | bool sizeDetected = getTermSize(width, height); |
| | 227 | if (sizeDetected) { |
| | 228 | // Update the environment in case we're running inside |
| | 229 | // an old/broken terminal or are using an old/broken |
| | 230 | // version of curses. |
| | 231 | #if HAVE_PUTENV |
| | 232 | // Note that the strings have to be static, since |
| | 233 | // putenv() does not always copy the string arguments |
| | 234 | // but points the environment to the application's |
| | 235 | // address space (and therefore the application would |
| | 236 | // segfault if the strings were not static). |
| | 237 | static char linesEnv[32]; |
| | 238 | static char columnsEnv[32]; |
| | 239 | char tmp[16]; |
| | 240 | strcpy(linesEnv, "LINES="); |
| | 241 | strcpy(columnsEnv, "COLUMNS="); |
| | 242 | sprintf(tmp, "%u", height); |
| | 243 | strcat(linesEnv, tmp); |
| | 244 | sprintf(tmp, "%u", width); |
| | 245 | strcat(columnsEnv, tmp); |
| | 246 | // Normally, since the strings are part of the |
| | 247 | // environment, we would need to use putenv() only once; |
| | 248 | // changing the strings later would also change the |
| | 249 | // environment, since the strings are part of it. |
| | 250 | // However, not every system follows the standard, so we |
| | 251 | // need to putenv() the strings over and over again. |
| | 252 | putenv(linesEnv); |
| | 253 | putenv(columnsEnv); |
| | 254 | // Enforce the detected size upon curses. |
| | 255 | // TODO: Is this needed? Or even allowed? For now, we |
| | 256 | // assume it's neither. |
| | 257 | //COLS = width; |
| | 258 | //LINES = height; |
| | 259 | #endif |
| | 260 | } |
| | 261 | |
| | 262 | // Reset curses, in case the terminal's size has changed. |
| | 263 | endwin(); |
| | 264 | refresh(); |
| | 265 | // Fixes problems with old curses versions. |
| | 266 | wclear(stdscr); |
| | 267 | |
| | 268 | // Create the window. |
| | 269 | this->fGameWindow = new FrobTadsWindow(height, width, 0, 0); |
| | 270 | |
| | 271 | // Disable scrolling. |
| | 272 | this->fGameWindow->enableScrolling(false); |
| | 273 | |
| | 274 | // Tell the window to recognize keypad keys (function keys, |
| | 275 | // cursor-keys, etc.) during input. |
| | 276 | this->fGameWindow->keypadMode(true); |
| | 277 | |
| | 278 | // Enable 8-bit characters during input. |
| | 279 | this->fGameWindow->input8bit(true); |
| | 280 | |
| | 281 | // Create the buffer that we use to optimize output. |
| | 282 | if (this->fDispBuf != 0) delete[] this->fDispBuf; |
| | 283 | this->fDispBuf = new chtype[this->fGameWindow->width() + 1]; |
| | 284 | |
| | 285 | // Explicitly mark the window as touched, to work around a |
| | 286 | // curses color and screen corruption issue. |
| | 287 | this->fGameWindow->touch(); |
| | 288 | |
| | 289 | // Fix for curses color corruption problem. The window is |
| | 290 | // cleared to a color (other than black and white) which contrasts |
| | 291 | // with the normal background color. |
| | 292 | if (sizeDetected) { |
| | 293 | int fillcolor; |
| | 294 | if (this->options.bgColor == FROB_BLUE) { |
| | 295 | fillcolor = ossgetcolor(OSGEN_COLOR_WHITE, OSGEN_COLOR_RED, 0, 0); |
| | 296 | } else { |
| | 297 | fillcolor = ossgetcolor(OSGEN_COLOR_WHITE, OSGEN_COLOR_BLUE, 0, 0); |
| | 298 | } |
| | 299 | ossclr(0, 0, height - 1, width - 1, fillcolor); |
| | 300 | this->fGameWindow->flush(); |
| | 301 | } |
| | 302 | } |
| | 303 | |
| | 304 | |
| | 305 | void |
| | 306 | FrobTadsApplicationCurses::clear( int top, int left, int bottom, int right, int attrs ) |
| | 307 | { |
| | 308 | const chtype c = attrs | ' '; |
| | 309 | for (int y = top; y <= bottom; ++y) { |
| | 310 | for (int x = left; x <= right; ++x) { |
| | 311 | this->fGameWindow->printChar(y, x, c); |
| | 312 | } |
| | 313 | } |
| | 314 | } |
| | 315 | |
| | 316 | |
| | 317 | void |
| | 318 | FrobTadsApplicationCurses::scrollRegionUp( int top, int left, int bottom, int right, int attrs ) |
| | 319 | { |
| | 320 | // Old curses versions might break when using overlapping |
| | 321 | // windows, so we do the scrolling by hand. |
| | 322 | for (int y = bottom; y > top; --y) { |
| | 323 | for (int x = left; x <= right; ++x) { |
| | 324 | const chtype c = this->fGameWindow->charAt(y-1, x); |
| | 325 | this->fGameWindow->printChar(y, x, c); |
| | 326 | } |
| | 327 | } |
| | 328 | |
| | 329 | // Clear the last line. |
| | 330 | const chtype c = attrs | ' '; |
| | 331 | for (int i = left; i <= right; ++i) this->fGameWindow->printChar(top, i, c); |
| | 332 | |
| | 333 | // If soft-scrolling is enabled, update the display so that we |
| | 334 | // have a visible scrolling-effect. |
| | 335 | if (this->options.softScroll) this->fGameWindow->flush(); |
| | 336 | } |
| | 337 | |
| | 338 | |
| | 339 | void |
| | 340 | FrobTadsApplicationCurses::scrollRegionDown( int top, int left, int bottom, int right, int attrs ) |
| | 341 | { |
| | 342 | // This works like scrollRegionUp(), just the other way around. |
| | 343 | for (int y = top; y < bottom; ++y) { |
| | 344 | for (int x = left; x <= right; ++x) { |
| | 345 | this->fGameWindow->printChar(y, x, this->fGameWindow->charAt(y+1, x)); |
| | 346 | } |
| | 347 | } |
| | 348 | const chtype c = attrs | ' '; |
| | 349 | for (int i = left; i <= right; ++i) this->fGameWindow->printChar(bottom, i, c); |
| | 350 | if (this->options.softScroll) this->fGameWindow->flush(); |
| | 351 | } |
| | 352 | |
| | 353 | |
| | 354 | int |
| | 355 | FrobTadsApplicationCurses::getRawChar( bool cursorVisible, int timeout ) |
| | 356 | { |
| | 357 | // The character we read. |
| | 358 | int c; |
| | 359 | |
| | 360 | // Tell osgen to check if it should redraw the screen. |
| | 361 | osssb_redraw_if_needed(); |
| | 362 | |
| | 363 | // Some curses versions don't automatically update the display |
| | 364 | // prior to input, so we must explicitly request it. |
| | 365 | this->fGameWindow->flush(); |
| | 366 | |
| | 367 | // Prepare for timed input. |
| | 368 | this->fGameWindow->setTimeout(timeout); |
| | 369 | this->fRemainingTimeout = timeout; |
| | 370 | |
| | 371 | // Enable the cursor, if needed. We won't enable it if the |
| | 372 | // timeout is very small, even if the caller requested it. |
| | 373 | // "Small" means under 1 second, which is what most terminals |
| | 374 | // use for a cursor blink. |
| | 375 | if (cursorVisible and (timeout > 1000 or timeout < 1)) curs_set(1); |
| | 376 | |
| | 377 | // On some systems, ncurses is configured to supply its own |
| | 378 | // SIGWINCH handler. If this is the case, getch() returns |
| | 379 | // KEY_RESIZE when the terminal has been resized. We can't rely |
| | 380 | // on this behavior to handle screen resizes, but we must still |
| | 381 | // recognize this return code; here we simply ignore it. Note |
| | 382 | // that curses doesn't know anything about KEY_RESIZE; it's an |
| | 383 | // ncurses thing. We only check for it if it is defined. |
| | 384 | #ifdef KEY_RESIZE |
| | 385 | while ((c = this->fGameWindow->getChar()) == KEY_RESIZE) |
| | 386 | ; |
| | 387 | #else |
| | 388 | c = this->fGameWindow->getChar(); |
| | 389 | #endif |
| | 390 | this->fRemainingTimeout = 0; |
| | 391 | |
| | 392 | // Some terminals don't return KEY_BACKSPACE for the |
| | 393 | // backspace-key, but some opaque platform-specific code. If |
| | 394 | // this is the case, map it to KEY_BACKSPACE. |
| | 395 | if (c == erasechar()) c = KEY_BACKSPACE; |
| | 396 | |
| | 397 | // Similarly for KEY_DL. |
| | 398 | if (c == killchar()) c = KEY_DL; |
| | 399 | |
| | 400 | curs_set(0); |
| | 401 | return c; |
| | 402 | } |