示例#1
0
 def enter_secure_desktop(self):
     """function ran when entering a secure desktop."""
     if self.slave_transport is None:
         return
     if not os.path.exists(self.temp_location):
         os.makedirs(self.temp_location)
     channel = str(uuid.uuid4())
     self.sd_server = server.Server(port=0,
                                    password=channel,
                                    bind_host='127.0.0.1')
     port = self.sd_server.server_socket.getsockname()[1]
     server_thread = threading.Thread(target=self.sd_server.run)
     server_thread.daemon = True
     server_thread.start()
     self.sd_relay = RelayTransport(address=('127.0.0.1', port),
                                    serializer=serializer.JSONSerializer(),
                                    channel=channel)
     self.sd_relay.callback_manager.register_callback(
         'msg_client_joined', self.on_master_display_change)
     self.slave_transport.callback_manager.register_callback(
         'msg_set_braille_info', self.on_master_display_change)
     self.sd_bridge = bridge.BridgeTransport(self.slave_transport,
                                             self.sd_relay)
     relay_thread = threading.Thread(target=self.sd_relay.run)
     relay_thread.daemon = True
     relay_thread.start()
     data = [port, channel]
     with open(self.ipc_file, 'wb') as fp:
         json.dump(data, fp)
示例#2
0
    def enter_secure_desktop(self):
        """function ran when entering a secure desktop.
			So far as I can tell, this function does a few things:
				* checks if any connections are active. If not, returns
				* If a temp directory doesn't exist, makes one, starts a local relay with a random (4 character?) id and writes that info to a file. I assume that this is somehow connecting back to the client running in a non-secure environment
		"""
        if self.control_connector is None:  # No connections are open
            return
        if not os.path.exists(self.temp_location):
            os.makedirs(self.temp_location)
        channel = str(uuid.uuid4())
        self.sd_server = server.Server(port=0,
                                       password=channel,
                                       bind_host='127.0.0.1')
        port = self.sd_server.server_socket.getsockname()[1]
        server_thread = threading.Thread(target=self.sd_server.run)
        server_thread.daemon = True
        server_thread.start()
        self.sd_relay = RelayTransport(address=('127.0.0.1', port),
                                       serializer=serializer.JSONSerializer(),
                                       channel=channel)
        self.sd_bridge = bridge.BridgeTransport(self.control_connector,
                                                self.sd_relay)
        relay_thread = threading.Thread(target=self.sd_relay.run)
        relay_thread.daemon = True
        relay_thread.start()
        data = [port, channel]
        with open(self.ipc_file, 'wb') as fp:
            json.dump(data, fp)
示例#3
0
 def enter_secure_desktop(self):
     """function ran when entering a secure desktop."""
     if self.control_connector is None:
         return
     if not os.path.exists(self.temp_location):
         os.makedirs(self.temp_location)
     channel = str(uuid.uuid4())
     self.sd_server = server.Server(port=0,
                                    password=channel,
                                    bind_host='127.0.0.1')
     port = self.sd_server.server_socket.getsockname()[1]
     server_thread = threading.Thread(target=self.sd_server.run)
     server_thread.daemon = True
     server_thread.start()
     self.sd_relay = RelayTransport(address=('127.0.0.1', port),
                                    serializer=serializer.JSONSerializer(),
                                    channel=channel)
     self.sd_bridge = bridge.BridgeTransport(self.control_connector,
                                             self.sd_relay)
     relay_thread = threading.Thread(target=self.sd_relay.run)
     relay_thread.daemon = True
     relay_thread.start()
     data = [port, channel]
     with open(self.ipc_file, 'wb') as fp:
         json.dump(data, fp)
示例#4
0
 def connect_as_slave(self, address, key):
     transport = RelayTransport(serializer=serializer.JSONSerializer(),
                                address=address,
                                channel=key,
                                connection_type='slave')
     self.slave_session = SlaveSession(transport=transport,
                                       local_machine=self.local_machine)
     self.slave_transport = transport
     self.slave_transport.callback_manager.register_callback(
         'transport_connected', self.on_connected_as_slave)
     self.slave_transport.reconnector_thread.start()
     self.disconnect_item.Enable(True)
     self.connect_item.Enable(False)
示例#5
0
 def connect_slave(self, address, channel):
     transport = RelayTransport(address=address,
                                serializer=serializer.JSONSerializer(),
                                channel=channel)
     self.master_session = MasterSession(transport=transport,
                                         local_machine=self.local_machine)
     transport.callback_manager.register_callback(
         'transport_connected', self.on_connected_to_slave)
     transport.callback_manager.register_callback(
         'transport_connection_failed', self.on_slave_connection_failed)
     transport.callback_manager.register_callback(
         'transport_disconnected', self.on_disconnected_from_slave)
     self.connector = transport
     self.connector_thread = ConnectorThread(connector=transport)
     self.connector_thread.start()
示例#6
0
 def connect_as_master(self, address, key):
     transport = RelayTransport(address=address,
                                serializer=serializer.JSONSerializer(),
                                channel=key,
                                connection_type='master')
     self.master_session = MasterSession(transport=transport,
                                         local_machine=self.local_machine)
     transport.callback_manager.register_callback(
         'transport_connected', self.on_connected_as_master)
     transport.callback_manager.register_callback(
         'transport_connection_failed', self.on_connected_as_master_failed)
     transport.callback_manager.register_callback(
         'transport_closing', self.disconnecting_as_master)
     transport.callback_manager.register_callback(
         'transport_disconnected', self.on_disconnected_as_master)
     self.master_transport = transport
     self.master_transport.reconnector_thread.start()
示例#7
0
    def connect_control(self, address=SERVER_ADDR, key=None):
        if self.control_connector_thread is not None:
            self.control_connector_thread.running = False
            if self.control_connector is not None:
                self.control_connector.close()
            self.control_connector_thread = None

        transport = RelayTransport(serializer=serializer.JSONSerializer(),
                                   address=address,
                                   channel=key)
        self.slave_session = SlaveSession(transport=transport,
                                          local_machine=self.local_machine)
        self.control_connector = transport
        self.control_connector.callback_manager.register_callback(
            'transport_connected', self.connected_to_relay)
        self.control_connector_thread = ConnectorThread(
            connector=self.control_connector)
        self.control_connector_thread.start()
        self.disconnect_item.Enable(True)
        self.connect_item.Enable(False)
示例#8
0
	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop."""
		if self.control_connector is None:
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_bridge = bridge.BridgeTransport(self.control_connector, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)
示例#9
0
	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop."""
		if self.slave_transport is None:
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_relay.callback_manager.register_callback('msg_client_joined', self.on_master_display_change)
		self.slave_transport.callback_manager.register_callback('msg_set_braille_info', self.on_master_display_change)
		self.sd_bridge = bridge.BridgeTransport(self.slave_transport, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)
示例#10
0
	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop.
			So far as I can tell, this function does a few things:
				* checks if any connections are active. If not, returns
				* If a temp directory doesn't exist, makes one, starts a local relay with a random (4 character?) id and writes that info to a file. I assume that this is somehow connecting back to the client running in a non-secure environment
		"""
		if self.control_connector is None: # No connections are open
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_bridge = bridge.BridgeTransport(self.control_connector, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)
示例#11
0
class GlobalPlugin(GlobalPlugin):
	scriptCategory = _("NVDA Remote")

	def __init__(self, *args, **kwargs):
		super(GlobalPlugin, self).__init__(*args, **kwargs)
		self.local_machine = local_machine.LocalMachine()
		self.slave_session = None
		self.master_session = None
		self.create_menu()
		self.connector = None
		self.control_connector_thread = None
		self.control_connector = None
		self.server = None
		self.hook_thread = None
		self.sending_keys = False
		self.key_modified = False
		self.sd_server = None
		cs = get_config()['controlserver']
		if cs['autoconnect']:

			if cs['autoconnect'] == 1: # self-hosted server
				self.server = server.Server(SERVER_PORT, cs['key'])
				server_thread = threading.Thread(target=self.server.run)
				server_thread.daemon = True
				server_thread.start()
				address = address_to_hostport('localhost')
			elif cs['autoconnect'] == '2':
				address = address_to_hostport(cs['host'])
			elif cs['autoconnect'] == True: # Previous version config, change value to 2 for external control server
				config = get_config()
				config['controlserver']['autoconnect'] = 2
				config.write()
				address = address_to_hostport(cs['host'])
			self.connect_control(address, cs['key']) # Connect to the server
		self.temp_location = os.path.join(shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), 'temp')
		self.ipc_file = os.path.join(self.temp_location, 'remote.ipc')
		self.sd_focused = False
		self.check_secure_desktop()

	def create_menu(self):
		self.menu = wx.Menu()
		tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
		# Translators: Item in NVDA Remote submenu to connect to a remote computer.
		self.connect_item = self.menu.Append(wx.ID_ANY, _("Connect..."), _("Remotely connect to another computer running NVDA Remote Access"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.do_connect, self.connect_item)
		# Translators: Item in NVDA Remote submenu to disconnect from a remote computer.
		self.disconnect_item = self.menu.Append(wx.ID_ANY, _("Disconnect"), _("Disconnect from another computer running NVDA Remote Access"))
		self.disconnect_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_disconnect_item, self.disconnect_item)
		# Translators: Menu item in NvDA Remote submenu to mute speech from the remote computer.
		self.mute_item = self.menu.Append(wx.ID_ANY, _("Mute remote speech"), _("Mute speech from the remote computer"))
		self.mute_item.SetCheckable(True)
		self.mute_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_mute_item, self.mute_item)
		# Translators: Menu item in NVDA Remote submenu to push clipboard content to the remote computer.
		self.push_clipboard_item = self.menu.Append(wx.ID_ANY, _("&Push clipboard"), _("Push the clipboard to the other machine"))
		self.push_clipboard_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_push_clipboard_item, self.push_clipboard_item)
		# Translators: Menu item in NvDA Remote submenu to open add-on options.
		self.options_item = self.menu.Append(wx.ID_ANY, _("&Options..."), _("Options"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_options_item, self.options_item)
		# Translators: Menu item in NVDA Remote submenu to send Control+Alt+Delete to the remote computer.
		self.send_ctrl_alt_del_item = self.menu.Append(wx.ID_ANY, _("Send Ctrl+Alt+Del"), _("Send Ctrl+Alt+Del"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_send_ctrl_alt_del, self.send_ctrl_alt_del_item)
		self.send_ctrl_alt_del_item.Enable(False)
		# Translators: Label of menu in NVDA tools menu.
		self.remote_item=tools_menu.AppendSubMenu(self.menu, _("R&emote"), _("NVDA Remote Access"))

	def terminate(self):
		self.do_disconnect_from_slave(True)
		self.local_machine = None
		self.menu.RemoveItem(self.connect_item)
		self.connect_item.Destroy()
		self.connect_item=None
		self.menu.RemoveItem(self.disconnect_item)
		self.disconnect_item.Destroy()
		self.disconnect_item=None
		self.menu.RemoveItem(self.mute_item)
		self.mute_item.Destroy()
		self.mute_item=None
		self.menu.RemoveItem(self.push_clipboard_item)
		self.push_clipboard_item.Destroy()
		self.push_clipboard_item=None
		self.menu.RemoveItem(self.options_item)
		self.options_item.Destroy()
		self.options_item=None
		self.menu.RemoveItem(self.send_ctrl_alt_del_item)
		self.send_ctrl_alt_del_item.Destroy()
		self.send_ctrl_alt_del_item=None
		tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
		tools_menu.RemoveItem(self.remote_item)
		self.remote_item.Destroy()
		self.remote_item=None
		try:
			self.menu.Destroy()
		except wx.PyDeadObjectError:
			pass
		self.menu=None

	def on_disconnect_item(self, evt):
		if evt != 'gesture':
			evt.Skip()
		self.do_disconnect_from_slave()

	def script_disconnect(self, gesture):
		self.do_disconnect_from_slave()
	script_disconnect.__doc__ = _("""Disconnect from a remote computer""")

	def on_mute_item(self, evt):
		evt.Skip()
		self.local_machine.is_muted = self.mute_item.IsChecked()

	def script_toggle_remote_mute(self, gesture):
		self.local_machine.is_muted = not self.local_machine.is_muted
		self.mute_item.Check(self.local_machine.is_muted)
	script_toggle_remote_mute.__doc__ = _("""Mute or unmute the speech coming from the remote computer""")



	def script_connect(self, gesture):
		if self.connector or self.control_connector : # a connection is already established
			speech.speakMessage(_("You can't open that dialog, a connection is already established"))
		elif self.connector is None and self.control_connector is None: # A connection doesn't yet exist, open the dialog
			self.do_connect('gesture')
		else:
			speech.speakMessage(_("Error, connection state can't be determined!"))
	script_connect.__doc__ = _("""Open the connect dialog if a connection isn't already established""")


	def script_status(self, gesture):
		statusmessage = "NVDA Remote "
		if self.connector is None and self.control_connector is None and self.server is None: 
			statusmessage = statusmessage + "isn't currently connected to a server or any clients."
		elif self.connector is not None and self.server is None:
			statusmessage = statusmessage+"is currently connected to the relay server '%s, port %d', and can control another computer using the same server and key" %(self.serveraddress, self.serverport)
		elif self.control_connector is not None and self.server is None:
			statusmessage = statusmessage+"is currently connected to the relay server '%s, port %d', and can be controlled by anyone who knows this relay and your key" %(self.serveraddress, self.serverport)
		elif self.server is not None and self.connector is not None:
			statusmessage = statusmessage+"is currently connected to your local server and can control another computer using your IP address and key"
		elif self.server is not None and self.control_connector is not None:
			statusmessage = statusmessage + "is currently connected to your local server and can be controlled by anyone who knows this IP address and your key"
		if self.local_machine.is_muted == True:
			statusmessage = statusmessage + ", and has remote speech muted"
		speech.speakMessage(statusmessage)
	script_status.__doc__= _("""Announce the status of NVDA remote, including connection state, if a local server is running, and if remote speech is muted""")


	def on_push_clipboard_item(self, evt):
		connector = self.control_connector or self.connector
		try:
			connector.send(type='set_clipboard_text', text=api.getClipData())
		except TypeError as e:
			log.debug("Error while pushing clipboard: %s" %(e))

	def script_push_clipboard_item(self, gesture):
		self.on_push_clipboard_item('gesture')
	script_push_clipboard_item.__doc__=_("""Push your clipboard to the remote computer""")

	def on_options_item(self, evt):
		evt.Skip()
		config = get_config()
		# Translators: The title of the add-on options dialog.
		dlg = dialogs.OptionsDialog(gui.mainFrame, wx.ID_ANY, title=_("Options"))
		dlg.set_from_config(config)
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			dlg.write_to_config(config)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_send_ctrl_alt_del(self, evt):
		self.connector.send('send_SAS')

	def do_disconnect_from_slave(self, quiet=False):
		if self.connector is None and self.control_connector is None:
			if not quiet:
				ui.message(_("Not connected."))
			return
		if self.connector is not None:
			self.disconnect_from_slave()
		if self.control_connector is not None:
			self.disconnect_control()
		if self.server is not None:
			self.server.close()
			self.server = None
		beep_sequence.beep_sequence((660, 60), (440, 60))
		self.disconnect_item.Enable(False)
		self.connect_item.Enable(True)
		self.push_clipboard_item.Enable(False)

	def disconnect_from_slave(self):
		self.connector.close()
		self.connector = None
		if self.connector_thread is not None:
			self.connector_thread.running = False
		self.connect_item.Enable(True)
		self.disconnect_item.Enable(False)
		self.mute_item.Check(False)
		self.mute_item.Enable(False)
		self.push_clipboard_item.Enable(False)
		self.send_ctrl_alt_del_item.Enable(False)
		self.sending_keys = False
		if self.hook_thread is not None:
			ctypes.windll.user32.PostThreadMessageW(self.hook_thread.ident, win32con.WM_QUIT, 0, 0)
			self.hook_thread.join()
			self.hook_thread = None
		self.key_modified = False

	def disconnect_control(self):
		self.control_connector_thread.running = False
		self.control_connector.close()
		self.control_connector = None

	def on_slave_connection_failed(self):
		if self.connector.successful_connects == 0:
			self.disconnect_from_slave()
			# Translators: Title of the connection error dialog.
			gui.messageBox(parent=gui.mainFrame, caption=_("Error Connecting"),
			# Translators: Message shown when cannot connect to the remote computer.
			message=_("Unable to connect to the remote computer"), style=wx.OK | wx.ICON_WARNING)


	def do_connect(self, evt):
		if evt != 'gesture':
			evt.Skip()
		last_cons = get_config()['connections']['last_connected']
		last = ''
		if last_cons:
			last = last_cons[-1]
		# Translators: Title of the connect dialog.
		dlg = dialogs.DirectConnectDialog(parent=gui.mainFrame, id=wx.ID_ANY, title=_("Connect"))
		dlg.panel.host.SetValue(last)
		dlg.panel.host.SelectAll()
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			if dlg.client_or_server.GetSelection() == 0: #client
				server_addr = dlg.panel.host.GetValue()
				server_addr, port = address_to_hostport(server_addr)
				channel = dlg.panel.key.GetValue()
				if dlg.connection_type.GetSelection() == 0:
					self.connect_slave((server_addr, port), channel)
				else:
					self.connect_control((server_addr, port), channel)
			else: #We want a server
				channel = dlg.panel.key.GetValue()
				self.server = server.Server(SERVER_PORT, channel)
				server_thread = threading.Thread(target=self.server.run)
				server_thread.daemon = True
				server_thread.start()
				if dlg.connection_type.GetSelection() == 0:
					self.connect_slave(('127.0.0.1', SERVER_PORT), channel)
				else:
					self.connect_control(('127.0.0.1', SERVER_PORT), channel)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_connected_to_slave(self):
		write_connection_to_config(self.connector.address)
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)
		self.mute_item.Enable(True)
		self.push_clipboard_item.Enable(True)
		self.send_ctrl_alt_del_item.Enable(True)
		self.hook_thread = threading.Thread(target=self.hook)
		self.hook_thread.daemon = True
		self.hook_thread.start()
		# Translators: Presented when connected to the remote computer.
		ui.message(_("Connected!"))
		beep_sequence.beep_sequence((440, 60), (660, 60))

	def on_disconnected_from_slave(self):
		# Translators: Presented when connection to a remote computer was interupted.
		ui.message(_("Connection interrupted"))

	def connect_slave(self, address, channel):
		transport = RelayTransport(address=address, serializer=serializer.JSONSerializer(), channel=channel)
		self.master_session = MasterSession(transport=transport, local_machine=self.local_machine)
		transport.callback_manager.register_callback('transport_connected', self.on_connected_to_slave)
		transport.callback_manager.register_callback('transport_connection_failed', self.on_slave_connection_failed)
		transport.callback_manager.register_callback('transport_disconnected', self.on_disconnected_from_slave)
		self.connector = transport
		self.connector_thread = ConnectorThread(connector=transport)
		self.connector_thread.start()

		self.serveraddress = address



	def connect_control(self, address=SERVER_ADDR, key=None):
		if self.control_connector_thread is not None:
			self.control_connector_thread.running = False
			if self.control_connector is not None:
				self.control_connector.close()
			self.control_connector_thread = None

		transport = RelayTransport(serializer=serializer.JSONSerializer(), address=address, channel=key)
		self.slave_session = SlaveSession(transport=transport, local_machine=self.local_machine)
		self.control_connector = transport
		self.control_connector.callback_manager.register_callback('transport_connected', self.connected_to_relay)
		self.control_connector_thread = ConnectorThread(connector=self.control_connector)
		self.control_connector_thread.start()

		self.serveraddress, self.serverport= address
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)

	def connected_to_relay(self):
		log.info("Control connector connected")
		beep_sequence.beep_sequence((720, 100), 50, (720, 100), 50, (720, 100))
		# Translators: Presented in direct (client to server) remote connection when the controlled computer is ready.
		speech.speakMessage(_("Connected to control server"))
		self.push_clipboard_item.Enable(True)
		write_connection_to_config(self.control_connector.address)

	def hook(self):
		log.debug("Hook thread start")
		keyhook = keyboard_hook.KeyboardHook()
		keyhook.register_callback(self.hook_callback)
		msg = ctypes.MSG()
		while ctypes.windll.user32.GetMessageW(ctypes.byref(msg), None, 0, 0):
			pass
		log.debug("Hook thread end")
		keyhook.free()

	def hook_callback(self, **kwargs):
		if kwargs['vk_code'] != win32con.VK_F11:
			self.key_modified = kwargs['pressed']
		if kwargs['vk_code'] == win32con.VK_F11 and kwargs['pressed'] and not self.key_modified:
			self.sending_keys = not self.sending_keys
			if self.sending_keys:
				# Translators: Presented when sending keyboard keys from the controlling computer to the controlled computer.
				ui.message(_("Sending keys."))
			else:
				# Translators: Presented when keyboard control is back to the controlling computer.
				ui.message(_("Not sending keys."))
			return True #Don't pass it on
		if not self.sending_keys:
			return False #Let the host have it
		self.connector.send(type="key", **kwargs)
		return True #Don't pass it on

	def event_gainFocus(self, obj, nextHandler):
		if isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			self.sd_focused = True
			self.enter_secure_desktop()
		elif self.sd_focused and not isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			#event_leaveFocus won't work for some reason
			self.sd_focused = False
			self.leave_secure_desktop()
		nextHandler()

	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop.
			So far as I can tell, this function does a few things:
				* checks if any connections are active. If not, returns
				* If a temp directory doesn't exist, makes one, starts a local relay with a random (4 character?) id and writes that info to a file. I assume that this is somehow connecting back to the client running in a non-secure environment
		"""
		if self.control_connector is None: # No connections are open
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_bridge = bridge.BridgeTransport(self.control_connector, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)

	def leave_secure_desktop(self):
		if self.sd_server is None:
			return #Nothing to do
		self.sd_bridge.disconnect()
		self.sd_server.close()
		self.sd_relay.close()

	def check_secure_desktop(self):
		if not globalVars.appArgs.secure:
			return
		with open(self.ipc_file) as fp:
			data = json.load(fp)
		os.unlink(self.ipc_file)
		port, channel = data
		self.connect_control(('127.0.0.1', port), channel)

	__gestures = {
	}
示例#12
0
class GlobalPlugin(GlobalPlugin):
    scriptCategory = _("NVDA Remote")

    def __init__(self, *args, **kwargs):
        super(GlobalPlugin, self).__init__(*args, **kwargs)
        self.local_machine = local_machine.LocalMachine()
        self.slave_session = None
        self.master_session = None
        self.create_menu()
        self.connecting = False
        self.url_handler_window = url_handler.URLHandlerWindow(
            callback=self.verify_connect)
        url_handler.register_url_handler()
        self.master_transport = None
        self.slave_transport = None
        self.server = None
        self.hook_thread = None
        self.sending_keys = False
        self.key_modified = False
        self.sd_server = None
        self.sd_relay = None
        self.sd_bridge = None
        cs = configuration.get_config()['controlserver']
        self.temp_location = os.path.join(
            shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), 'temp')
        self.ipc_file = os.path.join(self.temp_location, 'remote.ipc')
        if globalVars.appArgs.secure:
            self.handle_secure_desktop()
        if cs['autoconnect'] and not self.master_session and not self.slave_session:
            self.perform_autoconnect()
        self.sd_focused = False

    def perform_autoconnect(self):
        cs = configuration.get_config()['controlserver']
        channel = cs['key']
        if cs['self_hosted']:
            port = cs['port']
            address = ('localhost', port)
            self.start_control_server(port, channel)
        else:
            address = address_to_hostport(cs['host'])
        if cs['connection_type'] == 0:
            self.connect_as_slave(address, channel)
        else:
            self.connect_as_master(address, channel)

    def create_menu(self):
        self.menu = wx.Menu()
        tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
        # Translators: Item in NVDA Remote submenu to connect to a remote computer.
        self.connect_item = self.menu.Append(
            wx.ID_ANY, _("Connect..."),
            _("Remotely connect to another computer running NVDA Remote Access"
              ))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.do_connect,
                                       self.connect_item)
        # Translators: Item in NVDA Remote submenu to disconnect from a remote computer.
        self.disconnect_item = self.menu.Append(
            wx.ID_ANY, _("Disconnect"),
            _("Disconnect from another computer running NVDA Remote Access"))
        self.disconnect_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_disconnect_item,
                                       self.disconnect_item)
        # Translators: Menu item in NvDA Remote submenu to mute speech and sounds from the remote computer.
        self.mute_item = self.menu.Append(
            wx.ID_ANY,
            _("Mute remote"),
            _("Mute speech and sounds from the remote computer"),
            kind=wx.ITEM_CHECK)
        self.mute_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_mute_item,
                                       self.mute_item)
        # Translators: Menu item in NVDA Remote submenu to push clipboard content to the remote computer.
        self.push_clipboard_item = self.menu.Append(
            wx.ID_ANY, _("&Push clipboard"),
            _("Push the clipboard to the other machine"))
        self.push_clipboard_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU,
                                       self.on_push_clipboard_item,
                                       self.push_clipboard_item)
        # Translators: Menu item in NVDA Remote submenu to copy a link to the current session.
        self.copy_link_item = self.menu.Append(
            wx.ID_ANY, _("Copy &link"), _("Copy a link to the remote session"))
        self.copy_link_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_copy_link_item,
                                       self.copy_link_item)
        # Translators: Menu item in NvDA Remote submenu to open add-on options.
        self.options_item = self.menu.Append(wx.ID_ANY, _("&Options..."),
                                             _("Options"))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_options_item,
                                       self.options_item)
        # Translators: Menu item in NVDA Remote submenu to send Control+Alt+Delete to the remote computer.
        self.send_ctrl_alt_del_item = self.menu.Append(wx.ID_ANY,
                                                       _("Send Ctrl+Alt+Del"),
                                                       _("Send Ctrl+Alt+Del"))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_send_ctrl_alt_del,
                                       self.send_ctrl_alt_del_item)
        self.send_ctrl_alt_del_item.Enable(False)
        # Translators: Label of menu in NVDA tools menu.
        self.remote_item = tools_menu.AppendSubMenu(self.menu, _("R&emote"),
                                                    _("NVDA Remote Access"))

    def terminate(self):
        self.disconnect()
        self.local_machine = None
        self.menu.RemoveItem(self.connect_item)
        self.connect_item.Destroy()
        self.connect_item = None
        self.menu.RemoveItem(self.disconnect_item)
        self.disconnect_item.Destroy()
        self.disconnect_item = None
        self.menu.RemoveItem(self.mute_item)
        self.mute_item.Destroy()
        self.mute_item = None
        self.menu.RemoveItem(self.push_clipboard_item)
        self.push_clipboard_item.Destroy()
        self.push_clipboard_item = None
        self.menu.RemoveItem(self.copy_link_item)
        self.copy_link_item.Destroy()
        self.copy_link_item = None
        self.menu.RemoveItem(self.options_item)
        self.options_item.Destroy()
        self.options_item = None
        self.menu.RemoveItem(self.send_ctrl_alt_del_item)
        self.send_ctrl_alt_del_item.Destroy()
        self.send_ctrl_alt_del_item = None
        tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
        tools_menu.RemoveItem(self.remote_item)
        self.remote_item.Destroy()
        self.remote_item = None
        try:
            self.menu.Destroy()
        except (RuntimeError, AttributeError):
            pass
        try:
            os.unlink(self.ipc_file)
        except:
            pass
        self.menu = None
        if not isInstalledCopy():
            url_handler.unregister_url_handler()
        self.url_handler_window.destroy()
        self.url_handler_window = None

    def on_disconnect_item(self, evt):
        evt.Skip()
        self.disconnect()

    def on_mute_item(self, evt):
        evt.Skip()
        self.local_machine.is_muted = self.mute_item.IsChecked()

    def script_toggle_remote_mute(self, gesture):
        self.local_machine.is_muted = not self.local_machine.is_muted
        self.mute_item.Check(self.local_machine.is_muted)

    script_toggle_remote_mute.__doc__ = _(
        """Mute or unmute the speech coming from the remote computer""")

    def on_push_clipboard_item(self, evt):
        connector = self.slave_transport or self.master_transport
        try:
            connector.send(type='set_clipboard_text', text=api.getClipData())
        except TypeError:
            log.exception("Unable to push clipboard")

    def script_push_clipboard(self, gesture):
        connector = self.slave_transport or self.master_transport
        if not getattr(connector, 'connected', False):
            ui.message(_("Not connected."))
            return
        try:
            connector.send(type='set_clipboard_text', text=api.getClipData())
            ui.message(_("Clipboard pushed"))
        except TypeError:
            ui.message(_("Unable to push clipboard"))

    script_push_clipboard.__doc__ = _(
        "Sends the contents of the clipboard to the remote machine")

    def on_copy_link_item(self, evt):
        session = self.master_session or self.slave_session
        url = session.get_connection_info().get_url_to_connect()
        api.copyToClip(unicode(url))

    def script_copy_link(self, gesture):
        self.on_copy_link_item(None)
        ui.message(_("Copied link"))

    script_copy_link.__doc__ = _(
        "Copies a link to the remote session to the clipboard")

    def on_options_item(self, evt):
        evt.Skip()
        conf = configuration.get_config()
        # Translators: The title of the add-on options dialog.
        dlg = dialogs.OptionsDialog(gui.mainFrame,
                                    wx.ID_ANY,
                                    title=_("Options"))
        dlg.set_from_config(conf)

        def handle_dlg_complete(dlg_result):
            if dlg_result != wx.ID_OK:
                return
            dlg.write_to_config(conf)

        gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

    def on_send_ctrl_alt_del(self, evt):
        self.master_transport.send('send_SAS')

    def disconnect(self):
        if self.master_transport is None and self.slave_transport is None:
            return
        if self.server is not None:
            self.server.close()
            self.server = None
        if self.master_transport is not None:
            self.disconnect_as_master()
        if self.slave_transport is not None:
            self.disconnect_as_slave()
        beep_sequence.beep_sequence_async((660, 60), (440, 60))
        self.disconnect_item.Enable(False)
        self.connect_item.Enable(True)
        self.push_clipboard_item.Enable(False)
        self.copy_link_item.Enable(False)

    def disconnect_as_master(self):
        self.master_transport.close()
        self.master_transport = None
        self.master_session = None

    def disconnecting_as_master(self):
        if self.menu:
            self.connect_item.Enable(True)
            self.disconnect_item.Enable(False)
            self.mute_item.Check(False)
            self.mute_item.Enable(False)
            self.push_clipboard_item.Enable(False)
            self.copy_link_item.Enable(False)
            self.send_ctrl_alt_del_item.Enable(False)
        if self.local_machine:
            self.local_machine.is_muted = False
        self.sending_keys = False
        if self.hook_thread is not None:
            ctypes.windll.user32.PostThreadMessageW(self.hook_thread.ident,
                                                    win32con.WM_QUIT, 0, 0)
            self.hook_thread.join()
            self.hook_thread = None
            self.removeGestureBinding(REMOTE_KEY)
        self.key_modified = False

    def disconnect_as_slave(self):
        self.slave_transport.close()
        self.slave_transport = None
        self.slave_session = None

    def on_connected_as_master_failed(self):
        if self.master_transport.successful_connects == 0:
            self.disconnect_as_master()
            # Translators: Title of the connection error dialog.
            gui.messageBox(
                parent=gui.mainFrame,
                caption=_("Error Connecting"),
                # Translators: Message shown when cannot connect to the remote computer.
                message=_("Unable to connect to the remote computer"),
                style=wx.OK | wx.ICON_WARNING)

    def script_disconnect(self, gesture):
        if self.master_transport is None and self.slave_transport is None:
            ui.message(_("Not connected."))
            return
        self.disconnect()

    script_disconnect.__doc__ = _("""Disconnect a remote session""")

    def do_connect(self, evt):
        evt.Skip()
        last_cons = configuration.get_config()['connections']['last_connected']
        last = ''
        if last_cons:
            last = last_cons[-1]
        # Translators: Title of the connect dialog.
        dlg = dialogs.DirectConnectDialog(parent=gui.mainFrame,
                                          id=wx.ID_ANY,
                                          title=_("Connect"))
        dlg.panel.host.SetValue(last)
        dlg.panel.host.SelectAll()

        def handle_dlg_complete(dlg_result):
            if dlg_result != wx.ID_OK:
                return
            if dlg.client_or_server.GetSelection() == 0:  #client
                server_addr = dlg.panel.host.GetValue()
                server_addr, port = address_to_hostport(server_addr)
                channel = dlg.panel.key.GetValue()
                if dlg.connection_type.GetSelection() == 0:
                    self.connect_as_master((server_addr, port), channel)
                else:
                    self.connect_as_slave((server_addr, port), channel)
            else:  #We want a server
                channel = dlg.panel.key.GetValue()
                self.start_control_server(int(dlg.panel.port.GetValue()),
                                          channel)
                if dlg.connection_type.GetSelection() == 0:
                    self.connect_as_master(
                        ('127.0.0.1', int(dlg.panel.port.GetValue())), channel)
                else:
                    self.connect_as_slave(
                        ('127.0.0.1', int(dlg.panel.port.GetValue())), channel)

        gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

    def on_connected_as_master(self):
        configuration.write_connection_to_config(self.master_transport.address)
        self.disconnect_item.Enable(True)
        self.connect_item.Enable(False)
        self.mute_item.Enable(True)
        self.push_clipboard_item.Enable(True)
        self.copy_link_item.Enable(True)
        self.send_ctrl_alt_del_item.Enable(True)
        self.hook_thread = threading.Thread(target=self.hook)
        self.hook_thread.daemon = True
        self.hook_thread.start()
        self.bindGesture(REMOTE_KEY, "sendKeys")
        # Translators: Presented when connected to the remote computer.
        ui.message(_("Connected!"))
        beep_sequence.beep_sequence_async((440, 60), (660, 60))

    def on_disconnected_as_master(self):
        # Translators: Presented when connection to a remote computer was interupted.
        ui.message(_("Connection interrupted"))

    def connect_as_master(self, address, key):
        transport = RelayTransport(address=address,
                                   serializer=serializer.JSONSerializer(),
                                   channel=key,
                                   connection_type='master')
        self.master_session = MasterSession(transport=transport,
                                            local_machine=self.local_machine)
        transport.callback_manager.register_callback(
            'transport_connected', self.on_connected_as_master)
        transport.callback_manager.register_callback(
            'transport_connection_failed', self.on_connected_as_master_failed)
        transport.callback_manager.register_callback(
            'transport_closing', self.disconnecting_as_master)
        transport.callback_manager.register_callback(
            'transport_disconnected', self.on_disconnected_as_master)
        self.master_transport = transport
        self.master_transport.reconnector_thread.start()

    def connect_as_slave(self, address, key):
        transport = RelayTransport(serializer=serializer.JSONSerializer(),
                                   address=address,
                                   channel=key,
                                   connection_type='slave')
        self.slave_session = SlaveSession(transport=transport,
                                          local_machine=self.local_machine)
        self.slave_transport = transport
        self.slave_transport.callback_manager.register_callback(
            'transport_connected', self.on_connected_as_slave)
        self.slave_transport.reconnector_thread.start()
        self.disconnect_item.Enable(True)
        self.connect_item.Enable(False)

    def on_connected_as_slave(self):
        log.info("Control connector connected")
        beep_sequence.beep_sequence_async((720, 100), 50, (720, 100), 50,
                                          (720, 100))
        # Translators: Presented in direct (client to server) remote connection when the controlled computer is ready.
        speech.speakMessage(_("Connected to control server"))
        self.push_clipboard_item.Enable(True)
        self.copy_link_item.Enable(True)
        configuration.write_connection_to_config(self.slave_transport.address)

    def start_control_server(self, server_port, channel):
        self.server = server.Server(server_port, channel)
        server_thread = threading.Thread(target=self.server.run)
        server_thread.daemon = True
        server_thread.start()

    def hook(self):
        log.debug("Hook thread start")
        keyhook = keyboard_hook.KeyboardHook()
        keyhook.register_callback(self.hook_callback)
        msg = ctypes.MSG()
        while ctypes.windll.user32.GetMessageW(ctypes.byref(msg), None, 0, 0):
            pass
        log.debug("Hook thread end")
        keyhook.free()

    def hook_callback(self, **kwargs):
        #Prevent disabling sending keys if another key is held down
        if not self.sending_keys:
            return False
        if kwargs['vk_code'] != win32con.VK_F11:
            self.key_modified = kwargs['pressed']
        if kwargs['vk_code'] == win32con.VK_F11 and kwargs[
                'pressed'] and not self.key_modified:
            self.sending_keys = False
            self.set_receiving_braille(False)
            # Translators: Presented when keyboard control is back to the controlling computer.
            ui.message(_("Controlling local machine."))
            return True  #Don't pass it on
        self.master_transport.send(type="key", **kwargs)
        return True  #Don't pass it on

    def script_sendKeys(self, gesture):
        # Translators: Presented when sending keyboard keys from the controlling computer to the controlled computer.
        ui.message(_("Controlling remote machine."))
        self.sending_keys = True
        self.set_receiving_braille(True)

    def set_receiving_braille(self, state):
        if state and self.master_session.patch_callbacks_added and braille.handler.enabled:
            self.master_session.patcher.patch_braille_input()
            braille.handler.enabled = False
            if braille.handler._cursorBlinkTimer:
                braille.handler._cursorBlinkTimer.Stop()
                braille.handler._cursorBlinkTimer = None
            if braille.handler.buffer is braille.handler.messageBuffer:
                braille.handler.buffer.clear()
                braille.handler.buffer = braille.handler.mainBuffer
                if braille.handler._messageCallLater:
                    braille.handler._messageCallLater.Stop()
                    braille.handler._messageCallLater = None
            self.local_machine.receiving_braille = True
        elif not state:
            self.master_session.patcher.unpatch_braille_input()
            braille.handler.enabled = bool(braille.handler.displaySize)
            self.local_machine.receiving_braille = False

    def event_gainFocus(self, obj, nextHandler):
        if isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
            self.sd_focused = True
            self.enter_secure_desktop()
        elif self.sd_focused and not isinstance(
                obj, IAccessibleHandler.SecureDesktopNVDAObject):
            #event_leaveFocus won't work for some reason
            self.sd_focused = False
            self.leave_secure_desktop()
        nextHandler()

    def enter_secure_desktop(self):
        """function ran when entering a secure desktop."""
        if self.slave_transport is None:
            return
        if not os.path.exists(self.temp_location):
            os.makedirs(self.temp_location)
        channel = str(uuid.uuid4())
        self.sd_server = server.Server(port=0,
                                       password=channel,
                                       bind_host='127.0.0.1')
        port = self.sd_server.server_socket.getsockname()[1]
        server_thread = threading.Thread(target=self.sd_server.run)
        server_thread.daemon = True
        server_thread.start()
        self.sd_relay = RelayTransport(address=('127.0.0.1', port),
                                       serializer=serializer.JSONSerializer(),
                                       channel=channel)
        self.sd_relay.callback_manager.register_callback(
            'msg_client_joined', self.on_master_display_change)
        self.slave_transport.callback_manager.register_callback(
            'msg_set_braille_info', self.on_master_display_change)
        self.sd_bridge = bridge.BridgeTransport(self.slave_transport,
                                                self.sd_relay)
        relay_thread = threading.Thread(target=self.sd_relay.run)
        relay_thread.daemon = True
        relay_thread.start()
        data = [port, channel]
        with open(self.ipc_file, 'wb') as fp:
            json.dump(data, fp)

    def leave_secure_desktop(self):
        if self.sd_server is None:
            return  #Nothing to do
        self.sd_bridge.disconnect()
        self.sd_bridge = None
        self.sd_server.close()
        self.sd_server = None
        self.sd_relay.close()
        self.sd_relay = None
        self.slave_transport.callback_manager.unregister_callback(
            'msg_set_braille_info', self.on_master_display_change)
        self.slave_session.set_display_size()

    def on_master_display_change(self, **kwargs):
        self.sd_relay.send(type='set_display_size',
                           sizes=self.slave_session.master_display_sizes)

    def handle_secure_desktop(self):
        try:
            with open(self.ipc_file) as fp:
                data = json.load(fp)
            os.unlink(self.ipc_file)
            port, channel = data
            test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            test_socket = ssl.wrap_socket(test_socket)
            test_socket.connect(('127.0.0.1', port))
            test_socket.close()
            self.connect_as_slave(('127.0.0.1', port), channel)
        except:
            pass

    def verify_connect(self, con_info):
        if self.is_connected() or self.connecting:
            gui.messageBox(
                _("NVDA Remote is already connected. Disconnect before opening a new connection."
                  ), _("NVDA Remote Already Connected"),
                wx.OK | wx.ICON_WARNING)
            return
        self.connecting = True
        server_addr = con_info.get_address()
        key = con_info.key
        if con_info.mode == 'master':
            message = _(
                "Do you wish to control the machine on server {server} with key {key}?"
            ).format(server=server_addr, key=key)
        elif con_info.mode == 'slave':
            message = _(
                "Do you wish to allow this machine to be controlled on server {server} with key {key}?"
            ).format(server=server_addr, key=key)
        if gui.messageBox(message, _("NVDA Remote Connection Request"), wx.YES
                          | wx.NO | wx.NO_DEFAULT | wx.ICON_WARNING) != wx.YES:
            self.connecting = False
            return
        if con_info.mode == 'master':
            self.connect_as_master((con_info.hostname, con_info.port), key=key)
        elif con_info.mode == 'slave':
            self.connect_as_slave((con_info.hostname, con_info.port), key=key)
        self.connecting = False

    def is_connected(self):
        connector = self.slave_transport or self.master_transport
        if connector is not None:
            return connector.connected
        return False

    __gestures = {
        "kb:alt+NVDA+pageDown": "disconnect",
        "kb:control+shift+NVDA+c": "push_clipboard",
    }
示例#13
0
class GlobalPlugin(GlobalPlugin):
    scriptCategory = _("NVDA Remote")

    def __init__(self, *args, **kwargs):
        super(GlobalPlugin, self).__init__(*args, **kwargs)
        self.local_machine = local_machine.LocalMachine()
        self.slave_session = None
        self.master_session = None
        self.create_menu()
        self.connector = None
        self.control_connector_thread = None
        self.control_connector = None
        self.server = None
        self.hook_thread = None
        self.sending_keys = False
        self.key_modified = False
        self.sd_server = None
        cs = get_config()['controlserver']
        if cs['autoconnect']:
            address = address_to_hostport(cs['host'])
            self.connect_control(address, cs['key'])
        self.temp_location = os.path.join(
            shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), 'temp')
        self.ipc_file = os.path.join(self.temp_location, 'remote.ipc')
        self.sd_focused = False
        self.check_secure_desktop()

    def create_menu(self):
        self.menu = wx.Menu()
        tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
        # Translators: Item in NVDA Remote submenu to connect to a remote computer.
        self.connect_item = self.menu.Append(
            wx.ID_ANY, _("Connect..."),
            _("Remotely connect to another computer running NVDA Remote Access"
              ))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.do_connect,
                                       self.connect_item)
        # Translators: Item in NVDA Remote submenu to disconnect from a remote computer.
        self.disconnect_item = self.menu.Append(
            wx.ID_ANY, _("Disconnect"),
            _("Disconnect from another computer running NVDA Remote Access"))
        self.disconnect_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_disconnect_item,
                                       self.disconnect_item)
        # Translators: Menu item in NvDA Remote submenu to mute speech from the remote computer.
        self.mute_item = self.menu.Append(
            wx.ID_ANY, _("Mute remote speech"),
            _("Mute speech from the remote computer"))
        self.mute_item.SetCheckable(True)
        self.mute_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_mute_item,
                                       self.mute_item)
        # Translators: Menu item in NVDA Remote submenu to push clipboard content to the remote computer.
        self.push_clipboard_item = self.menu.Append(
            wx.ID_ANY, _("&Push clipboard"),
            _("Push the clipboard to the other machine"))
        self.push_clipboard_item.Enable(False)
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU,
                                       self.on_push_clipboard_item,
                                       self.push_clipboard_item)
        # Translators: Menu item in NvDA Remote submenu to open add-on options.
        self.options_item = self.menu.Append(wx.ID_ANY, _("&Options..."),
                                             _("Options"))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_options_item,
                                       self.options_item)
        # Translators: Menu item in NVDA Remote submenu to send Control+Alt+Delete to the remote computer.
        self.send_ctrl_alt_del_item = self.menu.Append(wx.ID_ANY,
                                                       _("Send Ctrl+Alt+Del"),
                                                       _("Send Ctrl+Alt+Del"))
        gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_send_ctrl_alt_del,
                                       self.send_ctrl_alt_del_item)
        self.send_ctrl_alt_del_item.Enable(False)
        # Translators: Label of menu in NVDA tools menu.
        self.remote_item = tools_menu.AppendSubMenu(self.menu, _("R&emote"),
                                                    _("NVDA Remote Access"))

    def terminate(self):
        self.do_disconnect_from_slave(True)
        self.local_machine = None
        self.menu.RemoveItem(self.connect_item)
        self.connect_item.Destroy()
        self.connect_item = None
        self.menu.RemoveItem(self.disconnect_item)
        self.disconnect_item.Destroy()
        self.disconnect_item = None
        self.menu.RemoveItem(self.mute_item)
        self.mute_item.Destroy()
        self.mute_item = None
        self.menu.RemoveItem(self.push_clipboard_item)
        self.push_clipboard_item.Destroy()
        self.push_clipboard_item = None
        self.menu.RemoveItem(self.options_item)
        self.options_item.Destroy()
        self.options_item = None
        self.menu.RemoveItem(self.send_ctrl_alt_del_item)
        self.send_ctrl_alt_del_item.Destroy()
        self.send_ctrl_alt_del_item = None
        tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
        tools_menu.RemoveItem(self.remote_item)
        self.remote_item.Destroy()
        self.remote_item = None
        try:
            self.menu.Destroy()
        except wx.PyDeadObjectError:
            pass
        self.menu = None

    def on_disconnect_item(self, evt):
        evt.Skip()
        self.do_disconnect_from_slave()

    def on_mute_item(self, evt):
        evt.Skip()
        self.local_machine.is_muted = self.mute_item.IsChecked()

    def script_toggle_remote_mute(self, gesture):
        self.local_machine.is_muted = not self.local_machine.is_muted
        self.mute_item.Check(self.local_machine.is_muted)

    script_toggle_remote_mute.__doc__ = _(
        """Mute or unmute the speech coming from the remote computer""")

    def on_push_clipboard_item(self, evt):
        connector = self.control_connector or self.connector
        try:
            connector.send(type='set_clipboard_text', text=api.getClipData())
        except TypeError:
            # JL: It might be helpful to provide a log.debug output for this.
            pass

    def on_options_item(self, evt):
        evt.Skip()
        config = get_config()
        # Translators: The title of the add-on options dialog.
        dlg = dialogs.OptionsDialog(gui.mainFrame,
                                    wx.ID_ANY,
                                    title=_("Options"))
        dlg.set_from_config(config)

        def handle_dlg_complete(dlg_result):
            if dlg_result != wx.ID_OK:
                return
            dlg.write_to_config(config)

        gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

    def on_send_ctrl_alt_del(self, evt):
        self.connector.send('send_SAS')

    def do_disconnect_from_slave(self, quiet=False):
        if self.connector is None and self.control_connector is None:
            if not quiet:
                ui.message(_("Not connected."))
            return
        if self.connector is not None:
            self.disconnect_from_slave()
        if self.control_connector is not None:
            self.disconnect_control()
        if self.server is not None:
            self.server.close()
            self.server = None
        beep_sequence.beep_sequence((660, 60), (440, 60))
        self.disconnect_item.Enable(False)
        self.connect_item.Enable(True)
        self.push_clipboard_item.Enable(False)

    def disconnect_from_slave(self):
        self.connector.close()
        self.connector = None
        if self.connector_thread is not None:
            self.connector_thread.running = False
        self.connect_item.Enable(True)
        self.disconnect_item.Enable(False)
        self.mute_item.Check(False)
        self.mute_item.Enable(False)
        self.push_clipboard_item.Enable(False)
        self.send_ctrl_alt_del_item.Enable(False)
        self.sending_keys = False
        if self.hook_thread is not None:
            ctypes.windll.user32.PostThreadMessageW(self.hook_thread.ident,
                                                    win32con.WM_QUIT, 0, 0)
            self.hook_thread.join()
            self.hook_thread = None
        self.key_modified = False

    def disconnect_control(self):
        self.control_connector_thread.running = False
        self.control_connector.close()
        self.control_connector = None

    def on_slave_connection_failed(self):
        if self.connector.successful_connects == 0:
            self.disconnect_from_slave()
            # Translators: Title of the connection error dialog.
            gui.messageBox(
                parent=gui.mainFrame,
                caption=_("Error Connecting"),
                # Translators: Message shown when cannot connect to the remote computer.
                message=_("Unable to connect to the remote computer"),
                style=wx.OK | wx.ICON_WARNING)

    def script_disconnect(self, gesture):
        self.do_disconnect_from_slave()

    def do_connect(self, evt):
        evt.Skip()
        last_cons = get_config()['connections']['last_connected']
        last = ''
        if last_cons:
            last = last_cons[-1]
        # Translators: Title of the connect dialog.
        dlg = dialogs.DirectConnectDialog(parent=gui.mainFrame,
                                          id=wx.ID_ANY,
                                          title=_("Connect"))
        dlg.panel.host.SetValue(last)
        dlg.panel.host.SelectAll()

        def handle_dlg_complete(dlg_result):
            if dlg_result != wx.ID_OK:
                return
            if dlg.client_or_server.GetSelection() == 0:  #client
                server_addr = dlg.panel.host.GetValue()
                server_addr, port = address_to_hostport(server_addr)
                channel = dlg.panel.key.GetValue()
                if dlg.connection_type.GetSelection() == 0:
                    self.connect_slave((server_addr, port), channel)
                else:
                    self.connect_control((server_addr, port), channel)
            else:  #We want a server
                channel = dlg.panel.key.GetValue()
                self.server = server.Server(SERVER_PORT, channel)
                server_thread = threading.Thread(target=self.server.run)
                server_thread.daemon = True
                server_thread.start()
                if dlg.connection_type.GetSelection() == 0:
                    self.connect_slave(('127.0.0.1', SERVER_PORT), channel)
                else:
                    self.connect_control(('127.0.0.1', SERVER_PORT), channel)

        gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

    def on_connected_to_slave(self):
        write_connection_to_config(self.connector.address)
        self.disconnect_item.Enable(True)
        self.connect_item.Enable(False)
        self.mute_item.Enable(True)
        self.push_clipboard_item.Enable(True)
        self.send_ctrl_alt_del_item.Enable(True)
        self.hook_thread = threading.Thread(target=self.hook)
        self.hook_thread.daemon = True
        self.hook_thread.start()
        # Translators: Presented when connected to the remote computer.
        ui.message(_("Connected!"))
        beep_sequence.beep_sequence((440, 60), (660, 60))

    def on_disconnected_from_slave(self):
        # Translators: Presented when connection to a remote computer was interupted.
        ui.message(_("Connection interrupted"))

    def connect_slave(self, address, channel):
        transport = RelayTransport(address=address,
                                   serializer=serializer.JSONSerializer(),
                                   channel=channel)
        self.master_session = MasterSession(transport=transport,
                                            local_machine=self.local_machine)
        transport.callback_manager.register_callback(
            'transport_connected', self.on_connected_to_slave)
        transport.callback_manager.register_callback(
            'transport_connection_failed', self.on_slave_connection_failed)
        transport.callback_manager.register_callback(
            'transport_disconnected', self.on_disconnected_from_slave)
        self.connector = transport
        self.connector_thread = ConnectorThread(connector=transport)
        self.connector_thread.start()

    def connect_control(self, address=SERVER_ADDR, key=None):
        if self.control_connector_thread is not None:
            self.control_connector_thread.running = False
            if self.control_connector is not None:
                self.control_connector.close()
            self.control_connector_thread = None

        transport = RelayTransport(serializer=serializer.JSONSerializer(),
                                   address=address,
                                   channel=key)
        self.slave_session = SlaveSession(transport=transport,
                                          local_machine=self.local_machine)
        self.control_connector = transport
        self.control_connector.callback_manager.register_callback(
            'transport_connected', self.connected_to_relay)
        self.control_connector_thread = ConnectorThread(
            connector=self.control_connector)
        self.control_connector_thread.start()
        self.disconnect_item.Enable(True)
        self.connect_item.Enable(False)

    def connected_to_relay(self):
        log.info("Control connector connected")
        beep_sequence.beep_sequence((720, 100), 50, (720, 100), 50, (720, 100))
        # Transaltors: Presented in direct (client to server) remote connection when the controlled computer is ready.
        speech.speakMessage(_("Connected to control server"))
        self.push_clipboard_item.Enable(True)
        write_connection_to_config(self.control_connector.address)

    def hook(self):
        log.debug("Hook thread start")
        keyhook = keyboard_hook.KeyboardHook()
        keyhook.register_callback(self.hook_callback)
        msg = ctypes.MSG()
        while ctypes.windll.user32.GetMessageW(ctypes.byref(msg), None, 0, 0):
            pass
        log.debug("Hook thread end")
        keyhook.free()

    def hook_callback(self, **kwargs):
        if kwargs['vk_code'] != win32con.VK_F11:
            self.key_modified = kwargs['pressed']
        if kwargs['vk_code'] == win32con.VK_F11 and kwargs[
                'pressed'] and not self.key_modified:
            self.sending_keys = not self.sending_keys
            if self.sending_keys:
                # Translators: Presented when sending keyboard keys from the controlling computer to the controlled computer.
                ui.message(_("Sending keys."))
            else:
                # Translators: Presented when keyboard control is back to the controlling computer.
                ui.message(_("Not sending keys."))
            return True  #Don't pass it on
        if not self.sending_keys:
            return False  #Let the host have it
        self.connector.send(type="key", **kwargs)
        return True  #Don't pass it on

    def event_gainFocus(self, obj, nextHandler):
        if isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
            self.sd_focused = True
            self.enter_secure_desktop()
        elif self.sd_focused and not isinstance(
                obj, IAccessibleHandler.SecureDesktopNVDAObject):
            #event_leaveFocus won't work for some reason
            self.sd_focused = False
            self.leave_secure_desktop()
        nextHandler()

    def enter_secure_desktop(self):
        """function ran when entering a secure desktop."""
        if self.control_connector is None:
            return
        if not os.path.exists(self.temp_location):
            os.makedirs(self.temp_location)
        channel = str(uuid.uuid4())
        self.sd_server = server.Server(port=0,
                                       password=channel,
                                       bind_host='127.0.0.1')
        port = self.sd_server.server_socket.getsockname()[1]
        server_thread = threading.Thread(target=self.sd_server.run)
        server_thread.daemon = True
        server_thread.start()
        self.sd_relay = RelayTransport(address=('127.0.0.1', port),
                                       serializer=serializer.JSONSerializer(),
                                       channel=channel)
        self.sd_bridge = bridge.BridgeTransport(self.control_connector,
                                                self.sd_relay)
        relay_thread = threading.Thread(target=self.sd_relay.run)
        relay_thread.daemon = True
        relay_thread.start()
        data = [port, channel]
        with open(self.ipc_file, 'wb') as fp:
            json.dump(data, fp)

    def leave_secure_desktop(self):
        if self.sd_server is None:
            return  #Nothing to do
        self.sd_bridge.disconnect()
        self.sd_server.close()
        self.sd_relay.close()

    def check_secure_desktop(self):
        if not globalVars.appArgs.secure:
            return
        with open(self.ipc_file) as fp:
            data = json.load(fp)
        os.unlink(self.ipc_file)
        port, channel = data
        self.connect_control(('127.0.0.1', port), channel)

    __gestures = {
        "kb:alt+NVDA+pageDown": "disconnect",
    }
示例#14
0
class GlobalPlugin(GlobalPlugin):
	scriptCategory = _("NVDA Remote")

	def __init__(self, *args, **kwargs):
		super(GlobalPlugin, self).__init__(*args, **kwargs)
		self.local_machine = local_machine.LocalMachine()
		self.slave_session = None
		self.master_session = None
		self.create_menu()
		self.connecting = False
		self.url_handler_window = url_handler.URLHandlerWindow(callback=self.verify_connect)
		url_handler.register_url_handler()
		self.master_transport = None
		self.slave_transport = None
		self.server = None
		self.hook_thread = None
		self.sending_keys = False
		self.key_modified = False
		self.sd_server = None
		self.sd_relay = None
		self.sd_bridge = None
		cs = configuration.get_config()['controlserver']
		self.temp_location = os.path.join(shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), 'temp')
		self.ipc_file = os.path.join(self.temp_location, 'remote.ipc')
		if globalVars.appArgs.secure:
			self.handle_secure_desktop()
		if cs['autoconnect'] and not self.master_session and not self.slave_session:
			self.perform_autoconnect()
		self.sd_focused = False

	def perform_autoconnect(self):
		cs = configuration.get_config()['controlserver']
		channel = cs['key']
		if cs['self_hosted']:
			port = cs['port']
			address = ('localhost',port)
			self.start_control_server(port, channel)
		else:
			address = address_to_hostport(cs['host'])
		if cs['connection_type']==0:
			self.connect_as_slave(address, channel)
		else:
			self.connect_as_master(address, channel)

	def create_menu(self):
		self.menu = wx.Menu()
		tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
		# Translators: Item in NVDA Remote submenu to connect to a remote computer.
		self.connect_item = self.menu.Append(wx.ID_ANY, _("Connect..."), _("Remotely connect to another computer running NVDA Remote Access"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.do_connect, self.connect_item)
		# Translators: Item in NVDA Remote submenu to disconnect from a remote computer.
		self.disconnect_item = self.menu.Append(wx.ID_ANY, _("Disconnect"), _("Disconnect from another computer running NVDA Remote Access"))
		self.disconnect_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_disconnect_item, self.disconnect_item)
		# Translators: Menu item in NvDA Remote submenu to mute speech and sounds from the remote computer.
		self.mute_item = self.menu.Append(wx.ID_ANY, _("Mute remote"), _("Mute speech and sounds from the remote computer"), kind=wx.ITEM_CHECK)
		self.mute_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_mute_item, self.mute_item)
		# Translators: Menu item in NVDA Remote submenu to push clipboard content to the remote computer.
		self.push_clipboard_item = self.menu.Append(wx.ID_ANY, _("&Push clipboard"), _("Push the clipboard to the other machine"))
		self.push_clipboard_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_push_clipboard_item, self.push_clipboard_item)
		# Translators: Menu item in NVDA Remote submenu to copy a link to the current session.
		self.copy_link_item = self.menu.Append(wx.ID_ANY, _("Copy &link"), _("Copy a link to the remote session"))
		self.copy_link_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_copy_link_item, self.copy_link_item)
		# Translators: Menu item in NvDA Remote submenu to open add-on options.
		self.options_item = self.menu.Append(wx.ID_ANY, _("&Options..."), _("Options"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_options_item, self.options_item)
		# Translators: Menu item in NVDA Remote submenu to send Control+Alt+Delete to the remote computer.
		self.send_ctrl_alt_del_item = self.menu.Append(wx.ID_ANY, _("Send Ctrl+Alt+Del"), _("Send Ctrl+Alt+Del"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_send_ctrl_alt_del, self.send_ctrl_alt_del_item)
		self.send_ctrl_alt_del_item.Enable(False)
		# Translators: Label of menu in NVDA tools menu.
		self.remote_item=tools_menu.AppendSubMenu(self.menu, _("R&emote"), _("NVDA Remote Access"))

	def terminate(self):
		self.disconnect()
		self.local_machine = None
		self.menu.RemoveItem(self.connect_item)
		self.connect_item.Destroy()
		self.connect_item=None
		self.menu.RemoveItem(self.disconnect_item)
		self.disconnect_item.Destroy()
		self.disconnect_item=None
		self.menu.RemoveItem(self.mute_item)
		self.mute_item.Destroy()
		self.mute_item=None
		self.menu.RemoveItem(self.push_clipboard_item)
		self.push_clipboard_item.Destroy()
		self.push_clipboard_item=None
		self.menu.RemoveItem(self.copy_link_item)
		self.copy_link_item.Destroy()
		self.copy_link_item = None
		self.menu.RemoveItem(self.options_item)
		self.options_item.Destroy()
		self.options_item=None
		self.menu.RemoveItem(self.send_ctrl_alt_del_item)
		self.send_ctrl_alt_del_item.Destroy()
		self.send_ctrl_alt_del_item=None
		tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
		tools_menu.RemoveItem(self.remote_item)
		self.remote_item.Destroy()
		self.remote_item=None
		try:
			self.menu.Destroy()
		except (RuntimeError, AttributeError):
			pass
		try:
			os.unlink(self.ipc_file)
		except:
			pass
		self.menu=None
		if not isInstalledCopy():
			url_handler.unregister_url_handler()
		self.url_handler_window.destroy()
		self.url_handler_window=None

	def on_disconnect_item(self, evt):
		evt.Skip()
		self.disconnect()

	def on_mute_item(self, evt):
		evt.Skip()
		self.local_machine.is_muted = self.mute_item.IsChecked()

	def script_toggle_remote_mute(self, gesture):
		self.local_machine.is_muted = not self.local_machine.is_muted
		self.mute_item.Check(self.local_machine.is_muted)
	script_toggle_remote_mute.__doc__ = _("""Mute or unmute the speech coming from the remote computer""")

	def on_push_clipboard_item(self, evt):
		connector = self.slave_transport or self.master_transport
		try:
			connector.send(type='set_clipboard_text', text=api.getClipData())
		except TypeError:
			log.exception("Unable to push clipboard")

	def script_push_clipboard(self, gesture):
		connector = self.slave_transport or self.master_transport
		if not getattr(connector,'connected',False):
			ui.message(_("Not connected."))
			return
		try:
			connector.send(type='set_clipboard_text', text=api.getClipData())
			ui.message(_("Clipboard pushed"))
		except TypeError:
			ui.message(_("Unable to push clipboard"))
	script_push_clipboard.__doc__ = _("Sends the contents of the clipboard to the remote machine")

	def on_copy_link_item(self, evt):
		session = self.master_session or self.slave_session
		url = session.get_connection_info().get_url_to_connect()
		api.copyToClip(unicode(url))

	def script_copy_link(self, gesture):
		self.on_copy_link_item(None)
		ui.message(_("Copied link"))
	script_copy_link.__doc__ = _("Copies a link to the remote session to the clipboard")

	def on_options_item(self, evt):
		evt.Skip()
		conf = configuration.get_config()
		# Translators: The title of the add-on options dialog.
		dlg = dialogs.OptionsDialog(gui.mainFrame, wx.ID_ANY, title=_("Options"))
		dlg.set_from_config(conf)
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			dlg.write_to_config(conf)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_send_ctrl_alt_del(self, evt):
		self.master_transport.send('send_SAS')

	def disconnect(self):
		if self.master_transport is None and self.slave_transport is None:
			return
		if self.server is not None:
			self.server.close()
			self.server = None
		if self.master_transport is not None:
			self.disconnect_as_master()
		if self.slave_transport is not None:
			self.disconnect_as_slave()
		beep_sequence.beep_sequence_async((660, 60), (440, 60))
		self.disconnect_item.Enable(False)
		self.connect_item.Enable(True)
		self.push_clipboard_item.Enable(False)
		self.copy_link_item.Enable(False)

	def disconnect_as_master(self):
		self.master_transport.close()
		self.master_transport = None
		self.master_session = None

	def disconnecting_as_master(self):
		if self.menu:
			self.connect_item.Enable(True)
			self.disconnect_item.Enable(False)
			self.mute_item.Check(False)
			self.mute_item.Enable(False)
			self.push_clipboard_item.Enable(False)
			self.copy_link_item.Enable(False)
			self.send_ctrl_alt_del_item.Enable(False)
		if self.local_machine:
			self.local_machine.is_muted = False
		self.sending_keys = False
		if self.hook_thread is not None:
			ctypes.windll.user32.PostThreadMessageW(self.hook_thread.ident, win32con.WM_QUIT, 0, 0)
			self.hook_thread.join()
			self.hook_thread = None
			self.removeGestureBinding(REMOTE_KEY)
		self.key_modified = False

	def disconnect_as_slave(self):
		self.slave_transport.close()
		self.slave_transport = None
		self.slave_session = None

	def on_connected_as_master_failed(self):
		if self.master_transport.successful_connects == 0:
			self.disconnect_as_master()
			# Translators: Title of the connection error dialog.
			gui.messageBox(parent=gui.mainFrame, caption=_("Error Connecting"),
			# Translators: Message shown when cannot connect to the remote computer.
			message=_("Unable to connect to the remote computer"), style=wx.OK | wx.ICON_WARNING)

	def script_disconnect(self, gesture):
		if self.master_transport is None and self.slave_transport is None:
			ui.message(_("Not connected."))
			return
		self.disconnect()
	script_disconnect.__doc__ = _("""Disconnect a remote session""")

	def do_connect(self, evt):
		evt.Skip()
		last_cons = configuration.get_config()['connections']['last_connected']
		last = ''
		if last_cons:
			last = last_cons[-1]
		# Translators: Title of the connect dialog.
		dlg = dialogs.DirectConnectDialog(parent=gui.mainFrame, id=wx.ID_ANY, title=_("Connect"))
		dlg.panel.host.SetValue(last)
		dlg.panel.host.SelectAll()
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			if dlg.client_or_server.GetSelection() == 0: #client
				server_addr = dlg.panel.host.GetValue()
				server_addr, port = address_to_hostport(server_addr)
				channel = dlg.panel.key.GetValue()
				if dlg.connection_type.GetSelection() == 0:
					self.connect_as_master((server_addr, port), channel)
				else:
					self.connect_as_slave((server_addr, port), channel)
			else: #We want a server
				channel = dlg.panel.key.GetValue()
				self.start_control_server(int(dlg.panel.port.GetValue()), channel)
				if dlg.connection_type.GetSelection() == 0:
					self.connect_as_master(('127.0.0.1', int(dlg.panel.port.GetValue())), channel)
				else:
					self.connect_as_slave(('127.0.0.1', int(dlg.panel.port.GetValue())), channel)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_connected_as_master(self):
		configuration.write_connection_to_config(self.master_transport.address)
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)
		self.mute_item.Enable(True)
		self.push_clipboard_item.Enable(True)
		self.copy_link_item.Enable(True)
		self.send_ctrl_alt_del_item.Enable(True)
		self.hook_thread = threading.Thread(target=self.hook)
		self.hook_thread.daemon = True
		self.hook_thread.start()
		self.bindGesture(REMOTE_KEY, "sendKeys")
		# Translators: Presented when connected to the remote computer.
		ui.message(_("Connected!"))
		beep_sequence.beep_sequence_async((440, 60), (660, 60))

	def on_disconnected_as_master(self):
		# Translators: Presented when connection to a remote computer was interupted.
		ui.message(_("Connection interrupted"))

	def connect_as_master(self, address, key):
		transport = RelayTransport(address=address, serializer=serializer.JSONSerializer(), channel=key, connection_type='master')
		self.master_session = MasterSession(transport=transport, local_machine=self.local_machine)
		transport.callback_manager.register_callback('transport_connected', self.on_connected_as_master)
		transport.callback_manager.register_callback('transport_connection_failed', self.on_connected_as_master_failed)
		transport.callback_manager.register_callback('transport_closing', self.disconnecting_as_master)
		transport.callback_manager.register_callback('transport_disconnected', self.on_disconnected_as_master)
		self.master_transport = transport
		self.master_transport.reconnector_thread.start()

	def connect_as_slave(self, address, key):
		transport = RelayTransport(serializer=serializer.JSONSerializer(), address=address, channel=key, connection_type='slave')
		self.slave_session = SlaveSession(transport=transport, local_machine=self.local_machine)
		self.slave_transport = transport
		self.slave_transport.callback_manager.register_callback('transport_connected', self.on_connected_as_slave)
		self.slave_transport.reconnector_thread.start()
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)

	def on_connected_as_slave(self):
		log.info("Control connector connected")
		beep_sequence.beep_sequence_async((720, 100), 50, (720, 100), 50, (720, 100))
		# Translators: Presented in direct (client to server) remote connection when the controlled computer is ready.
		speech.speakMessage(_("Connected to control server"))
		self.push_clipboard_item.Enable(True)
		self.copy_link_item.Enable(True)
		configuration.write_connection_to_config(self.slave_transport.address)

	def start_control_server(self, server_port, channel):
		self.server = server.Server(server_port, channel)
		server_thread = threading.Thread(target=self.server.run)
		server_thread.daemon = True
		server_thread.start()

	def hook(self):
		log.debug("Hook thread start")
		keyhook = keyboard_hook.KeyboardHook()
		keyhook.register_callback(self.hook_callback)
		msg = ctypes.MSG()
		while ctypes.windll.user32.GetMessageW(ctypes.byref(msg), None, 0, 0):
			pass
		log.debug("Hook thread end")
		keyhook.free()

	def hook_callback(self, **kwargs):
		#Prevent disabling sending keys if another key is held down
		if not self.sending_keys:
			return False
		if kwargs['vk_code'] != win32con.VK_F11:
			self.key_modified = kwargs['pressed']
		if kwargs['vk_code'] == win32con.VK_F11 and kwargs['pressed'] and not self.key_modified:
			self.sending_keys = False
			self.set_receiving_braille(False)
			# This is called from the hook thread and should be executed on the main thread.
			# Translators: Presented when keyboard control is back to the controlling computer.
			wx.CallAfter(ui.message, _("Controlling local machine."))
			return True #Don't pass it on
		self.master_transport.send(type="key", **kwargs)
		return True #Don't pass it on

	def script_sendKeys(self, gesture):
		# Translators: Presented when sending keyboard keys from the controlling computer to the controlled computer.
		ui.message(_("Controlling remote machine."))
		self.sending_keys = True
		self.set_receiving_braille(True)

	def set_receiving_braille(self, state):
		if state and self.master_session.patch_callbacks_added and braille.handler.enabled:
			self.master_session.patcher.patch_braille_input()
			braille.handler.enabled = False
			if braille.handler._cursorBlinkTimer:
				braille.handler._cursorBlinkTimer.Stop()
				braille.handler._cursorBlinkTimer=None
			if braille.handler.buffer is braille.handler.messageBuffer:
				braille.handler.buffer.clear()
				braille.handler.buffer = braille.handler.mainBuffer
				if braille.handler._messageCallLater:
					braille.handler._messageCallLater.Stop()
					braille.handler._messageCallLater = None
			self.local_machine.receiving_braille=True
		elif not state:
			self.master_session.patcher.unpatch_braille_input()
			braille.handler.enabled = bool(braille.handler.displaySize)
			self.local_machine.receiving_braille=False

	def event_gainFocus(self, obj, nextHandler):
		if isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			self.sd_focused = True
			self.enter_secure_desktop()
		elif self.sd_focused and not isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			#event_leaveFocus won't work for some reason
			self.sd_focused = False
			self.leave_secure_desktop()
		nextHandler()

	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop."""
		if self.slave_transport is None:
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_relay.callback_manager.register_callback('msg_client_joined', self.on_master_display_change)
		self.slave_transport.callback_manager.register_callback('msg_set_braille_info', self.on_master_display_change)
		self.sd_bridge = bridge.BridgeTransport(self.slave_transport, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)

	def leave_secure_desktop(self):
		if self.sd_server is None:
			return #Nothing to do
		self.sd_bridge.disconnect()
		self.sd_bridge = None
		self.sd_server.close()
		self.sd_server = None
		self.sd_relay.close()
		self.sd_relay = None
		self.slave_transport.callback_manager.unregister_callback('msg_set_braille_info', self.on_master_display_change)
		self.slave_session.set_display_size()

	def on_master_display_change(self, **kwargs):
		self.sd_relay.send(type='set_display_size', sizes=self.slave_session.master_display_sizes)

	def handle_secure_desktop(self):
		try:
			with open(self.ipc_file) as fp:
				data = json.load(fp)
			os.unlink(self.ipc_file)
			port, channel = data
			test_socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			test_socket=ssl.wrap_socket(test_socket)
			test_socket.connect(('127.0.0.1', port))
			test_socket.close()
			self.connect_as_slave(('127.0.0.1', port), channel)
		except:
			pass

	def verify_connect(self, con_info):
		if self.is_connected() or self.connecting:
			gui.messageBox(_("NVDA Remote is already connected. Disconnect before opening a new connection."), _("NVDA Remote Already Connected"), wx.OK|wx.ICON_WARNING)
			return
		self.connecting = True
		server_addr = con_info.get_address()
		key = con_info.key
		if con_info.mode == 'master':
			message = _("Do you wish to control the machine on server {server} with key {key}?").format(server=server_addr, key=key)
		elif con_info.mode == 'slave':
			message = _("Do you wish to allow this machine to be controlled on server {server} with key {key}?").format(server=server_addr, key=key)
		if gui.messageBox(message, _("NVDA Remote Connection Request"), wx.YES|wx.NO|wx.NO_DEFAULT|wx.ICON_WARNING) != wx.YES:
			self.connecting = False
			return
		if con_info.mode == 'master':
			self.connect_as_master((con_info.hostname, con_info.port), key=key)
		elif con_info.mode == 'slave':
			self.connect_as_slave((con_info.hostname, con_info.port), key=key)
		self.connecting = False

	def is_connected(self):
		connector = self.slave_transport or self.master_transport
		if connector is not None:
			return connector.connected
		return False

	__gestures = {
		"kb:alt+NVDA+pageDown": "disconnect",
		"kb:control+shift+NVDA+c": "push_clipboard",
	}
示例#15
0
YOUR_NVDAREMOTE_KEY = "key"

# Make the NVDA Remote modules importable
sys.path.insert(0, YOUR_ORCA_SCRIPTS_FOLDER)

from orca.speechdispatcherfactory import SpeechServer
from serializer import JSONSerializer
from transport import RelayTransport
import traceback
import threading

mySerializer = JSONSerializer()

transport = RelayTransport(
    mySerializer,
    (YOUR_NVDAREMOTE_SERVER_ADDRESS, YOUR_NVDAREMOTE_SERVER_PORT),
    channel=YOUR_NVDAREMOTE_KEY,
    connection_type="slave")


def try_run_thread():
    try:
        transport.run()
    except Exception:
        print("Error in thread")
        traceback.print_exc()


# Starts the thread that connects to the NVDA Remote server
t = threading.Thread(target=try_run_thread)
t.daemon = True
示例#16
0
class GlobalPlugin(GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super(GlobalPlugin, self).__init__(*args, **kwargs)
		self.local_machine = local_machine.LocalMachine()
		self.slave_session = None
		self.master_session = None
		self.create_menu()
		self.connector = None
		self.control_connector_thread = None
		self.control_connector = None
		self.server = None
		self.hook_thread = None
		self.sending_keys = False
		self.key_modified = False
		self.sd_server = None
		cs = get_config()['controlserver']
		if cs['autoconnect']:
			address = address_to_hostport(cs['host'])
			self.connect_control(address, cs['key'])
		self.temp_location = os.path.join(shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), 'temp')
		self.ipc_file = os.path.join(self.temp_location, 'remote.ipc')
		self.sd_focused = False
		self.check_secure_desktop()

	def create_menu(self):
		self.menu = wx.Menu()
		tools_menu = gui.mainFrame.sysTrayIcon.toolsMenu
		# Translators: Item in NVDA Remote submenu to connect to a remote computer.
		self.connect_item = self.menu.Append(wx.ID_ANY, _("Connect..."), _("Remotely connect to another computer running NVDA Remote Access"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.do_connect, self.connect_item)
		# Translators: Item in NVDA Remote submenu to disconnect from a remote computer.
		self.disconnect_item = self.menu.Append(wx.ID_ANY, _("Disconnect"), _("Disconnect from another computer running NVDA Remote Access"))
		self.disconnect_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_disconnect_item, self.disconnect_item)
		# Translators: Menu item in NvDA Remote submenu to mute speech from the remote computer.
		self.mute_item = self.menu.Append(wx.ID_ANY, _("Mute remote speech"), _("Mute speech from the remote computer"))
		self.mute_item.SetCheckable(True)
		self.mute_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_mute_item, self.mute_item)
		# Translators: Menu item in NVDA Remote submenu to push clipboard content to the remote computer.
		self.push_clipboard_item = self.menu.Append(wx.ID_ANY, _("&Push clipboard"), _("Push the clipboard to the other machine"))
		self.push_clipboard_item.Enable(False)
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_push_clipboard_item, self.push_clipboard_item)
		# Translators: Menu item in NvDA Remote submenu to open add-on options.
		self.options_item = self.menu.Append(wx.ID_ANY, _("&Options..."), _("Options"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_options_item, self.options_item)
		# Translators: Menu item in NVDA Remote submenu to send Control+Alt+Delete to the remote computer.
		self.send_ctrl_alt_del_item = self.menu.Append(wx.ID_ANY, _("Send Ctrl+Alt+Del"), _("Send Ctrl+Alt+Del"))
		gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_send_ctrl_alt_del, self.send_ctrl_alt_del_item)
		self.send_ctrl_alt_del_item.Enable(False)
		# Translators: Label of menu in NVDA tools menu.
		tools_menu.AppendSubMenu(self.menu, _("R&emote"), _("NVDA Remote Access"))


	def on_disconnect_item(self, evt):
		evt.Skip()
		self.do_disconnect_from_slave()

	def on_mute_item(self, evt):
		evt.Skip()
		self.local_machine.is_muted = self.mute_item.IsChecked()

	def on_push_clipboard_item(self, evt):
		connector = self.control_connector or self.connector
		try:
			connector.send(type='set_clipboard_text', text=api.getClipData())
		except TypeError:
			# JL: It might be helpful to provide a log.debug output for this.
			pass

	def on_options_item(self, evt):
		evt.Skip()
		config = get_config()
		# Translators: The title of the add-on options dialog.
		dlg = dialogs.OptionsDialog(gui.mainFrame, wx.ID_ANY, title=_("Options"))
		dlg.set_from_config(config)
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			dlg.write_to_config(config)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_send_ctrl_alt_del(self, evt):
		self.connector.send('send_SAS')

	def do_disconnect_from_slave(self, quiet=False):
		if self.connector is None and self.control_connector is None:
			if not quiet:
				ui.message(_("Not connected."))
			return
		if self.connector is not None:
			self.disconnect_from_slave()
		if self.control_connector is not None:
			self.disconnect_control()
		if self.server is not None:
			self.server.close()
			self.server = None
		beep_sequence.beep_sequence((660, 60), (440, 60))
		self.disconnect_item.Enable(False)
		self.connect_item.Enable(True)
		self.push_clipboard_item.Enable(False)

	def disconnect_from_slave(self):
		self.connector.close()
		self.connector = None
		if self.connector_thread is not None:
			self.connector_thread.running = False
		self.connect_item.Enable(True)
		self.disconnect_item.Enable(False)
		self.mute_item.Check(False)
		self.mute_item.Enable(False)
		self.push_clipboard_item.Enable(False)
		self.send_ctrl_alt_del_item.Enable(False)
		self.sending_keys = False
		if self.hook_thread is not None:
			ctypes.windll.user32.PostThreadMessageW(self.hook_thread.ident, win32con.WM_QUIT, 0, 0)
			self.hook_thread.join()
			self.hook_thread = None
		self.key_modified = False

	def disconnect_control(self):
		self.control_connector_thread.running = False
		self.control_connector.close()
		self.control_connector = None

	def on_slave_connection_failed(self):
		if self.connector.successful_connects == 0:
			self.disconnect_from_slave()
			# Translators: Title of the connection error dialog.
			gui.messageBox(parent=gui.mainFrame, caption=_("Error Connecting"),
			# Translators: Message shown when cannot connect to the remote computer.
			message=_("Unable to connect to the remote computer"), style=wx.OK | wx.ICON_WARNING)

	def script_disconnect(self, gesture):
		self.do_disconnect_from_slave()

	def do_connect(self, evt):
		evt.Skip()
		last_cons = get_config()['connections']['last_connected']
		last = ''
		if last_cons:
			last = last_cons[-1]
		# Translators: Title of the connect dialog.
		dlg = dialogs.DirectConnectDialog(parent=gui.mainFrame, id=wx.ID_ANY, title=_("Connect"))
		dlg.panel.host.SetValue(last)
		dlg.panel.host.SelectAll()
		def handle_dlg_complete(dlg_result):
			if dlg_result != wx.ID_OK:
				return
			if dlg.client_or_server.GetSelection() == 0: #client
				server_addr = dlg.panel.host.GetValue()
				server_addr, port = address_to_hostport(server_addr)
				channel = dlg.panel.key.GetValue()
				if dlg.connection_type.GetSelection() == 0:
					self.connect_slave((server_addr, port), channel)
				else:
					self.connect_control((server_addr, port), channel)
			else: #We want a server
				channel = dlg.panel.key.GetValue()
				self.server = server.Server(SERVER_PORT, channel)
				server_thread = threading.Thread(target=self.server.run)
				server_thread.daemon = True
				server_thread.start()
				if dlg.connection_type.GetSelection() == 0:
					self.connect_slave(('127.0.0.1', SERVER_PORT), channel)
				else:
					self.connect_control(('127.0.0.1', SERVER_PORT), channel)
		gui.runScriptModalDialog(dlg, callback=handle_dlg_complete)

	def on_connected_to_slave(self):
		write_connection_to_config(self.connector.address)
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)
		self.mute_item.Enable(True)
		self.push_clipboard_item.Enable(True)
		self.send_ctrl_alt_del_item.Enable(True)
		self.hook_thread = threading.Thread(target=self.hook)
		self.hook_thread.daemon = True
		self.hook_thread.start()
		# Translators: Presented when connected to the remote computer.
		ui.message(_("Connected!"))
		beep_sequence.beep_sequence((440, 60), (660, 60))

	def on_disconnected_from_slave(self):
		# Translators: Presented when connection to a remote computer was interupted.
		ui.message(_("Connection interrupted"))

	def connect_slave(self, address, channel):
		transport = RelayTransport(address=address, serializer=serializer.JSONSerializer(), channel=channel)
		self.master_session = MasterSession(transport=transport, local_machine=self.local_machine)
		transport.callback_manager.register_callback('transport_connected', self.on_connected_to_slave)
		transport.callback_manager.register_callback('transport_connection_failed', self.on_slave_connection_failed)
		transport.callback_manager.register_callback('transport_disconnected', self.on_disconnected_from_slave)
		self.connector = transport
		self.connector_thread = ConnectorThread(connector=transport)
		self.connector_thread.start()

	def connect_control(self, address=SERVER_ADDR, key=None):
		if self.control_connector_thread is not None:
			self.control_connector_thread.running = False
			if self.control_connector is not None:
				self.control_connector.close()
			self.control_connector_thread = None

		transport = RelayTransport(serializer=serializer.JSONSerializer(), address=address, channel=key)
		self.slave_session = SlaveSession(transport=transport, local_machine=self.local_machine)
		self.control_connector = transport
		self.control_connector.callback_manager.register_callback('transport_connected', self.connected_to_relay)
		self.control_connector_thread = ConnectorThread(connector=self.control_connector)
		self.control_connector_thread.start()
		self.disconnect_item.Enable(True)
		self.connect_item.Enable(False)

	def connected_to_relay(self):
		log.info("Control connector connected")
		beep_sequence.beep_sequence((720, 100), 50, (720, 100), 50, (720, 100))
		# Transaltors: Presented in direct (client to server) remote connection when the controlled computer is ready.
		speech.speakMessage(_("Connected to control server"))
		self.push_clipboard_item.Enable(True)
		write_connection_to_config(self.control_connector.address)

	def hook(self):
		log.debug("Hook thread start")
		keyhook = keyboard_hook.KeyboardHook()
		keyhook.register_callback(self.hook_callback)
		msg = ctypes.MSG()
		while ctypes.windll.user32.GetMessageW(ctypes.byref(msg), None, 0, 0):
			pass
		log.debug("Hook thread end")
		keyhook.free()

	def hook_callback(self, **kwargs):
		if kwargs['vk_code'] != win32con.VK_F11:
			self.key_modified = kwargs['pressed']
		if kwargs['vk_code'] == win32con.VK_F11 and kwargs['pressed'] and not self.key_modified:
			self.sending_keys = not self.sending_keys
			if self.sending_keys:
				# Translators: Presented when sending keyboard keys from the controlling computer to the controlled computer.
				ui.message(_("Sending keys."))
			else:
				# Translators: Presented when keyboard control is back to the controlling computer.
				ui.message(_("Not sending keys."))
			return True #Don't pass it on
		if not self.sending_keys:
			return False #Let the host have it
		self.connector.send(type="key", **kwargs)
		return True #Don't pass it on

	def event_gainFocus(self, obj, nextHandler):
		if isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			self.sd_focused = True
			self.enter_secure_desktop()
		elif self.sd_focused and not isinstance(obj, IAccessibleHandler.SecureDesktopNVDAObject):
			#event_leaveFocus won't work for some reason
			self.sd_focused = False
			self.leave_secure_desktop()
		nextHandler()

	def enter_secure_desktop(self):
		"""function ran when entering a secure desktop."""
		if self.control_connector is None:
			return
		if not os.path.exists(self.temp_location):
			os.makedirs(self.temp_location)
		channel = str(uuid.uuid4())
		self.sd_server = server.Server(port=0, password=channel, bind_host='127.0.0.1')
		port = self.sd_server.server_socket.getsockname()[1]
		server_thread = threading.Thread(target=self.sd_server.run)
		server_thread.daemon = True
		server_thread.start()
		self.sd_relay = RelayTransport(address=('127.0.0.1', port), serializer=serializer.JSONSerializer(), channel=channel)
		self.sd_bridge = bridge.BridgeTransport(self.control_connector, self.sd_relay)
		relay_thread = threading.Thread(target=self.sd_relay.run)
		relay_thread.daemon = True
		relay_thread.start()
		data = [port, channel]
		with open(self.ipc_file, 'wb') as fp:
			json.dump(data, fp)

	def leave_secure_desktop(self):
		if self.sd_server is None:
			return #Nothing to do
		self.sd_bridge.disconnect()
		self.sd_server.close()
		self.sd_relay.close()

	def check_secure_desktop(self):
		if not globalVars.appArgs.secure:
			return
		with open(self.ipc_file) as fp:
			data = json.load(fp)
		os.unlink(self.ipc_file)
		port, channel = data
		self.connect_control(('127.0.0.1', port), channel)

	__gestures = {
		"kb:alt+NVDA+pageDown": "disconnect",
	}