src/extras/pyrepl/socket_console.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     1 #
       
     2 # This is a simple console class, which tries to handle a VT100-workalike
       
     3 # terminal at the other end of a given socket connection.
       
     4 #
       
     5 # Limitations:
       
     6 #  - currently the remote screen size is assumed to be 80x25
       
     7 #  - sockets are assumed to be blocking (since the platform in mind didn't
       
     8 #    have nonblocking sockets)
       
     9 #
       
    10 # Portions Copyright (c) 2005 Nokia Corporation 
       
    11 #
       
    12 # The code is derived from unix_console.py, which contained the
       
    13 # following copyright notice:
       
    14 #
       
    15 #   Copyright 2000-2004 Michael Hudson mwh@python.net
       
    16 #
       
    17 #                        All Rights Reserved
       
    18 #
       
    19 #
       
    20 # Permission to use, copy, modify, and distribute this software and
       
    21 # its documentation for any purpose is hereby granted without fee,
       
    22 # provided that the above copyright notice appear in all copies and
       
    23 # that both that copyright notice and this permission notice appear in
       
    24 # supporting documentation.
       
    25 #
       
    26 # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
       
    27 # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
       
    28 # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
       
    29 # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
       
    30 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
       
    31 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
       
    32 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       
    33 
       
    34 import sys
       
    35 from pyrepl.console import Console, Event
       
    36 from pyrepl import unix_eventqueue
       
    37 import dumbcurses as curses
       
    38 import socket
       
    39 
       
    40 def _my_getstr(cap, optional=0):
       
    41     r = curses.tigetstr(cap)
       
    42     if not optional and r is None:
       
    43         raise RuntimeError, \
       
    44               "terminal doesn't have the required '%s' capability"%cap
       
    45     return r
       
    46 
       
    47 class SocketConsole(Console):
       
    48     MAX_READ=16
       
    49     def __init__(self, socket, encoding=None):
       
    50 #        if encoding is None:
       
    51 #            encoding = sys.getdefaultencoding()
       
    52             
       
    53 #        self.encoding = encoding
       
    54         #print >>sys.stderr,"Encoding "+encoding
       
    55         self.encoding='latin-1'
       
    56 
       
    57         self._socket=socket
       
    58 
       
    59         self.__buffer = []
       
    60         
       
    61         self._bel   = _my_getstr("bel")
       
    62         self._civis = _my_getstr("civis", optional=1)
       
    63         self._clear = _my_getstr("clear")
       
    64         self._cnorm = _my_getstr("cnorm", optional=1)
       
    65         self._cub   = _my_getstr("cub",   optional=1)
       
    66         self._cub1  = _my_getstr("cub1",  1)
       
    67         self._cud   = _my_getstr("cud",   1)
       
    68         self._cud1  = _my_getstr("cud1",  1)
       
    69         self._cuf   = _my_getstr("cuf",   1)
       
    70         self._cuf1  = _my_getstr("cuf1",  1)
       
    71         self._cup   = _my_getstr("cup")
       
    72         self._cuu   = _my_getstr("cuu",   1)
       
    73         self._cuu1  = _my_getstr("cuu1",  1)
       
    74         self._dch1  = _my_getstr("dch1",  1)
       
    75         self._dch   = _my_getstr("dch",   1)
       
    76         self._el    = _my_getstr("el")
       
    77         self._hpa   = _my_getstr("hpa",   1)
       
    78         self._ich   = _my_getstr("ich",   1)
       
    79         self._ich1  = _my_getstr("ich1",  1)
       
    80         self._ind   = _my_getstr("ind",   1)
       
    81         self._pad   = _my_getstr("pad",   1)
       
    82         self._ri    = _my_getstr("ri",    1)
       
    83         self._rmkx  = _my_getstr("rmkx",  1)
       
    84         self._smkx  = _my_getstr("smkx",  1)
       
    85         
       
    86         ## work out how we're going to sling the cursor around
       
    87         if 0 and self._hpa: # hpa don't work in windows telnet :-(
       
    88             self.__move_x = self.__move_x_hpa
       
    89         elif self._cub and self._cuf:
       
    90             self.__move_x = self.__move_x_cub_cuf
       
    91         elif self._cub1 and self._cuf1:
       
    92             self.__move_x = self.__move_x_cub1_cuf1
       
    93         else:
       
    94             raise RuntimeError, "insufficient terminal (horizontal)"
       
    95 
       
    96         if self._cuu and self._cud:
       
    97             self.__move_y = self.__move_y_cuu_cud
       
    98         elif self._cuu1 and self._cud1:
       
    99             self.__move_y = self.__move_y_cuu1_cud1
       
   100         else:
       
   101             raise RuntimeError, "insufficient terminal (vertical)"
       
   102 
       
   103         if self._dch1:
       
   104             self.dch1 = self._dch1
       
   105         elif self._dch:
       
   106             self.dch1 = curses.tparm(self._dch, 1)
       
   107         else:
       
   108             self.dch1 = None
       
   109 
       
   110         if self._ich1:
       
   111             self.ich1 = self._ich1
       
   112         elif self._ich:
       
   113             self.ich1 = curses.tparm(self._ich, 1)
       
   114         else:
       
   115             self.ich1 = None
       
   116 
       
   117         self.__move = self.__move_short
       
   118 
       
   119         self.event_queue = unix_eventqueue.EventQueue()
       
   120         self.busy=False
       
   121 
       
   122     def _oswrite(self,str):
       
   123         try:
       
   124             self._socket.send(str)
       
   125         except socket.error:
       
   126             raise IOError("Socket error: %s %s"%(sys.exc_info()[0:2]))
       
   127 
       
   128     def _osread(self,n=1):
       
   129         try:
       
   130             out=self._socket.recv(n)
       
   131         except socket.error:
       
   132             raise EOFError("Socket error: %s %s"%(sys.exc_info()[0:2]))
       
   133         return out
       
   134 
       
   135     def write(self,str):
       
   136         self._oswrite(str.replace('\n','\n\r'))
       
   137 
       
   138     def flush(self):
       
   139         self.flushoutput()
       
   140         
       
   141     def read(self,n=1):        
       
   142         return self._osread(n)
       
   143 
       
   144     def readline(self,n=None):
       
   145         line=[]
       
   146         while 1:
       
   147             ch=self.read(1)
       
   148             line.append(ch)
       
   149             self.write(ch)
       
   150             if ch == '\n':
       
   151                 break
       
   152             if n and len(line)>=n:
       
   153                 break
       
   154         return ''.join(line)
       
   155 
       
   156     # No readlines() because reading until EOF doesn't make sense
       
   157     # for the console.
       
   158     
       
   159     def isatty(self):
       
   160         return True
       
   161 
       
   162     def writelines(self,seq):
       
   163         for k in seq:
       
   164             self.write(k)
       
   165 
       
   166     def change_encoding(self, encoding):
       
   167         self.encoding = encoding
       
   168     
       
   169     def refresh(self, screen, (cx, cy)):
       
   170         # this function is still too long (over 90 lines)
       
   171         self.__maybe_write_code(self._civis)
       
   172 
       
   173         if not self.__gone_tall:
       
   174             while len(self.screen) < min(len(screen), self.height):
       
   175                 self.__move(0, len(self.screen) - 1)
       
   176                 self.__write("\n")
       
   177                 self.__posxy = 0, len(self.screen)
       
   178                 self.screen.append("")
       
   179         else:
       
   180             while len(self.screen) < len(screen):
       
   181                 self.screen.append("")            
       
   182 
       
   183         if len(screen) > self.height:
       
   184             self.__gone_tall = 1
       
   185             self.__move = self.__move_tall
       
   186 
       
   187         px, py = self.__posxy
       
   188         old_offset = offset = self.__offset
       
   189         height = self.height
       
   190 
       
   191         # we make sure the cursor is on the screen, and that we're
       
   192         # using all of the screen if we can
       
   193         if cy < offset:
       
   194             offset = cy
       
   195         elif cy >= offset + height:
       
   196             offset = cy - height + 1
       
   197         elif offset > 0 and len(screen) < offset + height:
       
   198             offset = max(len(screen) - height, 0)
       
   199             screen.append([])
       
   200 
       
   201         oldscr = self.screen[old_offset:old_offset + height]
       
   202         newscr = screen[offset:offset + height]
       
   203 
       
   204         # use hardware scrolling if we have it.
       
   205         if old_offset > offset and self._ri:
       
   206             self.__write_code(self._cup, 0, 0)
       
   207             self.__posxy = 0, old_offset
       
   208             for i in range(old_offset - offset):
       
   209                 self.__write_code(self._ri)
       
   210                 oldscr.pop(-1)
       
   211                 oldscr.insert(0, "")
       
   212         elif old_offset < offset and self._ind:
       
   213             self.__write_code(self._cup, self.height - 1, 0)
       
   214             self.__posxy = 0, old_offset + self.height - 1
       
   215             for i in range(offset - old_offset):
       
   216                 self.__write_code(self._ind)
       
   217                 oldscr.pop(0)
       
   218                 oldscr.append("")
       
   219 
       
   220         self.__offset = offset
       
   221 
       
   222         for y, oldline, newline, in zip(range(offset, offset + height),
       
   223                                         oldscr,
       
   224                                         newscr):
       
   225             if oldline != newline:
       
   226                 self.write_changed_line(y, oldline, newline, px)
       
   227                 
       
   228         y = len(newscr)
       
   229         while y < len(oldscr):
       
   230             self.__move(0, y)
       
   231             self.__posxy = 0, y
       
   232             self.__write_code(self._el)
       
   233             y += 1
       
   234 
       
   235         self.__maybe_write_code(self._cnorm)
       
   236         
       
   237         #self.flushoutput()
       
   238         self.screen = screen
       
   239         self.move_cursor(cx, cy) # this does self.flushoutput()
       
   240 
       
   241     def write_changed_line(self, y, oldline, newline, px):
       
   242         # this is frustrating; there's no reason to test (say)
       
   243         # self.dch1 inside the loop -- but alternative ways of
       
   244         # structuring this function are equally painful (I'm trying to
       
   245         # avoid writing code generators these days...)
       
   246         x = 0
       
   247         minlen = min(len(oldline), len(newline))
       
   248         while x < minlen and oldline[x] == newline[x]:
       
   249             x += 1
       
   250         if oldline[x:] == newline[x+1:] and self.ich1:
       
   251             if ( y == self.__posxy[1] and x > self.__posxy[0]
       
   252                  and oldline[px:x] == newline[px+1:x+1] ):
       
   253                 x = px
       
   254             self.__move(x, y)
       
   255             self.__write_code(self.ich1)
       
   256             self.__write(newline[x])
       
   257             self.__posxy = x + 1, y
       
   258         elif x < minlen and oldline[x + 1:] == newline[x + 1:]:
       
   259             self.__move(x, y)
       
   260             self.__write(newline[x])
       
   261             self.__posxy = x + 1, y
       
   262         elif (self.dch1 and self.ich1 and len(newline) == self.width
       
   263               and x < len(newline) - 2
       
   264               and newline[x+1:-1] == oldline[x:-2]):
       
   265             self.__move(self.width - 2, y)
       
   266             self.__posxy = self.width - 2, y
       
   267             self.__write_code(self.dch1)
       
   268             self.__move(x, y)
       
   269             self.__write_code(self.ich1)
       
   270             self.__write(newline[x])
       
   271             self.__posxy = x + 1, y
       
   272         else:
       
   273             self.__move(x, y)
       
   274             if len(oldline) > len(newline):
       
   275                 self.__write_code(self._el)
       
   276             self.__write(newline[x:])
       
   277             self.__posxy = len(newline), y
       
   278         #self.flushoutput() # removed for efficiency
       
   279 
       
   280     def __write(self, text):
       
   281         self.__buffer.append((text, 0))
       
   282 
       
   283     def __write_code(self, fmt, *args):
       
   284         self.__buffer.append((curses.tparm(fmt, *args), 1))
       
   285 
       
   286     def __maybe_write_code(self, fmt, *args):
       
   287         if fmt:
       
   288             self.__write_code(fmt, *args)
       
   289 
       
   290     def __move_y_cuu1_cud1(self, y):
       
   291         dy = y - self.__posxy[1]
       
   292         if dy > 0:
       
   293             self.__write_code(dy*self._cud1)
       
   294         elif dy < 0:
       
   295             self.__write_code((-dy)*self._cuu1)
       
   296 
       
   297     def __move_y_cuu_cud(self, y):
       
   298         dy = y - self.__posxy[1]
       
   299         if dy > 0:
       
   300             self.__write_code(self._cud, dy)
       
   301         elif dy < 0:
       
   302             self.__write_code(self._cuu, -dy)
       
   303 
       
   304     def __move_x_hpa(self, x):
       
   305         if x != self.__posxy[0]:
       
   306             self.__write_code(self._hpa, x)
       
   307 
       
   308     def __move_x_cub1_cuf1(self, x):
       
   309         dx = x - self.__posxy[0]
       
   310         if dx > 0:
       
   311             self.__write_code(self._cuf1*dx)
       
   312         elif dx < 0:
       
   313             self.__write_code(self._cub1*(-dx))
       
   314 
       
   315     def __move_x_cub_cuf(self, x):
       
   316         dx = x - self.__posxy[0]
       
   317         if dx > 0:
       
   318             self.__write_code(self._cuf, dx)
       
   319         elif dx < 0:
       
   320             self.__write_code(self._cub, -dx)
       
   321 
       
   322     def __move_short(self, x, y):
       
   323         self.__move_x(x)
       
   324         self.__move_y(y)
       
   325 
       
   326     def __move_tall(self, x, y):
       
   327         assert 0 <= y - self.__offset < self.height, y - self.__offset
       
   328         self.__write_code(self._cup, y - self.__offset, x)
       
   329 
       
   330     def move_cursor(self, x, y):
       
   331         if y < self.__offset or y >= self.__offset + self.height:
       
   332             self.event_queue.insert(Event('scroll', None))
       
   333         else:
       
   334             self.__move(x, y)
       
   335             self.__posxy = x, y
       
   336             if not self.isbusy():
       
   337                 self.flushoutput()
       
   338 
       
   339     def prepare(self):
       
   340         self.screen = []
       
   341         self.height, self.width = self.getheightwidth()
       
   342 
       
   343         self.__buffer = []
       
   344         
       
   345         self.__posxy = 0, 0
       
   346         self.__gone_tall = 0
       
   347         self.__move = self.__move_short
       
   348         self.__offset = 0
       
   349 
       
   350         self.__maybe_write_code(self._rmkx) # Turn off application cursor mode.
       
   351 
       
   352     def restore(self):
       
   353         # We never put the cursor keys in application mode, so this
       
   354         # is redundant now:
       
   355         #self.__maybe_write_code(self._rmkx) 
       
   356         self.flushoutput()
       
   357 
       
   358     def __sigwinch(self, signum, frame):
       
   359         self.height, self.width = self.getheightwidth()
       
   360         self.event_queue.insert(Event('resize', None))
       
   361 
       
   362     def isbusy(self):
       
   363         return self.busy
       
   364 
       
   365     def get_event(self, block=1):        
       
   366         while self.event_queue.empty():
       
   367             chars = self._osread(self.MAX_READ)
       
   368 #            try:
       
   369 #            except EOFError:
       
   370 #                raise
       
   371 #            except:
       
   372 #                print >>sys.stderr,"Exception!"
       
   373 #                import traceback
       
   374 #                traceback.print_exc()
       
   375 #                raise
       
   376             for c in chars:
       
   377                 self.event_queue.push(c)
       
   378             if not block:
       
   379                 break
       
   380         self.busy=len(self.event_queue.events)>1
       
   381         return self.event_queue.get()
       
   382 
       
   383     def wait(self):
       
   384         pass 
       
   385     #self.pollob.poll()
       
   386 
       
   387     def set_cursor_vis(self, vis):
       
   388         if vis:
       
   389             self.__maybe_write_code(self._cnorm)
       
   390         else:
       
   391             self.__maybe_write_code(self._civis)
       
   392 
       
   393     def repaint_prep(self):
       
   394         if not self.__gone_tall:
       
   395             self.__posxy = 0, self.__posxy[1]
       
   396             self.__write("\r")
       
   397             ns = len(self.screen)*['\000'*self.width]
       
   398             self.screen = ns
       
   399         else:
       
   400             self.__posxy = 0, self.__offset
       
   401             self.__move(0, self.__offset)
       
   402             ns = self.height*['\000'*self.width]
       
   403             self.screen = ns
       
   404 
       
   405     def getheightwidth(self):
       
   406         return 25, 80
       
   407 
       
   408     def forgetinput(self):
       
   409         pass
       
   410 
       
   411     def flushoutput(self):
       
   412         if not self.event_queue.empty():
       
   413             return
       
   414         if len(self.__buffer)==0:
       
   415             return 
       
   416         outbuf=[]
       
   417         for text, iscode in self.__buffer:
       
   418             if iscode:
       
   419                 outbuf.append(text)
       
   420                 # we don't use delays here, so we don't need tputs
       
   421                 # processing.                
       
   422                 # outbuf.append(self.__tputs(text))
       
   423             else:
       
   424                 outbuf.append(text.encode(self.encoding))
       
   425  
       
   426         self._oswrite(''.join(outbuf))
       
   427         del self.__buffer[:]
       
   428 
       
   429     def finish(self):
       
   430         y = len(self.screen) - 1
       
   431         while y >= 0 and not self.screen[y]:
       
   432             y -= 1
       
   433         self.__move(0, min(y, self.height + self.__offset - 1))
       
   434         self.__write("\n\r")
       
   435         self.flushoutput()
       
   436 
       
   437     def beep(self):
       
   438         self.__maybe_write_code(self._bel)
       
   439         self.flushoutput()
       
   440 
       
   441     def getpending(self):
       
   442         e = Event('key', '', '')
       
   443         
       
   444         while not self.event_queue.empty():
       
   445             e2 = self.event_queue.get()
       
   446             e.data += e2.data
       
   447             e.raw += e2.raw
       
   448 
       
   449         amount = 1000
       
   450         raw = unicode(self._osread(amount), self.encoding, 'replace')
       
   451         e.data += raw
       
   452         e.raw += raw
       
   453         return e
       
   454 
       
   455     def clear(self):
       
   456         self.__write_code(self._clear)
       
   457         self.__gone_tall = 1
       
   458         self.__move = self.__move_tall
       
   459         self.__posxy = 0, 0
       
   460         self.screen = []
       
   461