4 * Copyright (c) 1997 Ben Harrison, and others
6 * This software may be copied and distributed for educational, research,
7 * and not for profit purposes provided that this copyright and statement
8 * are included in all such copies.
11 /* Purpose: Allow use of Unix "curses" with Angband -BEN- */
14 * This file has been modified to use multiple text windows if your screen
15 * is larger than 80x25. By Keldon Jones (keldon@umr.edu).
17 * Also included is Keldon Jones patch to get better colors. To switch to
18 * a term that supports this, see this posting:
20 * From keldon@umr.edu Thu Apr 01 05:40:14 1999
21 * Sender: KELDON JONES <keldon@saucer.cc.umr.edu>
22 * From: Keldon Jones <keldon@umr.edu>
23 * Subject: Re: Linux colour prob (Or: question for Greg)
24 * Newsgroups: rec.games.roguelike.angband
25 * References: <slrn7g1jlp.gj9.scarblac-spamtrap@flits104-37.flits.rug.nl> <3700f96b.1593384@news.polsl.gliwice.pl> <slrn7g36er.fm4.wooledge@jekyll.local>
26 * X-Newsreader: TIN [UNIX 1.3 unoff BETA 970625; 9000/780 HP-UX B.10.20]
27 * NNTP-Posting-Host: saucer.cc.umr.edu
28 * X-NNTP-Posting-Host: saucer.cc.umr.edu
29 * Message-ID: <370306be.0@news.cc.umr.edu>
30 * Date: 1 Apr 99 05:40:14 GMT
31 * Organization: University of Missouri - Rolla
34 * xs4all!xs4all!newsfeed.wirehub.nl!news-peer.gip.net!news.gsl.net!gip.net!news.he.net!mercury.cts.com!alpha.sky.net!news.missouri.edu!news.cc.umr.edu!not-for-mail
35 * Xref: xs4all rec.games.roguelike.angband:86332
37 * Greg Wooledge <wooledge@kellnet.com> wrote:
38 * > Gwidon S. Naskrent (naskrent@artemida.amu.edu.pl) wrote:
40 * > >On 30 Mar 1999 13:17:18 GMT, scarblac-spamtrap@pino.selwerd.cx (Remco
43 * > >>I recently switched to Linux, and *bands work fine. I like
44 * > >>to play them in consoles, not in X. However, colour is wrong.
45 * > >>"Slate" and "light slate" are always light blue, instead
46 * > >>of some shade of grey. Colours are fine in X.
48 * > I actually noticed the Linux console color issue a very long time ago,
49 * > but since I always play under X, I never really investigated it.
51 * > You're absolutely right, though -- the Linux console colors are not
52 * > "right" for Angband.
54 * I've noticed this myself, so I spent the evening fixing it.
55 * Well, sorta fixing it. It's not perfect yet, and it may not be
56 * possible to get it perfect with VGA hardware and/or the current
59 * > OK, reading on in terminfo(5):
62 * > Most color terminals are either `Tektronix-like' or `HP-
63 * > like'. Tektronix-like terminals have a predefined set of
64 * > N colors (where N usually 8), and can set character-cell
65 * > foreground and background characters independently, mixing
66 * > them into N * N color-pairs. On HP-like terminals, the
67 * > use must set each color pair up separately (foreground and
68 * > background are not independently settable). Up to M
69 * > color-pairs may be set up from 2*M different colors.
70 * > ANSI-compatible terminals are Tektronix-like.
72 * > The "linux" terminfo entry is definitely in the "Tektronix-like" family.
73 * > It has the "setaf" and "setab" capabilities for setting the foreground
74 * > and background colors to one of 8 basically hard-coded values:
76 * > Color #define Value RGB
77 * > black COLOR_BLACK 0 0, 0, 0
78 * > red COLOR_RED 1 max,0,0
79 * > green COLOR_GREEN 2 0,max,0
80 * > yellow COLOR_YELLOW 3 max,max,0
81 * > blue COLOR_BLUE 4 0,0,max
82 * > magenta COLOR_MAGENTA 5 max,0,max
83 * > cyan COLOR_CYAN 6 0,max,max
84 * > white COLOR_WHITE 7 max,max,max
86 * Well, not quite. Using certain escape sequences, an
87 * application (or better yet, curses) can redefine the colors (at
88 * least some of them) and then those are used. Read the
89 * curs_color manpage, and the part about "ccc" and "initc" in the
90 * terminfo manpage. This is what the part of main-gcu inside the
91 * "if (can_fix_color)" code does.
93 * > So, what does this mean to the Angband player? Well, it means that
94 * > either there's nothing you can do about the console colors as long as
95 * > straight curses/ncurses is used, or if there is something to be done,
96 * > I'm not clever enough to figure out how to do it.
98 * Well, it is possible, though you have to patch main-gcu
99 * and edit a terminfo entry. Apparently the relevant code in
100 * main-gcu was never tested (it's broken in at least one major
101 * way). Apply the patch at the end of this message (notice that
102 * we need to define REDEFINE_COLORS at some point near the
103 * beginning of the file).
104 * Next, write this termcap entry to a file:
106 * linux-c|linux console 1.3.6+ with private palette for each virtual console,
108 * colors#16, pairs#64,
109 * initc=\E]P%x%p1%{16}%/%02x%p1%{16}%/%02x%p1%{16}%/%02x,
113 * and run "tic" on it to produce a new terminfo entry called
114 * "linux-c". Especially note the "ccc" flag which says that we
115 * can redefine colors. The ugly "initc" string is what tells
116 * the console how to redefine a color. Now, just set your TERM
117 * variable to "linux-c" and try Angband again. If I've
118 * remembered to tell you everything that I've done, you should
119 * get the weird light-blue slate changed to a gray.
120 * Now, there are still lots of problems with this.
121 * Something (I don't think it's curses, either the kernel or
122 * the hardware itself) seems to be ignoring my color changes to
123 * colors 6 and 7, which is annoying. Also, the normal "white"
124 * color is now way too bright, but it's now necessary to
125 * distinguish it from the other grays.
126 * The kernel seems to support 16 colors, but you can
127 * only switch to 8 of those, due to VT102 compatibility, it
128 * seems. I think it would be possible to patch the kernel and
129 * allow all 16 colors to be used, but I haven't built up the
130 * nerve to try that yet.
131 * Let me know if you can improve on this any. Some of
132 * this may actually work differently on other hardware (ugh).
139 * To use this file, you must define "USE_GCU" in the Makefile.
141 * Hack -- note that "angband.h" is included AFTER the #ifdef test.
142 * This was necessary because of annoying "curses.h" silliness.
144 * Note that this file is not "intended" to support non-Unix machines,
145 * nor is it intended to support VMS or other bizarre setups.
147 * Also, this package assumes that the underlying "curses" handles both
148 * the "nonl()" and "cbreak()" commands correctly, see the "OPTION" below.
150 * This code should work with most versions of "curses" or "ncurses",
151 * and the "main-ncu.c" file (and USE_NCU define) are no longer used.
153 * See also "USE_CAP" and "main-cap.c" for code that bypasses "curses"
154 * and uses the "termcap" information directly, or even bypasses the
155 * "termcap" information and sends direct vt100 escape sequences.
157 * XXX XXX XXX Consider the use of "savetty()" and "resetty()".
160 #include "game-option/runtime-arguments.h"
161 #include "game-option/special-options.h"
162 #include "io/exit-panic.h"
163 #include "io/files-util.h"
164 #include "locale/japanese.h"
165 #include "main/sound-definitions-table.h"
166 #include "main/sound-of-music.h"
167 #include "system/angband-version.h"
168 #include "system/angband.h"
169 #include "system/player-type-definition.h"
170 #include "term/gameterm.h"
171 #include "term/term-color-types.h"
172 #include "term/z-form.h"
173 #include "util/angband-files.h"
174 #include "view/display-map.h"
179 * Hack -- play games with "bool"
181 #if __STDC_VERSION__ < 199901L
186 * Include the proper "header" file
191 * Simple rectangle type
198 /* Trivial rectangle utility to make code a bit more readable */
199 static rect_t rect(int x, int y, int cx, int cy)
210 * Information about a term
218 /* Max number of windows on screen */
219 #define MAX_TERM_DATA 8
221 /* Information about our windows */
222 static term_data data[MAX_TERM_DATA];
225 * Hack -- try to guess which systems use what commands
226 * Hack -- allow one of the "USE_Txxxxx" flags to be pre-set.
227 * Mega-Hack -- try to guess when "POSIX" is available.
228 * If the user defines two of these, we will probably crash.
230 #if !defined(USE_TCHARS)
231 #if defined(_POSIX_VERSION)
239 * Try redefining the colors at startup.
241 #define REDEFINE_COLORS
247 #include <sys/ioctl.h>
252 * One version needs this file
255 #include <sys/ioctl.h>
260 * The other needs this file
263 #include <sys/file.h>
264 #include <sys/ioctl.h>
265 #include <sys/param.h>
266 #include <sys/resource.h>
267 #include <sys/types.h>
273 * XXX XXX Hack -- POSIX uses "O_NONBLOCK" instead of "O_NDELAY"
275 * They should both work due to the "(i != 1)" test below.
278 #define O_NDELAY O_NONBLOCK
282 * OPTION: some machines lack "cbreak()"
283 * On these machines, we use an older definition
285 /* #define cbreak() crmode() */
288 * OPTION: some machines cannot handle "nonl()" and "nl()"
289 * On these machines, we can simply ignore those commands.
294 static std::filesystem::path ANGBAND_DIR_XTRA_SOUND;
298 * Flag set once "sound" has been initialized
300 static bool can_use_sound = false;
303 * An array of sound file names
305 static concptr sound_file[SOUND_MAX];
308 * Save the "normal" and "angband" terminal settings
313 static struct termios norm_termios;
315 static struct termios game_termios;
321 static struct termio norm_termio;
323 static struct termio game_termio;
328 static struct ltchars norm_speciax_chars;
329 static struct sgttyb norm_ttyb;
330 static struct tchars norm_tchars;
331 static int norm_locax_chars;
333 static struct ltchars game_speciax_chars;
334 static struct sgttyb game_ttyb;
335 static struct tchars game_tchars;
336 static int game_locax_chars;
340 * Hack -- Number of initialized "term" structures
342 static int active = 0;
346 * Hack -- define "A_BRIGHT" to be "A_BOLD", because on many
347 * machines, "A_BRIGHT" produces ugly "inverse" video.
350 #define A_BRIGHT A_BOLD
354 * Software flag -- we are allowed to use color
356 static int can_use_color = false;
359 * Software flag -- we are allowed to change the colors
361 static int can_fix_color = false;
364 * Simple Angband to Curses color conversion table
366 static int colortable[16];
369 * Background color we should draw with; either BLACK or DEFAULT
371 static int bg_color = COLOR_BLACK;
376 * Place the "keymap" into its "normal" state
378 static void keymap_norm(void)
381 /* restore the saved values of the special chars */
382 (void)tcsetattr(0, TCSAFLUSH, &norm_termios);
386 /* restore the saved values of the special chars */
387 (void)ioctl(0, TCSETA, (char *)&norm_termio);
391 /* restore the saved values of the special chars */
392 (void)ioctl(0, TIOCSLTC, (char *)&norm_speciax_chars);
393 (void)ioctl(0, TIOCSETP, (char *)&norm_ttyb);
394 (void)ioctl(0, TIOCSETC, (char *)&norm_tchars);
395 (void)ioctl(0, TIOCLSET, (char *)&norm_locax_chars);
400 * Place the "keymap" into the "game" state
402 static void keymap_game(void)
405 /* restore the saved values of the special chars */
406 (void)tcsetattr(0, TCSAFLUSH, &game_termios);
410 /* restore the saved values of the special chars */
411 (void)ioctl(0, TCSETA, (char *)&game_termio);
415 /* restore the saved values of the special chars */
416 (void)ioctl(0, TIOCSLTC, (char *)&game_speciax_chars);
417 (void)ioctl(0, TIOCSETP, (char *)&game_ttyb);
418 (void)ioctl(0, TIOCSETC, (char *)&game_tchars);
419 (void)ioctl(0, TIOCLSET, (char *)&game_locax_chars);
424 * Save the normal keymap
426 static void keymap_norm_prepare(void)
429 /* Get the normal keymap */
430 tcgetattr(0, &norm_termios);
434 /* Get the normal keymap */
435 (void)ioctl(0, TCGETA, (char *)&norm_termio);
439 /* Get the normal keymap */
440 (void)ioctl(0, TIOCGETP, (char *)&norm_ttyb);
441 (void)ioctl(0, TIOCGLTC, (char *)&norm_speciax_chars);
442 (void)ioctl(0, TIOCGETC, (char *)&norm_tchars);
443 (void)ioctl(0, TIOCLGET, (char *)&norm_locax_chars);
448 * Save the keymaps (normal and game)
450 static void keymap_game_prepare(void)
453 /* Acquire the current mapping */
454 tcgetattr(0, &game_termios);
456 /* Force "Ctrl-C" to interupt */
457 game_termios.c_cc[VINTR] = (char)3;
459 /* Force "Ctrl-Z" to suspend */
460 game_termios.c_cc[VSUSP] = (char)26;
462 /* Hack -- Leave "VSTART/VSTOP" alone */
464 /* Disable the standard control characters */
465 game_termios.c_cc[VQUIT] = (char)-1;
466 game_termios.c_cc[VERASE] = (char)-1;
467 game_termios.c_cc[VKILL] = (char)-1;
468 game_termios.c_cc[VEOF] = (char)-1;
469 game_termios.c_cc[VEOL] = (char)-1;
471 /* Normally, block until a character is read */
472 game_termios.c_cc[VMIN] = 1;
473 game_termios.c_cc[VTIME] = 0;
477 /* Acquire the current mapping */
478 (void)ioctl(0, TCGETA, (char *)&game_termio);
480 /* Force "Ctrl-C" to interupt */
481 game_termio.c_cc[VINTR] = (char)3;
483 /* Force "Ctrl-Z" to suspend */
484 game_termio.c_cc[VSUSP] = (char)26;
486 /* Disable the standard control characters */
487 game_termio.c_cc[VQUIT] = (char)-1;
488 game_termio.c_cc[VERASE] = (char)-1;
489 game_termio.c_cc[VKILL] = (char)-1;
490 game_termio.c_cc[VEOF] = (char)-1;
491 game_termio.c_cc[VEOL] = (char)-1;
493 /* Normally, block until a character is read */
494 game_termio.c_cc[VMIN] = 1;
495 game_termio.c_cc[VTIME] = 0;
499 /* Get the default game characters */
500 (void)ioctl(0, TIOCGETP, (char *)&game_ttyb);
501 (void)ioctl(0, TIOCGLTC, (char *)&game_speciax_chars);
502 (void)ioctl(0, TIOCGETC, (char *)&game_tchars);
503 (void)ioctl(0, TIOCLGET, (char *)&game_locax_chars);
505 /* Force suspend (^Z) */
506 game_speciax_chars.t_suspc = (char)26;
508 /* Cancel some things */
509 game_speciax_chars.t_dsuspc = (char)-1;
510 game_speciax_chars.t_rprntc = (char)-1;
511 game_speciax_chars.t_flushc = (char)-1;
512 game_speciax_chars.t_werasc = (char)-1;
513 game_speciax_chars.t_lnextc = (char)-1;
515 /* Force interupt (^C) */
516 game_tchars.t_intrc = (char)3;
518 /* Force start/stop (^Q, ^S) */
519 game_tchars.t_startc = (char)17;
520 game_tchars.t_stopc = (char)19;
522 /* Cancel some things */
523 game_tchars.t_quitc = (char)-1;
524 game_tchars.t_eofc = (char)-1;
525 game_tchars.t_brkc = (char)-1;
532 static errr game_term_xtra_gcu_alive(int v)
535 /* Go to normal keymap mode */
543 /* Hack -- make sure the cursor is visible */
544 term_xtra(TERM_XTRA_SHAPE, 1);
546 /* Flush the curses buffer */
549 /* this moves curses to bottom right corner */
550 mvcur(getcury(curscr), getcurx(curscr), LINES - 1, 0);
555 /* Flush the output */
556 (void)fflush(stdout);
558 /* Restore the settings */
563 /* Go to angband keymap mode */
571 * Check for existance of a file
573 static bool check_file(concptr s)
588 static bool init_sound(void)
591 return can_use_sound;
594 for (auto i = 1; i < SOUND_MAX; i++) {
595 std::string wav = angband_sound_name[i];
597 const auto &path = path_build(ANGBAND_DIR_XTRA_SOUND, wav);
598 const auto &filename = path.string();
599 if (check_file(filename.data())) {
600 sound_file[i] = string_make(filename.data());
604 /* Sound available */
605 can_use_sound = true;
606 return can_use_sound;
610 * Init the "curses" system
612 static void game_term_init_gcu(term_type *t)
614 term_data *td = (term_data *)(t->data);
616 /* Count init's, handle first */
621 /* Erase the screen */
622 (void)wclear(td->win);
624 /* Reset the cursor */
625 (void)wmove(td->win, 0, 0);
628 (void)wrefresh(td->win);
635 * Nuke the "curses" system
637 static void game_term_nuke_gcu(term_type *t)
639 term_data *td = (term_data *)(t->data);
641 /* Delete this window */
644 /* Count nuke's, handle last */
649 /* Hack -- make sure the cursor is visible */
650 term_xtra(TERM_XTRA_SHAPE, 1);
653 /* Reset colors to defaults */
657 /* This moves curses to bottom right corner */
658 mvcur(getcury(curscr), getcurx(curscr), LINES - 1, 0);
660 /* Flush the curses buffer */
666 /* Flush the output */
667 (void)fflush(stdout);
674 * Push multiple keys reversal
676 static void term_string_push(char *buf)
678 int i, l = strlen(buf);
679 for (i = l; i >= 0; i--) {
680 term_key_push(buf[i]);
687 * Process events, with optional wait
689 static errr game_term_xtra_gcu_event(int v)
698 /* Paranoia -- Wait for it */
699 nodelay(stdscr, false);
704 /* Broken input is special */
706 exit_game_panic(p_ptr);
709 exit_game_panic(p_ptr);
714 /* Do not wait for it */
715 nodelay(stdscr, true);
717 while ((i = getch()) != EOF) {
719 exit_game_panic(p_ptr);
722 if (bp == &buf[255]) {
727 /* Wait for it next time */
728 nodelay(stdscr, false);
732 char eucbuf[sizeof(buf)];
733 /* strlen + 1 を渡して文字列終端('\0')を含めて変換する */
734 if (utf8_to_euc(buf, strlen(buf) + 1, eucbuf, sizeof(eucbuf)) < 0) {
738 term_string_push(_(eucbuf, buf));
743 /* Do not wait for it */
744 nodelay(stdscr, true);
746 /* Check for keypresses */
749 /* Wait for it next time */
750 nodelay(stdscr, false);
760 /* Enqueue the keypress */
768 #else /* USE_GETCH */
771 * Process events (with optional wait)
773 static errr game_term_xtra_gcu_event(int v)
783 /* Wait for one byte */
784 i = read(0, bp++, 1);
786 /* Hack -- Handle bizarre "errors" */
787 if ((i <= 0) && (errno != EINTR)) {
788 exit_game_panic(p_ptr);
791 /* Get the current flags for stdin */
792 k = fcntl(0, F_GETFL, 0);
799 /* Tell stdin not to block */
800 if (fcntl(0, F_SETFL, k | O_NDELAY) >= 0) {
801 if ((i = read(0, bp, 254)) > 0) {
805 /* Replace the flags for stdin */
806 if (fcntl(0, F_SETFL, k)) {
813 char eucbuf[sizeof(buf)];
814 /* strlen + 1 を渡して文字列終端('\0')を含めて変換する */
815 if (utf8_to_euc(buf, strlen(buf) + 1, eucbuf, sizeof(eucbuf)) < 0) {
819 term_string_push(_(eucbuf, buf));
824 /* Get the current flags for stdin */
825 k = fcntl(0, F_GETFL, 0);
832 /* Tell stdin not to block */
833 if (fcntl(0, F_SETFL, k | O_NDELAY) < 0) {
837 /* Read one byte, if possible */
840 /* Replace the flags for stdin */
841 if (fcntl(0, F_SETFL, k)) {
845 /* Ignore "invalid" keys */
846 if ((i != 1) || (!buf[0])) {
850 /* Enqueue the keypress */
851 term_key_push(buf[0]);
858 #endif /* USE_GETCH */
861 * Hack -- make a sound
863 static errr game_term_xtra_gcu_sound(int v)
871 if ((v < 0) || (v >= SOUND_MAX)) {
876 if (!sound_file[v]) {
880 std::string buf = "./gcusound.sh ";
881 buf.append(sound_file[v]).append("\n");
882 return system(buf.data()) < 0;
885 static int scale_color(int i, int j, int scale)
887 return (angband_color_table[i][j] * (scale - 1) + 127) / 255;
890 static int create_color(int i, int scale)
892 int r = scale_color(i, 1, scale);
893 int g = scale_color(i, 2, scale);
894 int b = scale_color(i, 3, scale);
895 int rgb = 16 + scale * scale * r + scale * g + b;
896 /* In the case of white and black we need to use the ANSI colors */
897 if (r == g && g == b) {
911 static errr game_term_xtra_gcu_react(void)
916 if (!can_change_color()) {
917 if (COLORS == 256 || COLORS == 88) {
918 /* If we have more than 16 colors, find the best matches. These numbers
919 * correspond to xterm/rxvt's builtin color numbers--they do not
920 * correspond to curses' constants OR with curses' color pairs.
922 * XTerm has 216 (6*6*6) RGB colors, with each RGB setting 0-5.
923 * RXVT has 64 (4*4*4) RGB colors, with each RGB setting 0-3.
925 * Both also have the basic 16 ANSI colors, plus some extra grayscale
926 * colors which we do not use.
928 int scale = COLORS == 256 ? 6 : 4;
929 for (int i = 0; i < 16; i++) {
930 int fg = create_color(i, scale);
931 init_pair(i + 1, fg, bg_color);
932 colortable[i] = COLOR_PAIR(i + 1) | A_NORMAL;
936 for (int i = 0; i < 16; ++i) {
938 (angband_color_table[i][1] * 1000) / 255,
939 (angband_color_table[i][2] * 1000) / 255,
940 (angband_color_table[i][3] * 1000) / 255);
951 * Handle a "special request"
953 static errr game_term_xtra_gcu(int n, int v)
955 term_data *td = (term_data *)(game_term->data);
957 /* Analyze the request */
960 case TERM_XTRA_CLEAR:
962 (void)werase(td->win);
966 case TERM_XTRA_NOISE:
967 return write(1, "\007", 1) != 1;
969 /* Make a special sound */
970 case TERM_XTRA_SOUND:
971 return game_term_xtra_gcu_sound(v);
973 /* Flush the Curses buffer */
974 case TERM_XTRA_FRESH:
975 (void)wrefresh(td->win);
978 /* Change the cursor visibility */
979 case TERM_XTRA_SHAPE:
983 /* Suspend/Resume curses */
984 case TERM_XTRA_ALIVE:
985 return game_term_xtra_gcu_alive(v);
988 case TERM_XTRA_EVENT:
989 return game_term_xtra_gcu_event(v);
992 case TERM_XTRA_FLUSH:
993 while (!game_term_xtra_gcu_event(false)) {
999 case TERM_XTRA_DELAY:
1003 /* React to events */
1004 case TERM_XTRA_REACT:
1005 game_term_xtra_gcu_react();
1014 * Actually MOVE the hardware cursor
1016 static errr game_term_curs_gcu(int x, int y)
1018 term_data *td = (term_data *)(game_term->data);
1020 /* Literally move the cursor */
1021 wmove(td->win, y, x);
1028 * Erase a grid of space
1029 * Hack -- try to be "semi-efficient".
1031 static errr game_term_wipe_gcu(int x, int y, int n)
1033 term_data *td = (term_data *)(game_term->data);
1036 wmove(td->win, y, x);
1038 /* Clear to end of line */
1039 if (x + n >= td->t.wid) {
1043 /* Clear some characters */
1046 waddch(td->win, ' ');
1054 #ifdef USE_NCURSES_ACS
1056 * this function draws some ACS characters on the screen
1057 * for DOS-based users: these are the graphical chars (blocks, lines etc)
1059 * unix-gurus: before you start adding other attributes like A_REVERSE
1060 * think hard about how map_info() in cave.c should handle the color
1061 * of something that we here draw in reverse. It's not so simple, alas.
1063 static void game_term_acs_text_gcu(int x, int y, int n, byte a, concptr s)
1065 term_data *td = (term_data *)(game_term->data);
1068 /* position the cursor */
1069 wmove(td->win, y, x);
1073 wattrset(td->win, colortable[a & 0x0F]);
1076 for (i = 0; i < n; i++) {
1077 /* add acs_map of a */
1078 waddch(td->win, acs_map[(int)s[i]]);
1080 wattrset(td->win, WA_NORMAL);
1085 * Place some text on the screen using an attribute
1087 static errr game_term_text_gcu(int x, int y, int n, byte a, concptr s)
1089 term_data *td = (term_data *)(game_term->data);
1091 #ifdef USE_NCURSES_ACS
1092 /* do we have colors + 16 ? */
1093 /* then call special routine for drawing special characters */
1095 game_term_acs_text_gcu(x, y, n, a, s);
1100 /* Move the cursor and dump the string */
1101 wmove(td->win, y, x);
1105 if (can_use_color) {
1106 wattrset(td->win, colortable[a & 0x0F]);
1112 int text_len = euc_to_utf8(s, n, text, sizeof(text));
1118 waddnstr(td->win, _(text, s), _(text_len, n));
1125 * Create a window for the given "term_data" argument.
1127 * Assumes legal arguments.
1129 static errr term_data_init_gcu(term_data *td, int rows, int cols, int y, int x)
1131 term_type *t = &td->t;
1133 /* Make sure the window has a positive size */
1134 if (rows <= 0 || cols <= 0) {
1138 /* Create a window */
1139 td->win = newwin(rows, cols, y, x);
1141 /* Make sure we succeed */
1143 plog("Failed to setup curses window.");
1147 /* Initialize the term */
1148 term_init(t, cols, rows, 256);
1150 /* Avoid the bottom right corner */
1151 t->icky_corner = true;
1153 /* Erase with "white space" */
1154 t->attr_blank = TERM_WHITE;
1155 t->char_blank = ' ';
1157 /* Set some hooks */
1158 t->init_hook = game_term_init_gcu;
1159 t->nuke_hook = game_term_nuke_gcu;
1161 /* Set some more hooks */
1162 t->text_hook = game_term_text_gcu;
1163 t->wipe_hook = game_term_wipe_gcu;
1164 t->curs_hook = game_term_curs_gcu;
1165 t->xtra_hook = game_term_xtra_gcu;
1180 static errr term_data_init(term_data *td)
1182 return term_data_init_gcu(td, td->r.cy, td->r.cx, td->r.y, td->r.x);
1185 /* Parse 27,15,*x30 up to the 'x'. * gets converted to a big number
1186 Parse 32,* until the end. Return count of numbers parsed */
1187 static int _parse_size_list(const char *arg, int sizes[], int max)
1190 const char *start = arg;
1191 const char *stop = arg;
1194 if (!*stop || !isdigit(*stop)) {
1198 if (*start == '*') {
1201 /* rely on atoi("23,34,*") -> 23
1202 otherwise, copy [start, stop) into a new buffer first.*/
1203 sizes[i] = atoi(start);
1206 if (!*stop || *stop != ',') {
1219 static void hook_quit(concptr str)
1229 * Prepare "curses" for use by the file "term.c"
1231 * Installs the "hook" functions defined above, and then activates
1232 * the main screen "term", which clears the screen and such things.
1234 * Someone should really check the semantics of "initscr()"
1236 errr init_gcu(int argc, char *argv[])
1238 int num_term = 4, next_win = 0;
1244 setlocale(LC_ALL, "");
1246 ANGBAND_DIR_XTRA_SOUND = path_build(ANGBAND_DIR_XTRA, "sound");
1247 keymap_norm_prepare();
1248 auto nobigscreen = false;
1249 for (auto i = 1; i < argc; i++) {
1250 if (prefix(argv[i], "-o")) {
1255 if (initscr() == (WINDOW *)ERR) {
1259 quit_aux = hook_quit;
1260 core_aux = hook_quit;
1261 if ((LINES < MAIN_TERM_MIN_ROWS) || (COLS < MAIN_TERM_MIN_COLS)) {
1262 quit_fmt("%s needs an %dx%d 'curses' screen", std::string(VARIANT_NAME).data(), MAIN_TERM_MIN_COLS, MAIN_TERM_MIN_ROWS);
1267 /*** Init the Color-pairs and set up a translation table ***/
1269 /* Do we have color, and enough color, available? */
1270 can_use_color = ((start_color() != ERR) && has_colors() && (COLORS >= 8) && (COLOR_PAIRS >= 8));
1272 #ifdef REDEFINE_COLORS
1273 /* Can we change colors? */
1274 can_fix_color = (can_use_color && can_change_color() && (COLORS >= 16) && (COLOR_PAIRS > 8));
1277 /* Attempt to use customized colors */
1278 if (can_fix_color) {
1279 /* Prepare the color pairs */
1280 for (auto i = 1; i <= 15; i++) {
1281 if (init_pair(i, i, 0) == ERR) {
1282 quit("Color pair init failed");
1285 colortable[i] = COLOR_PAIR(i);
1286 game_term_xtra_gcu_react();
1289 /* Attempt to use colors */
1290 else if (can_use_color) {
1291 /* Color-pair 0 is *always* WHITE on BLACK */
1293 /* Prepare the color pairs */
1294 init_pair(1, COLOR_RED, COLOR_BLACK);
1295 init_pair(2, COLOR_GREEN, COLOR_BLACK);
1296 init_pair(3, COLOR_YELLOW, COLOR_BLACK);
1297 init_pair(4, COLOR_BLUE, COLOR_BLACK);
1298 init_pair(5, COLOR_MAGENTA, COLOR_BLACK);
1299 init_pair(6, COLOR_CYAN, COLOR_BLACK);
1300 init_pair(7, COLOR_BLACK, COLOR_BLACK);
1302 /* Prepare the "Angband Colors" -- Bright white is too bright */
1303 /* Changed in Drangband. Cyan as grey sucks -- -TM- */
1304 colortable[0] = (COLOR_PAIR(7) | A_NORMAL); /* Black */
1305 colortable[1] = (COLOR_PAIR(0) | A_BRIGHT); /* White */
1306 colortable[2] = (COLOR_PAIR(0) | A_NORMAL); /* Grey XXX */
1307 colortable[3] = (COLOR_PAIR(1) | A_BRIGHT); /* Orange XXX */
1308 colortable[4] = (COLOR_PAIR(1) | A_NORMAL); /* Red */
1309 colortable[5] = (COLOR_PAIR(2) | A_NORMAL); /* Green */
1310 colortable[6] = (COLOR_PAIR(4) | A_BRIGHT); /* Blue */
1311 colortable[7] = (COLOR_PAIR(3) | A_NORMAL); /* Umber */
1312 colortable[8] = (COLOR_PAIR(7) | A_BRIGHT); /* Dark-grey XXX */
1313 colortable[9] = (COLOR_PAIR(0) | A_NORMAL); /* Light-grey XXX */
1314 colortable[10] = (COLOR_PAIR(5) | A_BRIGHT); /* Purple */
1315 colortable[11] = (COLOR_PAIR(3) | A_BRIGHT); /* Yellow */
1316 colortable[12] = (COLOR_PAIR(5) | A_NORMAL); /* Light Red XXX */
1317 colortable[13] = (COLOR_PAIR(2) | A_BRIGHT); /* Light Green */
1318 colortable[14] = (COLOR_PAIR(6) | A_BRIGHT); /* Light Blue */
1319 colortable[15] = (COLOR_PAIR(3) | A_NORMAL); /* Light Umber XXX */
1324 /* Handle "arg_sound" */
1325 if (use_sound != arg_sound) {
1326 /* Initialize (if needed) */
1327 if (arg_sound && !init_sound()) {
1329 plog("Cannot initialize sound!");
1335 /* Change setting */
1336 use_sound = arg_sound;
1341 /* if USE_NCURSES_ACS is defined, we can do something with graphics in curses! */
1342 #ifdef USE_NCURSES_ACS
1343 use_graphics = true;
1347 /*** Low level preparation ***/
1351 /* Paranoia -- Assume no waiting */
1352 nodelay(stdscr, false);
1362 /* Extract the game keymap */
1363 keymap_game_prepare();
1365 /*** Now prepare the term(s) ***/
1367 /* Create several terms */
1368 for (auto i = 0; i < num_term; i++) {
1369 int rows, cols, y, x;
1371 /* Decide on size and position */
1375 rows = TERM_DEFAULT_ROWS;
1376 cols = TERM_DEFAULT_COLS;
1383 rows = LINES - TERM_DEFAULT_ROWS - 1;
1384 cols = TERM_DEFAULT_COLS;
1385 y = TERM_DEFAULT_ROWS + 1;
1392 rows = TERM_DEFAULT_ROWS;
1393 cols = COLS - TERM_DEFAULT_COLS - 1;
1395 x = TERM_DEFAULT_COLS + 1;
1401 rows = LINES - TERM_DEFAULT_ROWS - 1;
1402 cols = COLS - TERM_DEFAULT_COLS - 1;
1403 y = TERM_DEFAULT_ROWS + 1;
1404 x = TERM_DEFAULT_COLS + 1;
1410 rows = cols = y = x = 0;
1415 /* Skip non-existant windows */
1416 if (rows <= 0 || cols <= 0) {
1421 term_data_init_gcu(&data[next_win], rows, cols, y, x);
1423 /* Remember the term */
1424 angband_terms[next_win] = &data[next_win].t;
1426 /* One more window */
1430 /* Parse Args and Prepare the Terminals. Rectangles are specified
1431 as Width x Height, right? The game will allow you to have two
1432 strips of extra terminals, one on the right and one on the bottom.
1433 The map terminal will than fit in as big as possible in the remaining
1436 angband -mgcu -- -right 30x27,* -bottom *x7 will layout as
1437 Term-0: Map (COLS-30)x(LINES-7) | Term-1: 30x27
1438 --------------------------------|----------------------
1439 <----Term-3: (COLS-30)x7------->| Term-2: 30x(LINES-27)
1440 composband -mgcu -- -bottom *x7 -right 30x27,* will layout as
1441 Term-0: Map (COLS-30)x(LINES-7) | Term-2: 30x27
1442 |------------------------------
1443 | Term-3: 30x(LINES-27)
1444 ---------------------------------------------------------------
1445 <----------Term-1: (COLS)x7----------------------------------->
1446 Notice the effect on the bottom terminal by specifying its argument
1447 second or first. Notice the sequence numbers for the various terminals
1448 as you will have to blindly configure them in the window setup screen.
1449 EDIT: Added support for -left and -top.
1452 rect_t remaining = rect(0, 0, COLS, LINES);
1458 for (auto i = 1; i < argc; i++) {
1459 if (streq(argv[i], "-spacer")) {
1462 quit("Missing size specifier for -spacer");
1464 sscanf(argv[i], "%dx%d", &spacer_cx, &spacer_cy);
1465 } else if (streq(argv[i], "-right") || streq(argv[i], "-left")) {
1466 const char *arg, *tmp;
1467 bool left = streq(argv[i], "-left");
1468 int cx, cys[MAX_TERM_DATA] = { 0 }, ct, j, x, y;
1472 quit_fmt("Missing size specifier for -%s", left ? "left" : "right");
1476 tmp = strchr(arg, 'x');
1478 quit_fmt("Expected something like -%s 60x27,* for two %s hand terminals of 60 columns, the first 27 lines and the second whatever is left.", left ? "left" : "right", left ? "left" : "right");
1487 x = remaining.x + remaining.cx;
1490 remaining.cx -= spacer_cx;
1492 remaining.x += spacer_cx;
1496 ct = _parse_size_list(tmp, cys, MAX_TERM_DATA);
1497 for (j = 0; j < ct; j++) {
1499 if (y + cy > remaining.y + remaining.cy) {
1500 cy = remaining.y + remaining.cy - y;
1502 if (next_term >= MAX_TERM_DATA) {
1503 quit_fmt("Too many terminals. Only %d are allowed.", MAX_TERM_DATA);
1506 quit_fmt("Out of bounds in -%s: %d is too large (%d rows max for this strip)",
1507 left ? "left" : "right", cys[j], remaining.cy);
1509 data[next_term++].r = rect(x, y, cx, cy);
1510 y += cy + spacer_cy;
1513 } else if (streq(argv[i], "-top") || streq(argv[i], "-bottom")) {
1514 const char *arg, *tmp;
1515 bool top = streq(argv[i], "-top");
1516 int cy, cxs[MAX_TERM_DATA] = { 0 }, ct, j, x, y;
1520 quit_fmt("Missing size specifier for -%s", top ? "top" : "bottom");
1524 tmp = strchr(arg, 'x');
1526 quit_fmt("Expected something like -%s *x7 for a single %s terminal of 7 lines using as many columns as are available.", top ? "top" : "bottom", top ? "top" : "bottom");
1530 ct = _parse_size_list(arg, cxs, MAX_TERM_DATA);
1539 y = remaining.y + remaining.cy;
1541 remaining.cy -= spacer_cy;
1543 remaining.y += spacer_cy;
1547 for (j = 0; j < ct; j++) {
1549 if (x + cx > remaining.x + remaining.cx) {
1550 cx = remaining.x + remaining.cx - x;
1552 if (next_term >= MAX_TERM_DATA) {
1553 quit_fmt("Too many terminals. Only %d are allowed.", MAX_TERM_DATA);
1556 quit_fmt("Out of bounds in -%s: %d is too large (%d cols max for this strip)",
1557 top ? "top" : "bottom", cxs[j], remaining.cx);
1559 data[next_term++].r = rect(x, y, cx, cy);
1560 x += cx + spacer_cx;
1567 if (remaining.cx < MAIN_TERM_MIN_COLS || remaining.cy < MAIN_TERM_MIN_ROWS) {
1568 quit_fmt("Failed: %s needs an %dx%d map screen, not %dx%d", std::string(VARIANT_NAME).data(), MAIN_TERM_MIN_COLS, MAIN_TERM_MIN_ROWS, remaining.cx, remaining.cy);
1570 data[0].r = remaining;
1571 term_data_init(&data[0]);
1572 angband_terms[0] = game_term;
1574 /* Child Terminals */
1575 for (next_term = 1; next_term < term_ct; next_term++) {
1576 term_data_init(&data[next_term]);
1577 angband_terms[next_term] = game_term;
1581 /* Activate the "Angband" window screen */
1582 term_activate(&data[0].t);
1585 term_screen = &data[0].t;
1591 #endif /* USE_GCU */