File: cursmisc.c

package info (click to toggle)
nethack 3.6.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,468 kB
  • sloc: ansic: 266,495; cpp: 13,652; yacc: 2,903; perl: 1,426; lex: 581; sh: 535; xml: 372; awk: 98; makefile: 68; fortran: 51; sed: 11
file content (1081 lines) | stat: -rw-r--r-- 29,671 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
/* 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.6 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"

#include <ctype.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
# ifndef NHSTDC
#  define M(c)          (0x80 | (c))
# else
#  define M(c)          ((c) - 128)
# endif/* NHSTDC */
#endif
#ifndef C
# define C(c)           (0x1f & (c))
#endif


/* Read a character of input from the user */

int
curses_read_char()
{
    int ch;
#if defined(ALT_0) || defined(ALT_9) || defined(ALT_A) || defined(ALT_Z)
    int tmpch;
#endif

    /* cancel message suppression; all messages have had a chance to be read */
    curses_got_input();

    ch = getch();
#if defined(ALT_0) || defined(ALT_9) || defined(ALT_A) || defined(ALT_Z)
    tmpch = ch;
#endif
    ch = curses_convert_keys(ch);

    if (ch == 0) {
        ch = '\033'; /* 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 = C('r'); /* 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)
{
#ifdef TEXTCOLOR
    int curses_color;

    /* 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)) {
#endif
        if (attr != NONE) {
            if (onoff == ON)
                wattron(win, attr);
            else
                wattroff(win, attr);
        }
        return;
#ifdef TEXTCOLOR
    }

    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);
        }
    }
#else
    nhUse(color);
#endif /* TEXTCOLOR */
}

/* 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;
    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
    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) {
        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;
#if __STDC_VERSION__ >= 199901L
    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;
        }
        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
    }
}


/* Perform actions that should be done every turn before nhgetch() */

void
curses_prehousekeeping()
{
#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()
{
    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");

    if (fp == NULL) {
        if (must_exist)
            pline("Cannot open \"%s\" for reading!", filename);
        return;
    }

    wid = curses_get_wid(NHW_MENU);
    curses_create_nhmenu(wid);
    Id = zeroany;

    while (dlb_fgets(buf, BUFSZ, fp) != NULL) {
        curses_add_menu(wid, NO_GLYPH, &Id, 0, 0, A_NORMAL, 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);
    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)
{
    long current_count = first_digit;
    int current_char;

    current_char = curses_read_char();

    while (isdigit(current_char)) {
        current_count = (current_count * 10) + (current_char - '0');
        if (current_count > LARGEST_INT) {
            current_count = LARGEST_INT;
        }

        custompline(SUPPRESS_HISTORY, "Count: %ld", current_count);
        current_char = curses_read_char();
    }

    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. */

int
curses_convert_attr(int attr)
{
    int 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;
    default:
        curses_attr = A_NORMAL;
    }

    return curses_attr;
}


/* Map letter attributes from a string to bitmask.  Return mask on
   success (might be 0), or -1 if not found. */

int
curses_read_attrs(const char *attrs)
{
    int retattr = 0;

    if (!attrs || !*attrs)
        return A_NORMAL;

    if (strchr(attrs, 'b') || strchr(attrs, 'B'))
        retattr |= A_BOLD;
    if (strchr(attrs, 'i') || strchr(attrs, 'I')) /* inverse */
        retattr |= A_REVERSE;
    if (strchr(attrs, 'u') || strchr(attrs, 'U'))
        retattr |= A_UNDERLINE;
    if (strchr(attrs, 'k') || strchr(attrs, 'K'))
        retattr |= A_BLINK;
    if (strchr(attrs, 'd') || strchr(attrs, 'D'))
        retattr |= A_DIM;
#ifdef A_ITALIC
    if (strchr(attrs, 't') || strchr(attrs, 'T'))
        retattr |= A_ITALIC;
#endif
#ifdef A_LEFTLINE
    if (strchr(attrs, 'l') || strchr(attrs, 'L'))
        retattr |= A_LEFTLINE;
#endif
#ifdef A_RIGHTLINE
    if (strchr(attrs, 'r') || strchr(attrs, 'R'))
        retattr |= A_RIGHTLINE;
#endif
    if (retattr == 0) {
        /* still default; check for none/normal */
        if (strchr(attrs, 'n') || strchr(attrs, 'N'))
            retattr = A_NORMAL;
        else
            retattr = -1; /* error */
    }
    return retattr;
}

/* format iflags.wc2_petattr into "+a+b..." for set bits a, b, ...
   (used by core's 'O' command; return value points past leading '+') */
char *
curses_fmt_attrs(outbuf)
char *outbuf;
{
    int attr = iflags.wc2_petattr;

    outbuf[0] = '\0';
    if (attr == A_NORMAL) {
        Strcpy(outbuf, "+N(None)");
    } else {
        if (attr & A_BOLD)
            Strcat(outbuf, "+B(Bold)");
        if (attr & A_REVERSE)
            Strcat(outbuf, "+I(Inverse)");
        if (attr & A_UNDERLINE)
            Strcat(outbuf, "+U(Underline)");
        if (attr & A_BLINK)
            Strcat(outbuf, "+K(blinK)");
        if (attr & A_DIM)
            Strcat(outbuf, "+D(Dim)");
#ifdef A_ITALIC
        if (attr & A_ITALIC)
            Strcat(outbuf, "+T(iTalic)");
#endif
#ifdef A_LEFTLINE
        if (attr & A_LEFTLINE)
            Strcat(outbuf, "+L(Left line)");
#endif
#ifdef A_RIGHTLINE
        if (attr & A_RIGHTLINE)
            Strcat(outbuf, "+R(Right line)");
#endif
    }
    if (!*outbuf)
        Sprintf(outbuf, "+unknown [%d]", attr);
    return &outbuf[1];
}

/* 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_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');
        break;
#ifdef KEY_B1
    case KEY_B1:
#endif
    case KEY_LEFT:
        if (iflags.num_pad) {
            ret = '4';
        } else {
            ret = 'h';
        }
        break;
#ifdef KEY_B3
    case KEY_B3:
#endif
    case KEY_RIGHT:
        if (iflags.num_pad) {
            ret = '6';
        } else {
            ret = 'l';
        }
        break;
#ifdef KEY_A2
    case KEY_A2:
#endif
    case KEY_UP:
        if (iflags.num_pad) {
            ret = '8';
        } else {
            ret = 'k';
        }
        break;
#ifdef KEY_C2
    case KEY_C2:
#endif
    case KEY_DOWN:
        if (iflags.num_pad) {
            ret = '2';
        } else {
            ret = 'j';
        }
        break;
#ifdef KEY_A1
    case KEY_A1:
#endif
    case KEY_HOME:
        if (iflags.num_pad) {
            ret = '7';
        } else {
            ret = !Cmd.swap_yz ? 'y' : 'z';
        }
        break;
#ifdef KEY_A3
    case KEY_A3:
#endif
    case KEY_PPAGE:
        if (iflags.num_pad) {
            ret = '9';
        } else {
            ret = 'u';
        }
        break;
#ifdef KEY_C1
    case KEY_C1:
#endif
    case KEY_END:
        if (iflags.num_pad) {
            ret = '1';
        } else {
            ret = 'b';
        }
        break;
#ifdef KEY_C3
    case KEY_C3:
#endif
    case KEY_NPAGE:
        if (iflags.num_pad) {
            ret = '3';
        } else {
            ret = 'n';
        }
        break;
#ifdef KEY_B2
    case KEY_B2:
        if (iflags.num_pad) {
            ret = '5';
        } else {
            ret = 'g';
        }
        break;
#endif /* KEY_B2 */
    }

    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(int *mousex, int *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;
            }
        }
    }
#endif /* NCURSES_MOUSE_VERSION */

    return key;
}

void
curses_mouse_support(mode)
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
}

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 */
}