예제 #1
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()
예제 #2
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()
예제 #3
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)
예제 #4
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)
예제 #5
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 = {
	}
예제 #6
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",
    }
예제 #7
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",
	}