def _check_input(self): """ Handle keyboard events. """ while True: # s is one unicode char or one scancode uc, sc = self.input_handler.get_key() if not uc and not sc: break if uc == eof: # ctrl-D (unix) / ctrl-Z (windows) backend.input_queue.put(backend.Event(backend.KEYB_QUIT)) elif uc == u'\x7f': # backspace backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (eascii.BACKSPACE, scancode.BACKSPACE, []))) elif sc or uc: # check_full=False to allow pasting chunks of text backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (uc, sc, [], False))) if sc == scancode.F12: self.f12_active = True else: backend.input_queue.put( backend.Event(backend.KEYB_UP, (scancode.F12, ))) self.f12_active = False
def _handle_text_input(self, event): """ Handle text-input event. """ c = event.text.text.decode('utf-8') if self.f11_active: # F11+f to toggle fullscreen mode if c.upper() == u'F': self.fullscreen = not self.fullscreen self._do_create_window(*self._find_display_size( self.size[0], self.size[1], self.border_width)) self.clipboard.handle_key(None, c) # the text input event follows the key down event immediately elif self.last_down is None: # no key down event waiting: other input method backend.input_queue.put(backend.Event(backend.KEYB_CHAR, (c, ))) else: eascii, scan, mod, ts = self.last_down if eascii: c = eascii if ts == event.text.timestamp: # combine if same time stamp backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (c, scan, mod))) else: # two separate events # previous keypress has no corresponding textinput self._flush_keypress() # current textinput has no corresponding keypress backend.input_queue.put(backend.Event(backend.KEYB_CHAR, (c, ))) self.last_down = None
def play_noise(self, source, volume, duration, loop=False): """ Play a sound on the noise generator. """ frequency = self.noise_freq[source] noise = backend.Event( backend.AUDIO_NOISE, (source > 3, frequency, duration, 1, loop, volume)) state.console_state.tone_queue[3].put(noise)
def _flush_keypress(self): """ Flush last keypress from buffer. """ if self.last_down is not None: # insert into keyboard queue; no text event c, scan, mod, _ = self.last_down backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (c, scan, mod))) self.last_down = None
def _init_thread(self): """ Complete SDL2 interface initialisation. """ # initialise SDL sdl2.SDL_Init(sdl2.SDL_INIT_EVERYTHING) # set clipboard handler to SDL2 backend.clipboard_handler = SDL2Clipboard() # display palettes for blink states 0, 1 self.show_palette = [ sdl2.SDL_AllocPalette(256), sdl2.SDL_AllocPalette(256) ] # get physical screen dimensions (needs to be called before set_mode) display_mode = sdl2.SDL_DisplayMode() sdl2.SDL_GetCurrentDisplayMode(0, ctypes.byref(display_mode)) self.physical_size = display_mode.w, display_mode.h # create the window initially, size will be corrected later self.display = None # create window in same thread that manipulates it # "NOTE: You should not expect to be able to create a window, render, or receive events on any thread other than the main one" # https://wiki.libsdl.org/CategoryThread # http://stackoverflow.com/questions/27751533/sdl2-threading-seg-fault self._do_create_window(640, 400) # load an all-black 16-colour game palette to get started self.set_palette([(0, 0, 0)] * 16, None) self.move_cursor(1, 1) self.set_page(0, 0) self.set_mode(self.kwargs['initial_mode']) # support for CGA composite self.composite_palette = sdl2.SDL_AllocPalette(256) composite_colors = video_graphical.composite_640.get( self.composite_card, video_graphical.composite_640['cga']) colors = (sdl2.SDL_Color * 256)( *[sdl2.SDL_Color(r, g, b, 255) for (r, g, b) in composite_colors]) sdl2.SDL_SetPaletteColors(self.composite_palette, colors, 0, 256) # check if we can honour scaling=smooth if self.smooth: # pointer to the zoomed surface self.zoomed = None pixelformat = self.display_surface.contents.format if pixelformat.contents.BitsPerPixel != 32: logging.warning( 'Smooth scaling not available on this display of %d-bit colour depth: needs 32-bit', self.display_surface.format.contents.BitsPerPixel) self.smooth = False if not hasattr(sdl2, 'sdlgfx'): logging.warning( 'Smooth scaling not available: SDL_GFX extension not found.' ) self.smooth = False # available joysticks num_joysticks = sdl2.SDL_NumJoysticks() for j in range(num_joysticks): sdl2.SDL_JoystickOpen(j) # if a joystick is present, its axes report 128 for mid, not 0 for axis in (0, 1): backend.input_queue.put( backend.Event(backend.STICK_MOVED, (j, axis, 128)))
def close(self): """ Close the audio interface. """ # drain signal queue (to allow for persistence) and request exit if backend.message_queue: backend.message_queue.put(backend.Event(backend.AUDIO_QUIT)) backend.message_queue.join() # don't wait for tone que, it will not drain but be pickled later. if self.thread and self.thread.is_alive(): # signal quit and wait for thread to finish self.thread.join()
def stop_all_sound(self): """ Terminate all sounds immediately. """ for q in state.console_state.tone_queue: while not q.empty(): try: q.get(False) except Queue.Empty: continue q.task_done() backend.message_queue.put(backend.Event(backend.AUDIO_STOP))
def _check_input(self): """ Handle keyboard events. """ s = '' i = 0 while True: i = self.window.getch() if i < 0: break elif i < 256: s += chr(i) else: if i == curses.KEY_BREAK: # this is fickle, on many terminals doesn't work backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (u'', scancode.BREAK, [scancode.CTRL]))) elif i == curses.KEY_RESIZE: self._resize(self.height, self.width) # scancode, insert here and now # there shouldn't be a mix of special keys and utf8 in one # uninterrupted string, since the only reason an uninterrupted # string would be longer than 1 char is because it's a single # utf-8 sequence or a pasted utf-8 string, neither of which # can contain special characters. # however, if that does occur, this won't work correctly. scan = curses_to_scan.get(i, None) c = curses_to_eascii.get(i, '') if scan or c: backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (c, scan, []))) if i == curses.KEY_F12: self.f12_active = True else: self._unset_f12() # convert into unicode chars u = s.decode(encoding, 'replace') # then handle these one by one for c in u: #check_full=False to allow pasting chunks of text backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (c, None, [], False))) self._unset_f12()
def _check_input(self): """ Handle screen and interface events. """ # check and handle pygame events for event in pygame.event.get(): if event.type == pygame.KEYDOWN: self._handle_key_down(event) if event.type == pygame.KEYUP: self._handle_key_up(event) elif event.type == pygame.MOUSEBUTTONDOWN: # copy, paste and pen may be on the same button, so no elifs if event.button == self.mousebutton_copy: # LEFT button: copy pos = self._normalise_pos(*event.pos) self.clipboard.start( 1 + pos[1] // self.font_height, 1 + (pos[0] + self.font_width // 2) // self.font_width) if event.button == self.mousebutton_paste: # MIDDLE button: paste self.clipboard.paste(mouse=True) if event.button == self.mousebutton_pen: # right mouse button is a pen press backend.input_queue.put( backend.Event(backend.PEN_DOWN, self._normalise_pos(*event.pos))) elif event.type == pygame.MOUSEBUTTONUP: backend.input_queue.put(backend.Event(backend.PEN_UP)) if event.button == self.mousebutton_copy: self.clipboard.copy(mouse=True) self.clipboard.stop() elif event.type == pygame.MOUSEMOTION: pos = self._normalise_pos(*event.pos) backend.input_queue.put(backend.Event(backend.PEN_MOVED, pos)) if self.clipboard.active(): self.clipboard.move( 1 + pos[1] // self.font_height, 1 + (pos[0] + self.font_width // 2) // self.font_width) elif event.type == pygame.JOYBUTTONDOWN: backend.input_queue.put( backend.Event(backend.STICK_DOWN, (event.joy, event.button))) elif event.type == pygame.JOYBUTTONUP: backend.input_queue.put( backend.Event(backend.STICK_UP, (event.joy, event.button))) elif event.type == pygame.JOYAXISMOTION: backend.input_queue.put( backend.Event( backend.STICK_MOVED, (event.joy, event.axis, int(event.value * 127 + 128)))) elif event.type == pygame.VIDEORESIZE: self.fullscreen = False self._resize_display(event.w, event.h) elif event.type == pygame.QUIT: if self.nokill: self.set_caption_message( 'to exit type <CTRL+BREAK> <ESC> SYSTEM') else: backend.input_queue.put(backend.Event(backend.KEYB_QUIT))
def _handle_key_up(self, e): """ Handle key-up event. """ if e.key == pygame.K_F11: self.clipboard.stop() self.f11_active = False # last key released gets remembered try: backend.input_queue.put( backend.Event(backend.KEYB_UP, (key_to_scan[e.key], ))) except KeyError: pass
def close(self): """ Close the interface. """ # drain signal queue (to allow for persistence) and request exit # signal quit and wait for thread to finish if backend.video_queue: backend.video_queue.put(backend.Event(backend.VIDEO_QUIT)) backend.video_queue.join() if self.thread and self.thread.is_alive(): # signal quit and wait for thread to finish self.thread.join() self.thread = None
def copy(self, mouse=False): """ Copy screen characters from selection into clipboard. """ start, stop = self.select_start, self.select_stop if not start or not stop: return if start[0] == stop[0] and start[1] == stop[1]: return if start[0] > stop[0] or (start[0] == stop[0] and start[1] > stop[1]): start, stop = stop, start backend.input_queue.put( backend.Event(backend.CLIP_COPY, (start[0], start[1], stop[0], stop[1], mouse)))
def _handle_key_down(self, e): """ Handle key-down event. """ # get scancode scan = key_to_scan.get(e.key, None) # get modifiers mod = [s for m, s in mod_to_scan.iteritems() if e.mod & m] # get eascii try: if e.mod & pygame.KMOD_LALT or (not self.altgr and e.mod & pygame.KMOD_RALT): c = alt_key_to_eascii[e.key] elif e.mod & pygame.KMOD_CTRL: c = ctrl_key_to_eascii[e.key] elif e.mod & pygame.KMOD_SHIFT: c = shift_key_to_eascii[e.key] else: c = key_to_eascii[e.key] except KeyError: key = e.key if e.mod & pygame.KMOD_CTRL and key >= ord('a') and key <= ord( 'z'): c = unichr(key - ord('a') + 1) elif e.mod & pygame.KMOD_CTRL and key >= ord('[') and key <= ord( '_'): c = unichr(key - ord('A') + 1) else: c = e.unicode # handle F11 home-key if e.key == pygame.K_F11: self.f11_active = True self.clipboard.start(self.cursor_row, self.cursor_col) elif self.f11_active: # F11+f to toggle fullscreen mode if e.key == pygame.K_f: self.fullscreen = not self.fullscreen self._resize_display(*self._find_display_size( self.size[0], self.size[1], self.border_width)) self.clipboard.handle_key(scan, c) else: # double NUL characters, as single NUL signals e-ASCII if c == u'\0': c = eascii.NUL # fix missing ascii for numeric keypad on Windows if e.mod & pygame.KMOD_NUM: try: c = key_to_eascii_num[e.key] except KeyError: pass # insert into keyboard queue backend.input_queue.put( backend.Event(backend.KEYB_DOWN, (c, scan, mod)))
def _check_input(self): """ Handle keyboard events. """ # avoid blocking on ttys if there's no input if plat.stdin_is_tty and not kbhit(): return # NOTE: errors occur when backspace is used with text input # only the last byte is erased, not the whole utf-8 sequence s = sys.stdin.readline().decode(encoding, errors='ignore') if s == '': redirect.input_closed = True for c in s: # replace LF -> CR if needed if c == u'\n' and lf_to_cr: c = u'\r' # check_full=False as all input may come at once backend.input_queue.put( backend.Event(backend.KEYB_CHAR, (c, False)))
def play_sound_no_wait(self, frequency, duration, fill=1, loop=False, voice=0, volume=15): """ Play a sound on the tone generator. """ if frequency < 0: frequency = 0 if ((pcjr_sound == 'tandy' or (pcjr_sound == 'pcjr' and self.sound_on)) and frequency < 110. and frequency != 0): # pcjr, tandy play low frequencies as 110Hz frequency = 110. tone = backend.Event(backend.AUDIO_TONE, (frequency, duration, fill, loop, volume)) state.console_state.tone_queue[voice].put(tone) if voice == 2 and frequency != 0: # reset linked noise frequencies # /2 because we're using a 0x4000 rotation rather than 0x8000 self.noise_freq[3] = frequency / 2. self.noise_freq[7] = frequency / 2.
def paste(self, mouse=False): """ Paste from clipboard into keyboard buffer. """ backend.input_queue.put(backend.Event(backend.CLIP_PASTE, (mouse, )))
def __init__(self, **kwargs): """ Initialise pygame interface. """ video_graphical.VideoGraphical.__init__(self, **kwargs) # set state objects to whatever is now in state (may have been unpickled) if not pygame: logging.warning('PyGame module not found.') raise video.InitFailed() if not numpy: logging.debug('NumPy module not found.') raise video.InitFailed() pygame.init() try: # poll the driver to force an exception if not initialised pygame.display.get_driver() except pygame.error: self._close_pygame() logging.warning('No suitable display driver for PyGame.') raise video.InitFailed() # display & border # display buffer self.canvas = [] # border attribute self.border_attr = 0 # palette and colours # composite colour artifacts self.composite_artifacts = False # working palette - attribute index in blue channel self.work_palette = [(0, 0, index) for index in range(256)] # display palettes for blink states 0, 1 self.show_palette = [None, None] # composite palette try: self.composite_640_palette = video_graphical.composite_640[ self.composite_card] except KeyError: self.composite_640_palette = video_graphical.composite_640['cga'] # text attributes supported self.mode_has_blink = True # update cycle # update flag self.screen_changed = True # refresh cycle parameters self.cycle = 0 self.last_cycle = 0 self.cycle_time = 120 self.blink_cycles = 5 # cursor # cursor shape self.cursor = None # current cursor location self.last_row = 1 self.last_col = 1 # cursor is visible self.cursor_visible = True # buffer for text under cursor self.under_top_left = None # fonts # prebuilt glyphs self.glyph_dict = {} # joystick and mouse # available joysticks self.joysticks = [] # # get physical screen dimensions (needs to be called before set_mode) display_info = pygame.display.Info() self.physical_size = display_info.current_w, display_info.current_h # determine initial display size self.display_size = self._find_display_size(640, 480, self.border_width) self._set_icon(kwargs['icon']) # first set the screen non-resizeable, to trick things like maximus into not full-screening # I hate it when applications do this ;) try: if not self.fullscreen: pygame.display.set_mode(self.display_size, 0) self._resize_display(*self.display_size) except pygame.error: self._close_pygame() logging.warning('Could not initialise PyGame display') raise video.InitFailed() if self.smooth and self.display.get_bitsize() < 24: logging.warning( "Smooth scaling not available on this display (depth %d < 24)", self.display.get_bitsize()) self.smooth = False pygame.display.set_caption(self.caption) pygame.key.set_repeat(500, 24) # load an all-black 16-colour game palette to get started self.set_palette([(0, 0, 0)] * 16, None) pygame.joystick.init() self.joysticks = [ pygame.joystick.Joystick(x) for x in range(pygame.joystick.get_count()) ] for j in self.joysticks: j.init() # if a joystick is present, its axes report 128 for mid, not 0 for joy in range(len(self.joysticks)): for axis in (0, 1): backend.input_queue.put( backend.Event(backend.STICK_MOVED, (joy, axis, 128))) # mouse setups buttons = {'left': 1, 'middle': 2, 'right': 3, 'none': -1} copy_paste = kwargs.get('copy-paste', ('left', 'middle')) self.mousebutton_copy = buttons[copy_paste[0]] self.mousebutton_paste = buttons[copy_paste[1]] self.mousebutton_pen = buttons[kwargs.get('pen', 'right')] self.move_cursor(0, 0) self.set_page(0, 0) self.set_mode(kwargs['initial_mode']) self.f11_active = False self.altgr = kwargs['altgr'] if not self.altgr: key_to_scan[pygame.K_RALT] = scancode.ALT mod_to_scan[pygame.KMOD_RALT] = scancode.ALT backend.clipboard_handler = get_clipboard_handler()
def _unset_f12(self): """ Deactivate F12 """ if self.f12_active: backend.input_queue.put( backend.Event(backend.KEYB_UP, (scancode.F12, ))) self.f12_active = False
def _check_input(self): """ Handle screen and interface events. """ # check and handle input events self.last_down = None event = sdl2.SDL_Event() while sdl2.SDL_PollEvent(ctypes.byref(event)) != 0: if event.type == sdl2.SDL_KEYDOWN: self._handle_key_down(event) elif event.type == sdl2.SDL_KEYUP: self._handle_key_up(event) elif event.type == sdl2.SDL_TEXTINPUT: self._handle_text_input(event) elif event.type == sdl2.SDL_MOUSEBUTTONDOWN: pos = self._normalise_pos(event.button.x, event.button.y) # copy, paste and pen may be on the same button, so no elifs if event.button.button == self.mousebutton_copy: # LEFT button: copy self.clipboard.start( 1 + pos[1] // self.font_height, 1 + (pos[0] + self.font_width // 2) // self.font_width) if event.button.button == self.mousebutton_paste: # MIDDLE button: paste self.clipboard.paste(mouse=True) if event.button.button == self.mousebutton_pen: # right mouse button is a pen press backend.input_queue.put( backend.Event(backend.PEN_DOWN, pos)) elif event.type == sdl2.SDL_MOUSEBUTTONUP: backend.input_queue.put(backend.Event(backend.PEN_UP)) if event.button.button == self.mousebutton_copy: self.clipboard.copy(mouse=True) self.clipboard.stop() elif event.type == sdl2.SDL_MOUSEMOTION: pos = self._normalise_pos(event.motion.x, event.motion.y) backend.input_queue.put(backend.Event(backend.PEN_MOVED, pos)) if self.clipboard.active(): self.clipboard.move( 1 + pos[1] // self.font_height, 1 + (pos[0] + self.font_width // 2) // self.font_width) elif event.type == sdl2.SDL_JOYBUTTONDOWN: backend.input_queue.put( backend.Event(backend.STICK_DOWN, (event.jbutton.which, event.jbutton.button))) elif event.type == sdl2.SDL_JOYBUTTONUP: backend.input_queue.put( backend.Event(backend.STICK_UP, (event.jbutton.which, event.jbutton.button))) elif event.type == sdl2.SDL_JOYAXISMOTION: backend.input_queue.put( backend.Event( backend.STICK_MOVED, (event.jaxis.which, event.jaxis.axis, int((event.jaxis.value / 32768.) * 127 + 128)))) elif event.type == sdl2.SDL_WINDOWEVENT: if event.window.event == sdl2.SDL_WINDOWEVENT_RESIZED: self._resize_display(event.window.data1, event.window.data2) # unset Alt modifiers on entering/leaving the window # workaround for what seems to be an SDL2 bug # where the ALT modifier sticks on the first Alt-Tab out # of the window elif event.window.event in (sdl2.SDL_WINDOWEVENT_LEAVE, sdl2.SDL_WINDOWEVENT_ENTER, sdl2.SDL_WINDOWEVENT_FOCUS_LOST, sdl2.SDL_WINDOWEVENT_FOCUS_GAINED): sdl2.SDL_SetModState(sdl2.SDL_GetModState() & ~sdl2.KMOD_ALT) elif event.type == sdl2.SDL_QUIT: if self.nokill: self.set_caption_message( 'to exit type <CTRL+BREAK> <ESC> SYSTEM') else: backend.input_queue.put(backend.Event(backend.KEYB_QUIT)) self._flush_keypress()
def persist(self, flag): """ Set mixer persistence flag (runmode). """ backend.message_queue.put(backend.Event(backend.AUDIO_PERSIST, flag))