root/dual/trunk/dual.py

リビジョン 24, 7.7 kB (コミッタ: sgk, コミット時期: 2 年 前)

書き込みを若干効率化。

  • svn:executable 属性の設定値: *
Line 
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2006 by KANEMOTO Shigeru
5
6 import os
7 import sys
8 import tty
9 import pty
10 import select
11 import struct
12 import fcntl
13 import termios
14 import errno
15 import signal
16 import socket
17 import getopt
18 import curses
19 import pwd
20
21 STDIN_FILENO = 0
22 STDOUT_FILENO = 1
23
24 ENV_FLAGNAME = 'DUALPY'
25
26 version = 'dual.py 2006/4/17 Copyright (c) 2006 by KANEMOTO Shigeru'
27 progname = sys.argv[0].split('/')[-1]
28
29
30 def 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
36 def setwinsz(fd, list):
37   # list = (row, col, xpixel, ypixel)
38   winsize = struct.pack('HHHH', *list)
39   fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
40
41 xterm = 0
42
43 def 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
63 owinsz = (0, 0, 0, 0)
64
65 def 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
75 def 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
81 def clearscreen():
82   os.write(STDOUT_FILENO, curses.tigetstr('clear'))
83
84 def writeall(fd, data):
85   while len(data) != 0:
86     n = os.write(fd, data)
87     data = buffer(data, n)
88
89 class 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
131 class 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
147 def 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
198 def 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
275 def usage():
276   print >>sys.stderr, "Usage: %s [-x] [-f] [-c port | program-to-exec]" % progname
277
278 xterm = os.environ.has_key('DUALPYXTERM') or 'xterm' in os.environ.get('TERM', '')
279
280 try:
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)
298 except getopt.GetoptError:
299   usage()
300   sys.exit(os.EX_USAGE)
301
302 if 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
306 try:
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
335 except SystemExit:
336   raise
337
338 except KeyboardInterrupt:
339   print >>sys.stderr, '^C'
340   sys.exit(os.EX_OK)
341
342 except OSError, e:
343   print >>sys.stderr, '%s: %s' % (progname, e[1])
344   sys.exit(os.EX_OSERR)
345
346 except Exception:
347   print >>sys.stderr, '%s: Unknown error' % progname
348   sys.exit(os.EX_SOFTWARE)
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。