remotePythonConsole.py 2.3 KB
#remotePythonConsole.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2011 NV Access Inc

"""Provides an interactive Python console run inside NVDA which can be accessed via TCP.
To use, call L{initialize} to start the server.
Then, connect to it using TCP port L{PORT}.
The server will only handle one connection at a time.
"""

import threading
import SocketServer
import wx
import pythonConsole
from logHandler import log

#: The TCP port on which the server will run.
#: @type: int
PORT = 6832

server = None

class RequestHandler(SocketServer.StreamRequestHandler):

	def setPrompt(self, prompt):
		if not self._keepRunning:
			# We're about to exit, so don't output the prompt.
			return
		self.wfile.write(prompt + " ")

	def exit(self):
		self._keepRunning = False

	def execute(self, line):
		self.console.push(line)
		# Notify handle() that the line has finished executing.
		self._execDoneEvt.set()

	def handle(self):
		# #3126: Remove the default socket timeout.
		# We can't use the class timeout attribute because None means don't set a timeout.
		self.connection.settimeout(None)
		self._keepRunning = True

		try:
			self.wfile.write("NVDA Remote Python Console\n")
			self.console = pythonConsole.PythonConsole(outputFunc=self.wfile.write, setPromptFunc=self.setPrompt, exitFunc=self.exit)
			self.console.namespace.update({
				"snap": self.console.updateNamespaceSnapshotVars,
				"rmSnap": self.console.removeNamespaceSnapshotVars,
			})

			self._execDoneEvt = threading.Event()
			while self._keepRunning:
				line = self.rfile.readline()
				if not line:
					break
				line = line.rstrip("\r\n")
				# Execute in the main thread.
				wx.CallAfter(self.execute, line)
				# Wait until the line has finished executing before retrieving the next.
				self._execDoneEvt.wait()
				self._execDoneEvt.clear()

		except:
			log.exception("Error handling remote Python console request")
		finally:
			# Clean up the console.
			self.console = None

def initialize():
	global server
	server = SocketServer.TCPServer(("", PORT), RequestHandler)
	server.daemon_threads = True
	thread = threading.Thread(target=server.serve_forever)
	thread.daemon = True
	thread.start()

def terminate():
	global server
	server.shutdown()
	server = None