Ticket #2157: conio.patch

File conio.patch, 15.5 KB (added by synapsis, 8 years ago)

patch for console support

  • _pollingfile.py

     
    66 
    77""" 
    88 
     9import sys 
    910from zope.interface import implements 
    1011 
    1112from twisted.internet.interfaces import IConsumer, IPushProducer 
     
    1314MIN_TIMEOUT = 0.000000001 
    1415MAX_TIMEOUT = 0.1 
    1516 
    16 class _PollableResource: 
     17 
     18class _PollableResource(object): 
    1719    active = True 
    1820 
    1921    def activate(self): 
     
    2224    def deactivate(self): 
    2325        self.active = False 
    2426 
    25 class _PollingTimer: 
     27 
     28class _PollingTimer(object): 
    2629    # Everything is private here because it is really an implementation detail. 
    2730 
    2831    def __init__(self, reactor): 
     
    9194# If we ever (let's hope not) need the above functionality on UNIX, this could 
    9295# be factored into a different module. 
    9396 
    94 import win32pipe 
    9597import win32file 
    9698import win32api 
    9799import pywintypes 
    98100 
    99 class _PollableReadPipe(_PollableResource): 
    100101 
     102class _PollableReader(_PollableResource): 
     103 
    101104    implements(IPushProducer) 
    102105 
    103     def __init__(self, pipe, receivedCallback, lostCallback): 
    104         # security attributes for pipes 
    105         self.pipe = pipe 
     106    def __init__(self, handle, receivedCallback, lostCallback): 
     107        self.handle = handle 
    106108        self.receivedCallback = receivedCallback 
    107109        self.lostCallback = lostCallback 
    108110 
    109111    def checkWork(self): 
    110         finished = 0 
    111         fullDataRead = [] 
     112        raise NotImplementedError() 
    112113 
    113         while 1: 
    114             try: 
    115                 buffer, bytesToRead, result = win32pipe.PeekNamedPipe(self.pipe, 1) 
    116                 # finished = (result == -1) 
    117                 if not bytesToRead: 
    118                     break 
    119                 hr, data = win32file.ReadFile(self.pipe, bytesToRead, None) 
    120                 fullDataRead.append(data) 
    121             except win32api.error: 
    122                 finished = 1 
    123                 break 
    124  
    125         dataBuf = ''.join(fullDataRead) 
    126         if dataBuf: 
    127             self.receivedCallback(dataBuf) 
    128         if finished: 
    129             self.cleanup() 
    130         return len(dataBuf) 
    131  
    132114    def cleanup(self): 
    133115        self.deactivate() 
    134116        self.lostCallback() 
    135117 
    136118    def close(self): 
    137119        try: 
    138             win32api.CloseHandle(self.pipe) 
     120            win32api.CloseHandle(self.handle) 
    139121        except pywintypes.error: 
    140122            # You can't close std handles...? 
    141123            pass 
     
    152134 
    153135FULL_BUFFER_SIZE = 64 * 1024 
    154136 
    155 class _PollableWritePipe(_PollableResource): 
     137class _PollableWriter(_PollableResource): 
    156138 
    157139    implements(IConsumer) 
    158140 
    159     def __init__(self, writePipe, lostCallback): 
     141    def __init__(self, handle, lostCallback): 
    160142        self.disconnecting = False 
    161143        self.producer = None 
    162144        self.producerPaused = 0 
    163145        self.streamingProducer = 0 
    164146        self.outQueue = [] 
    165         self.writePipe = writePipe 
     147        self.handle = handle 
    166148        self.lostCallback = lostCallback 
    167         try: 
    168             win32pipe.SetNamedPipeHandleState(writePipe, 
    169                                               win32pipe.PIPE_NOWAIT, 
    170                                               None, 
    171                                               None) 
    172         except pywintypes.error: 
    173             # Maybe it's an invalid handle.  Who knows. 
    174             pass 
    175149 
    176150    def close(self): 
    177151        self.disconnecting = True 
     
    219193    def writeConnectionLost(self): 
    220194        self.deactivate() 
    221195        try: 
    222             win32api.CloseHandle(self.writePipe) 
     196            win32api.CloseHandle(self.handle) 
    223197        except pywintypes.error: 
    224198            # OMG what 
    225199            pass 
     
    236210            self.bufferFull() 
    237211 
    238212    def checkWork(self): 
     213        raise NotImplementedError() 
     214 
     215 
     216 
     217# 
     218# Pipe support (XXX this should go in _dumbwin32proc.py) 
     219#  
     220import win32pipe 
     221 
     222 
     223class _PollableReadPipe(_PollableReader): 
     224    def __init__(self, pipe, receivedCallback, lostCallback): 
     225        _PollableReader.__init__(self, pipe, receivedCallback, lostCallback) 
     226        # security attributes for pipes 
     227 
     228    def checkWork(self): 
     229        finished = 0 
     230        fullDataRead = [] 
     231 
     232        while 1: 
     233            try: 
     234                buffer, bytesToRead, result = win32pipe.PeekNamedPipe(self.handle, 1) 
     235                # finished = (result == -1) 
     236                if not bytesToRead: 
     237                    break 
     238                hr, data = win32file.ReadFile(self.handle, bytesToRead, None) 
     239                fullDataRead.append(data) 
     240            except win32api.error: 
     241                finished = 1 
     242                break 
     243 
     244        dataBuf = ''.join(fullDataRead) 
     245        if dataBuf: 
     246            self.receivedCallback(dataBuf) 
     247        if finished: 
     248            self.cleanup() 
     249        return len(dataBuf) 
     250 
     251 
     252class _PollableWritePipe(_PollableWriter): 
     253    def __init__(self, writePipe, lostCallback): 
     254        _PollableWriter.__init__(self, writePipe, lostCallback) 
     255 
     256        try: 
     257            win32pipe.SetNamedPipeHandleState(writePipe, 
     258                                              win32pipe.PIPE_NOWAIT, 
     259                                              None, 
     260                                              None) 
     261        except pywintypes.error: 
     262            # Maybe it's an invalid handle.  Who knows. 
     263            pass 
     264 
     265    def checkWork(self): 
    239266        numBytesWritten = 0 
    240267        if not self.outQueue: 
    241268            if self.disconnecting: 
    242269                self.writeConnectionLost() 
    243270                return 0 
    244271            try: 
    245                 win32file.WriteFile(self.writePipe, '', None) 
     272                win32file.WriteFile(self.handle, '', None) 
    246273            except pywintypes.error: 
    247274                self.writeConnectionLost() 
    248275                return numBytesWritten 
     
    250277            data = self.outQueue.pop(0) 
    251278            errCode = 0 
    252279            try: 
    253                 errCode, nBytesWritten = win32file.WriteFile(self.writePipe, 
     280                errCode, nBytesWritten = win32file.WriteFile(self.handle, 
    254281                                                             data, None) 
    255282            except win32api.error: 
    256283                self.writeConnectionLost() 
     
    268295        return numBytesWritten 
    269296 
    270297 
     298 
     299# 
     300# Console support (XXX this should go in _conio.py) 
     301# 
     302# Only base support provided. 
     303# 
     304import win32console 
     305 
     306 
     307ENABLE_NORMAL_MODE = win32console.ENABLE_ECHO_INPUT | win32console.ENABLE_LINE_INPUT 
     308ENABLE_WINDOW_INPUT = win32console.ENABLE_WINDOW_INPUT 
     309 
     310 
     311class _PollableReadConsole(_PollableReader): 
     312    def __init__(self, conIn, conOut, receivedCallback, lostCallback): 
     313        _PollableReader.__init__(self, conIn, receivedCallback, lostCallback) 
     314        self.cp = "cp%d" % win32console.GetConsoleCP() 
     315        self.buf = [] 
     316         
     317        # We need this for echoing 
     318        self._stdout = conOut 
     319 
     320        defaultMode = conIn.GetConsoleMode() 
     321        conIn.SetConsoleMode(defaultMode | ENABLE_WINDOW_INPUT) 
     322        self._windowChangeCallback = None 
     323         
     324        self.checkWork = self._checkWork 
     325 
     326    def enableRawMode(self, enabled=True): 
     327        """Enable raw mode. 
     328        """ 
     329 
     330        mode = self.handle.GetConsoleMode() 
     331        if enabled: 
     332            # Flush buffer 
     333            dataBuf = ''.join(self.buf) 
     334            self.buf = [] 
     335 
     336            if dataBuf: 
     337                self.receivedCallback(dataBuf) 
     338 
     339            self.checkWork = self._checkWork_raw 
     340 
     341            # Set mode on the system console, too 
     342            # XXX check me (this seems not to work) 
     343            self.handle.SetConsoleMode(mode & ~ENABLE_NORMAL_MODE) 
     344        else: 
     345            self.checkWork = self._checkWork 
     346             
     347            # Set mode on the system console, too 
     348            self.handle.SetConsoleMode(mode | ENABLE_NORMAL_MODE) 
     349 
     350    def setWindowChandeCallback(self, callback): 
     351        """callback is called when the console window buffer is 
     352        changed. 
     353 
     354        Note: WINDOW_BUFFER_SIZE_EVENT is only raised when changing 
     355              the window *buffer* size from the console menu 
     356        """ 
     357         
     358        self._windowChangeCallback = callback 
     359 
     360             
     361    def _checkWork(self): 
     362        try: 
     363            info = self._stdout.GetConsoleScreenBufferInfo() 
     364        except pywintypes.error: 
     365            # stdout handle has been closed 
     366            self.cleanup() 
     367            return 0 
     368             
     369        rowSize = info["MaximumWindowSize"].X  
     370 
     371        # How much data we read 
     372        workUnits = 0 
     373         
     374        # Initialize the current cursor position 
     375        if not self.buf: 
     376            self.pos = info["CursorPosition"] 
     377             
     378        while 1: 
     379            n = self.handle.GetNumberOfConsoleInputEvents() 
     380            if n == 0: 
     381                break 
     382                 
     383            records = self.handle.ReadConsoleInput(n) 
     384                 
     385            # Process input 
     386            for record in records: 
     387                if record.EventType == win32console.WINDOW_BUFFER_SIZE_EVENT: 
     388                    rowSize = record.Size.X 
     389                    if self._windowChangeCallback: 
     390                        self._windowChangeCallback() 
     391                if record.EventType != win32console.KEY_EVENT \ 
     392                        or not record.KeyDown: 
     393                    continue 
     394 
     395                char = record.Char 
     396                n = record.RepeatCount 
     397                if char == '\b': 
     398                    pos = self._stdout.GetConsoleScreenBufferInfo()["CursorPosition"] 
     399                     
     400                    # Move the cursor 
     401                    x = pos.X - n 
     402                    if x >= 0: 
     403                        pos.X = x 
     404                    # XXX assuming |x| < rowSize (I'm lazy) 
     405                    elif pos.Y > self.pos.Y: 
     406                        pos.X = rowSize - 1 
     407                        pos.Y -= 1 
     408 
     409                    self._stdout.SetConsoleCursorPosition(pos) 
     410                    self._stdout.WriteConsoleOutputCharacter(' ' * n, pos) 
     411                     
     412                    self.buf = self.buf[:-n] 
     413                    continue 
     414                elif char == '\0': 
     415                    vCode = record.VirtualKeyCode 
     416                    # XXX TODO handle keyboard navigation 
     417                    continue 
     418                elif char == '\r': 
     419                    char = '\r\n' * n # XXX check me 
     420 
     421                    self.buf.append(char) 
     422                    self._stdout.WriteConsole(char) # do echo 
     423                     
     424                    dataBuf = ''.join(self.buf) 
     425                    self.buf = [] 
     426                    self.pos = info["CursorPosition"] 
     427 
     428                    self.receivedCallback(dataBuf) 
     429                    return len(dataBuf) 
     430 
     431                char = char * n 
     432                data = char.encode(self.cp) 
     433                self._stdout.WriteConsole(data) # do echo 
     434                 
     435                self.buf.append(data) 
     436                workUnits += n 
     437 
     438        return workUnits 
     439 
     440    def _checkWork_raw(self): 
     441        # local buffer 
     442        buf = [] 
     443 
     444        while 1: # XXX is this loop really needed? 
     445            n = self.handle.GetNumberOfConsoleInputEvents() 
     446            if n == 0: 
     447                break 
     448                 
     449            records = self.handle.ReadConsoleInput(n) 
     450                 
     451            # Process input 
     452            for record in records: 
     453                if record.EventType == win32console.WINDOW_BUFFER_SIZE_EVENT: 
     454                    if self._windowChangeCallback: 
     455                        self._windowChangeCallback() 
     456                if record.EventType != win32console.KEY_EVENT \ 
     457                        or not record.KeyDown: 
     458                    continue 
     459 
     460                char = record.Char 
     461                n = record.RepeatCount 
     462                if char == '\0': 
     463                    continue 
     464                elif char == '\r': 
     465                    char = '\r\n' * n # XXX check me 
     466 
     467                char = char * n 
     468                data = char.encode(self.cp) 
     469                 
     470                buf.append(data) 
     471 
     472 
     473        dataBuf = ''.join(buf) 
     474        if dataBuf: 
     475            self.receivedCallback(dataBuf) 
     476 
     477        return len(dataBuf) 
     478 
     479 
     480class _PollableWriteConsole(_PollableWriter): 
     481    def __init__(self, con, lostCallback): 
     482        _PollableWriter.__init__(self, con, lostCallback) 
     483 
     484        self.cp = "cp%d" % win32console.GetConsoleOutputCP() 
     485 
     486 
     487    def checkWork(self): 
     488        numBytesWritten = 0 
     489        if not self.outQueue: 
     490            if self.disconnecting: 
     491                self.writeConnectionLost() 
     492                return 0 
     493            try: 
     494                self.handle.WriteConsole('') 
     495            except pywintypes.error: 
     496                self.writeConnectionLost() 
     497                return numBytesWritten 
     498        while self.outQueue: 
     499            data = self.outQueue.pop(0) 
     500            errCode = 0 
     501            try: 
     502                # XXX check if this can block 
     503                nBytesWritten = self.handle.WriteConsole(data) 
     504            except win32console.error: 
     505                self.writeConnectionLost() 
     506                break 
     507            else: 
     508                # assert not errCode, "wtf an error code???" 
     509                numBytesWritten += nBytesWritten 
     510                if len(data) > nBytesWritten: 
     511                    self.outQueue.insert(0, data[nBytesWritten:]) 
     512                    break 
     513        else: 
     514            resumed = self.bufferEmpty() 
     515            if not resumed and self.disconnecting: 
     516                self.writeConnectionLost() 
     517        return numBytesWritten 
  • _win32stdio.py

     
    11# -*- test-case-name: twisted.test.test_process.ProcessTestCase.testStdio -*- 
    22 
    33import win32api 
     4import win32console 
    45import os, msvcrt 
    56 
    67from zope.interface import implements 
    78 
    89from twisted.internet.interfaces import IHalfCloseableProtocol, ITransport, IAddress 
    910from twisted.internet.interfaces import IConsumer, IPushProducer 
     11from twisted.internet import main 
    1012 
    11 from twisted.internet import _pollingfile, main 
     13import _pollingfile 
    1214 
     15 
    1316class Win32PipeAddress(object): 
    1417    implements(IAddress) 
    1518 
     
    3538 
    3639        _pollingfile._PollingTimer.__init__(self, reactor) 
    3740        self.proto = proto 
     41         
     42        # Check if we are connected to a console 
     43        if os.isatty(0): 
     44            hstdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE) 
     45            hstdout = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) 
    3846 
    39         hstdin = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE) 
    40         hstdout = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE) 
     47            self.stdin = _pollingfile._PollableReadConsole( 
     48                hstdin, hstdout, self.dataReceived, self.readConnectionLost) 
    4149 
    42         self.stdin = _pollingfile._PollableReadPipe( 
    43             hstdin, self.dataReceived, self.readConnectionLost) 
     50            self.stdout = _pollingfile._PollableWriteConsole( 
     51                hstdout, self.writeConnectionLost) 
     52        else: 
     53            hstdin = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE) 
     54            hstdout = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE) 
    4455 
    45         self.stdout = _pollingfile._PollableWritePipe( 
    46             hstdout, self.writeConnectionLost) 
     56            self.stdin = _pollingfile._PollableReadPipe( 
     57                hstdin, self.dataReceived, self.readConnectionLost) 
    4758 
     59            self.stdout = _pollingfile._PollableWritePipe( 
     60                hstdout, self.writeConnectionLost) 
     61             
    4862        self._addPollableResource(self.stdin) 
    4963        self._addPollableResource(self.stdout) 
    5064 
     
    113127 
    114128    def resumeProducing(self): 
    115129        self.stdin.resumeProducing() 
    116