186 lines
7.7 KiB
Python
Raw Normal View History

#!/bin/python
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
2018-03-25 15:54:12 +02:00
import os, struct, sys, pty, tty, termios, shlex, signal, select, pyte, time, fcntl ,getpass
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.eventData import fenrirEventType
from fenrirscreenreader.core.screenDriver import screenDriver
from fenrirscreenreader.utils import screen_utils
2018-03-23 23:09:24 +01:00
class Terminal:
def __init__(self, columns, lines, p_in):
2018-05-29 15:06:45 +02:00
self.text = ''
self.attributes = []
2018-03-23 23:09:24 +01:00
self.screen = pyte.HistoryScreen(columns, lines)
self.screen.set_mode(pyte.modes.LNM)
self.screen.write_process_input = \
lambda data: p_in.write(data.encode())
self.stream = pyte.ByteStream()
self.stream.attach(self.screen)
def feed(self, data):
self.stream.feed(data)
2018-03-24 18:48:04 +01:00
def resize(self, lines, columns):
self.screen.resize(lines, columns)
2018-03-24 18:39:11 +01:00
self.setCursor()
def setCursor(self, x = -1, y = -1):
xPos = x
yPos = y
if xPos == -1:
xPos = self.screen.cursor.x
if yPos == -1:
yPos = self.screen.cursor.y
self.screen.cursor.x = min(self.screen.cursor.x, self.screen.columns - 1)
self.screen.cursor.y = min(self.screen.cursor.y, self.screen.lines - 1)
2018-05-29 15:06:45 +02:00
def GetScreenContent(self):
2018-03-23 23:09:24 +01:00
cursor = self.screen.cursor
2018-05-29 15:06:45 +02:00
self.text = '\n'.join(self.screen.display)
2018-05-25 03:05:03 +02:00
buffer = self.screen.buffer
2018-05-29 15:06:45 +02:00
self.attributes = [[list(attribute[1:]) + [False, 'default', 'default'] for attribute in line.values()] for line in buffer.values()]
2018-05-29 14:04:26 +02:00
self.screen.dirty.clear()
2018-05-30 11:00:41 +02:00
return {"textCursor": (cursor.x, cursor.y),
2018-03-24 21:29:54 +01:00
'lines': self.screen.lines,
'columns': self.screen.columns,
2018-05-29 15:06:45 +02:00
"text": self.text,
'attributes': self.attributes.copy(),
2018-05-30 11:26:57 +02:00
'screen': 'pty'
'screenUpdateTime': time.time(),
2018-03-24 21:29:54 +01:00
}.copy()
2018-03-23 23:09:24 +01:00
class driver(screenDriver):
def __init__(self):
2018-05-23 23:51:18 +02:00
screenDriver.__init__(self)
2018-03-24 18:39:11 +01:00
self.signalPipe = os.pipe()
self.p_out = None
2018-03-25 23:03:53 +02:00
signal.signal(signal.SIGWINCH, self.handleSigwinch)
def initialize(self, environment):
self.env = environment
2018-03-25 23:03:53 +02:00
self.command = self.env['runtime']['settingsManager'].getSetting('general','shell')
2018-03-26 23:56:05 +02:00
self.shortcutType = self.env['runtime']['inputManager'].getShortcutType()
2018-03-25 16:05:10 +02:00
self.env['runtime']['processManager'].addCustomEventThread(self.terminalEmulation)
def getCurrScreen(self):
2018-05-17 17:32:18 +02:00
self.env['screen']['oldTTY'] = 'pty'
self.env['screen']['newTTY'] = 'pty'
2018-03-23 21:05:47 +01:00
2018-03-25 15:54:12 +02:00
def injectTextToScreen(self, msgBytes, screen = None):
if not screen:
screen = self.p_out.fileno()
if isinstance(msgBytes, str):
msgBytes = bytes(msgBytes, 'UTF-8')
os.write(screen, msgBytes)
def getSessionInformation(self):
2018-03-23 21:05:47 +01:00
self.env['screen']['autoIgnoreScreens'] = []
2018-03-25 15:54:12 +02:00
self.env['general']['prevUser'] = getpass.getuser()
self.env['general']['currUser'] = getpass.getuser()
2018-05-30 12:18:56 +02:00
def readAll(self,fd, timeout = 9999999, interruptFd = None):
2018-03-27 14:16:22 +02:00
starttime = time.time()
2018-05-29 22:47:10 +02:00
bytes = os.read(fd, 4096)
2018-03-23 23:09:24 +01:00
if bytes == b'':
raise EOFError
# respect timeout but wait a little bit of time to see if something more is here
2018-05-30 12:18:56 +02:00
fdList = [fd]
if interruptFd:
fdList += [interruptFd]
2018-05-30 13:15:50 +02:00
while True:
2018-05-30 13:18:09 +02:00
r = screen_utils.hasMoreWhat(fdList,0.1):
2018-05-30 13:15:50 +02:00
hasmore = fd in r
2018-05-30 12:18:56 +02:00
if not hasmore:
break
2018-05-30 13:15:50 +02:00
# exit on interrupt available
2018-05-30 12:18:56 +02:00
if interruptFd in r:
2018-05-30 13:15:50 +02:00
break
2018-03-27 14:16:22 +02:00
if (time.time() - starttime) >= timeout:
break
2018-05-29 22:47:10 +02:00
data = os.read(fd, 4096)
2018-03-23 23:09:24 +01:00
if data == b'':
raise EOFError
bytes += data
return bytes
2018-03-25 23:03:53 +02:00
def openTerminal(self, columns, lines, command):
2018-03-23 23:09:24 +01:00
p_pid, master_fd = pty.fork()
if p_pid == 0: # Child.
argv = shlex.split(command)
env = os.environ.copy()
2018-05-28 18:04:47 +02:00
#values are VT100,xterm-256color,linux
env["TERM"] = 'linux'
2018-03-23 23:09:24 +01:00
os.execvpe(argv[0], argv, env)
# File-like object for I/O with the child process aka command.
p_out = os.fdopen(master_fd, "w+b", 0)
return Terminal(columns, lines, p_out), p_pid, p_out
2018-03-24 18:48:04 +01:00
def resizeTerminal(self,fd):
2018-03-24 18:39:11 +01:00
s = struct.pack('HHHH', 0, 0, 0, 0)
s = fcntl.ioctl(0, termios.TIOCGWINSZ, s)
fcntl.ioctl(fd, termios.TIOCSWINSZ, s)
2018-03-24 18:48:04 +01:00
lines, columns, _, _ = struct.unpack('hhhh', s)
return lines, columns
def getTerminalSize(self, fd):
2018-03-24 18:39:11 +01:00
s = struct.pack('HHHH', 0, 0, 0, 0)
2018-03-24 18:48:04 +01:00
lines, columns, _, _ = struct.unpack('HHHH', fcntl.ioctl(fd, termios.TIOCGWINSZ, s))
return lines, columns
def handleSigwinch(self, *args):
2018-03-24 18:39:11 +01:00
os.write(self.signalPipe[1], b'w')
2018-03-23 23:09:24 +01:00
def terminalEmulation(self,active , eventQueue):
try:
old_attr = termios.tcgetattr(sys.stdin)
tty.setraw(0)
2018-03-24 18:48:04 +01:00
lines, columns = self.getTerminalSize(0)
if self.command == '':
self.command = screen_utils.getShell()
terminal, p_pid, self.p_out = self.openTerminal(columns, lines, self.command)
lines, columns = self.resizeTerminal(self.p_out)
terminal.resize(lines, columns)
2018-05-29 12:04:20 +02:00
fdList = [sys.stdin, self.p_out, self.signalPipe[0]]
2018-03-25 15:54:12 +02:00
while active.value:
2018-05-29 12:04:20 +02:00
r, _, _ = select.select(fdList, [], [], 1)
2018-03-23 23:09:24 +01:00
# none
if r == []:
continue
2018-03-24 18:39:11 +01:00
# signals
if self.signalPipe[0] in r:
os.read(self.signalPipe[0], 1)
lines, columns = self.resizeTerminal(self.p_out)
2018-03-26 13:22:29 +02:00
terminal.resize(lines, columns)
# input
if sys.stdin in r:
try:
msgBytes = self.readAll(sys.stdin.fileno())
except (EOFError, OSError):
active.value = False
2018-03-26 23:46:56 +02:00
break
2018-03-26 23:56:05 +02:00
if self.shortcutType == 'KEY':
2018-03-26 23:46:56 +02:00
try:
self.injectTextToScreen(msgBytes)
except:
active.value = False
break
else:
eventQueue.put({"Type":fenrirEventType.ByteInput,
"Data":msgBytes })
2018-03-23 23:09:24 +01:00
# output
if self.p_out in r:
2018-03-23 23:09:24 +01:00
try:
2018-05-30 13:19:48 +02:00
msgBytes = self.readAll(self.p_out.fileno(), timeout=0.3, interruptFd=sys.stdin)
2018-03-23 23:09:24 +01:00
except (EOFError, OSError):
active.value = False
2018-03-23 23:09:24 +01:00
break
terminal.feed(msgBytes)
os.write(sys.stdout.fileno(), msgBytes)
eventQueue.put({"Type":fenrirEventType.ScreenUpdate,
2018-05-29 15:06:45 +02:00
"Data":screen_utils.createScreenEventData(terminal.GetScreenContent())
2018-03-23 23:09:24 +01:00
})
except Exception as e: # Process died?
print(e)
active.value = False
2018-03-23 23:09:24 +01:00
finally:
os.kill(p_pid, signal.SIGTERM)
self.p_out.close()
2018-03-23 23:09:24 +01:00
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attr)
eventQueue.put({"Type":fenrirEventType.StopMainLoop,"Data":None})
sys.exit(0)
2018-05-23 23:51:18 +02:00
2018-03-23 21:00:16 +01:00
def getCurrApplication(self):
2018-03-23 21:05:47 +01:00
pass