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 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 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 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 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_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_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 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)
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 = { }
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", }
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", }
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", }
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
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", }