1/* $NetBSD: msg_sys.def,v 1.41 2012/03/06 16:26:01 mbalmer Exp $ */ 2 3/* 4 * Copyright 1997 Piermont Information Systems Inc. 5 * All rights reserved. 6 * 7 * Written by Philip A. Nelson for Piermont Information Systems Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. The name of Piermont Information Systems Inc. may not be used to endorse 18 * or promote products derived from this software without specific prior 19 * written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' 22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 31 * THE POSSIBILITY OF SUCH DAMAGE. 32 * 33 */ 34 35static WINDOW *msg_win = NULL; 36static char *cbuffer; 37static size_t cbuffersize; 38 39static int last_i_was_nl, last_i_was_space; 40static int last_o_was_punct, last_o_was_space; 41 42static void _msg_beep(void); 43static int _msg_vprintf(int, const char *, va_list); 44#define MSG_PROMPT_ECHO 1 45#define MSG_PROMPT_HIDE_DFLT 2 46static void _msg_vprompt(const char *, int, const char *, char *, 47 size_t, va_list); 48 49static char *msgmap = MAP_FAILED; 50static size_t msgmapsz; 51static unsigned int msgmapcount; 52 53/* Routines */ 54 55static void 56_msg_beep(void) 57{ 58 59 fprintf(stderr, "\a"); 60} 61 62WINDOW * 63msg_window(WINDOW *window) 64{ 65 size_t ncbuffersize; 66 char *ncbuffer; 67 WINDOW *old; 68 69 old = msg_win; 70 if (!window) 71 return old; 72 msg_win = window; 73 74 ncbuffersize = getmaxx(window) * getmaxy(window) + 1; 75 while (ncbuffersize > cbuffersize) { 76 ncbuffer = malloc(ncbuffersize); 77 if (ncbuffer == NULL) { 78 /* we might get truncated messages... */ 79 ncbuffersize <<= 1; 80 continue; 81 } 82 if (cbuffer != NULL) 83 free(cbuffer); 84 cbuffer = ncbuffer; 85 cbuffersize = ncbuffersize; 86 break; 87 } 88 last_o_was_punct = 0; 89 last_o_was_space = 1; 90 return old; 91} 92 93int 94msg_file(const char *file) 95{ 96 int fd; 97 98 if (msgmap != MAP_FAILED) 99 munmap(msgmap, msgmapsz); 100 msgmap = MAP_FAILED; 101 if (!file) 102 return 0; 103 fd = open(file, O_RDONLY, 0); 104 if (fd == -1) 105 return -1; 106 msgmapsz = lseek(fd, 0, SEEK_END); 107#ifdef __minix 108 msgmap = mmap(0, msgmapsz, PROT_READ, MAP_PRIVATE, fd, 0); 109#else /* ! __minix */ 110 msgmap = mmap(0, msgmapsz, PROT_READ, MAP_SHARED, fd, 0); 111#endif /* ! __minix */ 112 close(fd); 113 if (msgmap == MAP_FAILED) 114 return -1; 115 /* check_magic */ 116 if (strcmp(msgmap, "MSGTXTS") != 0) { 117 msg_file(NULL); 118 return -1; 119 } 120 msgmapcount = atoi(msgmap + 8); 121 return 0; 122} 123 124const char * 125msg_string(msg msg_no) 126{ 127 uintptr_t m = (uintptr_t)msg_no; 128 129 if (m > sizeof msg_list / sizeof msg_list[0]) 130 /* guess that we were passed a text string */ 131 return msg_no; 132 133 if (msgmap != MAP_FAILED && m != 0 && m <= msgmapcount) { 134 unsigned int offset = atoi(msgmap + 8 + 8 * m); 135 if (offset != 0 && offset < msgmapsz) 136 return msgmap + offset; 137 } 138 139 return msg_list[m]; 140} 141 142void 143msg_clear(void) 144{ 145 146 wclear(msg_win); 147 last_o_was_punct = 0; 148 last_o_was_space = 1; 149} 150 151void 152msg_standout(void) 153{ 154 155 wstandout(msg_win); 156} 157 158void 159msg_standend(void) 160{ 161 162 wstandend(msg_win); 163} 164 165static int 166_msg_vprintf(int auto_fill, const char *fmt, va_list ap) 167{ 168 const char *wstart, *afterw; 169 int wordlen, nspaces; 170 int ret; 171 172 ret = vsnprintf(cbuffer, cbuffersize, fmt, ap); 173 174 if (!auto_fill) { 175 waddstr(msg_win, cbuffer); 176 177 /* 178 * nothing is perfect if they flow text after a table, 179 * but this may be decent. 180 */ 181 last_i_was_nl = last_i_was_space = 1; 182 last_o_was_punct = 0; 183 last_o_was_space = 1; 184 goto out; 185 } 186 187 for (wstart = afterw = cbuffer; *wstart; wstart = afterw) { 188 189 /* eat one space, or a whole word of non-spaces */ 190 if (isspace((unsigned char)*afterw)) 191 afterw++; 192 else 193 while (*afterw && !isspace((unsigned char)*afterw)) 194 afterw++; 195 196 /* this is an nl: special formatting necessary */ 197 if (*wstart == '\n') { 198 if (last_i_was_nl || last_i_was_space) { 199 200 if (getcurx(msg_win) != 0) 201 waddch(msg_win, '\n'); 202 if (last_i_was_nl) { 203 /* last was an nl: paragraph break */ 204 waddch(msg_win, '\n'); 205 } else { 206 /* last was space: line break */ 207 } 208 last_o_was_punct = 0; 209 last_o_was_space = 1; 210 } else { 211 /* last_o_was_punct unchanged */ 212 /* last_o_was_space unchanged */ 213 } 214 last_i_was_space = 1; 215 last_i_was_nl = 1; 216 continue; 217 } 218 219 /* this is a tab: special formatting necessary. */ 220 if (*wstart == '\t') { 221 if (last_i_was_nl) { 222 /* last was an nl: list indent */ 223 if (getcurx(msg_win) != 0) 224 waddch(msg_win, '\n'); 225 } else { 226 /* last was not an nl: columns */ 227 } 228 waddch(msg_win, '\t'); 229 last_i_was_nl = 0; 230 last_i_was_space = 1; 231 last_o_was_punct = 0; 232 last_o_was_space = 1; 233 continue; 234 } 235 236 /* this is a space: ignore it but set flags */ 237 last_i_was_nl = 0; /* all newlines handled above */ 238 last_i_was_space = isspace((unsigned char)*wstart); 239 if (last_i_was_space) 240 continue; 241 242 /* 243 * we have a real "word," i.e. a sequence of non-space 244 * characters. wstart is now the start of the word, 245 * afterw is the next character after the end. 246 */ 247 wordlen = afterw - wstart; 248 nspaces = last_o_was_space ? 0 : (last_o_was_punct ? 2 : 1); 249 if ((getcurx(msg_win) + nspaces + wordlen) >= 250 getmaxx(msg_win) && 251 wordlen < (getmaxx(msg_win) / 3)) { 252 /* wrap the line */ 253 waddch(msg_win, '\n'); 254 nspaces = 0; 255 } 256 257 /* output the word, preceded by spaces if necessary */ 258 while (nspaces-- > 0) 259 waddch(msg_win, ' '); 260 waddbytes(msg_win, wstart, wordlen); 261 262 /* set up the 'last' state for the next time around */ 263 switch (afterw[-1]) { 264 case '.': 265 case '?': 266 case '!': 267 last_o_was_punct = 1; 268 break; 269 default: 270 last_o_was_punct = 0; 271 break; 272 } 273 last_o_was_space = 0; 274 275 /* ... and do it all again! */ 276 } 277 278 /* String ended with a newline. They really want a line break. */ 279 if (last_i_was_nl) { 280 if (getcurx(msg_win) != 0) 281 waddch(msg_win, '\n'); 282 last_o_was_punct = 0; 283 last_o_was_space = 1; 284 } 285 286out: 287 wrefresh(msg_win); 288 return ret; 289} 290 291void 292msg_display(msg msg_no, ...) 293{ 294 va_list ap; 295 296 msg_clear(); 297 298 va_start(ap, msg_no); 299 (void)_msg_vprintf(1, msg_string(msg_no), ap); 300 va_end(ap); 301} 302 303void 304msg_display_add(msg msg_no, ...) 305{ 306 va_list ap; 307 308 va_start(ap, msg_no); 309 (void)_msg_vprintf(1, msg_string(msg_no), ap); 310 va_end(ap); 311} 312 313void 314msg_printf(const char *fmt, ...) 315{ 316 va_list ap; 317 318 va_start(ap, fmt); 319 (void)_msg_vprintf(1, fmt, ap); 320 va_end(ap); 321} 322 323static void 324_msg_vprompt(const char *fmt, int flags, const char *def, char *val, 325 size_t val_buf_len, va_list ap) 326{ 327 int ch; 328 int len, pos, npos, off; 329 int first; 330 int txt_y, txt_x; 331 char *ibuf; 332 int maxx; 333 334 if (val == NULL || val_buf_len == 0) { 335 /* No answer wanted */ 336 val = NULL; 337 val_buf_len = 1; 338 } 339 340 ibuf = malloc(val_buf_len); 341 342 keypad(msg_win, TRUE); 343 _msg_vprintf(0, fmt, ap); 344 ibuf[0] = 0; 345 if (def != NULL && *def) { 346 if (flags & MSG_PROMPT_HIDE_DFLT) 347 strlcpy(ibuf, def, val_buf_len); 348 else { 349 waddstr(msg_win, " ["); 350 waddstr(msg_win, def); 351 waddstr(msg_win, "]"); 352 } 353 } 354 waddstr(msg_win, ": "); 355 len = strlen(ibuf); 356 pos = len; 357 getyx(msg_win, txt_y, txt_x); 358 maxx = getmaxx(msg_win) - txt_x - 1; 359 off = 0; 360 361 for (first = 1; ; first = 0) { 362 363 if (flags & MSG_PROMPT_ECHO) { 364 /* shift text right as we near the buffer start */ 365 if (pos - off < 4) 366 off = pos - 4; 367 /* keep offset to a minimum if we are at the end */ 368 if (pos == len) 369 off = pos - maxx; 370 if (off < 0 || len <= maxx) 371 off = 0; 372 /* shift text left as we near the buffer end */ 373 npos = pos + 4; 374 if (npos > len) 375 npos = len; 376 if (npos - off > maxx) 377 off = npos - maxx; 378 /* calc. length to display */ 379 npos = len - off; 380 if (npos > maxx) 381 npos = maxx; 382 mvwaddnstr(msg_win, txt_y, txt_x, ibuf + off, npos); 383 wclrtoeol(msg_win); 384 if (off != 0) 385 mvwaddstr(msg_win, txt_y, txt_x, "+"); 386 wmove(msg_win, txt_y, txt_x + pos - off); 387 wrefresh(msg_win); 388 } 389 390 ch = wgetch(msg_win); 391 if (ch == '\n') 392 break; 393 394 switch (ch) { 395 case KEY_BACKSPACE: 396 case 'h' & 0x1f: case 0x7f: /* bs or del - delete left */ 397 if (first) { 398 /* delete all of default string */ 399 len = pos = 0; 400 break; 401 } 402 if (pos > 0) { 403 memmove(ibuf + pos - 1, ibuf + pos, len - pos); 404 len--; 405 pos--; 406 } else 407 _msg_beep(); 408 break; 409 case 'u' & 0x1f: /* ^U; line kill */ 410 /* kill line */ 411 len = pos = 0; 412 break; 413 case 'w' & 0x1f: /* ^W; word kill */ 414 /* 415 * word kill kills the spaces and the 'word' 416 * (non-spaces) last typed. the spaces before 417 * the 'word' aren't killed. 418 */ 419 npos = pos; 420 while (npos > 0 && isspace((unsigned char)ibuf[npos - 1])) 421 npos--; 422 while (npos > 0 && !isspace((unsigned char)ibuf[npos - 1])) 423 npos--; 424 memmove(ibuf + npos, ibuf + pos, len - pos); 425 len -= pos - npos; 426 pos = npos; 427 break; 428 case KEY_LEFT: 429 if (pos > 0) 430 pos--; 431 break; 432 case KEY_RIGHT: 433 if (len == 0 && pos == 0 && def != NULL) { 434 /* restore default! */ 435 strlcpy(ibuf, def, val_buf_len); 436 len = pos = strlen(ibuf); 437 break; 438 } 439 if (pos < len) 440 pos++; 441 break; 442 default: 443 if (len < (int)(val_buf_len - 1) && isprint(ch)) { 444 memmove(ibuf + pos + 1, ibuf + pos, len - pos); 445 ibuf[pos++] = ch; 446 len++; 447 } else 448 _msg_beep(); 449 break; 450 } 451 } 452 453 if (flags & MSG_PROMPT_ECHO) { 454 mvwaddch(msg_win, txt_y, txt_x + len - off, '\n'); 455 last_o_was_punct = 0; 456 last_o_was_space = 1; 457 } 458 459 if (val != NULL) { 460 /* copy the appropriate string to the output */ 461 if (len != 0 || flags & MSG_PROMPT_HIDE_DFLT) { 462 ibuf[len] = '\0'; 463 strlcpy(val, ibuf, val_buf_len); 464 } else if (def != NULL && val != def) { 465 strlcpy(val, def, val_buf_len); 466 } 467 } 468 free(ibuf); 469} 470 471void 472msg_prompt(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 473{ 474 va_list ap; 475 476 msg_clear(); 477 478 va_start(ap, val_buf_len); 479 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, 480 def, val, val_buf_len, ap); 481 va_end(ap); 482} 483 484void 485msg_prompt_win(msg msg_no, int x, int y, int w, int h, 486 const char *def, char *val, size_t val_buf_len, ...) 487{ 488 va_list ap; 489 WINDOW *win; 490 WINDOW *svmsg = NULL, *sv_win = NULL; /* XXX -Wuninitialized [many] */ 491 int maxx, maxy; 492 int msg_flags = MSG_PROMPT_ECHO | MSG_PROMPT_HIDE_DFLT; 493 494 maxx = getmaxx(msg_win); 495 maxy = getmaxy(msg_win); 496 if (w == 0) { 497 va_start(ap, val_buf_len); 498 w = vsnprintf(NULL, 0, msg_string(msg_no), ap); 499 va_end(ap); 500 if (def != NULL && *def != 0 && w + (int)val_buf_len * 2 < maxx) { 501 w += 2 + strlen(def) + 1; 502 msg_flags &= ~MSG_PROMPT_HIDE_DFLT; 503 } 504 w += 1 + 2 + val_buf_len + 1; 505 if (w > maxx) { 506 if (!(msg_flags & MSG_PROMPT_HIDE_DFLT)) { 507 w -= 2 + strlen(def) + 1; 508 msg_flags |= MSG_PROMPT_HIDE_DFLT; 509 } 510 w = maxx; 511 } 512 } 513 514 if (x == -1) 515 x = (maxx - w) / 2 + 1; 516 if (h < 3) 517 h = 3; 518 if (y < 3) 519 y = (maxy - h) / 2; 520 if (y + h > maxy) 521 y = maxy - h; 522 523 win = subwin(msg_win, h, w, y, x); 524 if (win == NULL) 525 wprintw(msg_win, "msg_prompt_win: " 526 "newwin(%d, %d, %d, %d) failed\n", 527 h, w, y, x); 528 else { 529 /* 530 * Save screen contents from under our window 531 * Due to a mis-feature of NetBSD curses, curscr contains 532 * the data processed by doupdate() not that by wnoutrefresh(). 533 * We must call doupdate() here to ensure we save the correct 534 * data. See PR 26660 535 */ 536 doupdate(); 537 sv_win = dupwin(win); 538 if (sv_win) 539 overwrite(curscr, sv_win); 540 wbkgd(win, getbkgd(msg_win)); 541 wattrset(win, getattrs(msg_win)); 542 box(win, 0, 0); 543 wrefresh(win); 544 545 /* Change message window to be our little box */ 546 svmsg = msg_window(subwin(msg_win, h - 2, w - 2, y + 1, x + 1)); 547 wbkgd(msg_win, getbkgd(win)); 548 wattrset(msg_win, getattrs(win)); 549 550 msg_clear(); 551 } 552 553 va_start(ap, val_buf_len); 554 _msg_vprompt(msg_string(msg_no), msg_flags, def, val, val_buf_len, ap); 555 va_end(ap); 556 557 if (win != NULL) { 558 wclear(win); 559 if (sv_win) { 560 /* Restore original screen contents */ 561 overwrite(sv_win, win); 562 delwin(sv_win); 563 } 564 wnoutrefresh(win); 565 /* Restore normal message window */ 566 delwin(msg_window(svmsg)); 567 delwin(win); 568 } 569} 570 571void 572msg_prompt_add(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 573{ 574 va_list ap; 575 576 va_start(ap, val_buf_len); 577 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, def, val, val_buf_len, ap); 578 va_end(ap); 579} 580 581void 582msg_prompt_noecho(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 583{ 584 va_list ap; 585 586 msg_clear(); 587 588 va_start(ap, val_buf_len); 589 _msg_vprompt(msg_string(msg_no), 0, def, val, val_buf_len, ap); 590 va_end(ap); 591} 592 593void 594msg_table_add(msg msg_no, ...) 595{ 596 va_list ap; 597 598 va_start(ap, msg_no); 599 (void)_msg_vprintf(0, msg_string(msg_no), ap); 600 va_end(ap); 601} 602 603int 604msg_row(void) 605{ 606 607 return getcury(msg_win) + getbegy(msg_win); 608} 609