'OHRRPGCE - Curses/console graphics backend '(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and the OHRRPGCE Developers 'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability. ' ' gfx_console - Not a real graphics backend; for testing from the console ' Uses curses (ncurses on Unix, pdcurses on Windows). #include "config.bi" #include "surface.bi" #include "gfx.bi" #include "common.bi" #include "allmodex.bi" #include "curses.bi" #include once "crt.bi" #include once "crt/unistd.bi" #undef raw extern "C" declare function putenv (byval as zstring ptr) as integer 'WINDOW was renamed in FB 1.01 #ifndef WINDOW_ #define WINDOW_ WINDOW #endif 'Wrapper functions in curses_wrap.c (really, this backend should be written in C) declare function get_stdscr() as WINDOW_ ptr #ifndef set_ESCDELAY declare sub set_ESCDELAY(byval val as integer) #endif ' #undef stdscr ' #define stdscr get_stdscr() end extern #ifdef ERR_ #define CURSES_ERR ERR_ #else 'Bit of a blooper in older versions of curses.bi #ifdef __FB_WIN32__ #define CURSES_ERR PDC_ERR #else #define CURSES_ERR NCURSES_ERR #endif #endif type KeyMapPair curses_key as integer ohr_key as integer end type dim shared keymappairs(...) as KeyMapPair => { _ (ASC("A"), scA), _ (ASC("B"), scB), _ (ASC("C"), scC), _ (ASC("D"), scD), _ (ASC("E"), scE), _ (ASC("F"), scF), _ (ASC("G"), scG), _ (ASC("H"), scH), _ (ASC("I"), scI), _ (ASC("J"), scJ), _ (ASC("K"), scK), _ (ASC("L"), scL), _ (ASC("M"), scM), _ (ASC("N"), scN), _ (ASC("O"), scO), _ (ASC("P"), scP), _ (ASC("Q"), scQ), _ (ASC("R"), scR), _ (ASC("S"), scS), _ (ASC("T"), scT), _ (ASC("U"), scU), _ (ASC("V"), scV), _ (ASC("W"), scW), _ (ASC("X"), scX), _ (ASC("Y"), scY), _ (ASC("Z"), scZ), _ (ASC("0"), sc0), _ (ASC("1"), sc1), _ (ASC("2"), sc2), _ (ASC("3"), sc3), _ (ASC("4"), sc4), _ (ASC("5"), sc5), _ (ASC("6"), sc6), _ (ASC("7"), sc7), _ (ASC("8"), sc8), _ (ASC("9"), sc9), _ (ASC(" "), scSpace), _ (ASC(","), scComma), _ (ASC("."), scPeriod), _ (ASC("["), scLeftBracket), _ (ASC("]"), scRightBracket), _ (ASC("-"), scMinus), _ (ASC("+"), scPlus), _ (KEY_LEFT, scLeft), _ (KEY_RIGHT, scRight), _ (KEY_UP, scUp), _ (KEY_DOWN, scDown), _ (KEY_ENTER, scEnter), _ (10, scEnter), _ (KEY_BACKSPACE, scBackspace), _ (127, scBackspace), _ (KEY_DC, scDelete), _ (27, scEsc), _ (KEY_HOME, scHome), _ (KEY_END, scEnd), _ (KEY_NPAGE, scPageDown), _ (KEY_PPAGE, scPageUp) _ } dim shared keymap() as integer extern "C" dim shared curses_mode as bool = YES dim shared force_256_color as bool = YES dim shared window_state as WindowState dim shared init_gfx as integer = 0 dim shared as integer mousex = 0, mousey = 0 dim shared inputtext as string dim shared erasedscr as integer sub init_keymap() redim keymap(KEY_MAX) for i as integer = 0 to ubound(keymappairs) with keymappairs(i) keymap(.curses_key) = .ohr_key end with next for i as integer = 1 to 12 keymap(KEY_F0 + i) = scF1 - 1 + i next end sub function gfx_console_init(byval terminate_signal_handler as sub cdecl (), byval windowicon as zstring ptr, byval info_buffer as zstring ptr, byval info_buffer_size as integer) as integer dim retstr as string dim ret as integer = 1 window_state.structsize = WINDOWSTATE_SZ window_state.focused = YES window_state.minimised = NO window_state.mouse_over = NO window_state.zoom = 2 window_state.windowsize = XY(640, 400) 'We don't claim to have a resizable window init_keymap() if curses_mode then retstr = *curses_version() dim term as string = *getenv("TERM") retstr += " TERM=" & term if force_256_color and term = "xterm" then 'Enable 256 colour mode. 'Make this assumption, since 256-colour capable 'terminals seem to be misrepresented as 'xterm' very often putenv("TERM=xterm-256color") retstr += " (override to xterm-256color)" end if if init_gfx = 0 then if initscr() = NULL then retstr &= " ... initscr failed" ret = 0 else start_color() 'might fail cbreak() noecho() nonl() keypad(stdscr, 1) set_ESCDELAY(40) nodelay(stdscr, 1) scrollok(stdscr, 0) 'notimeout(stdscr, 1) intrflush(stdscr, 1) retstr &= " has_colors()=" & has_colors() & " can_change_color()=" & can_change_color() & " COLORS=" & COLORS & " COLOR_PAIRS=" & COLOR_PAIRS end if end if else retstr = "non-visual mode" end if *info_buffer = MID(retstr, 1, info_buffer_size) return ret end function sub gfx_console_close if curses_mode then 'This doesn't reset the colours, unfortunately endwin() 'Reset console 'fwrite(@!"\&o033[0m", 1, 4, stdout) end if end sub function gfx_console_getversion() as integer return 1 end function dim shared master_color_to_attr(255) as integer sub gfx_console_setup_colors(byval pal as RGBcolor ptr) 'Changes colours to the palette colours, if possible, 'then computes mapping from master palette colours to terminal 'colours. dim mult as double = 1. if can_change_color() then for i as integer = 1 to small(COLORS - 1, 255) with pal[i] init_color(i, 1000 * .r / 255, 1000 * .g / 255, 1000 * .b / 255) end with next elseif COLORS <= 8 then ' We assume that drawing bold text also brightens also the colours; compensate for that. ' (Otherwise there would likely be 16 colours). mult = 1.5 end if ' The number of color pairs that we will use dim num_pairs as integer = small(small(COLOR_PAIRS, COLORS), 256) ' Color pair 0 can't be changed for i as integer = 1 to num_pairs - 1 init_pair(i, i, COLOR_BLACK) next dim console_pal(255) as RGBcolor for i as integer = 0 to num_pairs - 1 dim as short r, g, b color_content(i, @r, @g, @b) 'debug i & " " & " rgb " & r & " " & g & " " & b console_pal(i).r = small(255, mult * cint(r) * 255 \ 1000) console_pal(i).g = small(255, mult * cint(g) * 255 \ 1000) console_pal(i).b = small(255, mult * cint(b) * 255 \ 1000) next ' for i as integer = 0 to num_pairs - 1 ' console_pal(COLORS + i).r = small(255, 128 + console_pal(i).r) ' console_pal(COLORS + i).g = small(255, 128 + console_pal(i).g) ' console_pal(COLORS + i).b = small(255, 128 + console_pal(i).b) ' next master_color_to_attr(0) = 0 'Default text colour rather than black. Don't want it to be invisible for i as integer = 1 to 255 'Bold text, to look more like the default font! dim col as integer = nearcolor(console_pal(), pal[i].r, pal[i].g, pal[i].b, 1) 'debug i & " -> " & col master_color_to_attr(i) = COLOR_PAIR(col) OR A_BOLD next end sub sub gfx_console_showpage(byval raw as ubyte ptr, byval w as integer, byval h as integer) if curses_mode = NO then exit sub move(0, 0) refresh() 'getch causes a refresh call, which would erase the screen contents... why?! 'Work around it by not erasing until later 'werase(stdscr) erasedscr = NO end sub sub gfx_console_setpal(byval pal as RGBcolor ptr) 'print "setpal" if curses_mode then gfx_console_setup_colors(pal) end sub sub gfx_console_printchar (byval ch as integer, byval x as integer, byval y as integer, byval col as integer) if curses_mode = NO then exit sub 'Workaround some stupid ncurses behaviour. See showpage if erasedscr = NO then werase(stdscr) erasedscr = YES end if if ch >= 32 and ch < 127 or ch >= 161 then ' if col mod 8 = 0 then ' col = 7 ' else ' col = col mod 8 ' end if 'col = 1 attron(master_color_to_attr(col)) 'mvaddch is broken in recent FB... move(y\8, x\8) addch(ch) 'attroff(COLOR_PAIR(col)) attrset(0) end if 'debug x & "," & y & " " & chr(ch) end sub function gfx_console_present(byval surfaceIn as Surface ptr, byval pal as RGBPalette ptr) as integer gfx_console_showpage(NULL, 0, 0) return 0 end function function gfx_console_screenshot(byval fname as zstring ptr) as integer return 0 end function sub gfx_console_setwindowed(byval iswindow as integer) end sub sub gfx_console_windowtitle(byval title as zstring ptr) 'print "window title: " & *title end sub function gfx_console_getwindowstate() as WindowState ptr return @window_state end function function gfx_console_setoption(byval opt as zstring ptr, byval arg as zstring ptr) as integer dim as integer value = str2int(*arg, -1) dim as integer ret = 0 if init_gfx = 0 then if *opt = "debuglog" or *opt = "d" then curses_mode = NO debug_to_console = YES ret = 1 elseif *opt = "nogfx" then curses_mode = NO ret = 1 elseif *opt = "dontforce256" then force_256_color = NO ret = 1 end if else debug "gfx_console_setoption: backend already started" end if return ret end function function gfx_console_describe_options() as zstring ptr return @!"-nogfx Disable curses, display nothing. Combine with --print or -d\n" _ !"-d -debuglog Print ?_debug.txt log, implies --nogfx\n" _ "-dontforce256 Don't assume that TERM=xterm means TERM=xterm-256color" end function '------------- IO Functions -------------- sub io_console_init end sub sub io_console_updatekeys(byval keybd as integer ptr) 'uses keybits instead end sub sub io_console_keybits(byval keybd as integer ptr) for i as integer = 0 to 127 keybd[i] = 0 next 'This only supports part of the keyboard, and some 'terminals, like linux vttys support even less (no page up/down) dim key as integer dim kmkey as integer while 1 if curses_mode then key = getch() if key = CURSES_ERR then exit while else 'This is quite crappy because it doesn't support escape codes for keys like Left, ESC, 'so they insert garbage. They should work if you instead use gfx_fb with --nogfx, 'so won't also use multikey here and create a dependency on fbgfx. 'Also, even without calling ncurses, keypresses still aren't echoed, 'apparently the rtlib sets it up that way. if file_ready_to_read(STDIN_FILENO) = NO then exit while 'Don't block key = getchar() if key = EOF_ then 'Shouldn't happen debug "getchar EOF!" exit while end if end if kmkey = key dim tmp as string = "" if key < 256 and key <> 10 and key <> 127 then inputtext &= chr(key) kmkey = toupper(key) end if 'print "key " & key & " -> " & keymap(kmkey) kmkey = keymap(kmkey) if kmkey then keybd[kmkey] = 3 end if wend end sub sub io_console_textinput (byval buf as wstring ptr, byval bufsize as integer) dim buflen as integer = bufsize \ 2 - 1 *buf = LEFT(inputtext, buflen) inputtext = MID(inputtext, buflen) end sub SUB io_console_show_virtual_keyboard() 'Does nothing on platforms that have real keyboards END SUB SUB io_console_hide_virtual_keyboard() 'Does nothing on platforms that have real keyboards END SUB sub io_console_setmousevisibility(byval visible as integer) end sub sub io_console_getmouse(byref mx as integer, byref my as integer, byref mwheel as integer, byref mbuttons as integer) mx = mousex my = mousey mwheel = 0 mbuttons = 0 end sub sub io_console_setmouse(byval x as integer, byval y as integer) mousex = x mousey = y end sub sub io_console_mouserect(byval xmin as integer, byval xmax as integer, byval ymin as integer, byval ymax as integer) mousex = xmin mousey = ymin end sub function gfx_console_setprocptrs() as integer gfx_init = @gfx_console_init gfx_close = @gfx_console_close gfx_getversion = @gfx_console_getversion gfx_setpal = @gfx_console_setpal gfx_screenshot = @gfx_console_screenshot gfx_setwindowed = @gfx_console_setwindowed gfx_windowtitle = @gfx_console_windowtitle gfx_getwindowstate = @gfx_console_getwindowstate gfx_setoption = @gfx_console_setoption gfx_describe_options = @gfx_console_describe_options gfx_printchar = @gfx_console_printchar io_init = @io_console_init io_keybits = @io_console_keybits io_updatekeys = @io_console_updatekeys io_textinput = @io_console_textinput io_show_virtual_keyboard = @io_console_show_virtual_keyboard io_hide_virtual_keyboard = @io_console_hide_virtual_keyboard io_mousebits = @io_amx_mousebits io_setmousevisibility = @io_console_setmousevisibility io_getmouse = @io_console_getmouse io_setmouse = @io_console_setmouse io_mouserect = @io_console_mouserect 'new render API gfx_present = @gfx_console_present return 1 end function end extern