source: dual/trunk/dual.py @ 24

Revision 24, 7.7 KB checked in by sgk, 12 years ago (diff)

書き込みを若干効率化。

  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2006 by KANEMOTO Shigeru
5
6import os
7import sys
8import tty
9import pty
10import select
11import struct
12import fcntl
13import termios
14import errno
15import signal
16import socket
17import getopt
18import curses
19import pwd
20
21STDIN_FILENO = 0
22STDOUT_FILENO = 1
23
24ENV_FLAGNAME = 'DUALPY'
25
26version = 'dual.py 2006/4/17 Copyright (c) 2006 by KANEMOTO Shigeru'
27progname = sys.argv[0].split('/')[-1]
28
29
30def getwinsz(fd):
31  winsize = struct.pack('HHHH', 0, 0, 0, 0)
32  winsize = fcntl.ioctl(fd, termios.TIOCGWINSZ, winsize)
33  return struct.unpack('HHHH', winsize)
34  # return (row, col, xpixel, ypixel)
35
36def setwinsz(fd, list):
37  # list = (row, col, xpixel, ypixel)
38  winsize = struct.pack('HHHH', *list)
39  fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
40
41xterm = 0
42
43def settermwinsz(fd, list):
44  # list = (row, col, hpixel, vpixel)
45  global xterm
46  if xterm:
47    (row, col, xpixel, ypixel) = list
48    os.write(fd, '\033[8;%d;%d;t\033[4;%d;%d;t' % (row, col, ypixel, xpixel))
49
50#
51# xterm specific escape sequences
52#
53# ESC [ 4 ; height ; width ; t
54#   set screen size in pixels
55# ESC [ 8 ; height ; width ; t
56#   set screen size in characters
57# ESC [ 14 ; height ; width ; t
58#   request screen size in pixels
59# ESC [ 18 ; height ; width ; t
60#   request screen size in characters
61#
62
63owinsz = (0, 0, 0, 0)
64
65def enter_mode():
66  try:
67    global owinsz, omode
68    owinsz = getwinsz(STDOUT_FILENO)
69    omode = tty.tcgetattr(STDIN_FILENO)
70    tty.setraw(STDIN_FILENO)
71  except tty.error, e:
72    print >>sys.stderr, "%s: Can't get/set tty mode: %s" % (progname, e[1])
73    sys.exit(os.EX_IOERR)
74
75def reset_mode():
76  global owinsz, omode
77  tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, omode)
78  if owinsz != (0, 0, 0, 0):
79    settermwinsz(STDOUT_FILENO, owinsz)
80
81def clearscreen():
82  os.write(STDOUT_FILENO, curses.tigetstr('clear'))
83
84def writeall(fd, data):
85  while len(data) != 0:
86    n = os.write(fd, data)
87    data = buffer(data, n)
88
89class Decoder:
90  def __init__(self, sink, screen):
91    self.sink_ = sink
92    self.screen_ = screen
93    self.first_ = 1
94    self.s_ = ''
95
96  def data(self, s):
97    self.s_ += s
98    while 1:
99      if len(self.s_) < 3:
100        return
101      (length, cmd) = struct.unpack('!HB', self.s_[:3])
102      if len(self.s_) < length:
103        return
104
105      s = self.s_[3:length]
106      self.s_ = self.s_[length:]
107
108      if cmd == 0:
109        writeall(self.sink_, s)
110      elif cmd == 1:
111        (height, width) = struct.unpack('!HH', s[:4])
112        (oheight, owidth, oxpixel, oypixel) = getwinsz(self.screen_)
113        change = 0
114        if self.first_:
115          if height < oheight:
116            change = 1
117            oheight = height
118          if width < owidth:
119            change = 1
120            owidth = width
121          self.first_ = 0
122        else:
123          if height != oheight or width != owidth:
124            change = 1
125          oheight = height
126          owidth = width
127        if change:
128          setwinsz(self.screen_, (oheight, owidth, oxpixel, oypixel))
129          settermwinsz(self.screen_, (oheight, owidth, oxpixel, oypixel))
130
131class Encoder:
132  def __init__(self, dst):
133    self.dst_ = dst
134
135  def sink(self, s):
136    self.dst_.send(struct.pack('!HB', len(s) + 3, 0) + s)
137
138  def winsz(self, list):
139    (height, width, xpixel, ypixel) = list
140    self.dst_.send(struct.pack('!HBHH', 7, 1, height, width))
141
142
143#
144# Client
145#
146
147def client(dstport):
148  try:
149    print >>sys.stderr, '*** Connecting to port %d' % dstport
150    link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
151    link.connect(('127.0.0.1', dstport))
152  except socket.error, e:
153    print >>sys.stderr, "%s: Can't connect: %s" % (progname, e[1])
154    sys.exit(os.EX_UNAVAILABLE)
155
156  clearscreen()
157  enter_mode()
158  decoder = Decoder(STDOUT_FILENO, STDOUT_FILENO)
159  encoder = Encoder(link)
160  encoder.winsz(getwinsz(STDOUT_FILENO))
161
162  def sigwinch(s, frame):
163    encoder.winsz(getwinsz(STDOUT_FILENO))
164  signal.signal(signal.SIGWINCH, sigwinch)
165
166  try:
167    try:
168      while 1:
169        try:
170          (rfds, wfds, xfds) = select.select([STDIN_FILENO, link], [], [], 1)
171
172          if STDIN_FILENO in rfds:
173            encoder.sink(os.read(STDIN_FILENO, 1024))
174
175          if link in rfds:
176            data = link.recv(1024)
177            if len(data) == 0:
178              break
179            decoder.data(data)
180
181        except (select.error, OSError), e:
182          if e[0] != errno.EINTR:
183            raise e
184
185    except OSError, e:
186      if e[0] != errno.EIO:             # EIO when the link losts
187        raise
188
189  finally:
190    reset_mode()
191    print >>sys.stderr, '*** Closed the connection with port %d' % dstport
192
193
194#
195# Server
196#
197
198def server(argv):
199  (pid, fd) = pty.fork()
200  if pid == 0:
201    # child
202    try:
203      os.environ[ENV_FLAGNAME] = ''
204      os.execvp(argv[0], argv)
205    except OSError, e:
206      os.write(STDOUT_FILENO,
207        '\0\0\0Can\'t exec "%s": %s\0\0\0' % (argv[0], e[1]))
208      os._exit(os.EX_OSERR)
209
210  data = os.read(fd, 1024)
211  if data[0:3] == '\0\0\0':
212    data = data[3:]
213    data = data[:data.index('\0\0\0')]
214    print >>sys.stderr, '%s: %s' % (progname, data)
215    (pid, st) = os.waitpid(pid, 0)
216    sys.exit(os.WIFEXITED(st) and os.WEXITSTATUS(st) or os.EX_OSERR)
217
218  listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
219  listener.bind(('127.0.0.1', 0))       # localhost, any port
220  print >>sys.stderr, '*** Waiting on port %d' % listener.getsockname()[1]
221  listener.listen(1)
222  (link, sa) = listener.accept()
223  del listener
224  link.setblocking(0)
225
226  clearscreen()
227  enter_mode()
228  decoder = Decoder(fd, STDOUT_FILENO)
229  setwinsz(fd, getwinsz(STDOUT_FILENO))
230  encoder = Encoder(link)
231  encoder.winsz(getwinsz(STDOUT_FILENO))
232  encoder.sink(data)
233  writeall(STDOUT_FILENO, data)
234
235  def sigwinch(s, frame):
236    winsz = getwinsz(STDOUT_FILENO)
237    setwinsz(fd, winsz)
238    encoder.winsz(winsz)
239  signal.signal(signal.SIGWINCH, sigwinch)
240
241  try:
242    try:
243      while 1:
244        try:
245          (rfds, x, x) = select.select([fd, STDIN_FILENO, link], [], [], 1)
246
247          if fd in rfds:
248            data = os.read(fd, 1024)
249            writeall(STDOUT_FILENO, data)
250            encoder.sink(data)
251
252          if STDIN_FILENO in rfds:
253            writeall(fd, os.read(STDIN_FILENO, 1024))
254
255          if link in rfds:
256            decoder.data(link.recv(1024))
257
258        except (select.error, OSError), e:
259          if e[0] != errno.EINTR:
260            raise
261
262    except OSError, e:
263      if e[0] != errno.EIO:             # EIO when the shell exits
264        raise
265
266  finally:
267    reset_mode()
268    print >>sys.stderr, '*** Closed the session for "%s".' % ' '.join(argv)
269
270
271#
272# Main
273#
274
275def usage():
276  print >>sys.stderr, "Usage: %s [-x] [-f] [-c port | program-to-exec]" % progname
277
278xterm = os.environ.has_key('DUALPYXTERM') or 'xterm' in os.environ.get('TERM', '')
279
280try:
281  dstport = 0
282  force = False
283  (opts, argv) = getopt.getopt(
284      sys.argv[1:], 'xfc:hv', ['xterm', 'force', 'version', 'help'])
285  for (o, a) in opts:
286    if o == '-c':
287      dstport = a
288    elif o in ('-x', '--xterm'):
289      xterm = 1
290    elif o in ('-f', '--force'):
291      force = True
292    elif o in ('-v', '--version'):
293      print >>sys.stderr, '%s: %s' % (progname, version)
294      sys.exit(os.EX_OK)
295    elif o in ('-h', '--help'):
296      usage()
297      sys.exit(os.EX_OK)
298except getopt.GetoptError:
299  usage()
300  sys.exit(os.EX_USAGE)
301
302if not force and os.environ.has_key(ENV_FLAGNAME):
303  print >>sys.stderr, "%s: Already in the %s environment" % (progname, progname)
304  sys.exit(os.EX_USAGE)
305
306try:
307  curses.setupterm()
308
309  if dstport:
310    if argv:
311      usage()
312      sys.exit(os.EX_USAGE)
313    try:
314      client(int(dstport))
315    except ValueError:
316      usage()
317      sys.exit(os.EX_USAGE)
318
319  else:
320    if not argv:
321      s = os.environ.get('SUDO_USER') or \
322          os.environ.get('LOGNAME') or \
323          os.environ.get('USER')
324      try:
325        s = s and pwd.getpwnam(s)[6]
326      except KeyError:
327        pass
328      try:
329        s = s or pwd.getpwuid(os.getuid())[6]
330      except KeyError:
331        pass
332      argv = (s or '/bin/bash',)
333    server(argv)
334
335except SystemExit:
336  raise
337
338except KeyboardInterrupt:
339  print >>sys.stderr, '^C'
340  sys.exit(os.EX_OK)
341
342except OSError, e:
343  print >>sys.stderr, '%s: %s' % (progname, e[1])
344  sys.exit(os.EX_OSERR)
345
346except Exception:
347  print >>sys.stderr, '%s: Unknown error' % progname
348  sys.exit(os.EX_SOFTWARE)
Note: See TracBrowser for help on using the repository browser.