Ticket #2157: conio.patch

File conio.patch, 15.5 KB (added by synapsis, 10 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