コード例 #1
0
        # }}}

        ##############################################################
        # 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
コード例 #2
0
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