'' '' gfx_fb.bas - External graphics functions implemented in FB's '' built-in gfxlib. Multi-option version. '' '' part of OHRRPGCE - see elsewhere for license details '' #include "config.bi" #ifdef USE_X11 #include "lib/SDL/SDL_x11clipboard.bi" #undef font ' HACK: FB 1.05 and earlier don't have a way to get the Display ptr, only the Window ' But the Window ptr happens to be the first member of fb_x11 type X11DRIVER display as Display ptr 'rest omitted end type extern fb_x11 alias "fb_x11" as X11DRIVER #elseif defined(__FB_WIN32__) include_windows_bi() #include "lib/SDL/SDL_windowsclipboard.bi" #elseif defined(__FB_DARWIN__) #include "lib/SDL/SDL_cocoaclipboard.bi" #endif #include "util.bi" #include "fbgfx.bi" #include "surface.bi" #include "gfx.bi" #include "allmodex.bi" #include "common.bi" 'Use the FB namespace for the types and constants from fbgfx USING FB 'a public rtlib function that they seem to have forgotten to expose to FB programs? declare function fb_KeyHit alias "fb_KeyHit" () as long extern "C" 'subs only used internally declare sub gfx_fb_screenres() 'set screen res, etc declare sub calculate_screen_res() declare sub update_mouse_visibility() 'border required to fit standard 4:3 screen at zoom 1 #define BORDER 20 dim shared screen_buffer_offset as integer = 0 dim shared window_state as WindowState dim shared init_gfx as bool = NO 'defaults are 2x zoom and 640x400 in 8-bit dim shared zoom as integer = 2 dim shared screenmodex as integer = 640 dim shared screenmodey as integer = 400 dim shared bordered as integer = 0 '0 or 1 dim shared depth as integer = 32 '0 means use native dim shared smooth as integer = 0 '0 or 1 dim shared mouseclipped as bool = NO dim shared mouse_visibility as CursorVisibility = cursorDefault dim shared remember_windowtitle as string dim shared as integer mxmin = -1, mxmax = -1, mymin = -1, mymax = -1 dim shared inputtext as string dim shared extrakeys(127) as integer 'internal palette for 32-bit mode, with RGB colour components packed into a int dim shared truepal(255) as int32 function gfx_fb_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 if init_gfx = NO then dim bpp as size_t 'bits, not bytes. see, bits is b, bytes is B dim refreshrate as size_t dim driver as string dim as size_t w, h 'Poll the size of the screen screeninfo w, h, bpp, , , refreshrate, driver debuginfo "gfx_fb: native screensize=" & w & "*" & h & " bitdepth=" & bpp & " refreshrate=" & refreshrate if depth = 0 then depth = iif(bpp = 24, 32, bpp) calculate_screen_res gfx_fb_screenres screenset 1, 0 'Only want one FB video page init_gfx = YES screeninfo , , bpp, , , refreshrate, driver *info_buffer = MID(bpp & "bpp, " & refreshrate & "Hz, " & driver & " driver", 1, info_buffer_size) end if window_state.structsize = WINDOWSTATE_SZ window_state.focused = YES window_state.minimised = NO window_state.fullscreen = NO window_state.mouse_over = NO return 1 end function private sub gfx_fb_screenres if window_state.fullscreen = YES then screenres screenmodex, screenmodey, depth, 1, GFX_FULLSCREEN else screenres screenmodex, screenmodey, depth, 1, GFX_WINDOWED end if update_mouse_visibility() end sub private sub update_mouse_visibility dim vis as integer '0 or 1 if mouse_visibility = cursorDefault then ' window_state.fullscreen is an approximation (see process_events()), ' and because it's so unreliable we need a constant default. #ifdef IS_CUSTOM vis = 1 #else vis = 0 #endif 'if window_state.fullscreen = YES then vis = 0 else vis = 1 elseif mouse_visibility = cursorVisible then vis = 1 else vis = 0 end if setmouse , , vis end sub sub gfx_fb_update_screen_mode() if init_gfx then mutexlock keybdmutex calculate_screen_res gfx_fb_screenres windowtitle remember_windowtitle 'Palette must be re-set if depth = 8 then for i as integer = 0 to 255 palette i, (truepal(i) and &hFF0000) shr 16, (truepal(i) and &hFF00) shr 8, truepal(i) and &hFF next end if mutexunlock keybdmutex end if end sub sub gfx_fb_close screen 0 init_gfx = NO end sub function gfx_fb_getversion() as integer return 1 end function sub gfx_fb_setpal(byval pal as RGBcolor ptr) dim as integer i if depth = 8 then for i = 0 to 255 palette i, pal[i].r, pal[i].g, pal[i].b next end if 'copy the palette, both for 32bit colour mode and when changing 'res requires resetting the palette for i = 0 to 255 truepal(i) = RGB(pal[i].r, pal[i].g, pal[i].b) next 'FIXME: If running in 32 bitdepth, this does not update the page "live", like the 8-bit version 'so fades won't working, and there's no way to force an 'update from here at the moment because the screen buffer is not 'accessible. end sub function gfx_fb_present(byval surfaceIn as Surface ptr, byval pal as RGBPalette ptr) as integer '320x200 Surfaces supported only! dim ret as integer = 0 if surfaceIn->format = .SF_32bit and depth <> 32 then debuginfo "gfx_fb_present: switching to 32 bit mode" depth = 32 gfx_fb_screenres end if screenlock dim as ubyte ptr screenpixels = screenptr + (screen_buffer_offset * 320 * zoom) with *surfaceIn if .format = SF_8bit then gfx_fb_setpal(cast(RGBcolor ptr, @pal->col(0))) if depth = 8 then smoothzoomblit_8_to_8bit(.pPaletteData, screenpixels, .width, .height, .width * zoom, zoom, smooth) elseif depth = 32 then smoothzoomblit_8_to_32bit(.pPaletteData, cast(uint32 ptr, screenpixels), .width, .height, .width * zoom, zoom, smooth, @truepal(0)) end if else '32 bit if depth = 32 then smoothzoomblit_32_to_32bit(.pColorData, cast(uint32 ptr, screenpixels), .width, .height, .width * zoom, zoom, smooth) else ret = 1 end if end if end with screenunlock flip return ret end function function gfx_fb_screenshot(byval fname as zstring ptr) as integer gfx_fb_screenshot = 0 end function sub gfx_fb_setwindowed(byval iswindow as integer) dim wantfullscreen as bool = iif(iswindow, NO, YES) 'only 1 "true" value if window_state.fullscreen = wantfullscreen then exit sub window_state.fullscreen = wantfullscreen gfx_fb_update_screen_mode end sub sub gfx_fb_windowtitle(byval title as zstring ptr) remember_windowtitle = *title windowtitle *title end sub function gfx_fb_getwindowstate() as WindowState ptr return @window_state end function sub gfx_fb_get_screen_size(wide as integer ptr, high as integer ptr) dim as ssize_t wide_, high_ 'for 64 bit builds ScreenControl GET_DESKTOP_SIZE, wide_, high_ *wide = wide_ *high = high_ end sub function gfx_fb_setoption(byval opt as zstring ptr, byval arg as zstring ptr) as integer 'handle command-line options in a generic way, so that they 'can be ignored or supported as the library permits. 'This version supports ' zoom (1, 2*, ..., 16), ' depth (8*, 32), ' border (0*, 1) ' smooth (0*, 1) 'Changing mode after window already created isn't well tested! dim as integer value = str2int(*arg, -1) dim as integer ret = 0 dim as bool screen_mode_changed = NO if *opt = "zoom" or *opt = "z" then if value >= 1 and value <= 16 then zoom = value end if screen_mode_changed = YES ret = 1 elseif *opt = "depth" or *opt = "d" then if value = 24 or value = 32 then depth = 32 '24 would screw things up else depth = 8 end if screen_mode_changed = YES ret = 1 elseif *opt = "border" or *opt = "b" then if value = 1 or value = -1 then 'arg optional bordered = 1 else bordered = 0 end if screen_mode_changed = YES ret = 1 elseif *opt = "smooth" or *opt = "s" then if value = 1 or value = -1 then 'arg optional smooth = 1 else smooth = 0 end if ret = 1 end if 'all these take numeric arguments, so gobble the arg if it is 'a number, whether or not it was valid if ret = 1 and is_int(*arg) then ret = 2 if screen_mode_changed then gfx_fb_update_screen_mode end if return ret end function sub calculate_screen_res() 'FIXME: this is an utter mess 'FIXME: fullscreen doesn't work if the zoom results in an odd resolution like 960x600. 'calculate mode if zoom = 1 then if depth = 8 then screenmodex = 320 screenmodey = 200 + (bordered * BORDER * zoom) else 'only bordered is supported in 24-bit it seems bordered = 1 screenmodex = 320 screenmodey = 240 end if elseif zoom = 2 then screenmodex = 640 screenmodey = 400 + (bordered * BORDER * zoom) elseif zoom >= 3 then bordered = 0 ' bordered mode is not supported screen_buffer_offset = 0 screenmodex = 320 * zoom screenmodey = 200 * zoom end if 'calculate offset if bordered = 1 and zoom < 3 then screen_buffer_offset = (BORDER / 2) * zoom end sub function gfx_fb_describe_options() as zstring ptr return @"-z -zoom [1...16] Scale screen to 1,2, ... up to 16x normal size (2x default)" LINE_END _ "-b -border [0|1] Add a letterbox border (default off)" LINE_END _ "-d -depth [8|32] Set color bit-depth (default 8-bit)" LINE_END _ "-s -smooth Enable smoothing filter for zoom modes (default off)" end function '------------- IO Functions -------------- sub io_fb_init end sub sub process_key_event(e as Event, byval value as integer) 'NOTE: numpad 5 seems to be broken on Windows, events for that key have scancode = 0 regardless of numlock state! 'On linux, Pause, PrintScreen, and WindowsKey keypresses send events with scancode 0, and multikey shows nothing. select case e.scancode case scHome to scPageUp, scLeft to scRight, scEnd to scDelete #if defined(__FB_WIN32__) or __FB_VERSION__ >= "0.90" 'If numlock is on, then when a numerical/period numpad key is pressed the key will 'generate ascii 0-9 or ., and can be differentiated from arrow and home, etc., keys. 'Otherwise they both return e.ascii = 0 if e.ascii then extrakeys(e.scancode - scHome + scNumpad7) = value else extrakeys(e.scancode) = value end if #else 'On Linux, X11 driver & FB before 0.90, Home, Left, etc send "extended scancodes", 'with .ascii >256 (or 127 in the case of Delete), and numpad keys 'send 0 if numlock is off, and the normal ascii if on if e.ascii < 256 andalso e.ascii <> 127 then extrakeys(e.scancode - scHome + scNumpad7) = value else extrakeys(e.scancode) = value end if #endif end select end sub sub process_events() ' static last_enter_state as integer dim e as Event while ScreenEvent(@e) 'unhide the mouse when the window loses focus if e.type = EVENT_WINDOW_LOST_FOCUS then setmouse , , 1 window_state.focused = NO end if if e.type = EVENT_WINDOW_GOT_FOCUS then update_mouse_visibility() window_state.focused = YES end if if e.type = EVENT_KEY_PRESS then if e.ascii <> 0 then inputtext += chr(e.ascii) 'debug "key press scan=" & e.scancode & " (" & scancodename(e.scancode) & ") ascii=" & e.ascii process_key_event(e, 8) end if if e.type = EVENT_KEY_REPEAT then if e.ascii <> 0 then inputtext += chr(e.ascii) end if if e.type = EVENT_KEY_RELEASE then 'debug "key release scan=" & e.scancode & " (" & scancodename(e.scancode) & ") ascii=" & e.ascii process_key_event(e, 0) end if wend 'Don't eat memory if io_textinput is never called inputtext = RIGHT(inputtext, 128) 'the polling thread ought to ensure that these are caught timeously 'inkey does not seem to be threadsafe (bug 790) 'if inkey = chr(255) + "k" then post_terminate_signal while fb_keyhit dim a as integer = getkey 'there are two different getkey values that cause fb_GfxInkey to return "\255k" if a = &h100 or a = &h6bff then post_terminate_signal wend if multikey(SC_ALT) andalso multikey(SC_F4) then post_terminate_signal 'Try to catch fullscreening to reduce mouse visibility confusion. However, fullscreening 'with the window button is not considered. 'FB's X11 backend in effect doesn't report key events for alt+enter, nor will you ever see both 'pressed at once with multikey, because the moment that they are, FB (the X11 backend anyway) 'toggles fullscreen, resetting the state. 'And FB has no way to check whether we're fullscreen. /' if multikey(SC_ALT) andalso multikey(SC_ENTER) andalso last_enter_state = 0 then window_state.fullscreen xor= YES post_event(eventFullscreened, window_state.fullscreen) update_mouse_visibility() end if last_enter_state = multikey(SC_ENTER) '/ end sub sub io_fb_pollkeyevents() 'not really needed by this backend process_events() end sub sub io_fb_waitprocessing() 'not needed by this backend end sub sub io_fb_updatekeys(byval keybd as integer ptr) process_events() for key as integer = 0 to 127 select case key case 0 to scHome - 1, scNumpadMinus, scNumpadPlus, scDelete + 1 to scContext if multikey(key) then keybd[key] or= 8 end if case scNumpad5 'Events for this key are broken on Windows, luckily can fall back to multikey if multikey(76) then keybd[key] or= 8 end if case scHome to scDelete, scNumpad7 to scNumpadPeriod keybd[key] or= extrakeys(key) 'other keys are undetectable with fbgfx! end select next 'fbgfx reports separate shift keys, but combined alt and ctrl keys keybd[scShift] or= (keybd[scLeftShift] or keybd[scRightShift]) and 8 keybd[scLeftAlt] or= keybd[scUnfilteredAlt] and 8 keybd[scRightAlt] or= keybd[scUnfilteredAlt] and 8 keybd[scLeftCtrl] or= keybd[scCtrl] and 8 keybd[scRightCtrl] or= keybd[scCtrl] and 8 'Some other keys are also indistinguishable, and are mirrored keybd[scNumpadSlash] or= keybd[scSlash] and 8 keybd[scNumpadEnter] or= keybd[scEnter] and 8 keybd[scPrintScreen] or= keybd[scNumpadAsterisk] and 8 keybd[scPause] or= keybd[scNumlock] and 8 end sub sub io_fb_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_fb_show_virtual_keyboard() 'Does nothing on platforms that have real keyboards END SUB SUB io_fb_hide_virtual_keyboard() 'Does nothing on platforms that have real keyboards END SUB sub io_fb_setmousevisibility(visibility as CursorVisibility) mouse_visibility = visibility update_mouse_visibility() end sub sub io_fb_getmouse(byref mx as integer, byref my as integer, byref mwheel as integer, byref mbuttons as integer) 'FIXME: this is broken in fullscreen -z 2, with the Y position offset from the true, even if 'FB accurately knows that it's running in fullscreen. static as integer lastx = 0, lasty = 0, lastwheel = 0, lastbuttons = 0 dim as integer dmx, dmy, dw, db, remx, remy if getmouse(dmx, dmy, dw, db) = 0 then 'mouse is inside window window_state.mouse_over = YES if mouseclipped then remx = dmx remy = dmy dmx = bound(dmx, mxmin, mxmax) dmy = bound(dmy, mymin, mymax) 'calling setmouse at the same position rapidly causes the mouse to crawl if remx <> dmx or remy <> dmy then setmouse dmx, dmy end if dmx = dmx \ zoom dmy = (dmy \ zoom) - screen_buffer_offset lastx = dmx lasty = dmy lastwheel = 120 * dw lastbuttons = db else window_state.mouse_over = NO end if mx = lastx my = lasty mwheel = lastwheel mbuttons = lastbuttons end sub sub io_fb_setmouse(byval x as integer, byval y as integer) setmouse(x * zoom, y * zoom + screen_buffer_offset) end sub sub io_fb_mouserect(byval xmin as integer, byval xmax as integer, byval ymin as integer, byval ymax as integer) mxmin = xmin * zoom mxmax = xmax * zoom + zoom - 1 mymin = (ymin + screen_buffer_offset) * zoom mymax = (ymax + screen_buffer_offset) * zoom + zoom - 1 if xmin >= 0 then 'enable clipping mouseclipped = YES setmouse , , , 1 else 'disable clipping mouseclipped = NO setmouse , , , 0 end if end sub function io_fb_readjoysane(byval joynum as integer, byref button as integer, byref x as integer, byref y as integer) as integer dim as single xa, ya dim as size_t button_bits if getjoystick(joynum, button_bits, xa, ya) then 'returns 1 on failure return 0 end if button = button_bits x = int(xa * 100) y = int(ya * 100) 'if abs(x) > 10 then debug "X = " + str(x) return 1 end function sub io_fb_set_clipboard_text(text as zstring ptr) 'ustring if text = NULL then text = @"" #ifdef USE_X11 ' Not supported. For the selection to work, we would need to handle SelectionRequest ' X11 events, but FB discards these and doesn't forward them. We would have to do peeking ' of the X11 event queue. exit sub #elseif defined(__FB_WIN32__) dim hwnd as ssize_t 'as HWND screencontrol GET_WINDOW_HANDLE, hwnd WIN_SetClipboardText(cast(HWND, hwnd), text) #elseif defined(__FB_DARWIN__) Cocoa_SetClipboardText(text) #endif end sub function io_fb_get_clipboard_text() as zstring ptr 'ustring dim ret as zstring ptr #ifdef USE_X11 dim wndw as Window dim displayi as ssize_t dim display as Display ptr screencontrol GET_WINDOW_HANDLE, wndw, displayi display = cptr(Display ptr, displayi) 'Getting display via GET_WINDOW_HANDLE is only in FB 1.06.0 display = fb_x11.display ret = X11_GetClipboardText(display, wndw, NULL) 'No callback, just waits 40ms #elseif defined(__FB_WIN32__) dim hwnd as ssize_t 'as HWND screencontrol GET_WINDOW_HANDLE, hwnd ret = WIN_GetClipboardText(cast(HWND, hwnd)) #elseif defined(__FB_DARWIN__) ret = Cocoa_GetClipboardText() #endif return ret end function function gfx_fb_setprocptrs() as integer gfx_init = @gfx_fb_init gfx_close = @gfx_fb_close gfx_getversion = @gfx_fb_getversion gfx_setpal = @gfx_fb_setpal gfx_screenshot = @gfx_fb_screenshot gfx_setwindowed = @gfx_fb_setwindowed gfx_windowtitle = @gfx_fb_windowtitle gfx_getwindowstate = @gfx_fb_getwindowstate gfx_get_screen_size = @gfx_fb_get_screen_size gfx_setoption = @gfx_fb_setoption gfx_describe_options = @gfx_fb_describe_options io_init = @io_fb_init io_pollkeyevents = @io_fb_pollkeyevents io_waitprocessing = @io_fb_waitprocessing io_keybits = @io_amx_keybits io_updatekeys = @io_fb_updatekeys io_textinput = @io_fb_textinput io_get_clipboard_text = @io_fb_get_clipboard_text io_set_clipboard_text = @io_fb_set_clipboard_text io_show_virtual_keyboard = @io_fb_show_virtual_keyboard io_hide_virtual_keyboard = @io_fb_hide_virtual_keyboard io_mousebits = @io_amx_mousebits io_setmousevisibility = @io_fb_setmousevisibility io_getmouse = @io_fb_getmouse io_setmouse = @io_fb_setmouse io_mouserect = @io_fb_mouserect io_readjoysane = @io_fb_readjoysane 'new render API gfx_present = @gfx_fb_present return 1 end function end extern