diff --git a/include/ntconf.h b/include/ntconf.h index faeb6aad8..8323af597 100644 --- a/include/ntconf.h +++ b/include/ntconf.h @@ -26,6 +26,7 @@ #define USER_SOUNDS #ifdef WIN32CON +#define CHANGE_COLOR /* allow palette changes in win32 console */ #define SELECTSAVED /* Provide menu of saved games to choose from at start */ #endif @@ -231,6 +232,9 @@ extern int FDECL(set_win32_option, (const char *, const char *)); #define RIGHTBUTTON RIGHTMOST_BUTTON_PRESSED #define MIDBUTTON FROM_LEFT_2ND_BUTTON_PRESSED #define MOUSEMASK (LEFTBUTTON | RIGHTBUTTON | MIDBUTTON) +#ifdef CHANGE_COLOR +extern int FDECL(alternative_palette, (char *)); +#endif #endif /* WIN32CON */ #endif /* NTCONF_H */ diff --git a/src/options.c b/src/options.c index cddb16faf..21ac55689 100644 --- a/src/options.c +++ b/src/options.c @@ -311,8 +311,14 @@ static struct Comp_Opt { "packorder", "the inventory order of the items in your pack", MAXOCLASSES, SET_IN_GAME }, #ifdef CHANGE_COLOR - { "palette", "palette (00c/880/-fff is blue/yellow/reverse white)", + { "palette", +# ifndef WIN32CON + "palette (00c/880/-fff is blue/yellow/reverse white)", 15 , SET_IN_GAME }, +# else + "palette (adjust an RGB color in palette (color-R-G-B)", + 15 , SET_IN_FILE }, +# endif # if defined(MAC) { "hicolor", "same as palette, only order is reversed", 15, SET_IN_FILE }, @@ -1445,7 +1451,9 @@ boolean tinitial, tfrom_file; ) { int color_number, color_incr; +#ifndef WIN32CON if (duplicate) complain_about_duplicate(opts,1); +#endif # ifdef MAC if (match_optname(opts, "hicolor", 3, TRUE)) { if (negated) { @@ -1466,6 +1474,10 @@ boolean tinitial, tfrom_file; } # endif if ((op = string_for_opt(opts, FALSE)) != (char *)0) { +# ifdef WIN32CON + if (!alternative_palette(op)) + badoption(opts); +# else char *pt = op; int cnt, tmp, reverse; long rgb; @@ -1481,21 +1493,21 @@ boolean tinitial, tfrom_file; } while (cnt-- > 0) { if (*pt && *pt != '/') { -# ifdef AMIGA +# ifdef AMIGA rgb <<= 4; -# else +# else rgb <<= 8; -# endif +# endif tmp = *(pt++); if (isalpha(tmp)) { tmp = (tmp + 9) & 0xf; /* Assumes ASCII... */ } else { tmp &= 0xf; /* Digits in ASCII too... */ } -# ifndef AMIGA +# ifndef AMIGA /* Add an extra so we fill f -> ff and 0 -> 00 */ rgb += tmp << 4; -# endif +# endif rgb += tmp; } } @@ -1505,6 +1517,7 @@ boolean tinitial, tfrom_file; change_color(color_number, rgb, reverse); color_number += color_incr; } +# endif /* !WIN32CON */ } if (!initial) { need_redraw = TRUE; diff --git a/sys/winnt/defaults.nh b/sys/winnt/defaults.nh index b0cfe015c..be1d53ef7 100644 --- a/sys/winnt/defaults.nh +++ b/sys/winnt/defaults.nh @@ -125,6 +125,27 @@ OPTIONS=hilite_pet,!toptenwin #OPTIONS=subkeyvalue:184/91 #OPTIONS=subkeyvalue:188/124 +# +# Some versions of Windows allow you to adjust the win32 console port +# colors using R-G-B settings. +# +#OPTIONS=palette:black-0-0-0 +#OPTIONS=palette:red-210-0-0 +#OPTIONS=palette:green-80-200-0 +#OPTIONS=palette:brown-180-100-0 +#OPTIONS=palette:blue-0-0-200 +#OPTIONS=palette:magenta-128-0-128 +#OPTIONS=palette:cyan-50-180-180 +#OPTIONS=palette:gray-192-192-192 +#OPTIONS=palette:dark gray-100-100-100 +#OPTIONS=palette:orange-255-128-0 +#OPTIONS=palette:bright green-0-255-0 +#OPTIONS=palette:yellow-255-255-0 +#OPTIONS=palette:bright blue-100-100-240 +#OPTIONS=palette:bright magenta-255-0-255 +#OPTIONS=palette:bright cyan-0-255-255 +#OPTIONS=palette:white-255-255-255 + # # *** CHARACTER GRAPHICS *** # diff --git a/sys/winnt/nttty.c b/sys/winnt/nttty.c index fc4be4451..9f7885d1f 100644 --- a/sys/winnt/nttty.c +++ b/sys/winnt/nttty.c @@ -37,6 +37,7 @@ int FDECL(process_keystroke, (INPUT_RECORD *, boolean *, * ReadConsoleInput * WriteConsoleOutputCharacter * FillConsoleOutputAttribute + * GetConsoleOutputCP */ /* Win32 Console handles for input and output */ @@ -74,6 +75,26 @@ typedef int (__stdcall * PROCESS_KEYSTROKE)( int ); +#ifdef CHANGE_COLOR +static void NDECL(adjust_palette); +static int FDECL(match_color_name, (const char *)); +static boolean altered_palette; +static COLORREF UserDefinedColors[CLR_MAX]; +static COLORREF NetHackColors[CLR_MAX] = { + 0x00000000,0x00c80000,0x0000c850,0x00b4b432, + 0x000000d2,0x00800080,0x000064b4,0x00c0c0c0, + 0x00646464,0x00f06464,0x0000ff00,0x00ffff00, + 0x000000ff,0x00ff00ff,0x0000ffff,0x00ffffff + }; +static COLORREF DefaultColors[CLR_MAX] = { + 0x00000000, 0x00800000, 0x00008000, 0x00808000, + 0x00000080, 0x00800080, 0x00008080, 0x00c0c0c0, + 0x00808080, 0x00ff0000, 0x0000ff00, 0x00ffff00, + 0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff + }; + +#endif + typedef int (__stdcall * NHKBHIT)( HANDLE, INPUT_RECORD * @@ -166,6 +187,9 @@ const char *s; void setftty() { +#ifdef CHANGE_COLOR + if (altered_palette) adjust_palette(); +#endif start_screen(); } @@ -973,4 +997,346 @@ synch_cursor() { really_move_cursor(); } + +# ifdef CHANGE_COLOR +void tty_change_color(color_number, rgb, reverse) +int color_number, reverse; +long rgb; +{ + /* Map NetHack color index to NT Console palette index */ + int idx, win32_color_number[] = { + 0, /* CLR_BLACK 0 */ + 4, /* CLR_RED 1 */ + 2, /* CLR_GREEN 2 */ + 6, /* CLR_BROWN 3 */ + 1, /* CLR_BLUE 4 */ + 5, /* CLR_MAGENTA 5 */ + 3, /* CLR_CYAN 6 */ + 7, /* CLR_GRAY 7 */ + 8, /* NO_COLOR 8 */ + 12, /* CLR_ORANGE 9 */ + 10, /* CLR_BRIGHT_GREEN 10 */ + 14, /* CLR_YELLOW 11 */ + 9, /* CLR_BRIGHT_BLUE 12 */ + 13, /* CLR_BRIGHT_MAGENTA 13 */ + 11, /* CLR_BRIGHT_CYAN 14 */ + 15 /* CLR_WHITE 15 */ + }; + int k; + if (color_number < 0) { /* indicates OPTIONS=palette with no value */ + /* copy the NetHack palette into UserDefinedColors */ + for (k=0; k < CLR_MAX; k++) + UserDefinedColors[k] = NetHackColors[k]; + return; + } else if (color_number >= 0 && color_number < CLR_MAX) { + idx = win32_color_number[color_number]; + UserDefinedColors[idx] = rgb; + } + altered_palette = TRUE; +} + +char *tty_get_color_string() +{ + return ""; +} + +int +match_color_name(c) +const char *c; +{ + const struct others { + int idx; + const char *colorname; + } othernames[] = { + {CLR_MAGENTA, "purple"}, + {CLR_BRIGHT_MAGENTA, "bright purple"}, + {NO_COLOR, "dark gray"}, + {NO_COLOR, "dark grey"}, + {CLR_GRAY, "grey"}, + }; + + int cnt; + for (cnt = 0; cnt < CLR_MAX; ++cnt) { + if (!strcmpi(c, c_obj_colors[cnt])) + return cnt; + } + for (cnt = 0; cnt < SIZE(othernames); ++cnt) { + if (!strcmpi(c, othernames[cnt].colorname)) + return othernames[cnt].idx; + } + return -1; +} + +/* + * Returns 0 if badoption syntax + */ +int +alternative_palette(op) +char *op; +{ + /* + * palette:color-R-G-B + * OPTIONS=palette:green-4-3-1, palette:0-0-0-0 + */ + int fieldcnt, color_number, rgb, red, green, blue; + char *fields[4], *cp; + + if (!op) { + change_color(-1,0,0); /* indicates palette option with + no value meaning "load an entire + hard-coded NetHack palette." */ + return 1; + } + + cp = fields[0] = op; + for (fieldcnt = 1; fieldcnt < 4; ++fieldcnt) { + cp = index(cp, '-'); + if (!cp) return 0; + fields[fieldcnt] = cp; + cp++; + } + for (fieldcnt = 1; fieldcnt < 4; ++fieldcnt) { + *(fields[fieldcnt]) = '\0'; + ++fields[fieldcnt]; + } + rgb = 0; + for (fieldcnt = 0; fieldcnt < 4; ++fieldcnt) { + if (fieldcnt == 0 && isalpha(*(fields[0]))) { + color_number = match_color_name(fields[0]); + if (color_number == -1) return 0; + } else { + int dcount = 0, cval = 0; + cp = fields[fieldcnt]; + if (*cp == '\\' && index("0123456789xXoO", cp[1])) { + const char *dp, *hex = "00112233445566778899aAbBcCdDeEfF"; + + cp++; + if (*cp == 'x' || *cp == 'X') + for (++cp; (dp = index(hex, *cp)) && (dcount++ < 2); cp++) + cval = (int)((cval * 16) + (dp - hex) / 2); + else if (*cp == 'o' || *cp == 'O') + for (++cp; (index("01234567",*cp)) && (dcount++ < 3); cp++) + cval = (cval * 8) + (*cp - '0'); + else + return 0; + } else { + for (; *cp && (index("0123456789",*cp)) && (dcount++ < 3); cp++) + cval = (cval * 10) + (*cp - '0'); + } + switch(fieldcnt) { + case 0: + color_number = cval; + break; + case 1: + red = cval; + break; + case 2: + green = cval; + break; + case 3: + blue = cval; + break; + } + } + } + rgb = RGB(red,green,blue); + if (color_number >= 0 && color_number < CLR_MAX) + change_color(color_number, rgb, 0); + return 1; +} + +/* + * This uses an undocumented method to set console attributes + * at runtime including console palette + * + * VOID WINAPI SetConsolePalette(COLORREF palette[16]) + * + * Author: James Brown at www.catch22.net + * + * Set palette of current console. + * Palette should be of the form: + * + * COLORREF DefaultColors[CLR_MAX] = + * { + * 0x00000000, 0x00800000, 0x00008000, 0x00808000, + * 0x00000080, 0x00800080, 0x00008080, 0x00c0c0c0, + * 0x00808080, 0x00ff0000, 0x0000ff00, 0x00ffff00, + * 0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff + * }; + */ + +#pragma pack(push, 1) + +/* + * Structure to send console via WM_SETCONSOLEINFO + */ +typedef struct _CONSOLE_INFO +{ + ULONG Length; + COORD ScreenBufferSize; + COORD WindowSize; + ULONG WindowPosX; + ULONG WindowPosY; + + COORD FontSize; + ULONG FontFamily; + ULONG FontWeight; + WCHAR FaceName[32]; + + ULONG CursorSize; + ULONG FullScreen; + ULONG QuickEdit; + ULONG AutoPosition; + ULONG InsertMode; + + USHORT ScreenColors; + USHORT PopupColors; + ULONG HistoryNoDup; + ULONG HistoryBufferSize; + ULONG NumberOfHistoryBuffers; + + COLORREF ColorTable[16]; + + ULONG CodePage; + HWND Hwnd; + + WCHAR ConsoleTitle[0x100]; +} CONSOLE_INFO; + +#pragma pack(pop) + +BOOL SetConsoleInfo(HWND hwndConsole, CONSOLE_INFO *pci); +static void GetConsoleSizeInfo(CONSOLE_INFO *pci); +VOID WINAPI SetConsolePalette(COLORREF crPalette[16]); + +void +adjust_palette(VOID_ARGS) +{ + SetConsolePalette(UserDefinedColors); + altered_palette = 0; +} + +/* +/* only in Win2k+ (use FindWindow for NT4) */ +HWND WINAPI GetConsoleWindow(); + +/* Undocumented console message */ +#define WM_SETCONSOLEINFO (WM_USER+201) + + +VOID WINAPI SetConsolePalette(COLORREF palette[16]) +{ + CONSOLE_INFO ci = { sizeof(ci) }; + int i; + HWND hwndConsole = GetConsoleWindow(); + + /* get current size/position settings rather than using defaults.. */ + GetConsoleSizeInfo(&ci); + + /* set these to zero to keep current settings */ + ci.FontSize.X = 0; /* def = 8 */ + ci.FontSize.Y = 0; /* def = 12 */ + ci.FontFamily = 0; /* def = 0x30 = FF_MODERN|FIXED_PITCH */ + ci.FontWeight = 0; /* 0x400; */ + /* lstrcpyW(ci.FaceName, L"Terminal"); */ + ci.FaceName[0] = L'\0'; + + ci.CursorSize = 25; + ci.FullScreen = FALSE; + ci.QuickEdit = TRUE; + ci.AutoPosition = 0x10000; + ci.InsertMode = TRUE; + ci.ScreenColors = MAKEWORD(0x7, 0x0); + ci.PopupColors = MAKEWORD(0x5, 0xf); + + ci.HistoryNoDup = FALSE; + ci.HistoryBufferSize = 50; + ci.NumberOfHistoryBuffers = 4; + + // colour table + for(i = 0; i < 16; i++) + ci.ColorTable[i] = palette[i]; + + ci.CodePage = GetConsoleOutputCP(); + ci.Hwnd = hwndConsole; + + lstrcpyW(ci.ConsoleTitle, L""); + + SetConsoleInfo(hwndConsole, &ci); +} + +/* + * Wrapper around WM_SETCONSOLEINFO. We need to create the + * necessary section (file-mapping) object in the context of the + * process which owns the console, before posting the message + */ +BOOL SetConsoleInfo(HWND hwndConsole, CONSOLE_INFO *pci) +{ + DWORD dwConsoleOwnerPid; + HANDLE hProcess; + HANDLE hSection, hDupSection; + PVOID ptrView = 0; + HANDLE hThread; + + /* + * Open the process which "owns" the console + */ + GetWindowThreadProcessId(hwndConsole, &dwConsoleOwnerPid); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwConsoleOwnerPid); + + /* + * Create a SECTION object backed by page-file, then map a view of + * this section into the owner process so we can write the contents + * of the CONSOLE_INFO buffer into it + */ + hSection = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, pci->Length, 0); + + /* + * Copy our console structure into the section-object + */ + ptrView = MapViewOfFile(hSection, FILE_MAP_WRITE|FILE_MAP_READ, 0, 0, pci->Length); + memcpy(ptrView, pci, pci->Length); + UnmapViewOfFile(ptrView); + + /* + * Map the memory into owner process + */ + DuplicateHandle(GetCurrentProcess(), hSection, hProcess, &hDupSection, + 0, FALSE, DUPLICATE_SAME_ACCESS); + + /* Send console window the "update" message */ + SendMessage(hwndConsole, WM_SETCONSOLEINFO, (WPARAM)hDupSection, 0); + + /* + * clean up + */ + hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)CloseHandle, + hDupSection, 0, 0); + + CloseHandle(hThread); + CloseHandle(hSection); + CloseHandle(hProcess); + + return TRUE; +} + +/* + * Fill the CONSOLE_INFO structure with information + * about the current console window + */ +static void GetConsoleSizeInfo(CONSOLE_INFO *pci) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE); + + GetConsoleScreenBufferInfo(hConsoleOut, &csbi); + + pci->ScreenBufferSize = csbi.dwSize; + pci->WindowSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1; + pci->WindowSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + pci->WindowPosX = csbi.srWindow.Left; + pci->WindowPosY = csbi.srWindow.Top; +} +# endif /*CHANGE_COLOR*/ #endif /* WIN32CON */