def save(self, url): self._saving = True self.savingChanged.emit() self._savingProgress = 0 self.savingProgressChanged.emit() p = QProcess(self) p.setProcessChannelMode(QProcess.ForwardedErrorChannel) stdout_buffer = b"" def ready_read_stdout(): nonlocal stdout_buffer stdout_buffer += p.readAllStandardOutput().data() *messages, stdout_buffer = stdout_buffer.split(b"\n") for message in messages: progress = json.loads(messages[-1].decode()) self._savingProgress = progress["fraction"] self.savingProgressChanged.emit() p.readyReadStandardOutput.connect(ready_read_stdout) p.finished.connect(self._process_finished) args = ["-c", "from djpdf.scans2pdf import main; main()", os.path.abspath(url.toLocalFile())] if self._verbose: args.append("--verbose") p.start(sys.executable, args) self._process = p p.write(json.dumps([p._data for p in self._pages]).encode()) p.closeWriteChannel()
def execute(self, command, parsers=no_parsers): """ Enqueue a worker to run (at some point) by passing it to the QThreadPool. """ job_id = uuid.uuid4().hex # By default, the signals do not have access to any information about # the process that sent it. So we use this constructor to annotate # each signal with a job_id. def fwd_signal(target): return lambda *args: target(job_id, *args) self._parsers[job_id] = parsers # Set default status to waiting, 0 progress. self._state[job_id] = DEFAULT_STATE.copy() p = QProcess() p.readyReadStandardOutput.connect(fwd_signal(self.handle_output)) p.readyReadStandardError.connect(fwd_signal(self.handle_output)) p.stateChanged.connect(fwd_signal(self.handle_state)) p.finished.connect(fwd_signal(self.done)) self._jobs[job_id] = p p.start(command) self.layoutChanged.emit()
class EmcExe_Qt(EmcExe): """ PySide2 implementation of the EmcExec """ def __init__(self, *args, **kargs): super().__init__(*args, **kargs) self._proc = QProcess() self._proc.errorOccurred.connect(self._error_cb) if self._done_cb: self._proc.finished.connect(self._finished_cb) if self._grab_output: self._proc.readyReadStandardOutput.connect(self._stdout_cb) if self._params: self._proc.start(self._cmd, self._params) else: self._proc.start(self._cmd) def delete(self) -> None: super().delete() if self._proc and self._proc.state() == QProcess.Running: self._proc.kill() self._proc = None def _finished_cb(self, exit_code): self._call_user_callback(exit_code) def _error_cb(self, error): if self._proc and not self.deleted: self._call_user_callback(-1) def _stdout_cb(self): if self._proc and not self.deleted: self._out_buffer.append(self._proc.readAllStandardOutput().data())
class Installer: def __init__(self, output_widget: QTextEdit = None): # create install process self._output_widget = None self.process = QProcess() self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join( [site.getsitepackages()[0], env.systemEnvironment().value("PYTHONPATH")] ) env.insert("PATH", QProcessEnvironment.systemEnvironment().value("PATH")) env.insert("PYTHONPATH", combined_paths) self.process.setProcessEnvironment(env) self.set_output_widget(output_widget) def set_output_widget(self, output_widget: QTextEdit): if output_widget: self._output_widget = output_widget self.process.setParent(output_widget) def _on_stdout_ready(self): if self._output_widget: text = self.process.readAllStandardOutput().data().decode() self._output_widget.append(text) def install(self, pkg_list): cmd = ["-m", "pip", "install", "--upgrade"] self.process.setArguments(cmd + pkg_list) if self._output_widget: self._output_widget.clear() self.process.start()
def run_command(self, cmd: str): log.info( 'run_command', program=self.program, args=self.args, cmd=cmd ) self.output.append(f'> {cmd}\n') self.input.clear() process = QProcess() process.setProgram(self.program) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_error(process) ) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_output(process) ) connect_args = list(self.args) args = cmd.split(' ') if args[0] == self.program.split('/')[-1]: args.pop(0) process.setArguments(connect_args + args) process.start()
def save(self, url): self._saving = True self.savingChanged.emit() self._savingProgress = 0 self.savingProgressChanged.emit() p = QProcess(self) p.setProcessChannelMode(QProcess.ForwardedErrorChannel) stdout_buffer = b"" def ready_read_stdout(): nonlocal stdout_buffer stdout_buffer += p.readAllStandardOutput().data() *messages, stdout_buffer = stdout_buffer.split(b"\n") for message in messages: progress = json.loads(messages[-1].decode()) self._savingProgress = progress["fraction"] self.savingProgressChanged.emit() p.readyReadStandardOutput.connect(ready_read_stdout) def finished(status): self._saving = False self.savingChanged.emit() if status != 0: self.savingError.emit() p.finished.connect(finished) args = ["-c", "from djpdf.scans2pdf import main; main()", os.path.abspath(url.toLocalFile())] if self._verbose: args.append("--verbose") p.start(sys.executable, args) p.write(json.dumps([p._data for p in self._pages]).encode()) p.closeWriteChannel()
def runProcess(command, arguments): process = QProcess() process.start(command, arguments) process.waitForFinished() result = [] for line in str(process.readAllStandardOutput()).split(os.linesep): result.append(line) return result
def testNoArgs(self): '''Connecting a lambda to a signal without arguments''' proc = QProcess() dummy = Dummy() QObject.connect(proc, SIGNAL('started()'), lambda: setattr(dummy, 'called', True)) proc.start(sys.executable, ['-c', '""']) proc.waitForFinished() self.assert_(dummy.called)
def testWithArgs(self): '''Connecting a lambda to a signal with arguments''' proc = QProcess() dummy = Dummy() QObject.connect(proc, SIGNAL('finished(int)'), lambda x: setattr(dummy, 'called', x)) proc.start(sys.executable, ['-c', '""']) proc.waitForFinished() self.assertEqual(dummy.called, proc.exitCode())
def testNoArgs(self): '''Connecting a lambda to a signal without arguments''' proc = QProcess() dummy = Dummy() QObject.connect(proc, SIGNAL('started()'), lambda: setattr(dummy, 'called', True)) proc.start(sys.executable, ['-c', '""']) proc.waitForFinished() self.assertTrue(dummy.called)
class MotivoAccess: motivoDirectory = MOTIVO_DIR def __init__(self): self.motivoProcess = QProcess() self.buildProcess = QProcess() self.mergeProcess = QProcess() self.sampleProcess = QProcess() self.graphProcess = QProcess() # TODO check motivo directory and produce an error message if needed self.motivoProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/scripts/') self.motivoProcess.setProgram('motivo.sh') # Qproces emit readReady signal when data is ready to be read # Connect the handler for read the data self.buildProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.buildProcess.setProgram('motivo-build') self.mergeProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.mergeProcess.setProgram('motivo-merge') self.sampleProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.sampleProcess.setProgram('motivo-sample') self.graphProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.graphProcess.setProgram('motivo-graph') def runMotivo(self, motivoArguments): self.motivoProcess.setArguments(motivoArguments) self.motivoProcess.start() def runBuild(self, buildArguments): self.buildProcess.setArguments(buildArguments) self.buildProcess.start() def runMerge(self, mergeArguments): self.mergeProcess.setArguments(mergeArguments) self.mergeProcess.start() def runSample(self, sampleArguments): self.sampleProcess.setArguments(sampleArguments) self.sampleProcess.start() def runConvertTxtToBinary(self, convertTxtToBinaryArguments): self.graphProcess.setArguments(convertTxtToBinaryArguments) self.graphProcess.start()
def start_qt_process(self, cmd): """ 启动子进程执行耗时命令 :param cmd: :return: """ process = QProcess(self.ui) process.start(cmd) result = process.waitForStarted() return result
def exit(self): self.vivadoProcess.kill() vivado_command_to_kill = "cmd.exe /C Taskkill /IM vivado.exe /F" process = QProcess() process.start(vivado_command_to_kill) process.waitForFinished(5000) tcp_command_to_kill = "cmd.exe /C netstat -ano | find '0.0.0.0:9955'" return
class MainWindow(QMainWindow): def __init__(self): super().__init__() # Hold process reference. self.p = None layout = QVBoxLayout() self.text = QPlainTextEdit() layout.addWidget(self.text) self.progress = QProgressBar() layout.addWidget(self.progress) btn_run = QPushButton("Execute") btn_run.clicked.connect(self.start) layout.addWidget(btn_run) w = QWidget() w.setLayout(layout) self.setCentralWidget(w) self.show() def start(self): if self.p is not None: return self.p = QProcess() self.p.readyReadStandardOutput.connect(self.handle_stdout) self.p.readyReadStandardError.connect(self.handle_stderr) self.p.stateChanged.connect(self.handle_state) self.p.finished.connect(self.cleanup) self.p.start("python", ["dummy_script.py"]) def handle_stderr(self): result = bytes(self.p.readAllStandardError()).decode("utf8") progress = simple_percent_parser(result) self.progress.setValue(progress) def handle_stdout(self): result = bytes(self.p.readAllStandardOutput()).decode("utf8") data = extract_vars(result) self.text.appendPlainText(str(data)) def handle_state(self, state): self.statusBar().showMessage(STATES[state]) def cleanup(self): self.p = None
class Form(QPlainTextEdit): processFinished = Signal() def __init__(self, fileName, parent=None): super(Form, self).__init__(parent) self._fileName = fileName self.setStyleSheet("font-family: Source Code Pro; font-size: 16px; ") self._process = QProcess() self._process.readyReadStandardOutput.connect(self._processStdOut) self._process.readyReadStandardError.connect(self._processStdErr) self._process.finished.connect(self._processFinished) def run(self, args): self._append("> " + shlex.join(args.args) + "\n") self._lastPos = 0 self._process.setWorkingDirectory(args.dir) self._process.setProgram(args.args[0]) self._process.setArguments(args.args[1:]) self._process.start() @Slot() def putTextToFile(self): open(self._fileName, "w").write(self.toPlainText()) def keyPressEvent(self, event): k = event.key() if not self.textCursor().position() < self._lastPos: super().keyPressEvent(event) if k == Qt.Key_Return or k == Qt.Key_Enter: self._process.write( bytes(self.toPlainText()[self._lastPos:], "utf-8")) self._lastPos = self.textCursor().position() def _processStdOut(self): self._append(str(self._process.readAllStandardOutput(), "utf-8")) def _processStdErr(self): self._append(str(self._process.readAllStandardError(), "utf-8")) def _append(self, output): self.moveCursor(QTextCursor.End) self.insertPlainText(output) self.moveCursor(QTextCursor.End) self._lastPos = self.textCursor().position() def _processFinished(self, exitCode): self._append("\nProcess Finished with exit code: " + str(exitCode) + "\n") self.processFinished.emit()
class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() self.setWindowTitle('QProcess') self.setGeometry(100, 100, 600, 300) self.show() def initUI(self): tool_run = QToolButton() tool_run.setText('Run') tool_run.clicked.connect(self.callProgram) toolbar = QToolBar() self.addToolBar(toolbar) toolbar.addWidget(tool_run) self.output = QTextEdit() self.setCentralWidget(self.output) # QProcess object for external app self.process = QProcess(self) # QProcess emits `readyRead` when there is data to be read self.process.readyRead.connect(self.dataReady) # Just to prevent accidentally running multiple times # Disable the button when process starts, and enable it when it finishes self.process.started.connect(lambda: tool_run.setEnabled(False)) self.process.finished.connect(lambda: tool_run.setEnabled(True)) def dataReady(self): cursor = self.output.textCursor() cursor.movePosition(cursor.End) content = self.process.readAllStandardOutput() cursor.insertText(str(content, 'utf-8')) self.output.ensureCursorVisible() def callProgram(self): # run the process # `start` takes the exec and a list of arguments if os.name == 'nt': self.process.start('ping', ['127.0.0.1']) else: self.process.start('ping', ['-c', '4', '127.0.0.1'])
def testWithoutArgs(self): '''Connect QProcess.started() to QTimeLine.togglePaused()''' process = QProcess() timeline = QTimeLine() QObject.connect(process, SIGNAL('finished(int, QProcess::ExitStatus)'), timeline, SLOT('toggleDirection()')) orig_dir = timeline.direction() process.start(sys.executable, ['-c', '"print 42"']) process.waitForFinished() new_dir = timeline.direction() if orig_dir == QTimeLine.Forward: self.assertEqual(new_dir, QTimeLine.Backward) else: self.assertEqual(new_dir, QTimeLine.Forward)
def run_handler(self): self.py_ = G.config.installed_apps[self.pack_name].get('py_') if not self.py_: QMessageBox.warning(self.parent, "提示", "未选择Python解释器") self.act_setting_slot() return if not os.path.exists(self.py_) or not os.path.isfile(self.py_): QMessageBox.warning(self.parent, "提示", f"{self.py_} 不存在") return try: self.entry_, requirement_ = self.get_build() except Exception as e: QMessageBox.warning(self.parent, "提示", str(e)) return ##检测依赖 p = QProcess() p.start(' '.join(([self.py_, "-m", 'pip', "freeze"]))) p.waitForFinished() out = p.readAllStandardOutput().data().decode() output = out.splitlines() with open(requirement_, 'r') as f: requirement = f.read().splitlines() dissatisfy, version_less = diff_pip(output, requirement) if dissatisfy: msgbox = QMessageBox(self.parent) msgbox.setWindowTitle("缺少依赖") msgbox.setText("\n".join(dissatisfy[:15]) + "\n...") yes = msgbox.addButton('立即安装', QMessageBox.AcceptRole) no = msgbox.addButton('稍后', QMessageBox.RejectRole) msgbox.setDefaultButton(yes) reply = msgbox.exec_() if reply == QMessageBox.AcceptRole: self.requirement_ = dissatisfy self.install_handler() return # run TipDialog("正在启动...") cmd = ' '.join([self.py_, self.entry_]) QProcess().startDetached(cmd)
def run_command(self, command): try: if self.cli is None or self.cli_args is None: self.output_area.append( 'Node starting up, please try again later...') return False self.output_area.append(f'> {command}\n') process = QProcess() process.setProgram(self.cli) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_cli_error_output(process)) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_cli_output(process)) args = command.split(' ') if args[0] == self.cli.split('/')[-1]: args.pop(0) process.setArguments(self.cli_args + args) process.start() log.info('run_command', program=self.cli, args=self.cli_args, cmd=command) return True except Exception: self.output_area.append( 'Node starting up, please try again later...') return False
def start_convertion(self): """implement conversion task""" # check if output dir is already defined else get outdir if not self.conversion_started: self.get_output_dir() self.progressBarTotal.setMaximum(len(self.selected_files)) self.btnConvert.setEnabled(False) self.conversion_started = True # setting up arguments current_file_name = self.selected_files[self.current_file_idx] current_outfile_name = Path(self.output_dir).joinpath( self.get_file_name(self.current_file_idx, ".avi")) self.process_argument = " -i {} {}".format(current_file_name, current_outfile_name) # create a process everytime it's called # NOTE: this is a local instance of the process so it changes after every call process = QProcess() process.readyReadStandardError.connect( lambda: self.parse_output(process)) process.finished.connect(self.recursion_handler) process.started.connect(lambda: self.ref_process(process)) process.start("ffmpeg", self.process_argument.split())
class QProcessExecutionManager(ExecutionManager): """Class to manage tool instance execution using a PySide2 QProcess.""" def __init__(self, logger, program=None, args=None, silent=False, semisilent=False): """Class constructor. Args: logger (LoggerInterface): a logger instance program (str): Path to program to run in the subprocess (e.g. julia.exe) args (list): List of argument for the program (e.g. path to script file) silent (bool): Whether or not to emit logger msg signals """ super().__init__(logger) self._program = program self._args = args self._silent = silent # Do not show Event Log nor Process Log messages self._semisilent = semisilent # Do not show Event Log messages but show Process Log messages self.process_failed = False self.process_failed_to_start = False self._user_stopped = False self._process = QProcess(self) self.process_output = None # stdout when running silent self.error_output = None # stderr when running silent def program(self): """Program getter method.""" return self._program def args(self): """Program argument getter method.""" return self._args # noinspection PyUnresolvedReferences def start_execution(self, workdir=None): """Starts the execution of a command in a QProcess. Args: workdir (str): Work directory """ if workdir is not None: self._process.setWorkingDirectory(workdir) self._process.started.connect(self.process_started) self._process.finished.connect(self.on_process_finished) if not self._silent and not self._semisilent: # Loud self._process.readyReadStandardOutput.connect(self.on_ready_stdout) self._process.readyReadStandardError.connect(self.on_ready_stderr) self._process.error.connect(self.on_process_error) # errorOccurred available in Qt 5.6 self._process.stateChanged.connect(self.on_state_changed) elif self._semisilent: # semi-silent self._process.readyReadStandardOutput.connect(self.on_ready_stdout) self._process.readyReadStandardError.connect(self.on_ready_stderr) self._process.start(self._program, self._args) if not self._process.waitForStarted(msecs=10000): # This blocks until process starts or timeout happens self.process_failed = True self.process_failed_to_start = True self._process.deleteLater() self._process = None self.execution_finished.emit(-9998) def wait_for_process_finished(self, msecs=30000): """Wait for subprocess to finish. Return: True if process finished successfully, False otherwise """ if not self._process: return False if self.process_failed or self.process_failed_to_start: return False if not self._process.waitForFinished(msecs): self.process_failed = True self._process.close() self._process = None return False return True @Slot(name="process_started") def process_started(self): """Run when subprocess has started.""" @Slot("QProcess::ProcessState", name="on_state_changed") def on_state_changed(self, new_state): """Runs when QProcess state changes. Args: new_state (QProcess::ProcessState): Process state number """ if new_state == QProcess.Starting: self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program)) arg_str = " ".join(self._args) self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str)) elif new_state == QProcess.Running: self._logger.msg_warning.emit( "\tExecution is in progress. See Process Log for messages " "(stdout&stderr)" ) elif new_state == QProcess.NotRunning: # logging.debug("QProcess is not running") pass else: self._logger.msg_error.emit("Process is in an unspecified state") logging.error("QProcess unspecified state: %s", new_state) @Slot("QProcess::ProcessError", name="'on_process_error") def on_process_error(self, process_error): """Run if there is an error in the running QProcess. Args: process_error (QProcess::ProcessError): Process error number """ if process_error == QProcess.FailedToStart: self.process_failed = True self.process_failed_to_start = True elif process_error == QProcess.Timedout: self.process_failed = True self._logger.msg_error.emit("Timed out") elif process_error == QProcess.Crashed: self.process_failed = True if not self._user_stopped: self._logger.msg_error.emit("Process crashed") elif process_error == QProcess.WriteError: self._logger.msg_error.emit("Process WriteError") elif process_error == QProcess.ReadError: self._logger.msg_error.emit("Process ReadError") elif process_error == QProcess.UnknownError: self._logger.msg_error.emit("Unknown error in process") else: self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error)) def stop_execution(self): """See base class.""" self._logger.msg_error.emit("Terminating process") self._user_stopped = True self.process_failed = True if not self._process: return try: self._process.terminate() except Exception as ex: # pylint: disable=broad-except self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex)) logging.exception("Exception in closing QProcess: %s", ex) finally: self._process.deleteLater() self._process = None @Slot(int) def on_process_finished(self, exit_code): """Runs when subprocess has finished. Args: exit_code (int): Return code from external program (only valid for normal exits) """ # logging.debug("Error that occurred last: {0}".format(self._process.error())) if not self._process: return exit_status = self._process.exitStatus() # Normal or crash exit if exit_status == QProcess.CrashExit: if not self._silent: self._logger.msg_error.emit("\tProcess crashed") exit_code = -1 elif exit_status == QProcess.NormalExit: pass else: if not self._silent: self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status)) exit_code = -1 if not exit_code == 0: self.process_failed = True if not self._user_stopped: out = str(self._process.readAllStandardOutput().data(), "utf-8") errout = str(self._process.readAllStandardError().data(), "utf-8") if out is not None: if not self._silent: self._logger.msg_proc.emit(out.strip()) else: self.process_output = out.strip() self.error_output = errout.strip() else: self._logger.msg.emit("*** Terminating process ***") # Delete QProcess self._process.deleteLater() self._process = None self.execution_finished.emit(exit_code) @Slot(name="on_ready_stdout") def on_ready_stdout(self): """Emit data from stdout.""" if not self._process: return out = str(self._process.readAllStandardOutput().data(), "utf-8") self._logger.msg_proc.emit(out.strip()) @Slot(name="on_ready_stderr") def on_ready_stderr(self): """Emit data from stderr.""" if not self._process: return err = str(self._process.readAllStandardError().data(), "utf-8") self._logger.msg_proc_error.emit(err.strip())
class Litecoin(object): file: ConfigurationFile hard_drives: HardDrives process: QProcess software: LitecoinSoftware zmq_block_port: int zmq_tx_port: int def __init__(self, configuration_file_path: str): self.hard_drives = HardDrives() self.software = LitecoinSoftware() self.file = ConfigurationFile(configuration_file_path) self.running = False self.process = None if self.file['datadir'] is None: self.autoconfigure_datadir() if 'litecoin.conf' in os.listdir(self.file['datadir']): actual_conf_file = os.path.join(self.file['datadir'], 'litecoin.conf') log.info( 'datadir_redirect', configuration_file_path=configuration_file_path, actual_conf_file=actual_conf_file ) self.file = ConfigurationFile(actual_conf_file) if self.file['datadir'] is None: self.autoconfigure_datadir() if os.path.exists(os.path.join(self.file['datadir'], 'blocks')): if self.file['prune'] is None: self.set_prune(False) self.wallet_paths = self.get_wallet_paths() if self.file['server'] is None: self.file['server'] = True if self.file['disablewallet'] is None and not self.wallet_paths: self.file['disablewallet'] = True elif self.file['disablewallet'] is None and self.wallet_paths: self.file['disablewallet'] = False if self.file['timeout'] is None: self.file['timeout'] = 6000 if self.file['rpcuser'] is None: self.file['rpcuser'] = '******' if self.file['rpcpassword'] is None: self.file['rpcpassword'] = get_random_password() if self.file['prune'] is None: should_prune = self.hard_drives.should_prune(self.file['datadir'], has_litecoin=True) self.set_prune(should_prune) self.zmq_block_port = get_zmq_port() self.zmq_tx_port = get_zmq_port() self.file['zmqpubrawblock'] = f'tcp://127.0.0.1:{self.zmq_block_port}' self.file['zmqpubrawtx'] = f'tcp://127.0.0.1:{self.zmq_tx_port}' # noinspection PyBroadException try: memory = psutil.virtual_memory() free_mb = round(memory.available / 1000000) free_mb -= int(free_mb * .3) self.file['dbcache'] = free_mb except: log.warning( 'dbcache psutil.virtual_memory', exc_info=True ) self.file['dbcache'] = 1000 self.config_snapshot = self.file.snapshot.copy() self.file.file_watcher.fileChanged.connect(self.config_file_changed) self.process = QProcess() self.process.setProgram(self.software.litecoind) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.process.start() @property def network(self): if self.file['testnet']: return TESTNET return MAINNET def get_wallet_paths(self): exclude_files = { 'addr.dat', 'banlist.dat', 'fee_estimates.dat', 'mempool.dat', 'peers.dat' } candidate_paths = [] if self.file['testnet']: datadir = os.path.join(self.file['datadir'], 'testnet4') wallet_dir = self.file['test.walletdir'] wallets = self.file['test.wallet'] else: datadir = self.file['datadir'] wallet_dir = self.file['main.walletdir'] wallets = self.file['main.wallet'] for file in os.listdir(datadir): if file not in exclude_files: path = os.path.join(datadir, file) candidate_paths.append(path) default_walletdir = os.path.join(datadir, 'wallets') if os.path.exists(default_walletdir): for file in os.listdir(default_walletdir): if file not in exclude_files: candidate_paths.append( os.path.join(default_walletdir, file)) if wallet_dir is not None: for file in os.listdir(wallet_dir): if file not in exclude_files: candidate_paths += os.path.join( os.path.join(wallet_dir, file)) dat_files = [f for f in candidate_paths if f.endswith('.dat') and not f.startswith('blk')] dat_files = set(dat_files) wallet_paths = set(dat_files - exclude_files) if wallets is not None: if isinstance(wallets, list): for wallet in wallets: wallet_paths.add(wallet) else: wallet_paths.add(wallets) return wallet_paths @property def node_port(self): if self.file['testnet']: custom_port = self.file['test.port'] else: custom_port = self.file['main.port'] if custom_port is not None: return custom_port if self.file['testnet']: return LITECOIN_TESTNET_PEER_PORT return LITECOIN_MAINNET_PEER_PORT @property def rpc_port(self): if self.file['testnet']: custom_port = self.file['test.rpcport'] else: custom_port = self.file['main.rpcport'] if custom_port is not None: return custom_port if self.file['testnet']: return LITECOIN_TESTNET_RPC_PORT return LITECOIN_MAINNET_RPC_PORT def set_prune(self, should_prune: bool = None): if should_prune is None: should_prune = self.hard_drives.should_prune(self.file['datadir'], has_litecoin=True) if should_prune: if self.file['testnet']: prune = TESTNET_PRUNE else: prune = MAINNET_PRUNE self.file['prune'] = prune else: self.file['prune'] = 0 self.file['txindex'] = not should_prune def autoconfigure_datadir(self): default_datadir = LITECOIN_DATA_PATH[OPERATING_SYSTEM] big_drive = self.hard_drives.get_big_drive() default_is_big_enough = not self.hard_drives.should_prune( input_directory=default_datadir, has_litecoin=True ) default_is_biggest = self.hard_drives.is_default_partition(big_drive) log.info( 'autoconfigure_datadir', default_is_big_enough=default_is_big_enough, default_is_biggest=default_is_biggest ) if default_is_big_enough or default_is_biggest: self.file['datadir'] = default_datadir log.info( 'autoconfigure_datadir', datadir=default_datadir ) return if not self.hard_drives.should_prune(big_drive.mountpoint, False): datadir = os.path.join(big_drive.mountpoint, 'Litecoin') self.file['datadir'] = datadir log.info( 'autoconfigure_datadir', datadir=datadir ) if not os.path.exists(self.file['datadir']): os.mkdir(self.file['datadir']) else: self.file['datadir'] = default_datadir log.info( 'autoconfigure_datadir', datadir=default_datadir ) @property def args(self) -> List[str]: return [f'-conf={self.file.path}'] @property def litecoin_cli(self) -> str: command = [ f'"{self.software.litecoin_cli}"', f'-conf="{self.file.path}"', ] return ' '.join(command) def config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.zmq_block_port = int(self.file['zmqpubrawblock'].split(':')[-1]) self.zmq_tx_port = int(self.file['zmqpubrawtx'].split(':')[-1]) # Some text editors do not modify the file, they delete and replace the file # Check if file is still in file_watcher list of files, if not add back files_watched = self.file.file_watcher.files() if len(files_watched) == 0: self.file.file_watcher.addPath(self.file.path) @property def restart_required(self): old_config = self.config_snapshot.copy() new_config = self.file.snapshot # First check that both config files are still on the same network old_config_network = 'testnet' in old_config.keys() new_config_network = 'testnet' in new_config.keys() if (old_config_network == new_config_network) and self.running: common_fields = [ 'rpcuser', 'rpcpassword', 'disablewallet', 'datadir', 'disablewallet', 'zmqpubrawblock', 'zmqpubrawtx', 'prune', 'txindex', 'timeout' ] for field in common_fields: # First check if field is found in both configs found_in_old_config = field in old_config.keys() found_in_new_config = field in new_config.keys() if found_in_old_config != found_in_new_config: return True # Now check that values are the same if found_in_old_config: if old_config[field] != new_config[field]: return True if self.file['testnet']: # Only check testnet fields if currently running testnet testnet_fields = [ 'test.rpcport', 'test.port', 'test.wallet', 'test.walletdir' ] for field in testnet_fields: # First check if field is found in both configs found_in_old_config = field in old_config.keys() found_in_new_config = field in new_config.keys() if found_in_old_config != found_in_new_config: return True # Now check that values are the same if found_in_old_config: if old_config[field] != new_config[field]: return True else: # Only check mainnet fields if currently running mainnet mainnet_fields = [ 'rpcport', 'port' ] for field in mainnet_fields: # First check if field is found in both configs found_in_old_config = field in old_config.keys() found_in_new_config = field in new_config.keys() if found_in_old_config != found_in_new_config: return True # Now check that values are the same if found_in_old_config: if old_config[field] != new_config[field]: return True return False elif self.running: # Network has changed and the node is running - Restart is required return True return False
def runProcess(command, arguments): process = QProcess() process.start(command, arguments) process.waitForFinished() std_output = process.readAllStandardOutput().data().decode('utf-8') return std_output.split('\n')
class MainWindow(QDialog): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle(APP_NAME) self.setWindowIcon(QIcon(get_resource_path(os.path.join('resources', 'noun_Plant.ico')))) self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.lineEdit_invoiceNumber.setValidator(QIntValidator()) self.ui.progressBar.setMaximum(1) self.proc = QProcess() self.ui.tableWidget_invoiceContent.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.savePath = create_path_it_not_exist(os.path.join(get_config_path(APP_CONFIG_FOLDER), 'save.json')) self.saveData = SaveData(self.savePath) self.outputPath = os.path.join(get_exe_path(), create_path_it_not_exist(os.path.join(get_exe_path(), 'output'))) self.texGenerator = LatexTemplateGenerator(get_resource_path('resources').replace('\\', '/')) self.currentGeneralInfo = GeneralInfo() self.currentClientInfo = ClientInfo() self.currentInvoiceInfo = InvoiceInfo() self.ui.pushButton_saveQuickRecallInvoice.clicked.connect(self.save_invoice_info) self.ui.pushButton_saveQuickRecallClient.clicked.connect(self.save_client_info) self.ui.pushButton_saveQuickRecallGeneral.clicked.connect(self.save_general_info) self.ui.comboBox_quickRecallInvoice.activated.connect(self.on_combo_box_invoice_changed) self.ui.comboBox_quickRecallClient.activated.connect(self.on_combo_box_client_changed) self.ui.comboBox_quickRecallGeneral.activated.connect(self.on_combo_box_general_changed) self.ui.pushButton_generateInvoice.clicked.connect(self.generate_invoice) self.proc.finished.connect(functools.partial(self._handleProcFinished, self.proc)) self.ui.toolButton_add.clicked.connect(self.add_row) self.ui.toolButton_delete.clicked.connect(self.delete_row) self.update_ui() def _handleProcFinished(self, process, exitCode): stdOut = process.readAllStandardOutput() stdErr = process.readAllStandardError() print("Standard Out:") print(stdOut) print("Standard Error:") print(stdErr) if(exitCode == 0): self.success_message('Invoice Generated successfully') self.ui.progressBar.setMaximum(1) def make_pdf(self, input_folder, input_file): if self.proc.isOpen(): print('cancelled running process') self.proc.close() self.proc.setWorkingDirectory(input_folder) self.proc.start("xelatex", [os.path.join(input_folder, input_file)]) self.ui.progressBar.setMaximum(0) def save_data(self): self.save_invoice_info() self.save_client_info() self.save_general_info() if self.saveData.save_client(self.currentClientInfo) and \ self.saveData.save_invoice(self.currentInvoiceInfo) and \ self.saveData.save_general(self.currentGeneralInfo): self.saveData.save_to_file() else: self.warning_message("Invalid invoice formatting\n Check for empty or incorrect fields") def load_data(self): pass def list_client(self): self.ui.comboBox_quickRecallClient.clear() for client in self.saveData.clients: self.ui.comboBox_quickRecallClient.addItem(client.name) def list_general(self): self.ui.comboBox_quickRecallGeneral.clear() for general in self.saveData.generals: self.ui.comboBox_quickRecallGeneral.addItem(general.company_name) pass def list_invoice(self): self.ui.comboBox_quickRecallInvoice.clear() for invoice in self.saveData.invoices: self.ui.comboBox_quickRecallInvoice.addItem(invoice.invoice_number) def generate_invoice(self): print("generating invoice") self.save_data() filename = ''.join(e for e in unidecode.unidecode(self.currentInvoiceInfo.client.name) if e.isalnum()) self.texGenerator.render(SaveData.asflatdict(self.currentInvoiceInfo), create_path_it_not_exist( os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number, 'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex'))) self.make_pdf(os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number), 'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex') self.update_ui() def recall_general_info(self, company_name): newGeneral = self.saveData.get_general(company_name) self.ui.lineEdit_companyName.setText(newGeneral.company_name) self.ui.lineEdit_firstName.setText(newGeneral.first_name) self.ui.lineEdit_lastName.setText(newGeneral.last_name) self.ui.lineEdit_fullAddress.setText(newGeneral.full_address) self.ui.lineEdit_companySIRET.setText(newGeneral.company_siret) self.ui.lineEdit_companySIREN.setText(newGeneral.company_siren) self.ui.lineEdit_companyAPE.setText(newGeneral.company_ape) self.ui.lineEdit_companyEmail.setText(newGeneral.company_email) self.ui.lineEdit_companyTelephone.setText(newGeneral.company_phone) self.ui.lineEdit_bankIBAN.setText(newGeneral.bank_iban) self.ui.lineEdit_bankBIC.setText(newGeneral.bank_bic) def recall_client_info(self, name): newClient = self.saveData.get_client(name) self.ui.lineEdit_clientName.setText(newClient.name) self.ui.lineEdit_clientAddressFirst.setText(newClient.address_first_line) self.ui.lineEdit_clientAdressSecond.setText(newClient.address_second_line) self.update_infos_from_UI() def recall_invoice_info(self, invoice_number): newInvoice = self.saveData.get_invoice(invoice_number) self.ui.lineEdit_invoiceNumber.setText(newInvoice.invoice_number) self.ui.lineEdit_invoiceDate.setText(newInvoice.invoice_date) self.ui.lineEdit_invoiceName.setText(newInvoice.invoice_name) self.ui.tableWidget_invoiceContent.clearContents() for item in newInvoice.items: row_position = self.ui.tableWidget_invoiceContent.rowCount() self.ui.tableWidget_invoiceContent.insertRow(row_position) self.ui.tableWidget_invoiceContent.setItem(row_position, 0, QTableWidgetItem(str(item.product_name))) self.ui.tableWidget_invoiceContent.setItem(row_position, 1, QTableWidgetItem(str(item.quantity))) self.ui.tableWidget_invoiceContent.setItem(row_position, 2, QTableWidgetItem(str(item.price))) self.recall_client_info(newInvoice.client.name) self.recall_general_info(newInvoice.general.company_name) self.update_infos_from_UI() def on_combo_box_client_changed(self, index): self.recall_client_info(self.ui.comboBox_quickRecallClient.itemText(index)) def on_combo_box_general_changed(self, index): self.recall_general_info(self.ui.comboBox_quickRecallGeneral.itemText(index)) def on_combo_box_invoice_changed(self, index): self.recall_invoice_info(self.ui.comboBox_quickRecallInvoice.itemText(index)) def save_invoice_info(self): self.update_invoice_infos_from_UI() if not self.saveData.save_invoice(self.currentInvoiceInfo): self.warning_message("Couldn't save new Invoice") self.update_ui() def save_general_info(self): self.update_general_infos_from_UI() if not self.saveData.save_general(self.currentGeneralInfo): self.warning_message("Couldn't save new General") self.update_ui() def save_client_info(self): self.update_client_infos_from_UI() if not self.saveData.save_client(self.currentClientInfo): self.warning_message("Couldn't save new Client") self.update_ui() def add_row(self): self.ui.tableWidget_invoiceContent.insertRow(self.ui.tableWidget_invoiceContent.rowCount()) def delete_row(self): self.ui.tableWidget_invoiceContent.removeRow(self.ui.tableWidget_invoiceContent.currentRow()) def ask_validation(self, text, informative_text, title="Validation Dialog"): msgBox = QMessageBox() msgBox.setWindowTitle("hey") msgBox.setText(text) msgBox.setInformativeText(informative_text) msgBox.setStandardButtons(QMessageBox.Apply | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) ret = msgBox.exec_() return True if ret == QMessageBox.Apply else False def success_message(self, text): ret = QMessageBox.information(self, self.tr("Success"), self.tr(text), QMessageBox.Ok, QMessageBox.Ok) def warning_message(self, text): ret = QMessageBox.warning(self, self.tr("Warning"), self.tr(text), QMessageBox.Ok, QMessageBox.Ok) def update_client_infos_from_UI(self): currentClientInfo = ClientInfo() currentClientInfo.name = self.ui.lineEdit_clientName.text() currentClientInfo.address_first_line = self.ui.lineEdit_clientAddressFirst.text() currentClientInfo.address_second_line = self.ui.lineEdit_clientAdressSecond.text() self.currentClientInfo = currentClientInfo def update_general_infos_from_UI(self): currentGeneralInfo = GeneralInfo() currentGeneralInfo.company_name = self.ui.lineEdit_companyName.text() currentGeneralInfo.first_name = self.ui.lineEdit_firstName.text() currentGeneralInfo.last_name = self.ui.lineEdit_lastName.text() currentGeneralInfo.full_address = self.ui.lineEdit_fullAddress.text() currentGeneralInfo.company_siret = self.ui.lineEdit_companySIRET.text() currentGeneralInfo.company_siren = self.ui.lineEdit_companySIREN.text() currentGeneralInfo.company_ape = self.ui.lineEdit_companyAPE.text() currentGeneralInfo.company_email = self.ui.lineEdit_companyEmail.text() currentGeneralInfo.company_phone = self.ui.lineEdit_companyTelephone.text() currentGeneralInfo.bank_iban = self.ui.lineEdit_bankIBAN.text() currentGeneralInfo.bank_bic = self.ui.lineEdit_bankBIC.text() self.currentGeneralInfo = currentGeneralInfo def update_invoice_infos_from_UI(self): currentInvoiceInfo = InvoiceInfo() currentInvoiceInfo.invoice_number = self.ui.lineEdit_invoiceNumber.text() currentInvoiceInfo.invoice_date = self.ui.lineEdit_invoiceDate.text() currentInvoiceInfo.invoice_name = self.ui.lineEdit_invoiceName.text() currentInvoiceInfo.general = self.currentGeneralInfo currentInvoiceInfo.client = self.currentClientInfo currentInvoiceInfo.items = [] try: for row in range(self.ui.tableWidget_invoiceContent.rowCount()): newItem = InvoiceItem() newItem.product_name = self.ui.tableWidget_invoiceContent.item(row, 0).text() \ if self.ui.tableWidget_invoiceContent.item(row, 0) is not None else "" newItem.quantity = int(self.ui.tableWidget_invoiceContent.item(row, 1).text()) \ if self.ui.tableWidget_invoiceContent.item(row, 1) is not None else 0 newItem.price = float(self.ui.tableWidget_invoiceContent.item(row, 2).text()) \ if self.ui.tableWidget_invoiceContent.item(row, 2) is not None else 0 currentInvoiceInfo.items.append(newItem) except ValueError: self.warning_message("oops Something went wrong, make sure you entered appropriate values") self.currentInvoiceInfo = currentInvoiceInfo def update_infos_from_UI(self): self.update_general_infos_from_UI() self.update_client_infos_from_UI() self.update_invoice_infos_from_UI() def update_ui(self): self.list_invoice() self.list_client() self.list_general()
class Lnd(object): litecoin: Litecoin file: ConfigurationFile software: LndSoftware process: QProcess def __init__(self, configuration_file_path: str, litecoin: Litecoin): self.running = False self.is_unlocked = False self.litecoin = litecoin self.file = ConfigurationFile(configuration_file_path) self.software = LndSoftware() self.lnddir = LND_DIR_PATH[OPERATING_SYSTEM] # Previous versions of the launcher set lnddir in the config file, # but it is not a valid key so this helps old users upgrading if self.file['lnddir'] is not None: self.file['lnddir'] = None if self.file['debuglevel'] is None: self.file['debuglevel'] = 'info' self.file['litecoin.active'] = True self.file['litecoin.node'] = 'litecoind' self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}' self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser'] self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword'] self.file['litecoind.zmqpubrawblock'] = self.litecoin.file[ 'zmqpubrawblock'] self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx'] if self.file['restlisten'] is None: if self.litecoin.file['testnet']: self.rest_port = get_port(LND_DEFAULT_REST_PORT + 1) else: self.rest_port = get_port(LND_DEFAULT_REST_PORT) self.file['restlisten'] = f'127.0.0.1:{self.rest_port}' else: self.rest_port = self.file['restlisten'].split(':')[-1] if not self.file['rpclisten']: if self.litecoin.file['testnet']: self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT + 1) else: self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT) self.file['rpclisten'] = f'127.0.0.1:{self.grpc_port}' else: self.grpc_port = int(self.file['rpclisten'].split(':')[-1]) if not self.file['tlsextraip']: self.file['tlsextraip'] = '127.0.0.1' if self.file['color'] is None: self.file['color'] = '#000000' self.macaroon_path = os.path.join( self.lnddir, 'data', 'chain', 'litecoin', str(self.litecoin.network) ) self.config_snapshot = self.file.snapshot.copy() self.file.file_watcher.fileChanged.connect(self.config_file_changed) self.litecoin.file.file_watcher.fileChanged.connect(self.litecoin_config_file_changed) self.process = QProcess() self.process.setProgram(self.software.lnd) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.process.start() @property def args(self): if IS_WINDOWS: arg_list = [ f'--configfile={self.file.path}', ] else: arg_list = [ f'--configfile="{self.file.path}"', ] if self.litecoin.file['testnet']: arg_list += [ '--litecoin.testnet' ] else: arg_list += [ '--litecoin.mainnet' ] return arg_list @property def node_port(self) -> str: if self.file['listen'] is None: if self.litecoin.file['testnet']: port = get_port(LND_DEFAULT_PEER_PORT + 1) else: port = get_port(LND_DEFAULT_PEER_PORT) self.file['listen'] = f'127.0.0.1:{port}' else: if not isinstance(self.file['listen'], list): port = self.file['listen'].split(':')[-1] else: port = self.file['listen'][0].split(':')[-1] return port def test_tls_cert(self): context = ssl.create_default_context() context.load_verify_locations(cafile=self.tls_cert_path) conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname='127.0.0.1') conn.connect(('127.0.0.1', int(self.rest_port))) cert = conn.getpeercert() return cert @property def admin_macaroon_path(self) -> str: path = os.path.join(self.macaroon_path, 'admin.macaroon') return path @property def wallet_path(self) -> str: wallet_path = os.path.join(self.macaroon_path, 'wallet.db') return wallet_path @property def has_wallet(self) -> bool: return os.path.isfile(self.wallet_path) @property def tls_cert_path(self) -> str: tls_cert_path = os.path.join(self.lnddir, 'tls.cert') return tls_cert_path def lncli_arguments(self) -> List[str]: args = [] if self.grpc_port != LND_DEFAULT_GRPC_PORT: args.append(f'--rpcserver=127.0.0.1:{self.grpc_port}') if self.litecoin.file['testnet']: args.append(f'--network={self.litecoin.network}') if self.lnddir != LND_DIR_PATH[OPERATING_SYSTEM]: args.append(f'''--lnddir="{self.lnddir}"''') args.append(f'--macaroonpath="{self.macaroon_path}"') args.append(f'--tlscertpath="{self.tls_cert_path}"') return args @property def lncli(self) -> str: base_command = [ f'"{self.software.lncli}"', ] base_command += self.lncli_arguments() return ' '.join(base_command) @property def rest_url(self) -> str: return f'https://127.0.0.1:{self.rest_port}' @property def grpc_url(self) -> str: return f'127.0.0.1:{self.grpc_port}' def config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.rest_port = int(self.file['restlisten'].split(':')[-1]) self.grpc_port = int(self.file['rpclisten'].split(':')[-1]) # Some text editors do not modify the file, they delete and replace the file # Check if file is still in file_watcher list of files, if not add back files_watched = self.file.file_watcher.files() if len(files_watched) == 0: self.file.file_watcher.addPath(self.file.path) def litecoin_config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}' self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser'] self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword'] self.file['litecoind.zmqpubrawblock'] = self.litecoin.file['zmqpubrawblock'] self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx'] @property def restart_required(self): if self.running: # Did litecoin details change if self.litecoin.restart_required: return True and self.running old_config = self.config_snapshot.copy() new_config = self.file.snapshot fields = [ 'restlisten', 'listen', 'rpclisten' ] for field in fields: # First check if field is found in both configs found_in_old_config = field in old_config.keys() found_in_new_config = field in new_config.keys() if found_in_old_config != found_in_new_config: return True # Now check that values are the same if found_in_old_config: if old_config[field] != new_config[field]: return True return False
class TclInterfaceHandler(QObject): # The TCP server IP address HOST = "127.0.0.1" # TCP server port PORT = 9955 commandExecutionFinished = Signal(str) faultInjectionFinished = Signal(str) #--------------------------------------------------------------------------------- # @brief Class constructor. #--------------------------------------------------------------------------------- def __init__(self): super(TclInterfaceHandler, self).__init__() self.operation = Operation.NONE self.frame_address = "00000000" self.frames = "1" self.tcpClientSocket = QtNetwork.QTcpSocket() self.tcpClientSocketStatus = SocketStatus.DISCONNECTED self.startVivado() return #--------------------------------------------------------------------------------- # @brief This function should be called when the application terminates. #--------------------------------------------------------------------------------- def exit(self): self.vivadoProcess.kill() vivado_command_to_kill = "cmd.exe /C Taskkill /IM vivado.exe /F" process = QProcess() process.start(vivado_command_to_kill) process.waitForFinished(5000) tcp_command_to_kill = "cmd.exe /C netstat -ano | find '0.0.0.0:9955'" return #********************************************************************************** # TCP Client #********************************************************************************** #--------------------------------------------------------------------------------- # @brief This function starts the TCP client. #--------------------------------------------------------------------------------- def clientStart(self): if self.tcpClientSocketStatus == SocketStatus.CONNECTED: self.tcpClientSocket.abort() self.tcpClientSocket.connectToHost(TclInterfaceHandler.HOST, TclInterfaceHandler.PORT) self.tcpClientSocket.waitForConnected(3000) self.tcpClientSocket.readyRead.connect(self.clientReadReady) self.tcpClientSocket.error.connect(self.clientError) self.tcpClientSocketStatus = SocketStatus.CONNECTED return #--------------------------------------------------------------------------------- # @brief This function is called by the TCP client when it has data ready to be read. #--------------------------------------------------------------------------------- def clientReadReady(self): message = QTextStream(self.tcpClientSocket).readAll() if Operation.READBACK_CAPTURE.name in message: XtclLog.writeLine( "================ FPGA readback capture finished ================ ", XtclCommon.blue) elif Operation.READBACK_VERIFY.name in message: XtclLog.writeLine( "================ FPGA readback verify finished ================ ", XtclCommon.blue) #verify = filecmp.cmp(XtclSettings.readbackFilePathGolden, self.readbackFile, False) elif Operation.READBACK.name in message: XtclLog.writeLine( "================ FPGA readback finished ================ ", XtclCommon.blue) elif Operation.CONFIGURATION.name in message: XtclLog.writeLine( "================ FPGA configuration finished ================ ", XtclCommon.blue) elif Operation.FRAMES_READ.name in message or Operation.FAULT_INJECTION_READ.name in message: XtclLog.writeLine( "================ FPGA frame readback finished ================ ", XtclCommon.blue) elif Operation.FRAMES_WRITE.name in message or Operation.FAULT_INJECTION_WRITE.name in message: XtclLog.writeLine( "================ FPGA frame write finished ================ ", XtclCommon.blue) elif Operation.READ_FIFO.name in message: XtclLog.writeLine( "================ Reading internal FIFO finished ================ ", XtclCommon.blue) elif Operation.RESET_FIFO.name in message: XtclLog.writeLine( "================ Reseting internal FIFO finished ================ ", XtclCommon.blue) elif Operation.READ_HEARTBEAT.name in message: XtclLog.writeLine( "================ Reading heartbeat finished ================ ", XtclCommon.blue) elif Operation.LOGIC_STATUS.name in message: XtclLog.writeLine( "================ Reading active logic status finished ================ ", XtclCommon.blue) elif Operation.REGISTER_READ.name in message: XtclLog.writeLine( "================ Reading configuration register finished ================ ", XtclCommon.blue) elif Operation.REGISTER_WRITE.name in message: XtclLog.writeLine( "================ Writing configuration register finished ================ ", XtclCommon.blue) else: XtclLog.writeLine(message, XtclCommon.red) self.operation = Operation.NONE if "FAULT_INJECTION" in message: self.faultInjectionFinished.emit(message) else: self.commandExecutionFinished.emit(message) return #--------------------------------------------------------------------------------- # @brief Callback function for the client error. #--------------------------------------------------------------------------------- def clientError(self, socketError): if socketError == QtNetwork.QAbstractSocket.RemoteHostClosedError: pass elif socketError == QtNetwork.QAbstractSocket.HostNotFoundError: XtclLog.writeLine( "The host was not found. Please check the host name and port settings", XtclCommon.red) elif socketError == QtNetwork.QAbstractSocket.ConnectionRefusedError: XtclLog.writeLine( "The connection was refused by the peer. Make sure the " "server is running, and check that the host name " "and port settings are correct.", TclInterfaceHandler.red) else: XtclLog.writeLine( "The following error occurred: %s." % self.tcpSocket.errorString(), XtclCommon.red) return #--------------------------------------------------------------------------------- # @brief Send data to the TCP server #--------------------------------------------------------------------------------- def clientSend(self, data): bytesArray = bytes(data, 'utf-8') message = QByteArray.fromRawData(bytesArray) self.tcpClientSocket.write(message) self.tcpClientSocket.flush() return #********************************************************************************** # Vivado interface #********************************************************************************** #--------------------------------------------------------------------------------- # @brief This function starts the Vivado instanse. Please replace the command # with the appropriate command for the running platform #--------------------------------------------------------------------------------- def startVivado(self): self.operation = Operation.NONE TclInterfaceHandler.isOperationFinished = False XtclLog.writeLine( "================ Starting Vivado process ================ ", XtclCommon.blue) XtclLog.write("PLEASE WAIT UNTIL YOU SEE \"# vwait forever\" MESSAGE!", XtclCommon.red) command = XtclSettings.vivadoDirectory + "/vivado.bat -nojournal -nolog -mode batch -source jtag_configuration_engine.tcl" # Create runner self.vivadoProcess = QProcess() self.vivadoProcess.readyReadStandardError.connect(self.errorInfo) self.vivadoProcess.readyReadStandardOutput.connect( self.readAllStandardOutput) self.vivadoProcess.finished.connect(self.finished) self.vivadoProcess.start(command) return #--------------------------------------------------------------------------------- # @brief Callback function for the error of the Vivado process #--------------------------------------------------------------------------------- def errorInfo(self): info = self.vivadoProcess.readAllStandardError() info_text = QTextStream(info).readAll() XtclLog.write(info_text) return #--------------------------------------------------------------------------------- # @brief Callback function to rediarect the output of the Vivado process #--------------------------------------------------------------------------------- def readAllStandardOutput(self): info = self.vivadoProcess.readAllStandardOutput() info_text = QTextStream(info).readAll() XtclLog.write(info_text) return #--------------------------------------------------------------------------------- # @brief Callback function for the termination event of the Vivado process #--------------------------------------------------------------------------------- def finished(self, exitCode, exitStatus): return #********************************************************************************** # Interface commands #********************************************************************************** #--------------------------------------------------------------------------------- # @brief This function configures the FPGA # @param bitstream_filepath: The full path of the bitstream file # @param mask_filepath: The full path of the mask file #--------------------------------------------------------------------------------- def configure(self, bitstream_filepath, mask_filepath): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Starting FPGA configuration ================ ", XtclCommon.blue) self.clientSend(Operation.CONFIGURATION.name + "%" + bitstream_filepath + "%" + mask_filepath) self.operation = Operation.CONFIGURATION return #--------------------------------------------------------------------------------- # @brief This function reads-back the FPGA # @param filename: If provided should be the full path of the file where the readback # data will be saved. Othervise the readback data is saved in a # timestamp-based file name inside the "readback-files" of the # working folder. #--------------------------------------------------------------------------------- def readback(self, filename=None): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() if filename == None: readbackFileName = "readback-" + str( datetime.datetime.now().timestamp()) + ".rbd" else: readbackFileName = filename self.readbackFile = XtclSettings.workingDirectory + '/readback-files/' + readbackFileName XtclLog.writeLine( "================ Starting FPGA readback ================ ", XtclCommon.blue) XtclLog.writeLine(readbackFileName, XtclCommon.blue) self.clientSend(Operation.READBACK.name + "%" + self.readbackFile) self.operation = Operation.READBACK return #--------------------------------------------------------------------------------- # @brief This function reads-back the FPGA using the capture mode # @note The readback data is saved in a timestamp-based file name inside the "readback-capture-files" # of the working folder. #--------------------------------------------------------------------------------- def readbackCapture(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() readbackFileName = "readbackCapture-" + str( datetime.datetime.now().timestamp()) + ".rbd" self.readbackFile = XtclSettings.workingDirectory + '/readback-capture-files/' + readbackFileName XtclLog.writeLine( "================ Starting FPGA readback capture ================ ", XtclCommon.blue) XtclLog.writeLine(readbackFileName, XtclCommon.blue) self.clientSend(Operation.READBACK_CAPTURE.name + "%" + self.readbackFile) self.operation = Operation.READBACK_CAPTURE return #--------------------------------------------------------------------------------- # @brief This function verifies the FPGA. # @note The FPGA device should be configured before issuing this command. #--------------------------------------------------------------------------------- def readbackVerify(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Starting FPGA readback verify ================ ", XtclCommon.blue) self.clientSend(Operation.READBACK_VERIFY.name) self.operation = Operation.READBACK_VERIFY return #--------------------------------------------------------------------------------- # @brief This function reads the internal FIFO of the interface logic. #--------------------------------------------------------------------------------- def readInternalFifo(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Reading internal FIFO ================ ", XtclCommon.blue) self.clientSend(Operation.READ_FIFO.name) self.operation = Operation.READ_FIFO return #--------------------------------------------------------------------------------- # @brief This function resets the internal FIFO of the interface logic. #--------------------------------------------------------------------------------- def resetInternalFifo(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Reseting internal FIFO ================ ", XtclCommon.blue) self.clientSend(Operation.RESET_FIFO.name) self.operation = Operation.RESET_FIFO return #--------------------------------------------------------------------------------- # @brief This function reads the heartbeat signal the interface logic. #--------------------------------------------------------------------------------- def readHeartbeat(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Reading heartbeat ================ ", XtclCommon.blue) self.clientSend(Operation.READ_HEARTBEAT.name) self.operation = Operation.READ_HEARTBEAT return #--------------------------------------------------------------------------------- # @brief This function writes frames in configuration memory of the FPGA # @param frame_address: The frame address in HEX format (i.e. 00001002). # @param frame_file: The full path of the file which holds the frame data to be written. # @param append_dummy_frame: True to append a dummy frame after writing the frame data. # Set it to false if the file contains also the dummy frame. # @param is_frame_data_in_hex_format: Trus if the content of the @ref frame_file is in HEX foramt. # @param frames: The number of frames to be written. This should be the same as the frames # inside the @ref frame_file (do not include the dummy frame). # @param reset_fifo: True to reset the FIFO at the end of writing. # @param is_fault_injection: Trus if this write is for fault injection experiment. #--------------------------------------------------------------------------------- def writeFrames(self, frame_address, frame_file, append_dummy_frame=True, is_frame_data_in_hex_format=True, frames=1, reset_fifo=False, is_fault_injection=False): self.frame_address = str(frame_address) self.frames = str(frames) XtclLog.writeLine( "================ Starting FPGA frame write ================ ", XtclCommon.blue) append_dummy_frame_ = str(int(append_dummy_frame)) is_frame_data_in_hex_format_ = str(int(is_frame_data_in_hex_format)) reset_fifo_ = str(int(reset_fifo)) if is_fault_injection == True: command = Operation.FAULT_INJECTION_WRITE.name + "%" + frame_file + "%" + self.frame_address + "%" + self.frames + "%" + append_dummy_frame_ + "%" + is_frame_data_in_hex_format_ + "%" + reset_fifo_ else: command = Operation.FRAMES_WRITE.name + "%" + frame_file + "%" + self.frame_address + "%" + self.frames + "%" + append_dummy_frame_ + "%" + is_frame_data_in_hex_format_ + "%" + reset_fifo_ self.clientSend(command) self.operation = Operation.FRAMES_WRITE return #--------------------------------------------------------------------------------- # @brief This function reads frames from the configuration memory of the FPGA # @param frame_address: The frame address in HEX format (i.e. 00001002). # @param frames: The number of frames to be written. This should be the same as the frames # inside the @ref frame_file (do not include the dummy frame). # @param readback_file: If provided should be the full path of the file where the readback # data will be saved. Othervise the readback data is saved in a # timestamp-based file name inside the "readback-frame-files" of the # working folder. # @param is_fault_injection: Trus if this write is for fault injection experiment. #--------------------------------------------------------------------------------- def readFrames(self, frame_address, frames=1, readback_file=None, is_fault_injection=False): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() self.frame_address = str(frame_address) self.frames = str(frames) XtclLog.writeLine( "================ Starting FPGA frame read ================ ", XtclCommon.blue) if readback_file == None: readbackFileName = "readbackBlock-0x" + str( self.frame_address) + "-" + str( datetime.datetime.now().timestamp()) + ".rbd" self.readbackFile = XtclSettings.workingDirectory + '/readback-frame-files/' + readbackFileName else: self.readbackFile = XtclSettings.workingDirectory + '/readback-frame-files/' + readback_file if is_fault_injection == True: command = Operation.FAULT_INJECTION_READ.name + "%" + self.readbackFile + "%" + self.frame_address + "%" + self.frames else: command = Operation.FRAMES_READ.name + "%" + self.readbackFile + "%" + self.frame_address + "%" + self.frames self.clientSend(command) self.operation = Operation.FRAMES_READ return #--------------------------------------------------------------------------------- # @brief This function reads the status signal of the user logic #--------------------------------------------------------------------------------- def readLogicStatus(self): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Reading active logic status ================ ", XtclCommon.blue) self.clientSend(Operation.LOGIC_STATUS.name) self.operation = Operation.LOGIC_STATUS return #--------------------------------------------------------------------------------- # @brief This function reads a configuration register # @param register_address: The frame of the register in 5-bit format (i.e. 01010) of in HEX format (0A). #--------------------------------------------------------------------------------- def readRegister(self, register_address): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Reading configuration register ================ ", XtclCommon.blue) self.register_address = str(register_address) command = Operation.REGISTER_READ.name + "%" + self.register_address self.clientSend(command) self.operation = Operation.REGISTER_READ return #--------------------------------------------------------------------------------- # @brief This function writes a configuration register # @param register_address: The frame of the register in 5-bit format (i.e. 01010) of in HEX format (0A). # @param register_value: The register value to be written in 32-bit HEX format (i.e. A000029B) #--------------------------------------------------------------------------------- def writeRegister(self, register_address, register_value): if self.tcpClientSocketStatus != SocketStatus.CONNECTED: self.clientStart() XtclLog.writeLine( "================ Writing configuration register ================ ", XtclCommon.blue) self.register_address = str(register_address) self.register_value = str(register_value) command = Operation.REGISTER_WRITE.name + "%" + self.register_address + "%" + self.register_value self.clientSend(command) self.operation = Operation.REGISTER_WRITE return
class SerialControl(QObject): sig_keyseq_pressed = Signal(str) sig_CPU_comout = Signal(int) sig_CPU_comin = Signal(str) sig_port_change = Signal(str) sig_button_pressed = Signal(int) sig_terminal_open = Signal(bool) sig_firmware_update = Signal(str) def __init__(self, cpu, monitor_frame, terminal_frame, usb_frame, statusbar, config, sig_update, config_file_path): QObject.__init__(self) self.cpu = cpu self.monitor = monitor_frame self.terminal = terminal_frame self.statusbar = statusbar self.config = config self.ser_port = None self.sig_update = sig_update self.fd_thread = None self.fwth = None self.monitor_frame = monitor_frame self.usb_frame = usb_frame self.config_file_path = config_file_path # Connect signal self.sig_keyseq_pressed.connect(self.on_key_pressed) self.sig_CPU_comout.connect(self.on_comout) self.sig_CPU_comin.connect(self.on_comin) self.sig_port_change.connect(self.on_port_change) self.sig_button_pressed.connect(self.on_button_dispatch) self.sig_terminal_open.connect(self.on_terminal_open) self.sig_firmware_update.connect(self.on_firmware_update) self.monitor_frame.sig_keyseq_pressed = self.sig_keyseq_pressed self.monitor_frame.sig_button_pressed = self.sig_button_pressed self.cpu.sig_CPU_comout = self.sig_CPU_comout self.cpu.sig_CPU_comin = self.sig_CPU_comin self.usb_frame.usb_combo.sig_port_change = self.sig_port_change self.usb_frame.sig_button_pressed = self.sig_button_pressed self.usb_frame.sig_firmware_update = self.sig_firmware_update self.terminal.sig_terminal_open = self.sig_terminal_open # Disable fw flash button if udr2 binary is nor present self.udr2 = f"cli/udr2-{sys.platform}" if sys.platform != "win32" else "cli\\udr2-win32.exe" if not os.path.isfile(self.udr2): self.usb_frame.firmware_btn.setEnabled(False) self.init_serial() def init_serial(self, do_refresh=True): is_thread = InitSerialThread(self) is_thread.update_combo = self.usb_frame.usb_combo.set_ports is_thread.do_refresh = do_refresh is_thread.start() def init_OK(self): self.statusbar.sig_temp_message.emit("Serial port Initialized") # # Signal Handling # @Slot(str) def on_key_pressed(self, key): if self.cpu.rx is None and len(key) == 1: self.cpu.rx = key self.monitor_frame.serial_in.setText(key) @Slot(str) def on_comin(self, char): """Char is handled by CPU, we free the slot""" self.monitor_frame.serial_in.setText(" ") @Slot(int) def on_comout(self, byte): """Append the char to the console""" self.monitor_frame.append_serial_out(byte) @Slot(str) def on_port_change(self, port): self.config.set("serial", "port", port) if self.ser_port: self.ser_port.port = port with open(self.config_file_path, 'w') as configfile: self.config.write(configfile) self.init_serial(False) @Slot(int) def on_button_dispatch(self, btn_nbr): if btn_nbr == 0: self.to_digirule() elif btn_nbr == 1: self.from_digirule() elif btn_nbr == 2: self.init_serial() elif btn_nbr == 3: self.on_clear_button() @Slot(bool) def on_terminal_open(self, is_open): if is_open: # Terminal window is open : create the terminal serial thread self.statusbar.sig_temp_message.emit("open terminal thread") self.terminal.serth = SerialThread(self) # Start serial thread self.terminal.serth.start() else: # Terminal window is closed : quit the terminal serial thread if self.terminal.serth: self.terminal.serth.running = False sleep(0.5) self.terminal.serth = None def on_clear_button(self): self.monitor_frame.clear() # Clear the serial in/out content self.cpu.rx = None self.cpu.tx = None def to_digirule(self): if self.ser_port is None: self.statusbar.sig_temp_message.emit( "Error : No serial port configured") else: dump = ram2hex(self.cpu.ram) self.statusbar.sig_temp_message.emit("Dumpimg memory on port " + self.ser_port.port) try: self.ser_port.open() except serial.serialutil.SerialException as ex: self.statusbar.sig_temp_message.emit(ex.strerror) else: for line in dump.splitlines(): self.ser_port.write(line.encode("utf-8")) sleep(0.1) sleep(2) self.ser_port.close() self.statusbar.sig_temp_message.emit("Memory sent") def from_digirule(self): """Launch receive sequence in background""" if self.ser_port: self.statusbar.sig_temp_message.emit( "Waiting to receive Digirule on " + self.ser_port.port) self.fd_thread = FromDigiruleThread(self) self.fd_thread.start() else: self.init_serial() @Slot(str) def on_firmware_update(self, filepath): if self.ser_port: self.proc = QProcess(self) self.proc.readyReadStandardOutput.connect(self.stdoutReady) self.proc.readyReadStandardError.connect(self.stderrReady) if sys.platform == "win32": command = f'{self.udr2} --program {self.ser_port.port} < {filepath}' self.usb_frame.out.write( "Firmware update started, please wait ") # displays running dots on windows to pretend it is not stalled self.bullshitTimer = QTimer() self.bullshitTimer.timeout.connect(self.stdoutBullshit) self.bullshitTimer.start(1000) self.proc.setProcessChannelMode(QProcess.MergedChannels) self.proc.start('cmd.exe', ['/c', command]) else: command = f'{self.udr2} --program {self.ser_port.port} < "{filepath}"' self.bullshitTimer = None self.proc.start('bash', ['-c', command]) # print(command) def stdoutBullshit(self): self.usb_frame.out.write(".") def stdoutReady(self): if self.bullshitTimer: self.usb_frame.out.write("\n") self.bullshitTimer.stop() text = str(self.proc.readAllStandardOutput()) self.usb_frame.out.write(eval(text).decode('iso8859-1')) def stderrReady(self): text = str(self.proc.readAllStandardError()) self.usb_frame.out.write(eval(text).decode('iso8859-1'))
class MediaVideo(Media): def __init__(self, media, parent_widget): super(MediaVideo, self).__init__(media, parent_widget) self.widget = QWidget(parent_widget) self.process = QProcess(self.widget) self.process.setObjectName('%s-process' % self.objectName()) self.std_out = [] self.errors = [] self.stopping = False self.mute = False self.widget.setGeometry(media['geometry']) self.connect(self.process, SIGNAL('error()'), self.process_error) self.connect(self.process, SIGNAL('finished()'), self.process_finished) self.connect(self.process, SIGNAL('started()'), self.process_started) self.set_default_widget_prop() self.stop_timer = QTimer(self) self.stop_timer.setSingleShot(True) self.stop_timer.setInterval(1000) self.stop_timer.timeout.connect(self.process_timeout) #---- kong ---- for RPi self.rect = media['geometry'] #---- @Slot() def process_timeout(self): os.kill(self.process.pid(), signal.SIGTERM) self.stopping = False if not self.is_started(): self.started_signal.emit() super(MediaVideo, self).stop() @Slot(object) def process_error(self, err): print('---- process error ----') self.errors.append(err) self.stop() @Slot() def process_finished(self): self.stop() @Slot() def process_started(self): self.stop_timer.stop() if float(self.duration) > 0: self.play_timer.setInterval(int(float(self.duration) * 1000)) self.play_timer.start() self.started_signal.emit() pass @Slot() def play(self): self.finished = 0 self.widget.show() self.widget.raise_() uri = self.options['uri'] path = f'content/{uri}' #---- kong ---- for RPi left, top, right, bottom = self.rect.getCoords() rect = f'{left},{top},{right},{bottom}' args = [ '--win', rect, '--no-osd', '--layer', self.zindex, path ] self.process.start('omxplayer.bin', args) self.stop_timer.start() #---- @Slot() def stop(self, delete_widget=False): #---- kong ---- for RPi if not self.widget: return False if self.stopping or self.is_finished(): return False self.stop_timer.start() self.stopping = True if self.process.state() == QProcess.ProcessState.Running: self.process.write(b'q') self.process.waitForFinished() self.process.close() super(MediaVideo, self).stop(delete_widget) self.stopping = False self.stop_timer.stop() return True
class MediaText(Media): def __init__(self, media, parent_widget): super(MediaText, self).__init__(media, parent_widget) self.widget = QWidget(parent_widget) self.process = QProcess(self.widget) self.process.setObjectName('%s-process' % self.objectName()) self.std_out = [] self.errors = [] self.stopping = False self.mute = False self.widget.setGeometry(media['geometry']) self.connect(self.process, SIGNAL('error()'), self.process_error) self.connect(self.process, SIGNAL('finished()'), self.process_finished) self.connect(self.process, SIGNAL('started()'), self.process_started) self.set_default_widget_prop() self.stop_timer = QTimer(self) self.stop_timer.setSingleShot(True) self.stop_timer.setInterval(1000) self.stop_timer.timeout.connect(self.process_timeout) self.rect = self.widget.geometry() @Slot() def process_timeout(self): os.kill(self.process.pid(), signal.SIGTERM) self.stopping = False if not self.is_started(): self.started_signal.emit() super(MediaText, self).stop() @Slot(object) def process_error(self, err): print('---- process error ----') self.errors.append(err) self.stop() @Slot() def process_finished(self): self.stop() @Slot() def process_started(self): self.stop_timer.stop() if float(self.duration) > 0: self.play_timer.setInterval(int(float(self.duration) * 1000)) self.play_timer.start() self.started_signal.emit() pass @Slot() def play(self): self.finished = 0 self.widget.show() self.widget.raise_() #---- kong ---- path = f'file:///home/pi/rdtone/urd/content/{self.layout_id}_{self.region_id}_{self.id}.html' print(path) l = str(self.rect.left()) t = str(self.rect.top()) w = str(self.rect.width()) h = str(self.rect.height()) s = f'--window-size={w},{h}' p = f'--window-position={l},{t}' args = [ '--kiosk', s, p, path #l, t, w, h, path ] self.process.start('chromium-browser', args) #self.process.start('./xWeb', args) self.stop_timer.start() #---- @Slot() def stop(self, delete_widget=False): #---- kong ---- if not self.widget: return False if self.stopping or self.is_finished(): return False self.stop_timer.start() self.stopping = True if self.process.state() == QProcess.ProcessState.Running: #---- kill process ---- os.system('pkill chromium') #os.system('pkill xWeb') #---- self.process.waitForFinished() self.process.close() super(MediaText, self).stop(delete_widget) self.stopping = False self.stop_timer.stop() return True
class App(QApplication): def __init__(self, argv): super().__init__(argv) self._create_tray_icon() self._create_ui() self._create_interaction_server() self._session = None def open_preferences(self): prefs_dialog = PreferencesDialog() prefs_dialog.exec() def _mode_changed(self): action = self._mode_group.checkedAction() if action == self._mode_off: self._stop_session() elif action == self._mode_enabled: self._interaction_server.train = False self._start_session() elif action == self._mode_training: self._interaction_server.train = True self._start_session() def _start_session(self): if self._session is not None: return self._session = QProcess(self) self._session.finished.connect(self._session_ended) self._session.readyReadStandardOutput.connect(self._log_append_stdout) self._session.readyReadStandardError.connect(self._log_append_stderr) settings = QSettings() self._session.start(sys.executable, [ 'run_session.py', settings.value('CyKitAddress', app.DEFAULT_CYKIT_ADDRESS), str(settings.value('CyKitPort', app.DEFAULT_CYKIT_PORT)), str(self._interaction_server.port) ]) def _stop_session(self): if self._session is not None: self._session.close() # TODO: Handle non-null exit codes def _session_ended(self): self._session = None self._mode_off.setChecked(True) def _log_append_stdout(self): process = self.sender() self._log_window.moveCursor(QTextCursor.End) self._log_window.insertPlainText( process.readAllStandardOutput().data().decode('utf-8')) self._log_window.moveCursor(QTextCursor.End) def _log_append_stderr(self): process = self.sender() self._log_window.moveCursor(QTextCursor.End) self._log_window.insertPlainText( process.readAllStandardError().data().decode('utf-8')) self._log_window.moveCursor(QTextCursor.End) def _select_letter(self, letter): self._letter_ui.setText(letter) def _create_tray_icon(self): menu = QMenu() self._mode_group = QActionGroup(menu) self._mode_group.triggered.connect(self._mode_changed) self._mode_off = QAction("&Off", parent=menu) self._mode_off.setCheckable(True) self._mode_off.setChecked(True) self._mode_group.addAction(self._mode_off) menu.addAction(self._mode_off) self._mode_enabled = QAction("&Enabled", parent=menu) self._mode_enabled.setCheckable(True) self._mode_group.addAction(self._mode_enabled) menu.addAction(self._mode_enabled) self._mode_training = QAction("&Training mode", parent=menu) self._mode_training.setCheckable(True) self._mode_group.addAction(self._mode_training) menu.addAction(self._mode_training) menu.addSeparator() menu.addAction("&Preferences", self.open_preferences) menu.addSeparator() menu.addAction("E&xit", self.exit) pixmap = QPixmap(32, 32) pixmap.fill(Qt.white) icon = QIcon(pixmap) self._tray_icon = QSystemTrayIcon(parent=self) self._tray_icon.setContextMenu(menu) self._tray_icon.setIcon(icon) self._tray_icon.show() def _create_ui(self): self._keyboard_ui = KeyboardUI() self._keyboard_ui.show() # TODO: Get rid of this in favor of os_interaction self._letter_ui = QLabel("-") self._letter_ui.setWindowTitle("Selected letter") self._letter_ui.setStyleSheet('font-size: 72pt') self._letter_ui.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._letter_ui.setGeometry(600, 0, 100, 100) self._letter_ui.show() # TODO: Replace with more user-friendly log self._log_window = QTextBrowser() self._log_window.setWindowTitle("Session Log") self._log_window.setGeometry(700, 0, 500, 500) self._log_window.show() def _create_interaction_server(self): self._interaction_server = InteractionServer(self) self._interaction_server.keyboard_flash_row.connect( self._keyboard_ui.flash_row) self._interaction_server.keyboard_flash_col.connect( self._keyboard_ui.flash_col) self._interaction_server.keyboard_highlight_letter.connect( self._keyboard_ui.highlight_letter) self._interaction_server.keyboard_select_letter.connect( self._select_letter)
class QProcessExecutionManager(ExecutionManager): """Class to manage tool instance execution using a PySide2 QProcess.""" def __init__(self, logger, program="", args=None, silent=False, semisilent=False): """Class constructor. Args: logger (LoggerInterface): a logger instance program (str): Path to program to run in the subprocess (e.g. julia.exe) args (list, optional): List of argument for the program (e.g. path to script file) silent (bool): Whether or not to emit logger msg signals semisilent (bool): If True, show Process Log messages """ super().__init__(logger) self._program = program self._args = args if args is not None else [] self._silent = silent # Do not show Event Log nor Process Log messages self._semisilent = semisilent # Do not show Event Log messages but show Process Log messages self.process_failed = False self.process_failed_to_start = False self.user_stopped = False self._process = QProcess(self) self.process_output = None # stdout when running silent self.process_error = None # stderr when running silent self._out_chunks = [] self._err_chunks = [] def program(self): """Program getter method.""" return self._program def args(self): """Program argument getter method.""" return self._args # noinspection PyUnresolvedReferences def start_execution(self, workdir=None): """Starts the execution of a command in a QProcess. Args: workdir (str, optional): Work directory """ if workdir is not None: self._process.setWorkingDirectory(workdir) self._process.started.connect(self.process_started) self._process.finished.connect(self.on_process_finished) if not self._silent and not self._semisilent: # Loud self._process.readyReadStandardOutput.connect(self.on_ready_stdout) self._process.readyReadStandardError.connect(self.on_ready_stderr) self._process.errorOccurred.connect(self.on_process_error) self._process.stateChanged.connect(self.on_state_changed) elif self._semisilent: # semi-silent self._process.readyReadStandardOutput.connect(self.on_ready_stdout) self._process.readyReadStandardError.connect(self.on_ready_stderr) self._process.start(self._program, self._args) if self._process is not None and not self._process.waitForStarted(msecs=10000): self.process_failed = True self.process_failed_to_start = True self._process.deleteLater() self._process = None self.execution_finished.emit(-9998) def wait_for_process_finished(self, msecs=30000): """Wait for subprocess to finish. Args: msecs (int): Timeout in milliseconds Return: True if process finished successfully, False otherwise """ if self._process is None: return False if self.process_failed or self.process_failed_to_start: return False if not self._process.waitForFinished(msecs): self.process_failed = True self._process.close() self._process = None return False return True @Slot() def process_started(self): """Run when subprocess has started.""" @Slot(int) def on_state_changed(self, new_state): """Runs when QProcess state changes. Args: new_state (int): Process state number (``QProcess::ProcessState``) """ if new_state == QProcess.Starting: self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program)) arg_str = " ".join(self._args) self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str)) elif new_state == QProcess.Running: self._logger.msg_warning.emit("\tExecution in progress...") elif new_state == QProcess.NotRunning: # logging.debug("Process is not running") pass else: self._logger.msg_error.emit("Process is in an unspecified state") logging.error("QProcess unspecified state: %s", new_state) @Slot(int) def on_process_error(self, process_error): """Runs if there is an error in the running QProcess. Args: process_error (int): Process error number (``QProcess::ProcessError``) """ if process_error == QProcess.FailedToStart: self.process_failed = True self.process_failed_to_start = True self._logger.msg_error.emit("Process failed to start") elif process_error == QProcess.Timedout: self.process_failed = True self._logger.msg_error.emit("Timed out") elif process_error == QProcess.Crashed: self.process_failed = True if not self.user_stopped: self._logger.msg_error.emit("Process crashed") elif process_error == QProcess.WriteError: self._logger.msg_error.emit("Process WriteError") elif process_error == QProcess.ReadError: self._logger.msg_error.emit("Process ReadError") elif process_error == QProcess.UnknownError: self._logger.msg_error.emit("Unknown error in process") else: self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error)) self.teardown_process() def teardown_process(self): """Tears down the QProcess in case a QProcess.ProcessError occurred. Emits execution_finished signal.""" if not self._process: pass else: out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace") errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace") if out is not None: self._logger.msg_proc.emit(out.strip()) if errout is not None: self._logger.msg_proc.emit(errout.strip()) self._process.deleteLater() self._process = None self.execution_finished.emit(-9998) def stop_execution(self): """See base class.""" self.user_stopped = True self.process_failed = True if not self._process: return try: self._process.kill() if not self._process.waitForFinished(5000): self._process.finished.emit(-1, -1) self._process.deleteLater() except Exception as ex: # pylint: disable=broad-except self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex)) logging.exception("Exception in closing QProcess: %s", ex) finally: self._process = None @Slot(int, int) def on_process_finished(self, exit_code, exit_status): """Runs when subprocess has finished. Args: exit_code (int): Return code from external program (only valid for normal exits) exit_status (int): Crash or normal exit (``QProcess::ExitStatus``) """ if not self._process: return if exit_status == QProcess.CrashExit: if not self._silent: self._logger.msg_error.emit("\tProcess crashed") exit_code = -1 elif exit_status == QProcess.NormalExit: pass else: if not self._silent: self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status)) exit_code = -1 if not exit_code == 0: self.process_failed = True if not self.user_stopped: out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace") errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace") if out is not None: if not self._silent: self._logger.msg_proc.emit(out.strip()) else: self.process_output = out.strip() self.process_error = errout.strip() else: self._logger.msg.emit("*** Terminating process ***") # Delete QProcess self._process.deleteLater() self._process = None self.execution_finished.emit(exit_code) @Slot() def on_ready_stdout(self): """Emit data from stdout.""" if not self._process: return self._process.setReadChannel(QProcess.StandardOutput) chunk = self._process.readLine().data() self._out_chunks.append(chunk) if not chunk.endswith(b"\n"): return line = b"".join(self._out_chunks) line = str(line, "unicode_escape", errors="replace").strip() self._logger.msg_proc.emit(line) self._out_chunks.clear() @Slot() def on_ready_stderr(self): """Emit data from stderr.""" if not self._process: return self._process.setReadChannel(QProcess.StandardError) chunk = self._process.readLine().data() self._err_chunks.append(chunk) if not chunk.endswith(b"\n"): return line = b"".join(self._err_chunks) line = str(line, "utf-8", errors="replace").strip() self._logger.msg_proc_error.emit(line) self._err_chunks.clear()
class QSubProcess(QObject): """Class to handle starting, running, and finishing PySide2 QProcesses.""" subprocess_finished_signal = Signal(int, name="subprocess_finished_signal") def __init__(self, toolbox, program=None, args=None, silent=False): """Class constructor. Args: toolbox (ToolboxUI): Instance of Main UI class. program (str): Path to program to run in the subprocess (e.g. julia.exe) args (list): List of argument for the program (e.g. path to script file) silent (bool): Whether or not to emit toolbox msg signals """ super().__init__() self._toolbox = toolbox self._program = program self._args = args self._silent = silent self.process_failed = False self.process_failed_to_start = False self._user_stopped = False self._process = QProcess(self) self.output = None def program(self): """Program getter method.""" return self._program def args(self): """Program argument getter method.""" return self._args # noinspection PyUnresolvedReferences def start_process(self, workdir=None): """Start the execution of a command in a QProcess. Args: workdir (str): Directory for the script (at least with Julia this is a must) """ if workdir is not None: self._process.setWorkingDirectory(workdir) self._process.started.connect(self.process_started) self._process.finished.connect(self.process_finished) if not self._silent: self._process.readyReadStandardOutput.connect(self.on_ready_stdout) self._process.readyReadStandardError.connect(self.on_ready_stderr) self._process.error.connect( self.on_process_error) # errorOccurred available in Qt 5.6 self._process.stateChanged.connect(self.on_state_changed) # self._toolbox.msg.emit("\tStarting program: <b>{0}</b>".format(self._program)) self._process.start(self._program, self._args) if not self._process.waitForStarted( msecs=10000 ): # This blocks until process starts or timeout happens self.process_failed = True self.process_failed_to_start = True self._process.deleteLater() self._process = None self.subprocess_finished_signal.emit(-9998) def wait_for_finished(self, msecs=30000): """Wait for subprocess to finish. Return: True if process finished successfully, False otherwise """ if not self._process: return False if self.process_failed or self.process_failed_to_start: return False if not self._process.waitForFinished(msecs): self.process_failed = True self._process.close() self._process = None return False return True @Slot(name="process_started") def process_started(self): """Run when subprocess has started.""" pass # self._toolbox.msg.emit("\tSubprocess started...") @Slot("QProcess::ProcessState", name="on_state_changed") def on_state_changed(self, new_state): """Runs when QProcess state changes. Args: new_state (QProcess::ProcessState): Process state number """ if new_state == QProcess.Starting: self._toolbox.msg.emit("\tStarting program <b>{0}</b>".format( self._program)) arg_str = " ".join(self._args) self._toolbox.msg.emit("\tArguments: <b>{0}</b>".format(arg_str)) elif new_state == QProcess.Running: self._toolbox.msg_warning.emit( "\tExecution is in progress. See Process Log for messages " "(stdout&stderr)") elif new_state == QProcess.NotRunning: # logging.debug("QProcess is not running") pass else: self._toolbox.msg_error.emit("Process is in an unspecified state") logging.error("QProcess unspecified state: {0}".format(new_state)) @Slot("QProcess::ProcessError", name="'on_process_error") def on_process_error(self, process_error): """Run if there is an error in the running QProcess. Args: process_error (QProcess::ProcessError): Process error number """ if process_error == QProcess.FailedToStart: # self._toolbox.msg_error.emit("Failed to start") self.process_failed = True self.process_failed_to_start = True elif process_error == QProcess.Timedout: self.process_failed = True self._toolbox.msg_error.emit("Timed out") elif process_error == QProcess.Crashed: self.process_failed = True self._toolbox.msg_error.emit("Process crashed") elif process_error == QProcess.WriteError: self._toolbox.msg_error.emit("Process WriteError") elif process_error == QProcess.ReadError: self._toolbox.msg_error.emit("Process ReadError") elif process_error == QProcess.UnknownError: self._toolbox.msg_error.emit("Unknown error in process") else: self._toolbox.msg_error.emit( "Unspecified error in process: {0}".format(process_error)) def terminate_process(self): """Shutdown simulation in a QProcess.""" self._toolbox.msg_error.emit("<br/>Terminating process") # logging.debug("Terminating QProcess nr.{0}. ProcessState:{1} and ProcessError:{2}" # .format(self._process.processId(), self._process.state(), self._process.error())) self._user_stopped = True self.process_failed = True try: self._process.close() except Exception as ex: self._toolbox.msg_error.emit( "[{0}] exception when terminating process".format(ex)) logging.exception("Exception in closing QProcess: {}".format(ex)) @Slot(int, name="process_finished") def process_finished(self, exit_code): """Run when subprocess has finished. Args: exit_code (int): Return code from external program (only valid for normal exits) """ # logging.debug("Error that occurred last: {0}".format(self._process.error())) exit_status = self._process.exitStatus() # Normal or crash exit if exit_status == QProcess.CrashExit: if not self._silent: self._toolbox.msg_error.emit("\tProcess crashed") exit_code = -1 elif exit_status == QProcess.NormalExit: if not self._silent: self._toolbox.msg.emit("\tProcess finished") else: if not self._silent: self._toolbox.msg_error.emit( "Unknown QProcess exit status [{0}]".format(exit_status)) exit_code = -1 if not exit_code == 0: self.process_failed = True if not self._user_stopped: out = str(self._process.readAllStandardOutput().data(), "utf-8") if out is not None: if not self._silent: self._toolbox.msg_proc.emit(out.strip()) else: self.output = out.strip() else: self._toolbox.msg.emit("*** Terminating process ***") # Delete QProcess self._process.deleteLater() self._process = None self.subprocess_finished_signal.emit(exit_code) @Slot(name="on_ready_stdout") def on_ready_stdout(self): """Emit data from stdout.""" out = str(self._process.readAllStandardOutput().data(), "utf-8") self._toolbox.msg_proc.emit(out.strip()) @Slot(name="on_ready_stderr") def on_ready_stderr(self): """Emit data from stderr.""" err = str(self._process.readAllStandardError().data(), "utf-8") self._toolbox.msg_proc_error.emit(err.strip())