2 # -*- coding: utf-8 -*-
4 # Copyright (c) 2018-2021 Niklas Ekström
20 logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
21 logger = logging.getLogger(__name__)
22 logger.setLevel(logging.DEBUG)
24 FS_CFG_FILE = 'a314/files_pi/a314fs.conf'
25 PICMD_CFG_FILE = 'a314/files_pi/picmd.conf'
33 with open(FS_CFG_FILE, 'rt') as f:
36 for _, dev in devs.items():
37 volume_paths[dev['volume']] = dev['path']
40 search_path = os.getenv('PATH')
42 with open(PICMD_CFG_FILE, 'rt') as f:
46 search_path = ':'.join(cfg['paths']) + ':' + search_path
47 os.environ['PATH'] = search_path
50 for key, val in cfg['env_vars'].items():
54 for key, val in cfg['sgr_map'].items():
55 sgr_map[key] = str(val)
61 MSG_DEREGISTER_REQ = 3
62 MSG_DEREGISTER_RES = 4
68 MSG_CONNECT_RESPONSE = 10
75 while len(header) < 9:
76 data = drv.recv(9 - len(header))
78 logger.error('Connection to a314d was closed, terminating.')
81 (plen, stream_id, ptype) = struct.unpack('=IIB', header)
83 while len(payload) < plen:
84 data = drv.recv(plen - len(payload))
86 logger.error('Connection to a314d was closed, terminating.')
89 return (stream_id, ptype, payload)
91 def send_register_req(name):
92 m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
95 def send_read_mem_req(address, length):
96 m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
99 def read_mem(address, length):
100 send_read_mem_req(address, length)
101 stream_id, ptype, payload = wait_for_msg()
102 if ptype != MSG_READ_MEM_RES:
103 logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
107 def send_write_mem_req(address, data):
108 m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
111 def write_mem(address, data):
112 send_write_mem_req(address, data)
113 stream_id, ptype, payload = wait_for_msg()
114 if ptype != MSG_WRITE_MEM_RES:
115 logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
118 def send_connect_response(stream_id, result):
119 m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
122 def send_data(stream_id, data):
123 m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
126 def send_eos(stream_id):
127 m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
130 def send_reset(stream_id):
131 m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
136 class PiCmdSession(object):
137 def __init__(self, stream_id):
138 self.stream_id = stream_id
141 self.first_packet = True
142 self.reset_after = None
144 self.rasp_was_esc = False
145 self.rasp_in_cs = False
146 self.rasp_holding = ''
148 self.amiga_in_cs = False
149 self.amiga_holding = ''
151 def process_amiga_ansi(self, data):
152 data = data.decode('latin-1')
155 if not self.amiga_in_cs:
157 self.amiga_in_cs = True
158 self.amiga_holding = '\x1b['
161 else: # self.amiga_in_cs
162 self.amiga_holding += c
163 if c >= chr(0x40) and c <= chr(0x7e):
165 # Window Bounds Report
166 # ESC[1;1;rows;cols r
167 rows, cols = map(int, self.amiga_holding[6:-2].split(';'))
168 winsize = struct.pack('HHHH', rows, cols, 0, 0)
169 fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)
172 # ESC[12;0;0;x;x;x;x;x|
174 send_data(self.stream_id, b'\x9b' + b'0 q')
176 out += self.amiga_holding
177 self.amiga_holding = ''
178 self.amiga_in_cs = False
180 os.write(self.fd, out.encode('utf-8'))
182 def process_msg_data(self, data):
183 if self.first_packet:
185 send_reset(self.stream_id)
186 del sessions[self.stream_id]
188 address, length = struct.unpack('>II', data)
189 buf = read_mem(address, length)
192 rows, cols = struct.unpack('>HH', buf[ind:ind+4])
195 component_count = buf[ind]
199 for _ in range(component_count):
202 components.append(buf[ind:ind+n].decode('latin-1'))
209 for _ in range(arg_count):
212 args.append(buf[ind:ind+n].decode('latin-1'))
218 self.pid, self.fd = pty.fork()
220 for key, val in env_vars.items():
222 os.putenv('PATH', search_path)
223 os.putenv('TERM', 'ansi')
224 winsize = struct.pack('HHHH', rows, cols, 0, 0)
225 fcntl.ioctl(sys.stdin, termios.TIOCSWINSZ, winsize)
226 if component_count != 0 and components[0] in volume_paths:
227 path = volume_paths[components[0]]
228 os.chdir(os.path.join(path, *components[1:]))
230 os.chdir(os.getenv('HOME', '/'))
231 os.execvp(args[0], args)
233 self.first_packet = False
236 self.process_amiga_ansi(data)
240 os.kill(self.pid, signal.SIGTERM)
243 del sessions[self.stream_id]
245 def process_rasp_ansi(self, text):
246 text = text.decode('utf-8')
249 if not self.rasp_in_cs:
250 if not self.rasp_was_esc:
252 self.rasp_was_esc = True
255 else: # self.rasp_was_esc
257 self.rasp_was_esc = False
258 self.rasp_in_cs = True
259 self.rasp_holding = '\x1b['
265 self.rasp_was_esc = False
266 else: # self.rasp_in_cs
267 self.rasp_holding += c
268 if c >= chr(0x40) and c <= chr(0x7e):
270 # Select Graphic Rendition
272 attrs = self.rasp_holding[2:-1].split(';')
273 attrs = [sgr_map[a] if a in sgr_map else a for a in attrs]
274 out += '\x1b[' + (';'.join(attrs)) + 'm'
276 out += self.rasp_holding
277 self.rasp_holding = ''
278 self.rasp_in_cs = False
279 return out.encode('latin-1', 'replace')
281 def handle_text(self):
283 text = os.read(self.fd, 1024)
284 text = self.process_rasp_ansi(text)
286 take = min(len(text), 252)
287 send_data(self.stream_id, text[:take])
291 os.kill(self.pid, signal.SIGTERM)
293 send_eos(self.stream_id)
294 self.reset_after = time.time() + 10
296 def handle_timeout(self):
297 if self.reset_after and self.reset_after < time.time():
298 send_reset(self.stream_id)
299 del sessions[self.stream_id]
304 def process_drv_msg(stream_id, ptype, payload):
305 if ptype == MSG_CONNECT:
306 if payload == b'picmd':
307 s = PiCmdSession(stream_id)
308 sessions[stream_id] = s
309 send_connect_response(stream_id, 0)
311 send_connect_response(stream_id, 3)
312 elif stream_id in sessions:
313 s = sessions[stream_id]
315 if ptype == MSG_DATA:
316 s.process_msg_data(payload)
317 elif ptype == MSG_EOS:
319 send_eos(s.stream_id)
321 elif ptype == MSG_RESET:
327 idx = sys.argv.index('-ondemand')
332 fd = int(sys.argv[idx + 1])
333 drv = socket.socket(fileno=fd)
335 drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
336 drv.connect(('localhost', 7110))
337 drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
339 send_register_req(b'picmd')
340 _, _, payload = wait_for_msg()
342 logger.error('Unable to register picmd with driver, shutting down')
349 logger.info('picmd server is running')
352 sel_fds = [drv] + [s for s in sessions.values() if s.pid]
354 sel_fds.append(sys.stdin)
355 rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0)
359 line = sys.stdin.readline()
360 if not line or line.startswith('quit'):
361 for s in sessions.values():
368 for s in sessions.values():
378 (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9])
379 if len(rbuf) < 9 + plen:
383 payload = rbuf[:plen]
386 process_drv_msg(stream_id, ptype, payload)
390 for s in sessions.values():