# # This module defines a ctypes foreign function interface to a Glk # library that has been built as a shared library. For more information # on the Glk API, see http://www.eblong.com/zarf/glk/. # # Note that the way this module interfaces with a Glk library is # slightly different from the standard; the standard interface # actually assumes that a Glk library is in fact not a library, but a # "front-end" program that is statically linked to a Glk back-end, # also known as a Glk "program", by calling the back-end's glk_main() # function. # # Instead of this, we assume that the Glk library is actually a shared # library that is initialized by some external source--be it a Python # script or a compiled program--and used by this module. Note that # this is actually the way some Glk libraries, such as WindowsGlk, are # made to function. # # For the license of this file, please consult the LICENSE file in the # root directory of this distribution. # import ctypes # These are ctypes-style declarations that reflect the Glk.h file, # which defines the Glk API, version 0.7.0. The most recent version # of Glk.h can be found here: # # http://www.eblong.com/zarf/glk/glk.h # # Note that there are ctypes extension libraries that can do this kind # of thing for us (that is, take a .h file and automatically generate # a ctypes wrapper from it); however, the only one that exists at the # time of this writing is ctypeslib, which has dependencies that would # make our build process quite complex. Given the relatively small # size of the Glk API and the freedom we get from hand-coding the # interface ourselves, we're not using ctypeslib. glsi32 = ctypes.c_int32 glui32 = ctypes.c_uint32 winid_t = ctypes.c_void_p strid_t = ctypes.c_void_p frefid_t = ctypes.c_void_p schanid_t = ctypes.c_void_p # TRUE, FALSE, and NULL aren't defined in glk.h, but are mentioned in # Section 1.9 of the Glk spec 0.7.0. TRUE = 1 FALSE = 0 NULL = ctypes.pointer(glui32(0)) gestalt_Version = 0 gestalt_CharInput = 1 gestalt_LineInput = 2 gestalt_CharOutput = 3 gestalt_CharOutput_CannotPrint = 0 gestalt_CharOutput_ApproxPrint = 1 gestalt_CharOutput_ExactPrint = 2 gestalt_MouseInput = 4 gestalt_Timer = 5 gestalt_Graphics = 6 gestalt_DrawImage = 7 gestalt_Sound = 8 gestalt_SoundVolume = 9 gestalt_SoundNotify = 10 gestalt_Hyperlinks = 11 gestalt_HyperlinkInput = 12 gestalt_SoundMusic = 13 gestalt_GraphicsTransparency = 14 gestalt_Unicode = 15 evtype_None = 0 evtype_Timer = 1 evtype_CharInput = 2 evtype_LineInput = 3 evtype_MouseInput = 4 evtype_Arrange = 5 evtype_Redraw = 6 evtype_SoundNotify = 7 evtype_Hyperlink = 8 class event_t(ctypes.Structure): _fields_ = [("type", glui32), ("win", winid_t), ("val1", glui32), ("val2", glui32)] keycode_Unknown = 0xFFFFFFFF keycode_Left = 0xFFFFFFFE keycode_Right = 0xFFFFFFFD keycode_Up = 0xFFFFFFFC keycode_Down = 0xFFFFFFFB keycode_Return = 0xFFFFFFFA keycode_Delete = 0xFFFFFFF9 keycode_Escape = 0xFFFFFFF8 keycode_Tab = 0xFFFFFFF7 keycode_PageUp = 0xFFFFFFF6 keycode_PageDown = 0xFFFFFFF5 keycode_Home = 0xFFFFFFF4 keycode_End = 0xFFFFFFF3 keycode_Func1 = 0xFFFFFFEF keycode_Func2 = 0xFFFFFFEE keycode_Func3 = 0xFFFFFFED keycode_Func4 = 0xFFFFFFEC keycode_Func5 = 0xFFFFFFEB keycode_Func6 = 0xFFFFFFEA keycode_Func7 = 0xFFFFFFE9 keycode_Func8 = 0xFFFFFFE8 keycode_Func9 = 0xFFFFFFE7 keycode_Func10 = 0xFFFFFFE6 keycode_Func11 = 0xFFFFFFE5 keycode_Func12 = 0xFFFFFFE4 keycode_MAXVAL = 28 style_Normal = 0 style_Emphasized = 1 style_Preformatted = 2 style_Header = 3 style_Subheader = 4 style_Alert = 5 style_Note = 6 style_BlockQuote = 7 style_Input = 8 style_User1 = 9 style_User2 = 10 style_NUMSTYLES = 11 class stream_result_t(ctypes.Structure): _fields_ = [("readcount", glui32), ("writecount", glui32)] wintype_AllTypes = 0 wintype_Pair = 1 wintype_Blank = 2 wintype_TextBuffer = 3 wintype_TextGrid = 4 wintype_Graphics = 5 winmethod_Left = 0x00 winmethod_Right = 0x01 winmethod_Above = 0x02 winmethod_Below = 0x03 winmethod_DirMask = 0x0F winmethod_Fixed = 0x10 winmethod_Proportional = 0x20 winmethod_DivisionMask = 0xF0 fileusage_Data = 0x00 fileusage_SavedGame = 0x01 fileusage_Transcript = 0x02 fileusage_InputRecord = 0x03 fileusage_TypeMask = 0x0F fileusage_TextMode = 0x100 fileusage_BinaryMode = 0x000 filemode_Write = 0x01 filemode_Read = 0x02 filemode_ReadWrite = 0x03 filemode_WriteAppend = 0x05 seekmode_Start = 0 seekmode_Current = 1 seekmode_End = 2 stylehint_Indentation = 0 stylehint_ParaIndentation = 1 stylehint_Justification = 2 stylehint_Size = 3 stylehint_Weight = 4 stylehint_Oblique = 5 stylehint_Proportional = 6 stylehint_TextColor = 7 stylehint_BackColor = 8 stylehint_ReverseColor = 9 stylehint_NUMHINTS = 10 stylehint_just_LeftFlush = 0 stylehint_just_LeftRight = 1 stylehint_just_Centered = 2 stylehint_just_RightFlush = 3 # Function prototypes for the core Glk API. It is a list of 3-tuples; each # item in the list represents a function prototype, and each 3-tuple # is in the form (result_type, function_name, arg_types). CORE_GLK_LIB_API = [ (None, "glk_exit", ()), (None, "glk_tick", ()), (glui32, "glk_gestalt", (glui32, glui32)), (glui32, "glk_gestalt_ext", (glui32, glui32, ctypes.POINTER(glui32), glui32)), (winid_t, "glk_window_get_root", ()), (winid_t, "glk_window_open", (winid_t, glui32, glui32, glui32, glui32)), (None, "glk_window_close", (winid_t, ctypes.POINTER(stream_result_t))), ( None, "glk_window_get_size", (winid_t, ctypes.POINTER(glui32), ctypes.POINTER(glui32)), ), (None, "glk_window_set_arrangement", (winid_t, glui32, glui32, winid_t)), ( None, "glk_window_get_arrangement", ( winid_t, ctypes.POINTER(glui32), ctypes.POINTER(glui32), ctypes.POINTER(winid_t), ), ), (winid_t, "glk_window_iterate", (winid_t, ctypes.POINTER(glui32))), (glui32, "glk_window_get_rock", (winid_t,)), (glui32, "glk_window_get_type", (winid_t,)), (winid_t, "glk_window_get_parent", (winid_t,)), (winid_t, "glk_window_get_sibling", (winid_t,)), (None, "glk_window_clear", (winid_t,)), (None, "glk_window_move_cursor", (winid_t, glui32, glui32)), (strid_t, "glk_window_get_stream", (winid_t,)), (None, "glk_window_set_echo_stream", (winid_t, strid_t)), (strid_t, "glk_window_get_echo_stream", (winid_t,)), (None, "glk_set_window", (winid_t,)), (strid_t, "glk_stream_open_file", (frefid_t, glui32, glui32)), (strid_t, "glk_stream_open_memory", (ctypes.c_char_p, glui32, glui32, glui32)), (None, "glk_stream_close", (strid_t, ctypes.POINTER(stream_result_t))), (strid_t, "glk_stream_iterate", (strid_t, ctypes.POINTER(glui32))), (glui32, "glk_stream_get_rock", (strid_t,)), (None, "glk_stream_set_position", (strid_t, glsi32, glui32)), (glui32, "glk_stream_get_position", (strid_t,)), (None, "glk_stream_set_current", (strid_t,)), (strid_t, "glk_stream_get_current", ()), (None, "glk_put_char", (ctypes.c_ubyte,)), (None, "glk_put_char_stream", (strid_t, ctypes.c_ubyte)), (None, "glk_put_string", (ctypes.c_char_p,)), (None, "glk_put_string_stream", (strid_t, ctypes.c_char_p)), (None, "glk_put_buffer", (ctypes.c_char_p, glui32)), (None, "glk_put_buffer_stream", (strid_t, ctypes.c_char_p, glui32)), (None, "glk_set_style", (glui32,)), (None, "glk_set_style_stream", (strid_t, glui32)), (glsi32, "glk_get_char_stream", (strid_t,)), (glui32, "glk_get_line_stream", (strid_t, ctypes.c_char_p, glui32)), (glui32, "glk_get_buffer_stream", (strid_t, ctypes.c_char_p, glui32)), (None, "glk_stylehint_set", (glui32, glui32, glui32, glsi32)), (None, "glk_stylehint_clear", (glui32, glui32, glui32)), (glui32, "glk_style_distinguish", (winid_t, glui32, glui32)), (glui32, "glk_style_measure", (winid_t, glui32, glui32, ctypes.POINTER(glui32))), (frefid_t, "glk_fileref_create_temp", (glui32, glui32)), (frefid_t, "glk_fileref_create_by_name", (glui32, ctypes.c_char_p, glui32)), (frefid_t, "glk_fileref_create_by_prompt", (glui32, glui32, glui32)), (frefid_t, "glk_fileref_create_from_fileref", (glui32, frefid_t, glui32)), (None, "glk_fileref_destroy", (frefid_t,)), (frefid_t, "glk_fileref_iterate", (frefid_t, ctypes.POINTER(glui32))), (glui32, "glk_fileref_get_rock", (frefid_t,)), (None, "glk_fileref_delete_file", (frefid_t,)), (glui32, "glk_fileref_does_file_exist", (frefid_t,)), (None, "glk_select", (ctypes.POINTER(event_t),)), (None, "glk_select_poll", (ctypes.POINTER(event_t),)), (None, "glk_request_timer_events", (glui32,)), (None, "glk_request_line_event", (winid_t, ctypes.c_char_p, glui32, glui32)), (None, "glk_request_char_event", (winid_t,)), (None, "glk_request_mouse_event", (winid_t,)), (None, "glk_cancel_line_event", (winid_t, ctypes.POINTER(event_t))), (None, "glk_cancel_char_event", (winid_t,)), (None, "glk_cancel_mouse_event", (winid_t,)), ] # Function prototypes for the optional Unicode extension of the Glk # API. UNICODE_GLK_LIB_API = [ (None, "glk_put_char_uni", (glui32,)), (None, "glk_put_string_uni", (ctypes.POINTER(glui32),)), (None, "glk_put_buffer_uni", (ctypes.POINTER(glui32), glui32)), (None, "glk_put_char_stream_uni", (strid_t, glui32)), (None, "glk_put_string_stream_uni", (strid_t, ctypes.POINTER(glui32))), (None, "glk_put_buffer_stream_uni", (strid_t, ctypes.POINTER(glui32), glui32)), (glsi32, "glk_get_char_stream_uni", (strid_t,)), (glui32, "glk_get_buffer_stream_uni", (strid_t, ctypes.POINTER(glui32), glui32)), (glui32, "glk_get_line_stream_uni", (strid_t, ctypes.POINTER(glui32), glui32)), (strid_t, "glk_stream_open_file_uni", (frefid_t, glui32, glui32)), ( strid_t, "glk_stream_open_memory_uni", (ctypes.POINTER(glui32), glui32, glui32, glui32), ), (None, "glk_request_char_event_uni", (winid_t,)), ( None, "glk_request_line_event_uni", (winid_t, ctypes.POINTER(glui32), glui32, glui32), ), ] class GlkLib: """Encapsulates the ctypes interface to a Glk shared library. When instantiated, it wraps the shared library with the appropriate function prototypes from the Glk API to reduce the chance of mis-calls that may result in segfaults (this effectively simulates the strong type-checking a C compiler would perform).""" def __init__(self, lib_name): """Instantiates the instance, binding it to the given shared library (which is referenced by name).""" self._dll = ctypes.CDLL(lib_name) self.__bind_prototypes(CORE_GLK_LIB_API) if self.glk_gestalt(gestalt_Unicode, 0) == 1: # type: ignore[unresolved-attribute] self.__bind_prototypes(UNICODE_GLK_LIB_API) else: self.__bind_not_implemented_prototypes(UNICODE_GLK_LIB_API) def __bind_prototypes(self, function_prototypes): """Create function prototypes from the given list of 3-tuples of the form (result_type, function_name, arg_types), bind them to functions in our shared library, and then bind the function instances as methods to this object.""" for function_prototype in function_prototypes: result_type, function_name, arg_types = function_prototype prototype = ctypes.CFUNCTYPE(result_type, *arg_types) function = prototype((function_name, self._dll)) setattr(self, function_name, function) def __bind_not_implemented_prototypes(self, function_prototypes): """Create functions with the names from the given list of 3-tuples of the form (result_type, function_name, arg_types) that simply raise NotImplementedError, and bind them to this object. This should be used when a Glk library doesn't support some optional extension of the Glk API.""" def notImplementedFunction(*args, **kwargs): raise NotImplementedError("Function not implemented by this Glk library.") for function_prototype in function_prototypes: _, function_name, _ = function_prototype setattr(self, function_name, notImplementedFunction) def glk_char_to_lower(self, ch): raise NotImplementedError("Use unicode.lower() instead.") def glk_char_to_upper(self, ch): raise NotImplementedError("Use unicode.upper() instead.") def glk_buffer_to_lower_case_uni(self, buf, len, numchars): raise NotImplementedError("Use unicode.lower() instead.") def glk_buffer_to_upper_case_uni(self, buf, len, numchars): raise NotImplementedError("Use unicode.upper() instead.") def glk_buffer_to_title_case_uni(self, buf, len, numchars, lowerrest): raise NotImplementedError("Use unicode.title() instead.")