def __init__(self, masteruri, screen_name, nodename, user=None, parent=None): ''' Creates the window, connects the signals and init the class. ''' QWidget.__init__(self, parent) # load the UI file screen_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'ui', 'logscreen', 'ScreenWidget.ui') loadUi(screen_dock_file, self) self.setObjectName("ScreenWidget") self.setWindowIcon(nm.settings().icon('crystal_clear_show_io.png')) # self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) self.pauseButton.setIcon(nm.settings().icon('sekkyumu_pause.png')) self._valid = True self._logpath = '' self._lock = threading.RLock() self.finished = False self.qfile = None self.thread = None self._info = '' self._masteruri = '' self._nodename = nodename self._first_fill = True self._seek_start = -1 self._seek_end = -1 self._pause_read_end = False self._ssh_output_file = None self._ssh_error_file = None self._ssh_input_file = None self._on_pause = False self._char_format_end = None self.logframe.setVisible(False) self.loglevelButton.toggled.connect(self.on_toggle_loggers) self.logger_handler = None # connect to the button signals self.output.connect(self._on_output) self.output_prefix.connect(self._on_output_prefix) self.error_signal.connect(self._on_error) self.auth_signal.connect(self.on_request_pw) self.clearCloseButton.clicked.connect(self.clear) # self.pauseButton.clicked.connect(self.stop) self.pauseButton.toggled.connect(self.pause) self.clear_signal.connect(self.clear) self.loggerFilterInput.textChanged.connect(self.on_logger_filter_changed) self.textBrowser.verticalScrollBar().valueChanged.connect(self.on_scrollbar_position_changed) self.textBrowser.verticalScrollBar().rangeChanged.connect(self.on_scrollbar_range_changed) self.textBrowser.set_reader(self) self.tf = TerminalFormats() self.hl = ScreenHighlighter(self.textBrowser.document()) self.searchFrame.setVisible(False) self.grepFrame.setVisible(False) self.grepLineEdit.textChanged.connect(self.on_grep_changed) self._shortcut_search = QShortcut(QKeySequence(self.tr("Ctrl+F", "Activate search")), self) self._shortcut_search.activated.connect(self.on_search) self._shortcut_grep = QShortcut(QKeySequence(self.tr("Ctrl+G", "Activate grep")), self) self._shortcut_grep.activated.connect(self.on_grep) self.searchLineEdit.editingFinished.connect(self.on_search_prev) self.searchNextButton.clicked.connect(self.on_search_next) self.searchPrevButton.clicked.connect(self.on_search_prev) # self.visibilityChanged.connect(self.stop) self._connect(masteruri, screen_name, nodename, user)
def get_profile_file(self): ''' Opens file manager dialog to save to select a new file for node manager profile. :return: path to profile file :rtype: str ''' # save the profile (path, _) = QFileDialog.getSaveFileName( self, "New profile file", nm.settings().current_dialog_path, "node manager profile files (*.nmprofile);;All files (*)" ) # _:=filter if path: if not path.endswith('.nmprofile'): path = "%s.nmprofile" % path nm.settings().current_dialog_path = os.path.dirname(path) try: # we need a grpc url for local node manager daemon nmd_url = nmdurl.nmduri() (pkg, _) = package_name( nmdurl.join(nmd_url, os.path.dirname(path))) # _:=pkg_path if pkg is None: ret = MessageBox.warning( self, "New File Error", 'The new file is not in a ROS package', buttons=MessageBox.Ok | MessageBox.Cancel) if ret == MessageBox.Cancel: return None return path except EnvironmentError as e: MessageBox.warning(self, "New File Error", 'Error while create a new file', utf8(e)) return None
def _store_geometry(self): if self._geometry_name: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup(self._geometry_name) settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.endGroup()
def loadCache(self, history_file): ''' Loads the content of the given file and return it as cache. @param history_file: the name of the history file @type history_file: C{str} @return: the dictionary with arguments @rtype: C{dict(str(name):[str(value), ...], ...)} ''' result = {} historyFile = os.path.join(nm.settings().cfg_path, history_file) if os.path.isfile(historyFile): with codecs.open(historyFile, 'r', encoding='utf-8') as f: line = utf8(f.readline()) while line: if line: line = line.strip() if line: key, sep, value = line.partition(':=') if sep: if key not in result.keys(): result[key] = [value] elif len(result[key]) <= nm.settings( ).param_history_length: result[key].append(value) line = utf8(f.readline()) return result
def _on_checkbox_state_changed(self, state): if self.questionid == self.TYPE_NODELET: self.frameui.questionOkButton.setVisible(not state) nm.settings().check_for_nodelets_at_start = not state elif self.questionid == self.TYPE_NOSCREEN: self.frameui.questionCancelButton.setVisible(not state) nm.settings().show_noscreen_error = not state
def open_terminal(self, host): if nm.is_local(host): cmd = nm.settings().terminal_cmd(['cd'], '') SupervisedPopen(shlex.split(cmd), object_id="Start local terminal", shell=True) else: cmd = nm.settings().terminal_cmd(["ssh -XC %s@%s" % (nm.settings().host_user(host), host)], '', noclose=True) SupervisedPopen(shlex.split(cmd), object_id="Start Terminal on %s" % host)
def storeSetting(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.setValue("maximized", self.isMaximized()) settings.setValue("window_state", self.saveState()) settings.endGroup()
def _update_icon(self): if self.id in [self.NOTHING, self.NOT_FOUND]: return icon_pixmap = '' if self.id == self.FOLDER: icon_pixmap = nm.settings().pixmap('crystal_clear_folder.png') elif self.id == self.PACKAGE: icon_pixmap = nm.settings().pixmap('crystal_clear_package.png') elif self.id == self.LAUNCH_FILE: icon_pixmap = nm.settings().pixmap('crystal_clear_launch_file.png') elif self.id == self.RECENT_FILE: icon_pixmap = nm.settings().pixmap( 'crystal_clear_launch_file_recent.png') elif self.id == self.STACK: icon_pixmap = nm.settings().pixmap('crystal_clear_stack.png') elif self.id == self.PROFILE: icon_pixmap = nm.settings().pixmap('crystal_clear_profile.png') elif self.id == self.RECENT_PROFILE: icon_pixmap = nm.settings().pixmap( 'crystal_clear_profile_recent.png') elif self.id == self.REMOTE_DAEMON: icon_pixmap = nm.settings().pixmap('stock_connect.png') elif self.id == self.ROOT: icon_pixmap = nm.settings().pixmap('back.png') if icon_pixmap: self.setIcon(QIcon(icon_pixmap.scaled(16, 16)))
def on_load_xml_clicked(self): ''' Tries to load the selected launch file. The button is only enabled and this method is called, if the button was enabled by on_launch_selection_clicked() ''' selected = self._pathItemsFromIndexes( self.ui_file_view.selectionModel().selectedIndexes(), False) for item in selected: path = self.launchlist_model.expand_item(item.path, item.id) if path is not None: nm.settings().launch_history_add(item.path) self.load_signal.emit(path, {}, None)
def __init__(self, master): QObject.__init__(self) self.name = master.name self._master = master self._syncronized = MasterSyncButtonHelper.NOT_SYNC self.ICONS = {MasterSyncButtonHelper.SYNC: nm.settings().icon("%s_sync.png" % self.ICON_PREFIX), MasterSyncButtonHelper.NOT_SYNC: nm.settings().icon("%s_not_sync.png" % self.ICON_PREFIX), MasterSyncButtonHelper.SWITCHED: nm.settings().icon("%s_start_sync.png" % self.ICON_PREFIX)} self.widget = QPushButton() # self.widget.setFlat(True) self.widget.setIcon(self.ICONS[MasterSyncButtonHelper.NOT_SYNC]) self.widget.setMaximumSize(48, 48) self.widget.setCheckable(True) self.widget.clicked.connect(self.on_sync_clicked)
def open_screen_terminal(cls, masteruri, screen_name, nodename, use_log_widget=False, user=None): ''' Open the screen output in a new terminal. :param str masteruri: the masteruri where the screen is running. :param str screen_name: the name of the screen to show :param str nodename: the name of the node is used for the title of the terminal :raise Exception: on errors while resolving host :see: L{fkie_node_manager.is_local()} ''' # create a title of the terminal if use_log_widget: nm._MAIN_FORM.open_screen_dock(masteruri, screen_name, nodename, user) return host = get_hostname(masteruri) title_opt = 'SCREEN %s on %s' % (nodename, host) if nm.is_local(host): cmd = nm.settings().terminal_cmd( [screen.SCREEN, '-x', screen_name], title_opt) rospy.loginfo("Open screen terminal: %s", cmd) SupervisedPopen(shlex.split(cmd), object_id=title_opt, description="Open screen terminal: %s" % title_opt) else: rospy.loginfo("Open remote screen terminal for %s to %s" % (nodename, host)) _ps = nm.ssh().ssh_x11_exec(host, [screen.SCREEN, '-x', screen_name], title_opt, user)
def __init__(self, tabwidget, parent=None): QDockWidget.__init__(self, "LaunchGraph", parent) graph_ui_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), '..', 'ui', 'editor', 'GraphDockWidget.ui') loadUi(graph_ui_file, self) self.setObjectName('LaunchGraph') self.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self._info_icon = nm.settings().icon('info.png') self._tabwidget = tabwidget self._current_path = None self._root_path = None self._current_deep = 0 self.graphTreeView.setSelectionBehavior(QAbstractItemView.SelectRows) model = QStandardItemModel() self.graphTreeView.setModel(model) self.graphTreeView.setUniformRowHeights(True) self.graphTreeView.header().hide() self.htmlDelegate = HTMLDelegate(palette=self.palette()) self.graphTreeView.setItemDelegateForColumn(0, self.htmlDelegate) self.graphTreeView.activated.connect(self.on_activated) self.graphTreeView.clicked.connect(self.on_clicked) self._created_tree = False self.has_none_packages = True self.has_warnings = False self._refill_tree([], False) self._fill_graph_thread = None
def readSettings(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") maximized = settings.value("maximized", 'false') == 'true' if maximized: self.showMaximized() else: self.resize(settings.value("size", QSize(800, 640))) self.move(settings.value("pos", QPoint(0, 0))) try: self.restoreState(settings.value("window_state")) except Exception: import traceback print(traceback.format_exc()) settings.endGroup()
def show_question(self, questionid, text, data=MessageData(None), color=None): if questionid == 0: return try: if questionid == self.TYPE_LAUNCH_FILE and nm.settings().autoreload_launch: self.accept_signal.emit(questionid, data) return # is it in the list for not ask again? if self._do_not_ask[questionid] == 1: self.accept_signal.emit(questionid, data) elif self._do_not_ask[questionid] == 0: self.cancel_signal.emit(questionid, data) return except Exception: pass if self.questionid != questionid or self.text != text or data != self.data: self._queue.add(questionid, text, data) elif data.data_list: # same question again # update the list of files or nodes which causes this question in current question for dt in data.data_list: if dt not in self.data.data_list: self.data.data_list.append(dt) self._update_list_label(self.data.data_list) # if no question is active pop first element from the queue if self.questionid == self.TYPE_INVALID: self._new_request = self._read_next_item() self._frameui_4_request(self._new_request) if self.questionid in [self.TYPE_NODELET, self.TYPE_NOSCREEN]: self.ui.checkBox_dnaa.setText("don't %s again, never!" % self._ask) else: self.ui.checkBox_dnaa.setText("don't %s again, for session" % self._ask)
def delete_log(cls, nodename, grpc_uri, auto_pw_request=False, user=None, pw=None): ''' Deletes the log file associated with the given node. :param str nodename: the name of the node (with name space) :param str grpc_uri: uri of the node manager daemon where to delete log :raise Exception: on errors while resolving host :see: :meth:`fkie_node_manager.is_local()` ''' try: nm.nmd().screen.delete_log(grpc_uri, [nodename]) except Exception as err: rospy.logwarn("delete log using SSH because of error: %s" % utf8(err)) host = get_hostname(grpc_uri) if nm.is_local(host): screenLog = screen.get_logfile(node=nodename) pidFile = screen.get_pidfile(node=nodename) roslog = screen.get_ros_logfile(nodename) if os.path.isfile(screenLog): os.remove(screenLog) if os.path.isfile(pidFile): os.remove(pidFile) if os.path.isfile(roslog): os.remove(roslog) else: try: # output ignored: output, error, ok _, stdout, _, ok = nm.ssh().ssh_exec(host, [nm.settings().start_remote_script, '--delete_logs', nodename], user, pw, auto_pw_request, close_stdin=True, close_stdout=False, close_stderr=True) if ok: stdout.readlines() stdout.close() except nm.AuthenticationRequest as e: raise nm.InteractionNeededError(e, cls.delete_log, {'nodename': nodename, 'grpc_uri': host, 'auto_pw_request': auto_pw_request, 'user': user, 'pw': pw})
def _connect_ssh(self, host, nodename, user=None, pw=None): try: if user is not None: self.infoLabel.setText('connecting to %s@%s' % (user, host)) else: self.infoLabel.setText('connecting to %s' % host) ok = False self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh( ).ssh_exec(host, [ nm.settings().start_remote_script, '--tail_screen_log', nodename ], user, pw, auto_pw_request=False, get_pty=True) if ok: thread = threading.Thread(target=self._read_ssh_output, args=((self.ssh_output_file, ))) thread.setDaemon(True) thread.start() thread = threading.Thread(target=self._read_ssh_error, args=((self.ssh_error_file, ))) thread.setDaemon(True) thread.start() elif self.ssh_output_file: self.ssh_output_file.close() self.ssh_error_file.close() except nm.AuthenticationRequest as e: self.auth_signal.emit(host, nodename, user) except Exception as e: self.error_signal.emit('%s\n' % e)
def update_description(self, index, cfg, name, displayed_name, robot_type, description, images): ''' Sets the values of an existing item to the given items only if the current value is empty. ''' if index < len(self._data): obj = self._data[index] if cfg not in obj['cfgs']: obj['cfgs'].append(cfg) if not obj['name']: obj['name'] = name if not obj['displayed_name']: obj['displayed_name'] = displayed_name if not obj['type']: obj['type'] = robot_type if not obj['description']: obj['description'] = replace_paths(description) if not obj['images']: for image_path in images: img = interpret_path(image_path) if img and img[0] != os.path.sep: img = os.path.join(nm.settings().PACKAGE_DIR, image_path) if os.path.isfile(img): obj['images'].append(QPixmap(img))
def transfer(self, host, local_file, remote_file, user=None, pw=None, auto_pw_request=False): ''' Copies a file to a remote location using paramiko sfpt. @param host: the host @type host: C{str} @param local_file: the local file @type local_file: str @param remote_file: the remote file name @type remote_file: str @param user: user name @param pw: the password ''' with self.mutex: try: ssh = self._getSSH(host, nm.settings().host_user(host) if user is None else user, pw, True, auto_pw_request) if ssh is not None: sftp = ssh.open_sftp() try: sftp.mkdir(os.path.dirname(remote_file)) except Exception: pass sftp.put(local_file, remote_file) rospy.loginfo("SSH COPY %s -> %s@%s:%s", local_file, ssh._transport.get_username(), host, remote_file) except AuthenticationRequest as _aerr: raise except Exception as _err: raise
def run(self): ''' ''' try: if self._search_text.startswith('name="'): self.search_for_node(self._search_text, self._path, self._recursive) else: self.search(self._search_text, self._path, self._recursive) except exceptions.GrpcTimeout as tout: self.warning_signal.emit( "Search in launch failed! Daemon not responded within %.2f seconds while" " get configuration file: %s\nYou can try to increase" " the timeout for GRPC requests in node manager settings." % (nm.settings().timeout_grpc, tout.remote)) except Exception: import traceback # formatted_lines = traceback.format_exc(1).splitlines() msg = "Error while search for '%s' in '%s': %s" % ( self._search_text, self._path, traceback.print_exc()) rospy.logwarn(msg) self.warning_signal.emit(msg) finally: if self._isrunning: self.search_result_signal.emit(self._search_text, False, '', -1, -1, -1, '')
def storeCache(self, history_file, cache, history_len): ''' Stores the cache to a file. @param history_file: the name of the history file @type history_file: C{str} @param cache: the dictionary with values @type cache: C{dict} @param history_len: the maximal count of value for a key @type history_len: C{int} ''' ignored = dict() with codecs.open(os.path.join(nm.settings().cfg_path, history_file), 'w', encoding='utf-8') as f: for key in cache.keys(): count = 0 for value in cache[key]: if count < history_len: try: f.write(''.join([key, ':=', utf8(value), '\n'])) except UnicodeEncodeError, e: ignored[key] = (value, utf8(e)) except Exception: import traceback rospy.logwarn("Storing history aborted: %s", traceback.format_exc(3)) count += 1 else: break
def ntpdate(cls, host, cmd, user=None, pw=None): ''' Opens the log file associated with the given node in a new terminal. :param str host: the host name or ip where the log file are :param str cmd: command to set the time :return: True, if a log file was found :rtype: bool :raise Exception: on errors while resolving host :see: :meth:`fkie_node_manager.is_local()` ''' mesg = "synchronize time on '%s' using '%s'" % (utf8(host), cmd) rospy.loginfo(mesg) title_opt = "ntpdate on %s" % str(host) # '%s on %s' % (cmd, host) if nm.is_local(host): cmd = nm.settings().terminal_cmd([cmd], title_opt, noclose=True) rospy.loginfo("EXEC: %s" % cmd) _ps = SupervisedPopen(shlex.split(cmd), object_id=cmd, description=mesg) else: _ps = nm.ssh().ssh_x11_exec(host, [ cmd, ';echo "";echo "this terminal will be closed in 10 sec...";sleep 10' ], title_opt, user) return False
def _document_position_changed(self): if isinstance(self.hl, XmlHighlighter) and nm.settings().highlight_xml_blocks: # import time # start_time = time.time() self.hl.mark_block(self.textCursor().block(), self.textCursor().positionInBlock())
def __init__(self, context): super(NodeManager, self).__init__(context) # Give QObjects reasonable names self.setObjectName('NodeManagerFKIE') # Process standalone plugin command-line arguments from argparse import ArgumentParser parser = ArgumentParser() # Add argument(s) to the parser. parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", help="Put plugin in silent mode") args, unknowns = parser.parse_known_args(context.argv()) if not args.quiet: print('arguments: ', args) print('unknowns: ', unknowns) fkie_node_manager.init_settings() masteruri = fkie_node_manager.settings().masteruri() fkie_node_manager.init_globals(masteruri) # Create QWidget try: self._widget = MainWindow() # self._widget.read_view_history() except Exception, e: MessageBox.critical(None, "Node Manager", utf8(e)) raise
def _add_history(self): for hitem in nm.settings().launch_history: if not hitem.startswith(os.path.sep): hitem_uri, _ = nmdurl.split(hitem, with_scheme=True) current_uri = nmdurl.nmduri(self._current_path) if nmdurl.equal_uri(hitem_uri, current_uri): self._add_path(hitem, PathItem.RECENT_FILE, 0, 0, os.path.basename(hitem))
def set_current_master(self, masteruri, mastername): self.launchlist_model.set_current_master(masteruri, mastername) self._masteruri2name[masteruri.rstrip(os.path.sep)] = mastername mname = self.path2mastername(self.launchlist_model.current_path) if mname: color = QColor.fromRgb(nm.settings().host_color( mname, self._default_color.rgb())) self._new_color(color)
def on_launch_selection_activated(self, activated): ''' Tries to load the launch file, if one was activated. ''' selected = self._pathItemsFromIndexes( self.ui_file_view.selectionModel().selectedIndexes(), False) for item in selected: try: self.ui_search_line.set_process_active(True) lfile = self.launchlist_model.expand_item(item.path, item.id) # self.ui_search_line.setText('') if lfile is not None: self.ui_search_line.set_process_active(False) if item.is_launch_file(): nm.settings().launch_history_add(item.path) self.load_signal.emit(item.path, {}, None) elif item.is_profile_file(): nm.settings().launch_history_add(item.path) self.load_profile_signal.emit(item.path) elif item.is_config_file(): self.edit_signal.emit(lfile) if self.launchlist_model.current_path: self.setWindowTitle( 'Launch @%s' % get_hostname(self.launchlist_model.current_grpc)) else: self.setWindowTitle('Launch files') except Exception as e: import traceback print(traceback.format_exc()) rospy.logwarn("Error while load launch file %s: %s" % (item, utf8(e))) MessageBox.warning( self, "Load error", 'Error while load launch file:\n%s' % item.name, "%s" % utf8(e)) try: color = QColor.fromRgb(nm.settings().host_color( self._masteruri2name[nmdurl.masteruri( self.launchlist_model.current_path)], self._default_color.rgb())) self._new_color(color) except Exception as _: pass
def _poweroff_wo(self, host, auto_pw_request=False, user=None, pw=None): if nm.is_local(host): rospy.logwarn("shutdown localhost localhost!") cmd = nm.settings().terminal_cmd(['sudo poweroff'], "poweroff") SupervisedPopen(shlex.split(cmd), object_id="poweroff", description="poweroff") else: rospy.loginfo("poweroff %s", host) # kill on a remote machine cmd = ['sudo poweroff'] _ = nm.ssh().ssh_x11_exec(host, cmd, 'Shutdown %s' % host, user)
def on_launch_selection_activated(self, activated): ''' Tries to load the launch file, if one was activated. ''' selected = self._pathItemsFromIndexes( self.ui_file_view.selectionModel().selectedIndexes(), False) mname = self.path2mastername(self.launchlist_model.current_path) for item in selected: try: self.ui_search_line.set_process_active(True) lfile = self.launchlist_model.expand_item(item.path, item.id) # self.ui_search_line.setText('') if lfile is not None: self.ui_search_line.set_process_active(False) if item.is_launch_file(): nm.settings().launch_history_add(item.path) self.load_signal.emit(item.path, {}, None) elif item.is_profile_file(): nm.settings().launch_history_add(item.path) self.load_profile_signal.emit(item.path) elif item.is_config_file(): self.edit_signal.emit(lfile) mname = self.path2mastername( self.launchlist_model.current_path) self.hostLabel.setText('Remote @ <b>%s</b>' % mname) if mname and self._first_path != self.launchlist_model.current_path: self.hostLabel.setVisible(True) else: self.hostLabel.setVisible(False) if mname: color = QColor.fromRgb(nm.settings().host_color( mname, self._default_color.rgb())) self._new_color(color) except Exception as e: import traceback print(traceback.format_exc()) rospy.logwarn("Error while load launch file %s: %s" % (item, utf8(e))) MessageBox.warning( self, "Load error", 'Error while load launch file:\n%s' % item.name, "%s" % utf8(e))
def set_current_master(self, masteruri, mastername): self.launchlist_model.set_current_master(masteruri, mastername) self._masteruri2name[masteruri.rstrip(os.path.sep)] = mastername try: color = QColor.fromRgb(nm.settings().host_color( self._masteruri2name[nmdurl.masteruri( self.launchlist_model.current_path)], self._default_color.rgb())) self._new_color(color) except Exception as _: pass
def on_topic_control_btn_clicked(self): try: if self.sub is None and self.ssh_output_file is None: if self.__msg_class: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self._start_time = time.time() else: self._on_display_anchorClicked(QUrl(self._masteruri)) self.topicControlButton.setIcon(nm.settings().icon('sekkyumu_stop.png')) else: if self.sub is not None: self.sub.unregister() self.sub = None elif self.ssh_output_file is not None: self.ssh_output_file.close() self.ssh_error_file.close() self.ssh_output_file = None self.topicControlButton.setIcon(nm.settings().icon('sekkyumu_play.png')) except Exception as e: rospy.logwarn('Error while stop/play echo for topic %s: %s' % (self.topic, utf8(e)))