# }}} ############################################################## # Create the subprocess # {{{ proc = ConqueSoleSubprocess() res = proc.open(cmd, mem_key, options) if not res: logging.debug('process failed to open') exit() shm_command = ConqueSoleSharedMemory(CONQUE_SOLE_COMMANDS_SIZE, 'command', mem_key, serialize = True) shm_command.create('write') shm_command.clear() # }}} ############################################################## # main loop! loops = 0 while True: # check for idle/resume if is_idle or loops % 25 == 0: # check process health
class ConqueSoleSubprocess(): # Class properties {{{ #window = None handle = None pid = None # input / output handles stdin = None stdout = None # size of console window window_width = 160 window_height = 40 # max lines for the console buffer buffer_width = 160 buffer_height = 100 # keep track of the buffer number at the top of the window top = 0 line_offset = 0 # buffer height is CONQUE_SOLE_BUFFER_LENGTH * output_blocks output_blocks = 1 # cursor position cursor_line = 0 cursor_col = 0 # console data, array of lines data = [] # console attribute data, array of array of int attributes = [] attribute_cache = {} # default attribute default_attribute = 7 # shared memory objects shm_input = None shm_output = None shm_attributes = None shm_stats = None shm_command = None shm_rescroll = None shm_resize = None # are we still a valid process? is_alive = True # used for periodic execution of screen and memory redrawing screen_redraw_ct = 0 mem_redraw_ct = 0 # }}} # **************************************************************************** # initialize class instance def __init__ (self): # {{{ pass # }}} # **************************************************************************** # Create proccess cmd def open(self, cmd, mem_key, options = {}): # {{{ logging.debug('cmd is: ' + cmd) self.reset = True try: # if we're already attached to a console, then unattach try: ctypes.windll.kernel32.FreeConsole() except: pass # set buffer height self.buffer_height = CONQUE_SOLE_BUFFER_LENGTH logging.debug(str(options)) if 'LINES' in options and 'COLUMNS' in options: self.window_width = options['COLUMNS'] self.window_height = options['LINES'] self.buffer_width = options['COLUMNS'] # console window options si = STARTUPINFO() # hide window si.dwFlags |= STARTF_USESHOWWINDOW #si.wShowWindow = SW_HIDE si.wShowWindow = SW_MINIMIZE # process options flags = NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE # created process info pi = PROCESS_INFORMATION() logging.debug('using path ' + os.path.abspath('.')) # create the process! res = ctypes.windll.kernel32.CreateProcessW(None, u(cmd), None, None, 0, flags, None, u('.'), ctypes.byref(si), ctypes.byref(pi)) logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) self.pid = pi.dwProcessId self.handle = pi.hProcess logging.debug(str(self.pid)) logging.debug(str(self.handle)) # attach ourselves to the new console # console is not immediately available for i in range(10): time.sleep(1) try: logging.debug('attempt ' + str(i)) res = ctypes.windll.kernel32.AttachConsole(self.pid) logging.debug('attach result') logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) break except: logging.debug(traceback.format_exc()) pass # get input / output handles self.stdout = ctypes.windll.kernel32.GetStdHandle (STD_OUTPUT_HANDLE) self.stdin = ctypes.windll.kernel32.GetStdHandle (STD_INPUT_HANDLE) # set buffer size size = COORD (self.buffer_width, self.buffer_height) res = ctypes.windll.kernel32.SetConsoleScreenBufferSize (self.stdout, size) logging.debug('buffer size: ' + str(size.to_str())) logging.debug('size result') logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) # prev set size call needs to process time.sleep(0.2) # set window size self.set_window_size(self.window_width, self.window_height) # init shared memory self.init_shared_memory(mem_key) # init read buffers self.tc = ctypes.create_unicode_buffer(self.buffer_width) self.ac = ctypes.create_unicode_buffer(self.buffer_width) return True except: logging.debug(traceback.format_exc()) return False # }}} # **************************************************************************** # create shared memory objects def init_shared_memory(self, mem_key): # {{{ buf_info = self.get_buffer_info() logging.debug('-------------------------------------') logging.debug(buf_info.to_str()) logging.debug('-------------------------------------') self.shm_input = ConqueSoleSharedMemory(CONQUE_SOLE_INPUT_SIZE, 'input', mem_key) self.shm_input.create('write') self.shm_input.clear() self.shm_output = ConqueSoleSharedMemory(self.buffer_height * self.buffer_width, 'output', mem_key, True) self.shm_output.create('write') self.shm_output.clear() self.shm_attributes = ConqueSoleSharedMemory(self.buffer_height * self.buffer_width, 'attributes', mem_key, True, chr(buf_info.wAttributes), encoding = 'latin-1') self.shm_attributes.create('write') self.shm_attributes.clear() self.shm_stats = ConqueSoleSharedMemory(CONQUE_SOLE_STATS_SIZE, 'stats', mem_key, serialize = True) self.shm_stats.create('write') self.shm_stats.clear() self.shm_command = ConqueSoleSharedMemory(CONQUE_SOLE_COMMANDS_SIZE, 'command', mem_key, serialize = True) self.shm_command.create('write') self.shm_command.clear() self.shm_resize = ConqueSoleSharedMemory(CONQUE_SOLE_RESIZE_SIZE, 'resize', mem_key, serialize = True) self.shm_resize.create('write') self.shm_resize.clear() self.shm_rescroll = ConqueSoleSharedMemory(CONQUE_SOLE_RESCROLL_SIZE, 'rescroll', mem_key, serialize = True) self.shm_rescroll.create('write') self.shm_rescroll.clear() return True # }}} # **************************************************************************** # check for and process commands def check_commands(self): # {{{ cmd = self.shm_command.read() if cmd: # clear command self.shm_command.clear() # shut it all down if cmd['cmd'] == 'close': self.close() return cmd = self.shm_resize.read() if cmd: # clear command self.shm_resize.clear() # resize console if cmd['cmd'] == 'resize': logging.debug('resizing window to ' + str(cmd['data']['width']) + 'x' + str(cmd['data']['height'])) # only change buffer width if it's larger if cmd['data']['width'] > self.buffer_width: self.buffer_width = cmd['data']['width'] # always change console width and height self.window_width = cmd['data']['width'] self.window_height = cmd['data']['height'] # reset the console buf_info = self.get_buffer_info() self.reset_console(buf_info, add_block = False) # }}} # **************************************************************************** # read from windows console and update output buffer def read(self, timeout = 0): # {{{ # no point really if self.screen_redraw_ct == 0 and not self.is_alive(): stats = { 'top_offset' : 0, 'default_attribute' : 0, 'cursor_x' : 0, 'cursor_y' : self.cursor_line, 'is_alive' : 0 } logging.debug('is dead') self.shm_stats.write(stats) return # check for commands self.check_commands() # emulate timeout by sleeping timeout time if timeout > 0: read_timeout = float(timeout) / 1000 #logging.debug("sleep " + str(read_timeout) + " seconds") time.sleep(read_timeout) # get cursor position buf_info = self.get_buffer_info() curs_line = buf_info.dwCursorPosition.Y curs_col = buf_info.dwCursorPosition.X # set update range if curs_line != self.cursor_line or self.top != buf_info.srWindow.Top or self.screen_redraw_ct == CONQUE_SOLE_SCREEN_REDRAW: self.screen_redraw_ct = 0 logging.debug('screen redraw') read_start = self.top read_end = buf_info.srWindow.Bottom + 1 else: logging.debug('no screen redraw') read_start = curs_line read_end = curs_line + 1 # vars used in for loop coord = COORD (0, 0) chars_read = ctypes.c_int(0) # read new data for i in range(read_start, read_end): coord.Y = i res = ctypes.windll.kernel32.ReadConsoleOutputCharacterW (self.stdout, ctypes.byref(self.tc), self.buffer_width, coord, ctypes.byref(chars_read)) ctypes.windll.kernel32.ReadConsoleOutputAttribute (self.stdout, ctypes.byref(self.ac), self.buffer_width, coord, ctypes.byref(chars_read)) t = self.tc.value a = self.ac.value #logging.debug(str(chars_read)) #logging.debug("line " + str(i) + " is: " + str(self.tc.value)) #logging.debug("attributes " + str(i) + " is: " + str(a)) # add data if i >= len(self.data): self.data.append(t) self.attributes.append(a) else: self.data[i] = t self.attributes[i] = a # write new output to shared memory if self.mem_redraw_ct == CONQUE_SOLE_MEM_REDRAW: self.mem_redraw_ct = 0 logging.debug('mem redraw') self.shm_output.write(''.join(self.data)) self.shm_attributes.write(''.join(self.attributes)) else: logging.debug('no mem redraw') self.shm_output.write(text = ''.join(self.data[read_start : read_end]), start = read_start * self.buffer_width) self.shm_attributes.write(text = ''.join(self.attributes[read_start : read_end]), start = read_start * self.buffer_width) # write cursor position to shared memory stats = { 'top_offset' : buf_info.srWindow.Top, 'default_attribute' : buf_info.wAttributes, 'cursor_x' : curs_col, 'cursor_y' : curs_line, 'is_alive' : 1 } self.shm_stats.write(stats) #logging.debug('wtf cursor: ' + str(buf_info)) # adjust screen position self.top = buf_info.srWindow.Top self.cursor_line = curs_line # check for reset if curs_line > buf_info.dwSize.Y - 200: self.reset_console(buf_info) # increment redraw counters self.screen_redraw_ct += 1 self.mem_redraw_ct += 1 return None # }}} # **************************************************************************** # clear the console and set cursor at home position def reset_console(self, buf_info, add_block = True): # {{{ # sometimes we just want to change the buffer width, # in which case no need to add another block if add_block: self.output_blocks += 1 # close down old memory self.shm_output.close() self.shm_output = None self.shm_attributes.close() self.shm_attributes = None # new shared memory key mem_key = 'mk' + str(time.time()) # reallocate memory self.shm_output = ConqueSoleSharedMemory(self.buffer_height * self.buffer_width * self.output_blocks, 'output', mem_key, True) self.shm_output.create('write') self.shm_output.clear() # backfill data if len(self.data[0]) < self.buffer_width: for i in range(0, len(self.data)): self.data[i] = self.data[i] + ' ' * (self.buffer_width - len(self.data[i])) self.shm_output.write(''.join(self.data)) self.shm_attributes = ConqueSoleSharedMemory(self.buffer_height * self.buffer_width * self.output_blocks, 'attributes', mem_key, True, chr(buf_info.wAttributes), encoding = 'latin-1') self.shm_attributes.create('write') self.shm_attributes.clear() # backfill attributes if len(self.attributes[0]) < self.buffer_width: for i in range(0, len(self.attributes)): self.attributes[i] = self.attributes[i] + chr(buf_info.wAttributes) * (self.buffer_width - len(self.attributes[i])) self.shm_attributes.write(''.join(self.attributes)) # notify wrapper of new output block self.shm_rescroll.write ({ 'cmd' : 'new_output', 'data' : {'blocks' : self.output_blocks, 'mem_key' : mem_key } }) # set buffer size size = COORD(X=self.buffer_width, Y=self.buffer_height * self.output_blocks) logging.debug('new buffer size: ' + str(size)) res = ctypes.windll.kernel32.SetConsoleScreenBufferSize (self.stdout, size) logging.debug('buf size result') logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) # prev set size call needs to process time.sleep(0.2) # set window size self.set_window_size(self.window_width, self.window_height) # init read buffers self.tc = ctypes.create_unicode_buffer(self.buffer_width) self.ac = ctypes.create_unicode_buffer(self.buffer_width) # }}} # **************************************************************************** # write text to console. this function just parses out special sequences for # special key events and passes on the text to the plain or virtual key functions def write (self): # {{{ # get input from shared mem text = self.shm_input.read() # nothing to do here if text == '': return logging.debug('writing: ' + text) # clear input queue self.shm_input.clear() # split on VK codes chunks = CONQUE_SEQ_REGEX_VK.split(text) # if len() is one then no vks if len(chunks) == 1: self.write_plain(text) return logging.debug('split!: ' + str(chunks)) # loop over chunks and delegate for t in chunks: if t == '': continue if CONQUE_SEQ_REGEX_VK.match(t): logging.debug('match!: ' + str(t[2:-2])) self.write_vk(t[2:-2]) else: self.write_plain(t) # }}} # **************************************************************************** def write_plain (self, text): # {{{ li = INPUT_RECORD * len(text) list_input = li() for i in range(0, len(text)): # create keyboard input ke = KEY_EVENT_RECORD() ke.bKeyDown = ctypes.c_byte(1) ke.wRepeatCount = ctypes.c_short(1) cnum = ord(text[i]) ke.wVirtualKeyCode = ctypes.windll.user32.VkKeyScanA(cnum) if cnum > 31: ke.uChar.UnicodeChar = unichr(cnum) elif cnum == 3: ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, self.pid) continue else: ke.uChar.UnicodeChar = unichr(cnum) if str(cnum) in CONQUE_WINDOWS_VK: ke.wVirtualKeyCode = CONQUE_WINDOWS_VK[str(cnum)] else: ke.wVirtualKeyCode = ctypes.windll.user32.VkKeyScanA(cnum + 96) ke.dwControlKeyState = LEFT_CTRL_PRESSED kc = INPUT_RECORD(KEY_EVENT) kc.Event.KeyEvent = ke list_input[i] = kc #logging.debug(kc.to_str()) # write input array events_written = ctypes.c_int() res = ctypes.windll.kernel32.WriteConsoleInputW(self.stdin, list_input, len(text), ctypes.byref(events_written)) logging.debug('foo') logging.debug('events written ' + str(events_written)) logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) # }}} # **************************************************************************** def write_vk (self, vk_code): # {{{ li = INPUT_RECORD * 1 # create keyboard input ke = KEY_EVENT_RECORD() ke.wVirtualKeyCode = ctypes.c_short(CONQUE_WINDOWS_VK[vk_code]) ke.bKeyDown = ctypes.c_byte(1) ke.wRepeatCount = ctypes.c_short(1) kc = INPUT_RECORD(KEY_EVENT) kc.Event.KeyEvent = ke list_input = li(kc) # write input array events_written = ctypes.c_int() res = ctypes.windll.kernel32.WriteConsoleInputW(self.stdin, list_input, 1, ctypes.byref(events_written)) logging.debug('bar') logging.debug('events written ' + str(events_written)) logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) # }}} # **************************************************************************** def close(self): # {{{ # record status self.is_alive = False try: stats = { 'top_offset' : 0, 'default_attribute' : 0, 'cursor_x' : 0, 'cursor_y' : self.cursor_line, 'is_alive' : 0 } self.shm_stats.write(stats) except: pass pid_list = (ctypes.c_int * 10)() num = ctypes.windll.kernel32.GetConsoleProcessList(pid_list, 10) logging.debug("\n".join(self.data)) current_pid = os.getpid() logging.debug('closing down!') logging.debug(str(self.pid)) logging.debug(str(pid_list)) # kill subprocess pids for pid in pid_list[0:num]: if not pid: break # kill current pid last if pid == current_pid: continue try: self.close_pid(pid) except: logging.debug(traceback.format_exc()) pass # kill this process try: self.close_pid(current_pid) except: logging.debug(traceback.format_exc()) pass def close_pid (self, pid) : logging.debug('killing pid ' + str(pid)) handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, 0, pid) ctypes.windll.kernel32.TerminateProcess(handle, -1) ctypes.windll.kernel32.CloseHandle(handle) # }}} # **************************************************************************** # check process health def is_alive(self): # {{{ status = ctypes.windll.kernel32.WaitForSingleObject(self.handle, 1) if status == 0: logging.debug('process is no longer alive!') self.is_alive = False return self.is_alive # }}} # **************************************************************************** # return screen data as string def get_screen_text(self): # {{{ return "\n".join(self.data) # }}} # **************************************************************************** def set_window_size(self, width, height): # {{{ logging.debug('*** setting window size') # get current window size object window_size = SMALL_RECT(0, 0, 0, 0) # buffer info has maximum window size data buf_info = self.get_buffer_info() logging.debug(str(buf_info.to_str())) # set top left corner window_size.Top = 0 window_size.Left = 0 # set bottom right corner if buf_info.dwMaximumWindowSize.X < width: logging.debug(str(buf_info.dwMaximumWindowSize.X) + '<' + str(width)) window_size.Right = buf_info.dwMaximumWindowSize.X - 1 else: window_size.Right = width - 1 if buf_info.dwMaximumWindowSize.Y < height: logging.debug('b') window_size.Bottom = buf_info.dwMaximumWindowSize.Y - 1 else: window_size.Bottom = height - 1 logging.debug('window size: ' + str(window_size.to_str())) # set the window size! res = ctypes.windll.kernel32.SetConsoleWindowInfo (self.stdout, ctypes.c_bool(True), ctypes.byref(window_size)) logging.debug('win size result') logging.debug(str(res)) logging.debug(str(ctypes.GetLastError())) logging.debug(str(ctypes.FormatError(ctypes.GetLastError()))) # reread buffer info to get final console max lines buf_info = self.get_buffer_info() logging.debug('buffer size: ' + str(buf_info)) self.window_width = buf_info.srWindow.Right + 1 self.window_height = buf_info.srWindow.Bottom + 1 # }}} # **************************************************************************** # get buffer info, used a lot def get_buffer_info(self): # {{{ buf_info = CONSOLE_SCREEN_BUFFER_INFO() ctypes.windll.kernel32.GetConsoleScreenBufferInfo(self.stdout, ctypes.byref(buf_info)) return buf_info