def check_config(self): if IS_WIN32_ENV: log.info("using dummy config on win32") lnd_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(LND_CONF)) rb_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_CONF)) rb_info_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_INFO)) else: lnd_cfg_abs_path = LND_CONF rb_cfg_abs_path = RB_CONF rb_info_abs_path = RB_INFO # read config and info files if not os.path.exists(lnd_cfg_abs_path): log.warning("file does not exist: {}".format(lnd_cfg_abs_path)) if not os.path.exists(rb_cfg_abs_path): log.warning("file does not exist: {}".format(rb_cfg_abs_path)) if not os.path.exists(rb_info_abs_path): log.warning("file does not exist: {}".format(rb_info_abs_path)) log.debug("init lnd.conf") lnd_cfg_valid = False self.lnd_cfg = LndConfig(lnd_cfg_abs_path) try: self.lnd_cfg.reload() lnd_cfg_valid = True except Exception as err: pass log.debug("init raspiblitz.conf") rb_cfg_valid = False self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path) try: self.rb_cfg.reload() rb_cfg_valid = True except Exception as err: pass log.debug("init raspiblitz.info") rb_info_valid = False self.rb_info = RaspiBlitzInfo(rb_info_abs_path) try: self.rb_info.reload() rb_info_valid = True except Exception as err: pass self.cfg_valid = lnd_cfg_valid and rb_cfg_valid and rb_info_valid log.debug("checked cfg_valid with result: {}".format(self.cfg_valid))
class AppWindow(QMainWindow): def __init__(self, *args, **kwargs): super(AppWindow, self).__init__(*args, **kwargs) self.ui = Ui_MainWindow() self.ui.setupUi(self) # translations..?! self._translate = QCoreApplication.translate self.check_config() # initialize attributes self.invoice_to_check = None self.invoice_to_check_flag = None self.uptime = 0 self.status_lnd_due = 0 self.status_lnd_interval = STATUS_INTERVAL_LND self.status_lnd_pid_ok = False self.status_lnd_listen_ok = False self.status_lnd_unlocked = False self.status_lnd_synced_to_chain = False self.status_lnd_synced_to_graph = False self.status_lnd_channel_due = 0 self.status_lnd_channel_interval = STATUS_INTERVAL_LND_CHANNELS self.status_lnd_channel_total_active = 0 self.status_lnd_channel_total_remote_balance = 0 # initial updates self.update_uptime() if self.cfg_valid: self.update_status_lnd() self.update_status_lnd_channels() # initial update of Main Window Title Bar self.update_title_bar() # Align Main Window Top Left self.move(0, 0) # set as maximized (unless on Windows dev host) if IS_WIN32_ENV: log.info("not maximizing window on win32") else: self.setWindowState(Qt.WindowMaximized) # Bindings: buttons self.ui.pushButton_1.clicked.connect(self.on_button_1_clicked) self.ui.pushButton_2.clicked.connect(self.on_button_2_clicked) self.ui.pushButton_3.clicked.connect(self.on_button_3_clicked) self.ui.pushButton_4.clicked.connect(self.on_button_4_clicked) # disable button 1 for now self.ui.pushButton_1.setEnabled(False) # connect error dismiss button and hide for start self.ui.buttonBox_close.button(QDialogButtonBox.Close).setText("Ok") self.ui.buttonBox_close.button(QDialogButtonBox.Close).clicked.connect( self.hide_error) self.hide_error() # Show QR Code Dialog Windows self.w_qr_code = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint)) self.ui_qr_code = Ui_DialogShowQrCode() self.ui_qr_code.setupUi(self.w_qr_code) self.w_qr_code.move(0, 0) # SPINNER for CR Code Dialog Window self.ui_qr_code.spinner = WaitingSpinner(self.w_qr_code) self.beat_thread = BeatThread() self.beat_thread.signal.connect(self.process_beat) self.beat_thread.start() self.generate_qr_code_thread = GenerateQrCodeThread() self.generate_qr_code_thread.signal.connect( self.generate_qr_code_finished) self.file_watcher = FileWatcherThread( dir_names=[ os.path.dirname(LND_CONF), os.path.dirname(RB_CONF), os.path.dirname(RB_INFO) ], file_names=[ os.path.basename(LND_CONF), os.path.basename(RB_CONF), os.path.basename(RB_INFO) ], ) self.file_watcher.signal.connect(self.update_watched_attr) self.file_watcher.start() # finally start 00infoBlitz.sh in dedicated xterm frame self.start_info_lcd() self.show() def start_info_lcd(self, pause=12): # if system has been running for more than 180 seconds then skip pause if self.uptime > 180: pause = 0 process = QProcess(self) process.setProcessChannelMode(QProcess.MergedChannels) # connect the stdout_item to the Process StandardOutput # it gets constantly update as the process emit std output process.readyReadStandardOutput.connect(lambda: log.info( str(process.readAllStandardOutput().data().decode('utf-8')))) process.start('xterm', [ '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())), '+sb', '-hold', '-e', 'bash -c \"/home/admin/00infoLCD.sh --pause {}\"'.format(pause) ]) def check_config(self): if IS_WIN32_ENV: log.info("using dummy config on win32") lnd_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(LND_CONF)) rb_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_CONF)) rb_info_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_INFO)) else: lnd_cfg_abs_path = LND_CONF rb_cfg_abs_path = RB_CONF rb_info_abs_path = RB_INFO # read config and info files if not os.path.exists(lnd_cfg_abs_path): log.warning("file does not exist: {}".format(lnd_cfg_abs_path)) if not os.path.exists(rb_cfg_abs_path): log.warning("file does not exist: {}".format(rb_cfg_abs_path)) if not os.path.exists(rb_info_abs_path): log.warning("file does not exist: {}".format(rb_info_abs_path)) log.debug("init lnd.conf") lnd_cfg_valid = False self.lnd_cfg = LndConfig(lnd_cfg_abs_path) try: self.lnd_cfg.reload() lnd_cfg_valid = True except Exception as err: pass log.debug("init raspiblitz.conf") rb_cfg_valid = False self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path) try: self.rb_cfg.reload() rb_cfg_valid = True except Exception as err: pass log.debug("init raspiblitz.info") rb_info_valid = False self.rb_info = RaspiBlitzInfo(rb_info_abs_path) try: self.rb_info.reload() rb_info_valid = True except Exception as err: pass self.cfg_valid = lnd_cfg_valid and rb_cfg_valid and rb_info_valid log.debug("checked cfg_valid with result: {}".format(self.cfg_valid)) def check_invoice(self, flag, tick=0): log.info("checking invoice paid (Tick: {})".format(tick)) self.invoice_to_check_flag = flag if tick >= INVOICE_CHECK_TIMEOUT: log.debug("canceled checking invoice paid") flag.set() if IS_DEV_ENV: res = False amt_paid_sat = 123123402 if tick == 5: res = True else: with ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain) as stub_readonly: res, amt_paid_sat = check_invoice_paid(stub_readonly, self.invoice_to_check) log.debug("result of invoice check: {}".format(res)) if res: log.debug("paid!") self.ui_qr_code.qcode.setMargin(8) self.ui_qr_code.qcode.setPixmap( QPixmap(":/RaspiBlitz/images/Paid_Stamp.png")) if amt_paid_sat: self.ui_qr_code.status_value.setText("Paid") self.ui_qr_code.amt_paid_value.setText( "{}".format(amt_paid_sat)) else: self.ui_qr_code.status_value.setText("Paid") flag.set() def update_status_lnd(self): if IS_WIN32_ENV: return if self.status_lnd_due <= self.uptime: log.debug("updating status_lnd") try: with ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain) as stub_readonly: pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph = check_lnd( stub_readonly) self.status_lnd_pid_ok = pid_ok self.status_lnd_listen_ok = listen_ok self.status_lnd_unlocked = unlocked self.status_lnd_synced_to_chain = synced_to_chain self.status_lnd_synced_to_graph = synced_to_graph # set next due time self.status_lnd_due = self.uptime + self.status_lnd_interval except Exception as err: log.info("Exception on update_status_lnd") pass def update_status_lnd_channels(self): if IS_WIN32_ENV: return log.debug("update_status_lnd_channel due: {}".format( self.status_lnd_channel_due)) if self.status_lnd_channel_due <= self.uptime: log.debug("updating status_lnd_channels") try: with ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain) as stub_readonly: self.status_lnd_channel_total_active, self.status_lnd_channel_total_remote_balance = \ check_lnd_channels(stub_readonly) # set next due time self.status_lnd_channel_due = self.uptime + self.status_lnd_channel_interval except Exception as err: log.info("Exception on update_status_lnd_channels") pass def update_title_bar(self): log.debug("updating: Main Window Title Bar") self.setWindowTitle( self._translate( "MainWindow", "RaspiBlitz v{} - {} - {}net".format(self.rb_cfg.version, self.rb_cfg.network, self.rb_cfg.chain))) def update_uptime(self): if IS_WIN32_ENV: self.uptime += 1 else: with open('/proc/uptime', 'r') as f: self.uptime = float(f.readline().split()[0]) # log.info("Uptime: {}".format(self.uptime)) def process_beat(self, _): self.check_config() self.update_uptime() if self.cfg_valid: self.update_status_lnd() self.update_status_lnd_channels() def update_watched_attr(self): log.debug("updating: watched attributes") self.lnd_cfg.reload() self.rb_cfg.reload() self.rb_info.reload() # add anything here that should be updated now too self.update_title_bar() def hide_error(self): self.ui.error_label.hide() self.ui.buttonBox_close.hide() def show_qr_code(self, data, screen=None, memo=None, status=None, inv_amt=None, amt_paid="N/A"): log.debug("show_qr_code: {}".format(data)) # reset to logo and set text self.ui_qr_code.qcode.setMargin(48) self.ui_qr_code.qcode.setPixmap( QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png")) if screen == SCREEN_NODE_URI: self.ui_qr_code.memo_key.show() self.ui_qr_code.memo_key.setText("Node URI") _tmp = data.split("@") pub = _tmp[0] _tmp2 = _tmp[1].split(":") host = _tmp2[0] port = _tmp2[1] n = 16 pub = [(pub[i:i + n]) for i in range(0, len(pub), n)] host = [(host[i:i + n]) for i in range(0, len(host), n)] self.ui_qr_code.memo_value.show() self.ui_qr_code.memo_value.setText("{} \n@{} \n:{}".format( " ".join(pub), " ".join(host), port)) self.ui_qr_code.status_key.hide() self.ui_qr_code.status_value.hide() self.ui_qr_code.inv_amt_key.hide() self.ui_qr_code.inv_amt_value.hide() self.ui_qr_code.amt_paid_key.hide() self.ui_qr_code.amt_paid_value.hide() if screen == SCREEN_INVOICE: self.ui_qr_code.memo_key.show() self.ui_qr_code.memo_key.setText("Invoice Memo") self.ui_qr_code.memo_value.show() self.ui_qr_code.memo_value.setText(memo) self.ui_qr_code.status_key.show() self.ui_qr_code.status_value.show() self.ui_qr_code.status_value.setText(status) self.ui_qr_code.inv_amt_key.show() self.ui_qr_code.inv_amt_value.show() self.ui_qr_code.inv_amt_value.setText("{}".format(inv_amt)) self.ui_qr_code.amt_paid_key.show() self.ui_qr_code.amt_paid_value.show() self.ui_qr_code.amt_paid_value.setText("{}".format(amt_paid)) # set function and start thread self.generate_qr_code_thread.data = data self.generate_qr_code_thread.start() self.ui_qr_code.spinner.start() self.w_qr_code.activateWindow() self.w_qr_code.show() rsp = self.w_qr_code.exec_() if rsp == QDialog.Accepted: log.info("QR: pressed OK - canceling invoice check") if self.invoice_to_check_flag: self.invoice_to_check_flag.set() def generate_qr_code_finished(self, img): buf = BytesIO() img.save(buf, "PNG") qt_pixmap = QPixmap() qt_pixmap.loadFromData(buf.getvalue(), "PNG") self.ui_qr_code.spinner.stop() self.ui_qr_code.qcode.setMargin(2) self.ui_qr_code.qcode.setPixmap(qt_pixmap) def on_button_1_clicked(self): log.debug("clicked: B1: {}".format(self.winId())) # self.start_info_lcd(pause=0) def on_button_2_clicked(self): log.debug("clicked: B2: {}".format(self.winId())) if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok): log.warning("LND is not ready") self.ui.error_label.show() self.ui.error_label.setText("Err: LND is not ready!") self.ui.buttonBox_close.show() return if not self.status_lnd_unlocked: log.warning("LND is locked") self.ui.error_label.show() self.ui.error_label.setText("Err: LND is locked") self.ui.buttonBox_close.show() return data = self.get_node_uri() if data: self.show_qr_code(data, SCREEN_NODE_URI) else: log.warning("Node URI is none!") # TODO(frennkie) inform user def on_button_3_clicked(self): log.debug("clicked: B3: {}".format(self.winId())) if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok): log.warning("LND is not ready") self.ui.error_label.show() self.ui.error_label.setText("Err: LND is not ready!") self.ui.buttonBox_close.show() return if not self.status_lnd_unlocked: log.warning("LND is locked") self.ui.error_label.show() self.ui.error_label.setText("Err: LND is locked") self.ui.buttonBox_close.show() return if not self.status_lnd_channel_total_active: log.warning( "not creating invoice: unable to receive - no open channels") self.ui.error_label.show() self.ui.error_label.setText("Err: No open channels!") self.ui.buttonBox_close.show() return if not self.status_lnd_channel_total_remote_balance: log.warning( "not creating invoice: unable to receive - no remote capacity on any channel" ) self.ui.error_label.show() self.ui.error_label.setText("Err: No remote capacity!") self.ui.buttonBox_close.show() return dialog_b1 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint)) ui = Ui_DialogSelectInvoice() ui.setupUi(dialog_b1) dialog_b1.move(0, 0) ui.buttonBox.button(QDialogButtonBox.Yes).setText("{} SAT".format( self.rb_cfg.invoice_default_amount)) ui.buttonBox.button(QDialogButtonBox.Ok).setText("Donation") if self.rb_cfg.invoice_allow_donations: ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) else: ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel") ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect( self.b3_invoice_set_amt) ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect( self.b3_invoice_custom_amt) dialog_b1.show() rsp = dialog_b1.exec_() if not rsp == QDialog.Accepted: log.info("B3: pressed is: Cancel") def b3_invoice_set_amt(self): log.info("b1 option: set amount") check_invoice_thread = ClockStoppableThread( Event(), interval=INVOICE_CHECK_INTERVAL) check_invoice_thread.signal.connect(self.check_invoice) check_invoice_thread.start() a, n = adjective_noun_pair() inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize()) new_invoice = self.create_new_invoice( inv_memo, amt=self.rb_cfg.invoice_default_amount) data = new_invoice.payment_request self.show_qr_code(data, SCREEN_INVOICE, memo=inv_memo, status="Open", inv_amt=self.rb_cfg.invoice_default_amount) def b3_invoice_custom_amt(self): log.info("b1 option: custom amount") check_invoice_thread = ClockStoppableThread( Event(), interval=INVOICE_CHECK_INTERVAL) check_invoice_thread.signal.connect(self.check_invoice) check_invoice_thread.start() a, n = adjective_noun_pair() inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize()) new_invoice = self.create_new_invoice(inv_memo, amt=0) data = new_invoice.payment_request self.show_qr_code(data, SCREEN_INVOICE, memo=inv_memo, status="Open", inv_amt="Donation") def on_button_4_clicked(self): log.debug("clicked: B4: {}".format(self.winId())) dialog_b4 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint)) ui = Ui_DialogConfirmOff() ui.setupUi(dialog_b4) dialog_b4.move(0, 0) ui.buttonBox.button(QDialogButtonBox.Yes).setText("Shutdown") ui.buttonBox.button(QDialogButtonBox.Retry).setText("Restart") ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel") ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect( self.b4_shutdown) ui.buttonBox.button(QDialogButtonBox.Retry).clicked.connect( self.b4_restart) dialog_b4.show() rsp = dialog_b4.exec_() if rsp == QDialog.Accepted: log.info("B4: pressed is: Accepted - Shutdown or Restart") else: log.info("B4: pressed is: Cancel") def b4_shutdown(self): log.info("shutdown") if IS_WIN32_ENV: log.info("skipping on win32") return process = QProcess(self) process.start('xterm', [ '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())), '+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/XXshutdown.sh\"' ]) def b4_restart(self): log.info("restart") if IS_WIN32_ENV: log.info("skipping on win32") return process = QProcess(self) process.start('xterm', [ '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())), '+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/XXshutdown.sh reboot\"' ]) def create_new_invoice(self, memo="Pay to RaspiBlitz", amt=0): if IS_DEV_ENV: # Fake an invoice for dev class FakeAddInvoiceResponse(object): def __init__(self): self.add_index = 145 self.payment_request = "lnbc47110n1pwmfqcdpp5k55n5erv60mg6u4c8s3qggnw3dsn267e80ypjxxp6gj593" \ "p3c25sdq9vehk7cqzpgprn0ytv6ukxc2vclgag38nmsmlyggmd4zand9qay2l3gc5at" \ "ecxjynydyzhvxsysam9d46y5lgezh2nkufvn23403t3tz3lyhd070dgq625xp0" self.r_hash = b'\xf9\xe3(\xf5\x84\xdad\x88\xe4%\xa7\x1c\x95\xbe\x8baJ\x1c\xc1\xad*\xed\xc8' \ b'\x158\x13\xdf\xffF\x9c\x95\x84' new_invoice = FakeAddInvoiceResponse() else: with InvoiceStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain) as stub_invoice: new_invoice = create_invoice(stub_invoice, memo, amt) log.info("#{}: {}".format(new_invoice.add_index, new_invoice.payment_request)) invoice_r_hash_hex_str = convert_r_hash_hex_bytes(new_invoice.r_hash) self.invoice_to_check = invoice_r_hash_hex_str log.info("noting down for checking: {}".format(invoice_r_hash_hex_str)) return new_invoice def get_node_uri(self): if IS_DEV_ENV: return "535f209faaea75427949e3e6c1fc9edafbf751f08706506bb873fdc93ffc2d4e2c@pqcjuc47eqcv6mk2.onion:9735" with ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain) as stub_readonly: res = get_node_uri(stub_readonly) log.info("Node URI: {}".format(res)) return res
def __init__(self, *args, **kwargs): super(AppWindow, self).__init__(*args, **kwargs) self.ui = Ui_MainWindow() self.ui.setupUi(self) # translations..?! self._translate = QCoreApplication.translate if IS_WIN32_ENV: log.info("using dummy config on win32") lnd_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(LND_CONF)) rb_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_CONF)) rb_info_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_INFO)) else: lnd_cfg_abs_path = LND_CONF rb_cfg_abs_path = RB_CONF rb_info_abs_path = RB_INFO # read config and info files if not os.path.exists(lnd_cfg_abs_path): raise Exception("file does not exist: {}".format(lnd_cfg_abs_path)) if not os.path.exists(rb_cfg_abs_path): raise Exception("file does not exist: {}".format(rb_cfg_abs_path)) if not os.path.exists(rb_info_abs_path): raise Exception("file does not exist: {}".format(rb_info_abs_path)) self.lnd_cfg = LndConfig(lnd_cfg_abs_path) self.lnd_cfg.reload() self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path) self.rb_cfg.reload() self.rb_info = RaspiBlitzInfo(rb_info_abs_path) self.rb_info.reload() # initialize attributes self.invoice_to_check = None self.invoice_to_check_flag = None self.uptime = 0 self.status_lnd_due = 0 self.status_lnd_interval = STATUS_INTERVAL_LND self.status_lnd_pid_ok = False self.status_lnd_listen_ok = False self.status_lnd_unlocked = False self.status_lnd_synced_to_chain = False self.status_lnd_synced_to_graph = False self.status_lnd_channel_due = 0 self.status_lnd_channel_interval = STATUS_INTERVAL_LND_CHANNELS self.status_lnd_channel_total_active = 0 self.status_lnd_channel_total_remote_balance = 0 # initial updates self.update_uptime() self.update_status_lnd() self.update_status_lnd_channels() # initial update of Main Window Title Bar self.update_title_bar() # Align Main Window Top Left self.move(0, 0) # set as maximized (unless on Windows dev host) if IS_WIN32_ENV: log.info("not maximizing window on win32") else: self.setWindowState(Qt.WindowMaximized) # Bindings: buttons self.ui.pushButton_1.clicked.connect(self.on_button_1_clicked) self.ui.pushButton_2.clicked.connect(self.on_button_2_clicked) self.ui.pushButton_3.clicked.connect(self.on_button_3_clicked) self.ui.pushButton_4.clicked.connect(self.on_button_4_clicked) # disable button 1 for now self.ui.pushButton_1.setEnabled(False) # connect error dismiss button and hide for start self.ui.buttonBox_close.button(QDialogButtonBox.Close).setText("Ok") self.ui.buttonBox_close.button(QDialogButtonBox.Close).clicked.connect(self.hide_error) self.hide_error() # Show QR Code Dialog Windows self.w_qr_code = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint)) self.ui_qr_code = Ui_DialogShowQrCode() self.ui_qr_code.setupUi(self.w_qr_code) self.w_qr_code.move(0, 0) # SPINNER for CR Code Dialog Window self.ui_qr_code.spinner = WaitingSpinner(self.w_qr_code) self.beat_thread = BeatThread() self.beat_thread.signal.connect(self.process_beat) self.beat_thread.start() self.generate_qr_code_thread = GenerateQrCodeThread() self.generate_qr_code_thread.signal.connect(self.generate_qr_code_finished) self.file_watcher = FileWatcherThread( dir_names=[os.path.dirname(LND_CONF), os.path.dirname(RB_CONF), os.path.dirname(RB_INFO)], file_names=[os.path.basename(LND_CONF), os.path.basename(RB_CONF), os.path.basename(RB_INFO)], ) self.file_watcher.signal.connect(self.update_watched_attr) self.file_watcher.start() # finally start 00infoBlitz.sh in dedicated xterm frame self.start_info_lcd() self.show()