Ejemplo n.º 1
0
    def write(self, file_name, storage_device, mesh_data):
        Logger.log("i", "In X3gWriter.write")
        if "x3g" in file_name:
            scene = Application.getInstance().getController().getScene()
            gcode_list = getattr(scene, "gcode_list")
            if gcode_list:
                # f = storage_device.openFile(file_name, "wt")
                Logger.log("i", "Writing X3g to file %s", file_name)
                p = QProcess()
                p.setReadChannel(1)
                p.setStandardOutputFile(file_name)
                p.start("gpx", ["-i"])
                p.waitForStarted()

                for gcode in gcode_list:
                    p.write(gcode)
                    if p.canReadLine():
                        Logger.log("i", "gpx: %s", p.readLine().data().decode("utf-8"))

                p.closeWriteChannel()
                if p.waitForFinished():
                    Logger.log("i", "gpx: %s", p.readAll().data().decode("utf-8"))
                p.close()
                # storage_device.closeFile(f)
                return True

        return False
Ejemplo n.º 2
0
    def write(self, file_name, storage_device, mesh_data):
        Logger.log("i", "In X3gWriter.write")
        if "x3g" in file_name:
            scene = Application.getInstance().getController().getScene()
            gcode_list = getattr(scene, "gcode_list")
            if gcode_list:
                # f = storage_device.openFile(file_name, "wt")
                Logger.log("i", "Writing X3g to file %s", file_name)
                p = QProcess()
                p.setReadChannel(1)
                p.setStandardOutputFile(file_name)
                p.start("gpx", ["-i"])
                p.waitForStarted()

                for gcode in gcode_list:
                    p.write(gcode)
                    if (p.canReadLine()):
                        Logger.log("i", "gpx: %s", p.readLine().data().decode('utf-8'))

                p.closeWriteChannel()
                if p.waitForFinished():
                    Logger.log("i", "gpx: %s", p.readAll().data().decode('utf-8'))
                p.close()
                # storage_device.closeFile(f)
                return True

        return False
Ejemplo n.º 3
0
 def open_file_in_editor(self):
     editor = settings.value('editor/editor_path')
     if not editor:
         QMessageBox.critical(self, '', 'You need to set the editor to use that feature!')
     process = QProcess()
     process.start('%s %s' % (editor, self.document_viewer.get_current_file()))
     process.waitForFinished(-1);
     process.close()
Ejemplo n.º 4
0
class LinuxRecorder(QWidget):

    keystroke = pyqtSignal(object)
    stopped = pyqtSignal()

    def __init__(self):
        super().__init__()

        self.process = QProcess()
        self.process.readyReadStandardOutput.connect(self.on_output)

        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint
                            | QtCore.Qt.X11BypassWindowManagerHint)

        layout = QVBoxLayout()
        btn = QPushButton(tr("MacroRecorder", "Stop recording"))
        btn.clicked.connect(self.on_stop)
        layout.addWidget(btn)

        self.setLayout(layout)

    def start(self):
        self.show()

        center = QApplication.desktop().availableGeometry(self).center()
        self.move(center.x() - self.width() * 0.5, 0)

        args = [sys.executable]
        if os.getenv("APPIMAGE"):
            args = [os.getenv("APPIMAGE")]
        elif is_frozen():
            args += sys.argv[1:]
        else:
            args += sys.argv
        args += ["--linux-recorder"]

        self.process.start("pkexec", args,
                           QProcess.Unbuffered | QProcess.ReadWrite)

    def on_stop(self):
        self.stop()

    def stop(self):
        self.process.write(b"q")
        self.process.waitForFinished()
        self.process.close()
        self.hide()
        self.stopped.emit()

    def on_output(self):
        if self.process.canReadLine():
            line = bytes(self.process.readLine()).decode("utf-8")
            action, key = line.strip().split(":")
            code = Keycode.find_by_recorder_alias(key)
            if code is not None:
                action2cls = {"down": KeyDown, "up": KeyUp}
                self.keystroke.emit(action2cls[action](code))
Ejemplo n.º 5
0
class _Converter:
    """_Converter class to provide conversion functionality."""

    def __init__(self, library_path):
        """Class initializer."""
        self._library_path = library_path
        self._process = QProcess()

    def setup_converter(self, reader, finisher, process_channel):
        """Set up the QProcess object."""
        self._process.setProcessChannelMode(process_channel)
        self._process.readyRead.connect(reader)
        self._process.finished.connect(finisher)

    def start_converter(self, cmd):
        """Start the encoding process."""
        self._process.start(self._library_path, cmd)

    def stop_converter(self):
        """Terminate the encoding process."""
        self._process.terminate()
        if self.converter_is_running:
            self._process.kill()

    def converter_finished_disconnect(self, connected):
        """Disconnect the QProcess.finished method."""
        self._process.finished.disconnect(connected)

    def close_converter(self):
        """Call QProcess.close method."""
        self._process.close()

    def kill_converter(self):
        """Call QProcess.kill method."""
        self._process.kill()

    def converter_state(self):
        """Call QProcess.state method."""
        return self._process.state()

    def converter_exit_status(self):
        """Call QProcess.exit_status method."""
        return self._process.exitStatus()

    def read_converter_output(self):
        """Call QProcess.readAll method."""
        return str(self._process.readAll())

    @property
    def converter_is_running(self):
        """Return QProcess state."""
        return self._process.state() == QProcess.Running
Ejemplo n.º 6
0
    def connect_speaker_device(self, address):
        if platform.system() == "Darwin":
            print("Pairing...")
            process = QProcess()
            process.setProcessChannelMode(QProcess.SeparateChannels)
            process.start(f"blueutil --pair {address}")
            process.waitForFinished()
            print("Connecting...")
            process.start(f"blueutil --connect {address}")
            process.waitForFinished()
            print("Connected!")
        elif platform.system() == "Linux":
            process = QProcess()
            process.setProcessChannelMode(QProcess.SeparateChannels)
            process.start("bluetoothctl")
            process.waitForStarted()
            process.waitForReadyRead()
            process.writeData(bytes("connect B1:20:B5:86:AA:B9\n", 'utf-8'))
            process.waitForBytesWritten()

            while True:
                process.waitForReadyRead(1500)
                data = bytes(process.readAll()).decode('utf-8').split(" ")
                print(data)

                if any("successful" in x for x in data):
                    print("Connection successful")
                    break
                elif any("Failed" in x for x in data):
                    print(f"ERROR: bluez.failed")
                    self.bluetooth_speaker_cb(
                        BluetoothSpeakerError(
                            "Bluetooth speaker daemon failed to start"))

                time.sleep(1.0)

            process.writeData(bytes("exit\n", 'utf-8'))
            process.waitForBytesWritten()
            process.waitForReadyRead(1500)
            process.closeWriteChannel()
            process.waitForFinished()
            process.close()
            print("Waiting for notify_connect to fire")
        else:
            print("Unsupported platform")
Ejemplo n.º 7
0
class MCA_Widget(QWidget):
    """

    """
    mcaUpdating = pyqtSignal(int)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.mcaDir = '/tmp'
        self.init_UI()
        self.init_validation()
        self.init_signals()
        self.overrideEpicsCalib()
        self.mca_x = None
        self.mca_y = None
        self.mca_yerr = None
        self.data = {}
        self.expressions = {}
        self.plotColors = {}
        self.dlg_data = {}
        self.plotColIndex = {}
        self.mcaFnames = []
        self.mca_MEDM_running = False
        self.stopMEDMPushButton.setEnabled(False)

    def init_UI(self):
        loadUi('./UI_Forms/MCA_Widget.ui', self)
        self.change_MCA()

    def closeEvent(self, event):
        if self.mca_MEDM_running:
            self.medm.close()

    def init_pv(self):
        self.realTimeLineEdit.setPV(self.medm_P + 'mca1.PRTM')
        self.liveTimeLineEdit.setPV(self.medm_P + 'mca1.PLTM')
        self.modeComboBox.setPV(self.medm_P + self.medm_D + 'PresetMode')
        self.currentStatusLabel.setPV(self.medm_P + 'mca1.ACQG', type='str')
        self.readRealTimeLabel.setPV(self.medm_P + 'mca1.ERTM')
        self.readLiveTimeLabel.setPV(self.medm_P + 'mca1.ELTM')

    def init_signals(self):
        #Signals for MCA scans
        self.addMCAScanPushButton.clicked.connect(
            lambda x: self.add_mca_scans(fnames=None))
        self.mcaScanListWidget.itemDoubleClicked.connect(self.openDataDialog)
        self.mcaScanListWidget.itemSelectionChanged.connect(
            self.scanSelectionChanged)

        #Signals for mcaPlotWidget

        #Signals of MCA MEDM setup
        self.medm_P_LineEdit.returnPressed.connect(self.change_MCA)
        self.medm_D_LineEdit.returnPressed.connect(self.change_MCA)
        self.medm_M_LineEdit.returnPressed.connect(self.change_MCA)
        self.launchMEDMPushButton.clicked.connect(self.launch_MEDM)
        self.stopMEDMPushButton.clicked.connect(self.stop_MEDM)
        self.readMCAPushButton.clicked.connect(self.readMCA)
        self.saveMCAPushButton.clicked.connect(
            lambda x: self.saveMCA(fname=None))
        self.countPushButton.clicked.connect(self.startstopCountMCA)

        #Signals from MCA Calibration setup
        self.overrideMCACalibCheckBox.stateChanged.connect(
            self.overrideEpicsCalib)

        #Signals for Scan Statistics
        self.xminLineEdit.returnPressed.connect(self.xmin_xmax_Changed)
        self.xmaxLineEdit.returnPressed.connect(self.xmin_xmax_Changed)
        self.statsTableWidget.setSizeAdjustPolicy(
            QAbstractScrollArea.AdjustToContents)
        self.calcStatsPushButton.clicked.connect(self.calcStats)

    def roiRegionChanged(self):
        self.xmin, self.xmax = self.mcaPlotWidget.roi.getRegion()
        self.xminLineEdit.setText('%.4f' % self.xmin)
        self.xmaxLineEdit.setText('%.4f' % self.xmax)

    def overrideEpicsCalib(self):
        if self.overrideMCACalibCheckBox.isChecked():
            self.offsetLineEdit.setEnabled(True)
            self.linearLineEdit.setEnabled(True)
            self.quadraticLineEdit.setEnabled(True)
        else:
            self.offsetLineEdit.setEnabled(False)
            self.linearLineEdit.setEnabled(False)
            self.quadraticLineEdit.setEnabled(False)

    def scanSelectionChanged(self):
        fnames = [
            item.text() for item in self.mcaScanListWidget.selectedItems()
        ]
        if len(fnames) != 0:
            self.mcaPlotWidget.Plot(fnames)
            self.offsetLineEdit.setText(
                '%.4f' % self.data[fnames[0]]['Counts']['Offset'])
            self.linearLineEdit.setText(
                '%.4f' % self.data[fnames[0]]['Counts']['Linear'])
            self.quadraticLineEdit.setText(
                '%.4f' % self.data[fnames[0]]['Counts']['Quadratic'])
            stats_data = []
            keys = [
                'Energy', 'real_time', 'live_time', 'sum', 'sum_err',
                'corr_sum', 'corr_sum_err'
            ]  #self.dlg_data[fullfnames[0]]['meta'].keys()
            for fname in fnames:
                stats_data.append(
                    [self.data[fname]['Counts'][key] for key in keys])
            stats_data = np.array(stats_data)
            self.updateStatisticsTable(stats_data, keys)

    def updateStatisticsTable(self, data, keys):
        self.statsTableWidget.setData(data)
        self.statsTableWidget.setHorizontalHeaderLabels(keys)
        self.statsTableWidget.resizeColumnsToContents()

    #def addStatistics(self,):

    def init_validation(self):
        self.dbleValidator = QDoubleValidator()
        self.intValidator = QIntValidator()
        self.offsetLineEdit.setValidator(self.dbleValidator)
        self.linearLineEdit.setValidator(self.dbleValidator)
        self.quadraticLineEdit.setValidator(self.dbleValidator)
        self.xminLineEdit.setValidator(self.dbleValidator)
        self.xmaxLineEdit.setValidator(self.dbleValidator)

    def openDataDialog(self, item):
        fname = item.text()
        data_dlg = Data_Dialog(data=self.dlg_data[os.path.join(
            self.mcaDir, fname)],
                               parent=self,
                               plotIndex=self.plotColIndex[fname],
                               colors=self.plotColors[fname])
        if data_dlg.exec_():
            self.mcaPlotWidget.remove_data([fname])
            self.plotColIndex[fname] = data_dlg.plotColIndex
            self.dlg_data[fname] = copy.copy(data_dlg.data)
            self.data[fname] = copy.copy(data_dlg.externalData)
            self.expressions[fname] = data_dlg.expressions
            self.plotColors[fname] = data_dlg.plotColors
            for key in self.data[fname].keys():
                self.mcaPlotWidget.add_data(self.data[fname][key]['x'],
                                            self.data[fname][key]['y'],
                                            yerr=self.data[fname][key]['yerr'],
                                            name=fname,
                                            color=self.plotColors[fname][key])
            self.mcaPlotWidget.Plot([fname])

    def add_mca_scans(self, fnames=None):
        """
        Adds MCA scans to the mcaScanListWidget
        :param fnames: List of mca filenames. If None it will prompt for the files to be imported
        :return:
        """
        if fnames is None:
            self.mcaFnames = QFileDialog.getOpenFileNames(
                self, 'Select MCA files', directory=self.mcaDir)[0]
        else:
            self.mcaFnames = fnames
        if self.mcaFnames != []:
            self.mcaDir = os.path.dirname(self.mcaFnames[0])
            # self.mcaScanListWidget.addItems([os.path.basename(fname) for fname in self.mcaFnames])
            self.mcaScanListWidget.addItems(self.mcaFnames)
            for fname in self.mcaFnames:
                data_dlg = Data_Dialog(fname=fname, parent=self, colors=None)
                data_dlg.accept()
                self.dlg_data[fname] = data_dlg.data
                self.plotColIndex[fname] = data_dlg.plotColIndex
                self.data[fname] = data_dlg.externalData
                self.expressions[fname] = data_dlg.expressions
                self.plotColors[fname] = data_dlg.plotColors
                for key in self.data[fname].keys():
                    self.mcaPlotWidget.add_data(self.data[fname][key]['x'], self.data[fname][key]['y'], \
                                             yerr=self.data[fname][key]['yerr'],
                                             name='%s' % (fname),
                                             color=self.plotColors[fname][key])
                    print(self.plotColors[fname][key])

    def launch_MEDM(self):
        self.medm = QProcess()
        cmd = 'medm -x -macro "P=%s,D=%s, M=%s" ./adl/dxpSaturn.adl' % (
            self.medm_P, self.medm_D, self.medm_M)
        self.medm.start(cmd)
        self.mca_MEDM_running = True
        self.launchMEDMPushButton.setEnabled(False)
        self.stopMEDMPushButton.setEnabled(True)

    def stop_MEDM(self):
        if self.mca_MEDM_running:
            self.medm.close()
        self.mca_MEDM_running = False
        self.stopMEDMPushButton.setEnabled(False)
        self.launchMEDMPushButton.setEnabled(True)

    # def stop_MEDM(self):
    #     self.medm.terminate()

    def readMCA(self):
        self.mca_y = self.mcaPV.value
        self.mca_yerr = np.sqrt(self.mca_y)
        xmca = np.arange(len(self.mca_y))
        if self.overrideMCACalibCheckBox.isChecked():
            self.get_mca_manual_calib()
        else:
            self.get_mca_epics_calib()
        self.energy = epics.caget(BYTES2STR("15IDA:BraggERdbkAO"))
        self.mca_x = self.mca_offset + self.mca_linear * xmca + self.mca_quadratic * xmca**2
        self.mcaPlotWidget.add_data(self.mca_x,
                                    self.mca_y,
                                    yerr=self.mca_yerr,
                                    name='mca1')
        self.xmin = float(self.xminLineEdit.text())
        self.xmax = float(self.xmaxLineEdit.text())
        self.performStats(fname=None)
        keys = [
            'Energy', 'real_time', 'live_time', 'sum', 'sum_err', 'corr_sum',
            'corr_sum_err'
        ]  # self.dlg_data[fullfnames[0]]['meta'].keys()
        stats_data = [[
            self.energy, self.mca_realtime, self.mca_livetime, self.sum,
            self.sum_err, self.corr_sum, self.corr_sum_err
        ]]
        self.updateStatisticsTable(stats_data, keys)

    def calcStats(self):
        if len(self.mcaFnames) != 0:
            stats_data = []
            for fname in self.mcaFnames:
                self.mca_x = self.data[fname]['Counts']['x']
                self.mca_y = self.data[fname]['Counts']['y']
                self.mca_realtime = self.data[fname]['Counts']['real_time']
                self.mca_livetime = self.data[fname]['Counts']['live_time']
                self.performStats(fname=fname)
                keys = [
                    'Energy', 'real_time', 'live_time', 'sum', 'sum_err',
                    'corr_sum', 'corr_sum_err'
                ]  # self.dlg_data[fullfnames[0]]['meta'].keys()
                stats_data.append(
                    [self.data[fname]['Counts'][key] for key in keys])
        else:
            self.performStats(fname=None)
            keys = [
                'Energy', 'real_time', 'live_time', 'sum', 'sum_err',
                'corr_sum', 'corr_sum_err'
            ]  # self.dlg_data[fullfnames[0]]['meta'].keys()
            stats_data = [[
                self.energy, self.mca_realtime, self.mca_livetime, self.sum,
                self.sum_err, self.corr_sum, self.corr_sum_err
            ]]
        self.updateStatisticsTable(stats_data, keys)

    def performStats(self, fname=None):
        #self.xmin_xmax_Changed()
        if self.xmin is None:
            imin = 0
        else:
            imin = np.where(self.mca_x > self.xmin)[0][0]
        if self.xmax is None:
            imax = len(self.mca_x) - 1
        else:
            imax = np.where(self.mca_x < self.xmax)[0][-1]
        if imax <= imin:
            imax = imin + 1
        if fname is not None:
            self.data[fname]['Counts']['sum'] = np.sum(self.mca_y[imin:imax +
                                                                  1])
            self.data[fname]['Counts']['sum_err'] = np.sqrt(
                self.data[fname]['Counts']['sum'])
            self.data[fname]['Counts']['corr_sum'] = self.data[fname][
                'Counts']['sum'] * self.mca_realtime / self.mca_livetime
            self.data[fname]['Counts']['corr_sum_err'] = self.data[fname][
                'Counts']['sum_err'] * self.mca_realtime / self.mca_livetime
        else:
            self.sum = np.sum(self.mca_y[imin:imax + 1])
            self.sum_err = np.sqrt(self.sum)
            self.corr_sum = self.sum * self.mca_realtime / self.mca_livetime
            self.corr_sum_err = self.sum_err * self.mca_realtime / self.mca_livetime

    def xmin_xmax_Changed(self):
        self.xmin = float(self.xminLineEdit.text())
        self.xmax = float(self.xmaxLineEdit.text())
        try:
            self.mcaPlotWidget.roi.setRegion((self.xmin, self.xmax))
        except:
            self.mcaPlotWidget.addROI(values=(self.xmin, self.xmax),
                                      orientation='vertical',
                                      movable=True)
            self.mcaPlotWidget.roi.sigRegionChanged.connect(
                self.roiRegionChanged)
        try:
            self.calcStats()
        except:
            pass

    def saveMCA(self, fname=None):
        if self.mca_y is None:
            self.readMCA()
        if fname is None:
            fname = QFileDialog.getSaveFileName(self,
                                                caption="Provide filename",
                                                directory=self.mcaDir)[0]
        data = np.vstack((self.mca_x, self.mca_y, self.mca_yerr)).T
        header = 'MCA file saved on %s\n' % time.asctime()
        header += 'Energy=%.4f\n' % self.energy
        header += 'Offset=%.4e\n' % self.mca_offset
        header += 'Linear=%.4e\n' % self.mca_linear
        header += 'Quadratic=%.4e\n' % self.mca_quadratic
        header += 'real_time=%.4f\n' % self.mca_realtime
        header += 'live_time=%.4f\n' % self.mca_livetime
        header += 'sum=%.4f\n' % self.sum  #self.data['sum']
        header += 'sum_err=%.4f\n' % self.sum_err  #self.data['sum_err']
        header += 'corr_sum=%.4f\n' % self.corr_sum  #self.data['corr_sum']
        header += 'corr_sum_err=%.4f\n' % self.corr_sum_err  #self.data['corr_sum_err']
        header += 'col_names=["Channel","Counts","Err_Counts"]'
        np.savetxt(fname, data, header=header)
        self.add_mca_scans(fnames=[fname])

    def get_mca_manual_calib(self):
        """
        Get the manual calibration number from the MCA_Widget
        """
        self.mca_offset = float(self.offsetLineEdit.text())
        self.mca_linear = float(self.linearLineEdit.text())
        self.mca_quadratic = float(self.quadraticLineEdit.text())
        self.mca_realtime = epics.caget(BYTES2STR(self.medm_P + "mca1.ERTM"))
        self.mca_livetime = epics.caget(BYTES2STR(self.medm_P + "mca1.ELTM"))

    def get_mca_epics_calib(self):
        """
        Get the epics calibration numbers and reset the manual calibration numbers accordingly
        """
        self.mca_offset = epics.caget(BYTES2STR(self.medm_P + "mca1.CALO"))
        self.mca_linear = epics.caget(BYTES2STR(self.medm_P + "mca1.CALS"))
        self.mca_quadratic = epics.caget(BYTES2STR(self.medm_P + "mca1.CALQ"))
        self.mca_realtime = epics.caget(BYTES2STR(self.medm_P + "mca1.ERTM"))
        self.mca_livetime = epics.caget(BYTES2STR(self.medm_P + "mca1.ELTM"))

        if not self.overrideMCACalibCheckBox.isChecked():
            self.offsetLineEdit.setText('%.5f' % self.mca_offset)
            self.linearLineEdit.setText('%.5f' % self.mca_linear)
            self.quadraticLineEdit.setText('%.5f' % self.mca_quadratic)

    def change_MCA(self):
        """
        Resets the P, D and M values of MCA
        :return:
        """
        self.medm_P = self.medm_P_LineEdit.text()
        self.medm_D = self.medm_D_LineEdit.text()
        self.medm_M = self.medm_M_LineEdit.text()
        self.mcaPV = epics.PV(BYTES2STR(self.medm_P + self.medm_M))
        try:
            self.mcaStatusPV.clear_callbacks()
        except:
            self.mcaStatusPV = epics.PV(
                BYTES2STR(self.medm_P + self.medm_M + '.ACQG'))
        self.monitorIndex = self.mcaStatusPV.add_callback(self.mcaChanging)
        self.mcaUpdating.connect(self.mcaAutoUpdate)
        self.init_pv()

    def mcaChanging(self, **kwargs):
        value = kwargs['value']
        if value == 0:
            self.mcaUpdating.emit(value)

    def mcaAutoUpdate(self, value):
        self.readMCA()
        self.countPushButton.setText('Count')

    def startstopCountMCA(self):
        if self.countPushButton.text() == 'Count':
            epics.caput(BYTES2STR(self.medm_P + 'mca1EraseStart'), 1)
            self.countPushButton.setText('Stop')
        else:
            epics.caput(BYTES2STR(self.medm_P + 'Stop'), 1)
            self.countPushButton.setText('Count')
Ejemplo n.º 8
0
class RF_Qt(QMainWindow):
    """
    Class to create the main GUI window. It extends QMainWindow.
    The GUI allows the user to load up to four font files, provide a new font name, adjust some options,
    view a preview of font changes, and finally generate new TrueType font files.
    """

    def __init__(self):
        """
        Create the main window.
        :return:
        """
        super(RF_Qt, self).__init__()

        # Define variables
        self.fnt_styles = ["Regular", "Italic", "Bold", "Bold Italic"]
        self.fnt_sty_combo_list = []
        self.fnt_file_name_list = []
        self.font_files = None
        self.font_info = FontInfo()
        # Create a QProcess object, and connect it to appropriate slots
        self.cli_process = QProcess(self)
        self.cli_process.setProcessChannelMode(QProcess.MergedChannels)
        self.cli_process.readyRead.connect(self.read_proc_output)
        self.cli_process.started.connect(self.manage_proc)
        self.cli_process.finished.connect(self.manage_proc)
        self.cli_process.error.connect(self.manage_proc)

        # Style for all groupbox labels
        gb_style = "QGroupBox { font-weight: bold; }"
        # Top level layout manager for the window.
        win_layout = QVBoxLayout()
        gb_fnt_files = QGroupBox("Font Files")
        gb_fnt_files.setStyleSheet(gb_style)
        grid_f_f = QGridLayout()
        grid_pos = 0

        # Font Files and styles #

        # Create a grid of font names with their respective font style combo boxes
        for i in range(len(self.fnt_styles)):
            self.fnt_file_name_list.append(QLabel("Load font file..."))
            cmb = QComboBox()
            cmb.addItem("")
            cmb.addItems(self.fnt_styles)
            cmb.setEnabled(False)
            cmb.setToolTip(
                "<qt/>If not automatically detected when the font is added, allows you to select what font "
                "sub-family the font file belongs to"
            )
            self.fnt_sty_combo_list.append(cmb)
            row, col = helper.calc_grid_pos(grid_pos, 2)
            grid_f_f.addWidget(self.fnt_file_name_list[i], row, col)
            grid_pos += 1
            row, col = helper.calc_grid_pos(grid_pos, 2)
            grid_f_f.addWidget(self.fnt_sty_combo_list[i], row, col)
            grid_pos += 1
        grid_f_f.setColumnStretch(0, 1)
        gb_fnt_files.setLayout(grid_f_f)
        win_layout.addWidget(gb_fnt_files)

        # New Font Name #
        gb_fnt_name = QGroupBox("Font Family Name")
        gb_fnt_name.setStyleSheet(gb_style)
        hb_fnt_name = QHBoxLayout()
        self.new_fnt_name = QLineEdit()
        self.new_fnt_name.setToolTip("Enter a name for the modified font.")
        self.new_fnt_name.textEdited[str].connect(self.set_family_name)
        hb_fnt_name.addWidget(self.new_fnt_name)
        gb_fnt_name.setLayout(hb_fnt_name)
        win_layout.addWidget(gb_fnt_name)

        # Options #
        hb_options = QHBoxLayout()

        ## Kerning, Panose, Alt. Name ##
        gb_basic_opt = QGroupBox("Basic Options")
        gb_basic_opt.setStyleSheet(gb_style)
        hb_basic_opt = QHBoxLayout()
        self.basic_opt_list = []
        basic_tooltips = (
            "<qt/>Some readers and software require 'legacy', or 'old style' kerning to be "
            "present for kerning to work.",
            "<qt/>Kobo readers can get confused by PANOSE settings. This option sets all "
            "PANOSE information to 0, or 'any'",
            "<qt/>Some fonts have issues with renaming. If the generated font does not have "
            "the same internal font name as you entered, try enabling this option.",
        )

        for opt, tip in zip(("Legacy Kerning", "Clear PANOSE", "Alt. Name"), basic_tooltips):
            self.basic_opt_list.append(QCheckBox(opt))
            self.basic_opt_list[-1].setToolTip(tip)
            hb_basic_opt.addWidget(self.basic_opt_list[-1])

        gb_basic_opt.setLayout(hb_basic_opt)
        hb_options.addWidget(gb_basic_opt)

        ## Hinting ##
        gb_hint_opt = QGroupBox("Hinting Option")
        gb_hint_opt.setStyleSheet(gb_style)
        hb_hint_opt = QHBoxLayout()
        self.hint_opt_list = []
        hint_tooltips = (
            "<qt/>Keep font hinting as it exists in the orginal font files.<br />"
            "In most cases, this will look fine on most ebook reading devices.",
            '<qt/>Some fonts are manually, or "hand" hinted for specific display types (such as LCD). '
            "These fonts may not look good on other display types such as e-ink, therefore they can be "
            "removed.",
            "<qt/>If you don't like the original hinting, but you want your font to be hinted, "
            "this option will auto hint your font.",
        )
        for opt, tip in zip(("Keep Existing", "Remove Existing", "AutoHint"), hint_tooltips):
            self.hint_opt_list.append(QRadioButton(opt))
            self.hint_opt_list[-1].setToolTip(tip)
            self.hint_opt_list[-1].toggled.connect(self.set_hint)
            hb_hint_opt.addWidget(self.hint_opt_list[-1])

        self.hint_opt_list[0].setChecked(Qt.Checked)
        gb_hint_opt.setLayout(hb_hint_opt)
        hb_options.addWidget(gb_hint_opt)

        win_layout.addLayout(hb_options)

        ## Darken ##
        gb_dark_opt = QGroupBox("Darken Options")
        gb_dark_opt.setStyleSheet(gb_style)
        hb_dark_opt = QHBoxLayout()
        self.darken_opt = QCheckBox("Darken Font")
        self.darken_opt.setToolTip("<qt/>Darken, or add weight to a font to make it easier to read on e-ink screens.")
        self.darken_opt.toggled.connect(self.set_darken_opt)
        hb_dark_opt.addWidget(self.darken_opt)
        self.mod_bearing_opt = QCheckBox("Modify Bearings")
        self.mod_bearing_opt.setToolTip(
            "<qt/>By default, adding weight to a font increases glyph width. Enable this "
            "option to set the glyph width to be roughly equal to the original.<br/><br/>"
            "WARNING: This reduces the spacing between glyphs, and should not be used if "
            "you have added too much weight."
        )
        self.mod_bearing_opt.toggled.connect(self.set_mod_bearing)
        self.mod_bearing_opt.setEnabled(False)
        hb_dark_opt.addWidget(self.mod_bearing_opt)

        self.lbl = QLabel("Darken Amount:")
        self.lbl.setEnabled(False)
        hb_dark_opt.addWidget(self.lbl)
        self.darken_amount_opt = QSlider(Qt.Horizontal)
        self.darken_amount_opt.setMinimum(1)
        self.darken_amount_opt.setMaximum(50)
        self.darken_amount_opt.setValue(12)
        self.darken_amount_opt.setEnabled(False)
        self.darken_amount_opt.setToolTip(
            "<qt/>Set the amount to darken a font by. 50 is considered turning a "
            "regular weight font into a bold weight font. It is not recommended to "
            "darken a font that much however."
        )
        self.darken_amount_opt.valueChanged[int].connect(self.set_darken_amount)
        hb_dark_opt.addWidget(self.darken_amount_opt)
        self.darken_amount_lab = QLabel()
        self.darken_amount_lab.setText(str(self.darken_amount_opt.value()))
        self.darken_amount_lab.setEnabled(False)
        hb_dark_opt.addWidget(self.darken_amount_lab)
        gb_dark_opt.setLayout(hb_dark_opt)

        win_layout.addWidget(gb_dark_opt)

        # Buttons #
        hb_buttons = QHBoxLayout()
        # hb_buttons.addStretch()
        self.gen_ttf_btn = QPushButton("Generate TTF")
        self.gen_ttf_btn.setEnabled(False)
        self.gen_ttf_btn.setToolTip(
            "<qt/>Generate a new TrueType font based on the options chosen in this program. "
            "<br /><br />"
            "The new fonts are saved in a directory of your choosing."
        )
        self.gen_ttf_btn.clicked.connect(self.gen_ttf)
        hb_buttons.addWidget(self.gen_ttf_btn)
        self.load_font_btn = QPushButton("Load Fonts")
        self.load_font_btn.setToolTip("<qt/>Load font files to modify.")
        self.load_font_btn.clicked.connect(self.load_fonts)
        hb_buttons.addWidget(self.load_font_btn)
        self.prog_bar = QProgressBar()
        self.prog_bar.setRange(0, 100)
        hb_buttons.addWidget(self.prog_bar)
        win_layout.addLayout(hb_buttons)

        # Output Log #
        gb_log_win = QGroupBox("Log Window")
        gb_log_win.setStyleSheet(gb_style)
        vb_log = QVBoxLayout()
        out_font = QFont("Courier")
        out_font.setStyleHint(QFont.Monospace)
        self.log_win = QTextEdit()
        self.log_win.setAcceptRichText(False)
        self.log_win.setFont(out_font)
        vb_log.addWidget(self.log_win)
        gb_log_win.setLayout(vb_log)
        win_layout.addWidget(gb_log_win)

        # Show Window #
        self.setCentralWidget(QWidget(self))
        self.centralWidget().setLayout(win_layout)
        self.setWindowTitle("Readify Font")

        self.show()

        # Check if fontforge is actually in users PATH. If it isn't, prompt user to provice a location
        self.ff_path = helper.which("fontforge")
        if not self.ff_path:
            self.set_ff_path()

    def set_ff_path(self):
        """
        Let user choose location of fontforge
        :return:
        """
        QMessageBox.warning(
            self,
            "Fontforge Missing!",
            "FontForge is not in your PATH! If it is installed, " "please locate it now.",
            QMessageBox.Ok,
            QMessageBox.Ok,
        )
        path = QFileDialog.getOpenFileName(self, "Locate FontForge...")
        if path[0]:
            self.ff_path = os.path.normpath(path[0])

    def set_basic_opt(self):
        """
        Handler to set basic options
        :return:
        """
        opt = self.sender()
        if opt.isChecked():
            if "kerning" in opt.text().lower():
                self.font_info.leg_kern = True
            if "panose" in opt.text().lower():
                self.font_info.strip_panose = True
            if "alt" in opt.text().lower():
                self.font_info.name_hack = True
        else:
            if "kerning" in opt.text().lower():
                self.font_info.leg_kern = False
            if "panose" in opt.text().lower():
                self.font_info.strip_panose = False
            if "alt" in opt.text().lower():
                self.font_info.name_hack = False

    def set_family_name(self, name):
        """
        Handler to set name option. Also checks if buttons need enabling
        :param name:
        :return:
        """
        if name:
            if helper.valid_filename(name):
                self.font_info.font_name = name
                if self.font_files:
                    self.gen_ttf_btn.setEnabled(True)
            else:
                self.gen_ttf_btn.setEnabled(False)
        else:
            self.gen_ttf_btn.setEnabled(False)

    def set_darken_amount(self, amount):
        """
        Set Darken amount slider
        :param amount:
        :return:
        """
        self.darken_amount_lab.setText(str(amount))
        self.font_info.add_weight = amount

    def set_hint(self):
        """
        Set hint options
        :return:
        """
        hint = self.sender()
        if hint.isChecked():
            if "keep" in hint.text().lower():
                self.font_info.change_hint = "keep"
            elif "remove" in hint.text().lower():
                self.font_info.change_hint = "remove"
            elif "auto" in hint.text().lower():
                self.font_info.change_hint = "auto"

    def set_darken_opt(self):
        """
        Set darken options
        :return:
        """
        if self.sender().isChecked():
            self.mod_bearing_opt.setEnabled(True)
            self.lbl.setEnabled(True)
            self.darken_amount_lab.setEnabled(True)
            self.darken_amount_opt.setEnabled(True)
            self.set_darken_amount(self.darken_amount_opt.value())
        else:
            self.mod_bearing_opt.setEnabled(False)
            self.lbl.setEnabled(False)
            self.darken_amount_lab.setEnabled(False)
            self.darken_amount_opt.setEnabled(False)
            self.set_darken_amount(0)

    def set_mod_bearing(self):
        """
        Set mod bearing options
        :return:
        """
        if self.mod_bearing_opt.isChecked():
            self.font_info.mod_bearings = True
        else:
            self.font_info.mod_bearings = False

    def load_fonts(self):
        """
        Load fonts from a directory, and sets appropriate options
        :return:
        """
        f_f = QFileDialog.getOpenFileNames(self, "Load Fonts", "", "Font Files (*.ttf *.otf)")
        if f_f[0]:
            for f_label, f_style in zip(self.fnt_file_name_list, self.fnt_sty_combo_list):
                f_label.setText("Load font file...")
                f_style.setCurrentIndex(SEL_NONE)
                f_style.setEnabled(False)

            self.font_files = f_f[0]
            f_f_names = []
            for file in self.font_files:
                file = os.path.normpath(file)
                base, fn = os.path.split(file)
                f_f_names.append(fn)

            for f_file, f_label, f_style in zip(f_f_names, self.fnt_file_name_list, self.fnt_sty_combo_list):
                f_label.setText(f_file)
                f_style.setEnabled(True)
                if "regular" in f_file.lower():
                    f_style.setCurrentIndex(SEL_REGULAR)
                elif "bold" in f_file.lower() and "italic" in f_file.lower():
                    f_style.setCurrentIndex(SEL_BOLDITALIC)
                elif "bold" in f_file.lower():
                    f_style.setCurrentIndex(SEL_BOLD)
                elif "italic" in f_file.lower():
                    f_style.setCurrentIndex(SEL_ITALIC)

            if self.new_fnt_name.text():
                self.gen_ttf_btn.setEnabled(True)

    def read_proc_output(self):
        """
        Read any stdout data available from the process and displays it in the output log window.
        :return:
        """
        if sys.version_info.major == 2:
            output = unicode(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding())
        else:
            output = str(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding())
        self.log_win.append(output)

    def manage_proc(self):
        """
        Manage the progress bar
        :return:
        """
        proc = self.sender()
        if proc.state() == QProcess.Running:
            self.prog_bar.setRange(0, 0)
        if proc.state() == QProcess.NotRunning:
            self.prog_bar.setRange(0, 100)
            self.prog_bar.setValue(100)

    def gen_ttf(self):
        """
        Generate modified TrueType font files, by calling the CLI script with the appropriate arguments.
        :param prev:
        :return:
        """
        self.log_win.clear()
        if not self.ff_path:
            self.set_ff_path()
        if self.ff_path:
            if not self.font_info.out_dir:
                save_dir = os.path.normpath(
                    QFileDialog.getExistingDirectory(self, "Select save directory...", options=QFileDialog.ShowDirsOnly)
                )
                if save_dir == "." or save_dir == "":
                    return
                else:
                    self.font_info.out_dir = save_dir
            else:
                save_dir = os.path.normpath(
                    QFileDialog.getExistingDirectory(
                        self, "Select Save directory...", self.font_info.out_dir, options=QFileDialog.ShowDirsOnly
                    )
                )
                if save_dir == "." or save_dir == "":
                    return
                else:
                    self.font_info.out_dir = save_dir

            for file, style in zip(self.font_files, self.fnt_sty_combo_list):
                if style.currentIndex() == SEL_REGULAR:
                    self.font_info.font_file_reg = file
                elif style.currentIndex() == SEL_BOLDITALIC:
                    self.font_info.font_file_bi = file
                elif style.currentIndex() == SEL_BOLD:
                    self.font_info.font_file_bd = file
                elif style.currentIndex() == SEL_ITALIC:
                    self.font_info.font_file_it = file

            cli_opt_list = self.font_info.gen_cli_command()
            self.cli_process.start(self.ff_path, cli_opt_list)

    def closeEvent(self, event):
        """
        Cleaning up...
        :param event:
        :return:
        """
        self.cli_process.close()
        event.accept()
Ejemplo n.º 9
0
class MainWin(QMainWindow):
    def __init__(self):
        super(MainWin, self).__init__()
        self.settings = QSettings("RadioBOB", "settings")
        self.setStyleSheet(mystylesheet(self))
        self.radioNames = []
        self.radiolist = []
        self.channels = []
        self.imagelist = []
        self.radiofile = ""
        self.radioStations = ""
        self.rec_name = ""
        self.rec_url = ""
        self.old_meta = ""
        self.notificationsEnabled = True
        self.headerlogo = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]), "headerlogo.png"))
        self.setContentsMargins(5, 0, 5, 0)

        self.wg = QWidget()
        self.er_label = QLabel("Image")

        self.er_label.setPixmap(self.headerlogo.pixmap(QSize(300, 140)))
        self.er_label.setScaledContents(False)
        self.er_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.layout = QVBoxLayout()

        ### combo box
        self.urlCombo = QComboBox()

        self.er_label.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.er_label, 0, Qt.AlignCenter)

        self.layout1 = QHBoxLayout()
        self.layout1.setContentsMargins(50, 0, 50, 0)
        self.tIcon = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]), "logo.png"))

        self.playIcon = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]),
                         "media-playback-start.svg"))
        self.stopIcon = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]),
                         "media-playback-stop.svg"))
        self.recordIcon = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]), "media-record.svg"))
        self.hideIcon = QIcon(
            os.path.join(os.path.dirname(sys.argv[0]), "hide.png"))

        self.outfile = QStandardPaths.standardLocations(
            QStandardPaths.TempLocation)[0] + "/er_tmp.mp3"
        self.recording_enabled = False
        self.is_recording = False

        spc1 = QSpacerItem(6, 10, QSizePolicy.Expanding, QSizePolicy.Maximum)

        self.play_btn = QPushButton("", self)
        self.play_btn.setFixedWidth(btnwidth)
        self.play_btn.setIcon(self.playIcon)
        self.layout1.addWidget(self.play_btn)

        self.stop_btn = QPushButton("", self)
        self.stop_btn.setFixedWidth(btnwidth)
        self.stop_btn.setIcon(self.stopIcon)
        self.layout1.addWidget(self.stop_btn)
        ### record
        self.rec_btn = QPushButton("", self)
        self.rec_btn.setFixedWidth(btnwidth)
        self.rec_btn.setIcon(self.recordIcon)
        self.rec_btn.clicked.connect(self.recordRadio)
        self.rec_btn.setToolTip("Aufnehmen")
        self.layout1.addWidget(self.rec_btn)
        ### stop record
        self.stoprec_btn = QPushButton("", self)
        self.stoprec_btn.setFixedWidth(btnwidth)
        self.stoprec_btn.setIcon(self.stopIcon)
        self.stoprec_btn.clicked.connect(self.stop_recording)
        self.stoprec_btn.setToolTip("Aufnahme stoppen")
        self.layout1.addWidget(self.stoprec_btn)
        ### Hauptfenster verbergen
        self.hide_btn = QPushButton("", self)
        self.hide_btn.setFixedWidth(btnwidth)
        self.hide_btn.setToolTip("Fenster ins Tray minimieren")
        self.hide_btn.setIcon(self.hideIcon)
        self.hide_btn.clicked.connect(self.showMain)
        self.layout1.addWidget(self.hide_btn)

        self.level_sld = QSlider(self)
        self.level_sld.setFixedWidth(310)
        self.level_sld.setToolTip("Lautstärkeregler")
        self.level_sld.setTickPosition(1)
        self.level_sld.setOrientation(Qt.Horizontal)
        self.level_sld.setValue(65)
        self.level_lbl = QLabel(self)
        self.level_lbl.setAlignment(Qt.AlignHCenter)
        self.level_lbl.setText("Lautstärke 65")
        self.layout.addItem(spc1)

        self.layout.addWidget(self.level_sld, Qt.AlignCenter)
        self.layout.addWidget(self.level_lbl, Qt.AlignCenter)
        self.layout.addItem(spc1)

        self.layout.addLayout(self.layout1)

        self.player = RadioPlayer(self)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.player.error.connect(self.handleError)
        self.play_btn.clicked.connect(self.playRadioStation)
        self.stop_btn.clicked.connect(self.stop_preview)
        self.level_sld.valueChanged.connect(self.set_sound_level)
        self.urlCombo.currentIndexChanged.connect(self.url_changed)
        self.current_station = ""

        self.process = QProcess()
        self.process.started.connect(self.getPID)

        self.wg.setLayout(self.layout)
        self.setCentralWidget(self.wg)

        self.stoprec_btn.setVisible(False)
        self.readStations()

        self.createStatusBar()
        self.setAcceptDrops(True)
        self.setWindowTitle("Radio BOB")

        self.setWindowIcon(self.tIcon)
        self.stationActs = []

        self.layout.addItem(spc1)
        self.setFixedSize(340, 360)
        self.move(30, 30)

        # Init tray icon
        trayIcon = QIcon(self.tIcon)

        self.trayIcon = QSystemTrayIcon()
        self.trayIcon.setIcon(self.headerlogo)
        self.trayIcon.show()
        self.trayIcon.activated.connect(self.showMainfromTray)
        self.geo = self.geometry()
        self.showWinAction = QAction(QIcon.fromTheme("view-restore"),
                                     "Hauptfenster anzeigen",
                                     triggered=self.showMain)
        self.notifAction = QAction(QIcon.fromTheme("dialog-information"),
                                   "Tray Meldungen ausschalten",
                                   triggered=self.toggleNotif)
        self.togglePlayerAction = QAction("Wiedergabe stoppen",
                                          triggered=self.togglePlay)
        self.togglePlayerAction.setIcon(QIcon.fromTheme("media-playback-stop"))
        self.recordAction = QAction(QIcon.fromTheme("media-record"),
                                    "Aufnahme starten",
                                    triggered=self.recordRadio)
        self.stopRecordAction = QAction(QIcon.fromTheme("media-playback-stop"),
                                        "Aufnahme stoppen",
                                        triggered=self.stop_recording)
        self.findExecutable()
        self.readSettings()
        self.makeTrayMenu()
        self.createWindowMenu()

        if QSystemTrayIcon.isSystemTrayAvailable():
            print("System Tray Icon verfügbar")
        else:
            print("System Tray Icon nicht verfügbar")
        if self.player.state() == QMediaPlayer.StoppedState:
            self.togglePlayerAction.setText("Wiedergabe starten")
            self.togglePlayerAction.setIcon(
                QIcon.fromTheme("media-playback-start"))
        elif self.player.state() == QMediaPlayer.PlayingState:
            self.togglePlayerAction.setText("Wiedergabe stoppen")
            self.togglePlayerAction.setIcon(
                QIcon.fromTheme("media-playback-stop"))

    def showTrayMessage(self, title, message, icon, timeout=4000):
        self.trayIcon.showMessage(title, message, icon, timeout)

    def handleError(self):
        print(f"Fehler: {self.player.errorString()}")
        self.showTrayMessage(f"Error:\n{self.player.errorString()}",
                             self.tIcon, 3000)
        self.statusLabel.setText(f"Fehler:\n{self.player.errorString()}")

    def togglePlay(self):
        if self.togglePlayerAction.text() == "Wiedergabe stoppen":
            self.stop_preview()
            self.togglePlayerAction.setText("Wiedergabe starten")
            self.togglePlayerAction.setIcon(
                QIcon.fromTheme("media-playback-start"))
        else:
            self.playRadioStation()
            self.togglePlayerAction.setText("Aufnahme stoppen")
            self.togglePlayerAction.setIcon(
                QIcon.fromTheme("media-playback-stop"))

    def createWindowMenu(self):
        self.tb = self.addToolBar("Menu")
        self.tb_menu = QMenu()
        self.tb.setIconSize(QSize(44, 20))

        ##### submenus from categories ##########
        b = self.radioStations.splitlines()
        for x in reversed(range(len(b))):
            line = b[x]
            if line == "":
                print(f"empty line {x} removed")
                del (b[x])

        i = 0
        for x in range(0, len(b)):
            line = b[x]
            menu_line = line.split(",")
            ch = menu_line[0]
            data = menu_line[1]
            if len(menu_line) > 2:
                image = menu_line[2]
            self.tb_menu.addAction(self.stationActs[i])
            i += 1

        ####################################
        toolButton = QToolButton()
        toolButton.setIcon(self.headerlogo)
        toolButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        toolButton.setText("   Stationen")
        toolButton.setFixedWidth(120)
        toolButton.setMenu(self.tb_menu)
        toolButton.setPopupMode(QToolButton.InstantPopup)
        self.tb.addWidget(toolButton)

        empty = QWidget()
        self.tb.addWidget(empty)
        self.tb.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tb.setMovable(False)
        self.tb.setAllowedAreas(Qt.TopToolBarArea)

    def makeTrayMenu(self):
        self.stationActs = []
        self.tray_menu = QMenu()
        self.tray_menu.addAction(self.togglePlayerAction)
        ##### submenus from categories ##########
        b = self.radioStations.splitlines()
        for x in reversed(range(len(b))):
            line = b[x]
            if line == "":
                print(f"empty line {x} removed")
                del (b[x])

        i = 0
        for x in range(0, len(b)):
            line = b[x]
            menu_line = line.split(",")
            ch = menu_line[0]
            data = menu_line[1]
            if len(menu_line) > 2:
                image = menu_line[2]
            self.stationActs.append(
                QAction(self.tIcon, ch, triggered=self.openTrayStation))
            self.stationActs[i].setData(str(i))
            self.tray_menu.addAction(self.stationActs[i])
            i += 1

        ####################################
        self.tray_menu.addSeparator()
        if not self.is_recording:
            if not self.urlCombo.currentText().startswith("--"):
                self.tray_menu.addAction(self.recordAction)
                self.recordAction.setText(
                    f"starte Aufnahme von {self.urlCombo.currentText()}")
        if self.is_recording:
            self.tray_menu.addAction(self.stopRecordAction)
        self.tray_menu.addSeparator()
        self.tray_menu.addAction(self.showWinAction)
        self.tray_menu.addSeparator()
        self.tray_menu.addAction(self.notifAction)
        self.tray_menu.addSeparator()
        exitAction = self.tray_menu.addAction(
            QIcon.fromTheme("application-exit"), "Beenden")
        exitAction.triggered.connect(self.exitApp)
        self.trayIcon.setContextMenu(self.tray_menu)

    def showMain(self):
        if self.isVisible() == False:
            self.showWinAction.setText("Hauptfenster verbergen")
            self.setVisible(True)
        elif self.isVisible() == True:
            self.showWinAction.setText("Hauptfenster anzeigen")
            self.setVisible(False)

    def showMainfromTray(self):
        buttons = qApp.mouseButtons()
        if buttons == Qt.LeftButton:
            if self.isVisible() == False:
                self.showWinAction.setText("Hauptfenster verbergen")
                self.setVisible(True)
            elif self.isVisible() == True:
                self.showWinAction.setText("Hauptfenster anzeigen")
                self.setVisible(False)

    def toggleNotif(self):
        if self.notifAction.text() == "Tray Meldungen ausschalten":
            self.notifAction.setText("Tray Meldungen einschalten")
            self.notificationsEnabled = False
        elif self.notifAction.text() == "Tray Meldungen einschalten":
            self.notifAction.setText("Tray Meldungen ausschalten")
            self.notificationsEnabled = True
        print(f"Notifications {self.notificationsEnabled}")
        self.metaDataChanged()

    def openTrayStation(self):
        action = self.sender()
        if action:
            ind = action.data()
            name = action.text()
            self.urlCombo.setCurrentIndex(self.urlCombo.findText(name))
            print(f"swith to Station: {ind} - {self.urlCombo.currentText()}")

    def exitApp(self):
        self.close()
        QApplication.quit()

    def message(self, message):
        QMessageBox.information(None, 'Meldung', message)

    def closeEvent(self, e):
        self.writeSettings()
        print("schreibe Konfiguratinsdatei ...\nbis bald, keep on rocking...")
        QApplication.quit()

    def readSettings(self):
        print("lese Konfiguratinsdatei ...")
        if self.settings.contains("pos"):
            pos = self.settings.value("pos", QPoint(200, 200))
            self.move(pos)
        else:
            self.move(0, 26)
        if self.settings.contains("lastChannel"):
            lch = self.settings.value("lastChannel")
            self.urlCombo.setCurrentIndex(self.urlCombo.findText(lch))
        if self.settings.contains("notifications"):
            self.notificationsEnabled = self.settings.value("notifications")
            if self.settings.value("notifications") == "false":
                self.notificationsEnabled = False
                self.notifAction.setText("Tray Meldungen einschalten")
            else:
                self.notifAction.setText("Tray Meldungen ausschalten")
                self.notificationsEnabled = True
        if self.settings.contains("windowstate"):
            print(self.settings.value("windowstate"))
            if self.settings.value("windowstate") == "Hauptfenster anzeigen":
                self.show()
                self.showWinAction.setText("Hauptfenster verbergen")
            else:
                self.hide()
                self.showWinAction.setText("Hauptfenster anzeigen")
        if self.settings.contains("volume"):
            vol = self.settings.value("volume")
            print(f"set volume to {vol}")
            self.level_sld.setValue(int(vol))

    def writeSettings(self):
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("index", self.urlCombo.currentIndex())
        self.settings.setValue("lastChannel", self.urlCombo.currentText())
        self.settings.setValue("notifications", self.notificationsEnabled)
        if self.isVisible():
            self.settings.setValue("windowstate", "Hauptfenster anzeigen")
        else:
            self.settings.setValue("windowstate", "Hauptfenster verbergen")
        self.settings.setValue("volume", self.level_sld.value())
        self.settings.sync()

    def readStations(self):
        self.urlCombo.clear()
        self.radiolist = []
        self.channels = []
        self.imagelist = []
        dir = os.path.dirname(sys.argv[0])
        self.radiofile = os.path.join(dir, "bob.txt")
        with open(self.radiofile, 'r') as f:
            self.radioStations = f.read()
            f.close()
            newlist = [list(x) for x in self.radioStations.splitlines()]
            for lines in self.radioStations.splitlines():
                mLine = lines.split(",")
                if not mLine[0].startswith("--"):
                    self.urlCombo.addItem(self.tIcon, mLine[0],
                                          Qt.UserRole - 1)
                    self.radiolist.append(mLine[1])

    def findExecutable(self):
        wget = QStandardPaths.findExecutable("wget")
        if wget != "":
            print(f"found wget at {wget} *** recording available")
            self.statusLabel.setText("Aufnahmen möglich")
            self.showTrayMessage("Hinweis", "wget gefunden\nAufnahmen möglich",
                                 self.tIcon)
            self.recording_enabled = True
        else:
            self.showTrayMessage(
                "Hinweis", "wget icht gefunden\nkeine Aufnahmen möglich",
                self.tIcon)
            print("wget icht gefunden\nkeine Aufnahmen möglich")
            self.recording_enabled = False

    def remove_last_line_from_string(self, s):
        return s[:s.rfind('\n')]

    def createStatusBar(self):
        self.statusLabel = QLabel("Info")
        self.statusLabel.setWordWrap(True)
        self.statusLabel.setAlignment(Qt.AlignCenter)
        #self.statusLabel.setStyleSheet("color:#73d216;")
        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.setStatusBar(self.statusBar)
        self.statusLabel.setText("Willkommen bei Radio BOB")
        self.statusBar.addWidget(self.statusLabel, 1)
        pixmap = QIcon(self.headerlogo)
        self.home_label = QPushButton()
        self.home_label.setIconSize(QSize(52, 26))
        self.home_label.setFixedSize(60, 32)
        self.home_label.setToolTip("Radio BOB Homepage besuchen")
        self.home_label.setIcon(self.headerlogo)
        self.home_label.clicked.connect(self.showHomepage)
        self.statusBar.addPermanentWidget(self.home_label)

    def showHomepage(self):
        url = QUrl('https://radiobob.de')
        QDesktopServices.openUrl(url)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            trackInfo = (self.player.metaData("Title"))
            if trackInfo is None:
                self.statusLabel.setText(
                    f"playing {self.urlCombo.currentText()}")
            new_trackInfo = ""
            new_trackInfo = str(trackInfo)
            #print(new_trackInfo)
            if not new_trackInfo == "None" and " - " in new_trackInfo:
                self.statusLabel.setText(
                    f"{new_trackInfo.split(' - ')[0]}\n{new_trackInfo.split(' - ')[1]}"
                )
            else:
                self.statusLabel.setText(
                    f" playing {self.urlCombo.currentText()}")
            mt = new_trackInfo
            if not mt == "None" and " - " in mt:
                if self.notificationsEnabled:
                    if not mt == self.old_meta:
                        print(mt)
                        self.showTrayMessage(
                            "Radio BOB",
                            f"{mt.split(' - ')[0]}\n{mt.split(' - ')[1]}",
                            self.tIcon)
                        self.old_meta = mt
                    self.trayIcon.setToolTip(mt)
                else:
                    self.trayIcon.setToolTip(mt)
                    self.old_meta = mt
        else:
            self.statusLabel.setText(f"playing {self.urlCombo}")

    def url_changed(self):
        if self.urlCombo.currentIndex() < self.urlCombo.count() - 1:
            if not self.urlCombo.currentText().startswith("--"):
                ind = self.urlCombo.currentIndex()
                url = self.radiolist[ind]

                self.current_station = url
                self.player.stop()
                self.rec_btn.setVisible(True)
                self.stop_btn.setVisible(True)
                self.play_btn.setVisible(True)
                name = self.urlCombo.currentText()
                print(f"playing {name} from {url}")
                self.playRadioStation()
                if self.togglePlayerAction.text() == "Wiedergabe stoppen":
                    self.togglePlayerAction.setText("Wiedergabe starten")
                    self.togglePlayerAction.setIcon(
                        QIcon.fromTheme("media-playback-start"))
                else:
                    self.togglePlayerAction.setText("Wiedergabe stoppen")
                    self.togglePlayerAction.setIcon(
                        QIcon.fromTheme("media-playback-stop"))
            else:
                self.rec_btn.setVisible(False)
                self.stop_btn.setVisible(False)
                self.play_btn.setVisible(False)

    def playRadioStation(self):
        if self.player.is_on_pause:
            self.set_running_player()
            self.player.start()
            self.stop_btn.setFocus()
            self.togglePlayerAction.setText("Aufnahme stoppen")
            self.togglePlayerAction.setIcon(
                QIcon.fromTheme("media-playback-stop"))

        if not self.current_station:
            return

        self.player.set_media(self.current_station)
        self.set_running_player()
        self.player.start()
        if self.is_recording:
            self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}")
            self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop"))
        else:
            self.recordAction.setText(
                f"starte Aufnahme von {self.urlCombo.currentText()}")
            self.recordAction.setIcon(QIcon.fromTheme("media-record"))
        self.statusLabel.setText(f"playing {self.urlCombo.currentText()}")
        self.setWindowTitle(self.urlCombo.currentText())

    def set_running_player(self):
        self.play_btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        self.rec_btn.setEnabled(True)

    def stop_preview(self):
        self.player.finish()
        self.play_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.rec_btn.setEnabled(False)
        self.statusLabel.setText("stopped")
        self.togglePlayerAction.setText("Wiedergabe starten")
        self.togglePlayerAction.setIcon(
            QIcon.fromTheme("media-playback-start"))

    def set_sound_level(self, level):
        self.player.set_sound_level(level)
        self.level_lbl.setText("Lautstärke " + str(level))
        self.player.setVolume(level)

    def update_volume_slider(self, level):
        self.level_lbl.setText("Lautstärke " + str(level))
        self.level_sld.blockSignals(True)
        self.level_sld.setValue(value)
        self.level_lbl.setText("Lautstärke " + str(level))
        self.level_sld.blockSignals(False)

    def recordRadio(self):
        if not self.is_recording:
            self.deleteOutFile()
            self.rec_url = self.current_station
            self.rec_name = self.urlCombo.currentText()
            cmd = ("wget -q " + self.rec_url + " -O " + self.outfile)
            print(cmd)
            self.is_recording = True
            self.process.startDetached(cmd)
            self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}")
            self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop"))
            self.rec_btn.setVisible(False)
            self.stoprec_btn.setVisible(True)
        else:
            self.stop_recording()

    def stop_recording(self):
        if self.is_recording:
            self.process.close()
            print("stoppe Aufnahme")
            self.is_recording = False
            QProcess.execute("killall wget")
            self.saveRecord()
            self.stoprec_btn.setVisible(False)
            self.rec_btn.setVisible(True)
            self.recordAction.setText(
                f"starte Aufnahme von {self.urlCombo.currentText()}")
            self.recordAction.setIcon(QIcon.fromTheme("media-record"))
        else:
            self.showTrayMessage("Hinweis", "keine Aufnahme gestartet",
                                 self.tIcon)

    def saveRecord(self):
        if not self.is_recording:
            print("saving Audio")
            musicfolder = QStandardPaths.standardLocations(
                QStandardPaths.MusicLocation)[0]
            recname = self.rec_name.replace("-", " ").replace(" - ",
                                                              " ") + ".mp3"
            infile = QFile(self.outfile)
            savefile, _ = QFileDialog.getSaveFileName(
                None, "Speichern als...", f'{musicfolder}/{recname}',
                "Audio (*.mp3)")
            if (savefile != ""):
                if QFile(savefile).exists:
                    QFile(savefile).remove()
                print(f"saving {savefile}")
                if not infile.copy(savefile):
                    QMessageBox.warning(
                        self, "Fehler",
                        f"File {savefile} {infile.errorString()}")
                print(f"Prozess-State: {str(self.process.state())}")
                if QFile(self.outfile).exists:
                    print(f"{self.outfile} existiert")
                    QFile(self.outfile).remove()

    def deleteOutFile(self):
        if QFile(self.outfile).exists:
            print(f"delete file {self.outfile}")
            if QFile(self.outfile).remove:
                print(f"{self.outfile} deleted")
            else:
                print(f"{self.outfile} not deleted")

    def getPID(self):
        print(f"{self.process.pid()} {self.process.processId()}")
Ejemplo n.º 10
0
 def start_subprocess(self, cmd, delay):
     args = shlex.split(cmd)
     p = QProcess(self)
     p.start(args[0], args[1:])
     p.waitForFinished(delay)
     p.close()
Ejemplo n.º 11
0
class RF_Qt(QMainWindow):
    """
    Class to create the main GUI window. It extends QMainWindow.
    The GUI allows the user to load up to four font files, provide a new font name, adjust some options,
    view a preview of font changes, and finally generate new TrueType font files.
    """
    def __init__(self):
        """
        Create the main window.
        :return:
        """
        super(RF_Qt, self).__init__()

        # Define variables
        self.fnt_styles = ['Regular', 'Italic', 'Bold', 'Bold Italic']
        self.fnt_sty_combo_list = []
        self.fnt_file_name_list = []
        self.font_files = None
        self.font_info = FontInfo()
        # Create a QProcess object, and connect it to appropriate slots
        self.cli_process = QProcess(self)
        self.cli_process.setProcessChannelMode(QProcess.MergedChannels)
        self.cli_process.readyRead.connect(self.read_proc_output)
        self.cli_process.started.connect(self.manage_proc)
        self.cli_process.finished.connect(self.manage_proc)
        self.cli_process.error.connect(self.manage_proc)

        # Style for all groupbox labels
        gb_style = 'QGroupBox { font-weight: bold; }'
        # Top level layout manager for the window.
        win_layout = QVBoxLayout()
        gb_fnt_files = QGroupBox('Font Files')
        gb_fnt_files.setStyleSheet(gb_style)
        grid_f_f = QGridLayout()
        grid_pos = 0

        # Font Files and styles #

        # Create a grid of font names with their respective font style combo boxes
        for i in range(len(self.fnt_styles)):
            self.fnt_file_name_list.append(QLabel('Load font file...'))
            cmb = QComboBox()
            cmb.addItem('')
            cmb.addItems(self.fnt_styles)
            cmb.setEnabled(False)
            cmb.setToolTip(
                '<qt/>If not automatically detected when the font is added, allows you to select what font '
                'sub-family the font file belongs to')
            self.fnt_sty_combo_list.append(cmb)
            row, col = helper.calc_grid_pos(grid_pos, 2)
            grid_f_f.addWidget(self.fnt_file_name_list[i], row, col)
            grid_pos += 1
            row, col = helper.calc_grid_pos(grid_pos, 2)
            grid_f_f.addWidget(self.fnt_sty_combo_list[i], row, col)
            grid_pos += 1
        grid_f_f.setColumnStretch(0, 1)
        gb_fnt_files.setLayout(grid_f_f)
        win_layout.addWidget(gb_fnt_files)

        # New Font Name #
        gb_fnt_name = QGroupBox('Font Family Name')
        gb_fnt_name.setStyleSheet(gb_style)
        hb_fnt_name = QHBoxLayout()
        self.new_fnt_name = QLineEdit()
        self.new_fnt_name.setToolTip('Enter a name for the modified font.')
        self.new_fnt_name.textEdited[str].connect(self.set_family_name)
        hb_fnt_name.addWidget(self.new_fnt_name)
        gb_fnt_name.setLayout(hb_fnt_name)
        win_layout.addWidget(gb_fnt_name)

        # Options #
        hb_options = QHBoxLayout()

        ## Kerning, Panose, Alt. Name ##
        gb_basic_opt = QGroupBox('Basic Options')
        gb_basic_opt.setStyleSheet(gb_style)
        hb_basic_opt = QHBoxLayout()
        self.basic_opt_list = []
        basic_tooltips = (
            '<qt/>Some readers and software require \'legacy\', or \'old style\' kerning to be '
            'present for kerning to work.',
            '<qt/>Kobo readers can get confused by PANOSE settings. This option sets all '
            'PANOSE information to 0, or \'any\'',
            '<qt/>Some fonts have issues with renaming. If the generated font does not have '
            'the same internal font name as you entered, try enabling this option.'
        )

        for opt, tip in zip(('Legacy Kerning', 'Clear PANOSE', 'Alt. Name'),
                            basic_tooltips):
            self.basic_opt_list.append(QCheckBox(opt))
            self.basic_opt_list[-1].setToolTip(tip)
            hb_basic_opt.addWidget(self.basic_opt_list[-1])

        gb_basic_opt.setLayout(hb_basic_opt)
        hb_options.addWidget(gb_basic_opt)

        ## Hinting ##
        gb_hint_opt = QGroupBox('Hinting Option')
        gb_hint_opt.setStyleSheet(gb_style)
        hb_hint_opt = QHBoxLayout()
        self.hint_opt_list = []
        hint_tooltips = (
            '<qt/>Keep font hinting as it exists in the orginal font files.<br />'
            'In most cases, this will look fine on most ebook reading devices.',
            '<qt/>Some fonts are manually, or "hand" hinted for specific display types (such as LCD). '
            'These fonts may not look good on other display types such as e-ink, therefore they can be '
            'removed.',
            '<qt/>If you don\'t like the original hinting, but you want your font to be hinted, '
            'this option will auto hint your font.')
        for opt, tip in zip(('Keep Existing', 'Remove Existing', 'AutoHint'),
                            hint_tooltips):
            self.hint_opt_list.append(QRadioButton(opt))
            self.hint_opt_list[-1].setToolTip(tip)
            self.hint_opt_list[-1].toggled.connect(self.set_hint)
            hb_hint_opt.addWidget(self.hint_opt_list[-1])

        self.hint_opt_list[0].setChecked(Qt.Checked)
        gb_hint_opt.setLayout(hb_hint_opt)
        hb_options.addWidget(gb_hint_opt)

        win_layout.addLayout(hb_options)

        ## Darken ##
        gb_dark_opt = QGroupBox('Darken Options')
        gb_dark_opt.setStyleSheet(gb_style)
        hb_dark_opt = QHBoxLayout()
        self.darken_opt = QCheckBox('Darken Font')
        self.darken_opt.setToolTip(
            '<qt/>Darken, or add weight to a font to make it easier to read on e-ink screens.'
        )
        self.darken_opt.toggled.connect(self.set_darken_opt)
        hb_dark_opt.addWidget(self.darken_opt)
        self.mod_bearing_opt = QCheckBox('Modify Bearings')
        self.mod_bearing_opt.setToolTip(
            '<qt/>By default, adding weight to a font increases glyph width. Enable this '
            'option to set the glyph width to be roughly equal to the original.<br/><br/>'
            'WARNING: This reduces the spacing between glyphs, and should not be used if '
            'you have added too much weight.')
        self.mod_bearing_opt.toggled.connect(self.set_mod_bearing)
        self.mod_bearing_opt.setEnabled(False)
        hb_dark_opt.addWidget(self.mod_bearing_opt)

        self.lbl = QLabel('Darken Amount:')
        self.lbl.setEnabled(False)
        hb_dark_opt.addWidget(self.lbl)
        self.darken_amount_opt = QSlider(Qt.Horizontal)
        self.darken_amount_opt.setMinimum(1)
        self.darken_amount_opt.setMaximum(50)
        self.darken_amount_opt.setValue(12)
        self.darken_amount_opt.setEnabled(False)
        self.darken_amount_opt.setToolTip(
            '<qt/>Set the amount to darken a font by. 50 is considered turning a '
            'regular weight font into a bold weight font. It is not recommended to '
            'darken a font that much however.')
        self.darken_amount_opt.valueChanged[int].connect(
            self.set_darken_amount)
        hb_dark_opt.addWidget(self.darken_amount_opt)
        self.darken_amount_lab = QLabel()
        self.darken_amount_lab.setText(str(self.darken_amount_opt.value()))
        self.darken_amount_lab.setEnabled(False)
        hb_dark_opt.addWidget(self.darken_amount_lab)
        gb_dark_opt.setLayout(hb_dark_opt)

        win_layout.addWidget(gb_dark_opt)

        # Buttons #
        hb_buttons = QHBoxLayout()
        #hb_buttons.addStretch()
        self.gen_ttf_btn = QPushButton('Generate TTF')
        self.gen_ttf_btn.setEnabled(False)
        self.gen_ttf_btn.setToolTip(
            '<qt/>Generate a new TrueType font based on the options chosen in this program. '
            '<br /><br />'
            'The new fonts are saved in a directory of your choosing.')
        self.gen_ttf_btn.clicked.connect(self.gen_ttf)
        hb_buttons.addWidget(self.gen_ttf_btn)
        self.load_font_btn = QPushButton('Load Fonts')
        self.load_font_btn.setToolTip('<qt/>Load font files to modify.')
        self.load_font_btn.clicked.connect(self.load_fonts)
        hb_buttons.addWidget(self.load_font_btn)
        self.prog_bar = QProgressBar()
        self.prog_bar.setRange(0, 100)
        hb_buttons.addWidget(self.prog_bar)
        win_layout.addLayout(hb_buttons)

        # Output Log #
        gb_log_win = QGroupBox('Log Window')
        gb_log_win.setStyleSheet(gb_style)
        vb_log = QVBoxLayout()
        out_font = QFont('Courier')
        out_font.setStyleHint(QFont.Monospace)
        self.log_win = QTextEdit()
        self.log_win.setAcceptRichText(False)
        self.log_win.setFont(out_font)
        vb_log.addWidget(self.log_win)
        gb_log_win.setLayout(vb_log)
        win_layout.addWidget(gb_log_win)

        # Show Window #
        self.setCentralWidget(QWidget(self))
        self.centralWidget().setLayout(win_layout)
        self.setWindowTitle('Readify Font')

        self.show()

        # Check if fontforge is actually in users PATH. If it isn't, prompt user to provice a location
        self.ff_path = helper.which('fontforge')
        if not self.ff_path:
            self.set_ff_path()

    def set_ff_path(self):
        """
        Let user choose location of fontforge
        :return:
        """
        QMessageBox.warning(
            self, 'Fontforge Missing!',
            'FontForge is not in your PATH! If it is installed, '
            'please locate it now.', QMessageBox.Ok, QMessageBox.Ok)
        path = QFileDialog.getOpenFileName(self, 'Locate FontForge...')
        if path[0]:
            self.ff_path = os.path.normpath(path[0])

    def set_basic_opt(self):
        """
        Handler to set basic options
        :return:
        """
        opt = self.sender()
        if opt.isChecked():
            if 'kerning' in opt.text().lower():
                self.font_info.leg_kern = True
            if 'panose' in opt.text().lower():
                self.font_info.strip_panose = True
            if 'alt' in opt.text().lower():
                self.font_info.name_hack = True
        else:
            if 'kerning' in opt.text().lower():
                self.font_info.leg_kern = False
            if 'panose' in opt.text().lower():
                self.font_info.strip_panose = False
            if 'alt' in opt.text().lower():
                self.font_info.name_hack = False

    def set_family_name(self, name):
        """
        Handler to set name option. Also checks if buttons need enabling
        :param name:
        :return:
        """
        if name:
            if helper.valid_filename(name):
                self.font_info.font_name = name
                if self.font_files:
                    self.gen_ttf_btn.setEnabled(True)
            else:
                self.gen_ttf_btn.setEnabled(False)
        else:
            self.gen_ttf_btn.setEnabled(False)

    def set_darken_amount(self, amount):
        """
        Set Darken amount slider
        :param amount:
        :return:
        """
        self.darken_amount_lab.setText(str(amount))
        self.font_info.add_weight = amount

    def set_hint(self):
        """
        Set hint options
        :return:
        """
        hint = self.sender()
        if hint.isChecked():
            if 'keep' in hint.text().lower():
                self.font_info.change_hint = 'keep'
            elif 'remove' in hint.text().lower():
                self.font_info.change_hint = 'remove'
            elif 'auto' in hint.text().lower():
                self.font_info.change_hint = 'auto'

    def set_darken_opt(self):
        """
        Set darken options
        :return:
        """
        if self.sender().isChecked():
            self.mod_bearing_opt.setEnabled(True)
            self.lbl.setEnabled(True)
            self.darken_amount_lab.setEnabled(True)
            self.darken_amount_opt.setEnabled(True)
            self.set_darken_amount(self.darken_amount_opt.value())
        else:
            self.mod_bearing_opt.setEnabled(False)
            self.lbl.setEnabled(False)
            self.darken_amount_lab.setEnabled(False)
            self.darken_amount_opt.setEnabled(False)
            self.set_darken_amount(0)

    def set_mod_bearing(self):
        """
        Set mod bearing options
        :return:
        """
        if self.mod_bearing_opt.isChecked():
            self.font_info.mod_bearings = True
        else:
            self.font_info.mod_bearings = False

    def load_fonts(self):
        """
        Load fonts from a directory, and sets appropriate options
        :return:
        """
        f_f = QFileDialog.getOpenFileNames(self, 'Load Fonts', '',
                                           'Font Files (*.ttf *.otf)')
        if f_f[0]:
            for f_label, f_style in zip(self.fnt_file_name_list,
                                        self.fnt_sty_combo_list):
                f_label.setText('Load font file...')
                f_style.setCurrentIndex(SEL_NONE)
                f_style.setEnabled(False)

            self.font_files = f_f[0]
            f_f_names = []
            for file in self.font_files:
                file = os.path.normpath(file)
                base, fn = os.path.split(file)
                f_f_names.append(fn)

            for f_file, f_label, f_style in zip(f_f_names,
                                                self.fnt_file_name_list,
                                                self.fnt_sty_combo_list):
                f_label.setText(f_file)
                f_style.setEnabled(True)
                if 'regular' in f_file.lower():
                    f_style.setCurrentIndex(SEL_REGULAR)
                elif 'bold' in f_file.lower() and 'italic' in f_file.lower():
                    f_style.setCurrentIndex(SEL_BOLDITALIC)
                elif 'bold' in f_file.lower():
                    f_style.setCurrentIndex(SEL_BOLD)
                elif 'italic' in f_file.lower():
                    f_style.setCurrentIndex(SEL_ITALIC)

            if self.new_fnt_name.text():
                self.gen_ttf_btn.setEnabled(True)

    def read_proc_output(self):
        """
        Read any stdout data available from the process and displays it in the output log window.
        :return:
        """
        if sys.version_info.major == 2:
            output = unicode(self.cli_process.readAllStandardOutput(),
                             encoding=sys.getdefaultencoding())
        else:
            output = str(self.cli_process.readAllStandardOutput(),
                         encoding=sys.getdefaultencoding())
        self.log_win.append(output)

    def manage_proc(self):
        """
        Manage the progress bar
        :return:
        """
        proc = self.sender()
        if proc.state() == QProcess.Running:
            self.prog_bar.setRange(0, 0)
        if proc.state() == QProcess.NotRunning:
            self.prog_bar.setRange(0, 100)
            self.prog_bar.setValue(100)

    def gen_ttf(self):
        """
        Generate modified TrueType font files, by calling the CLI script with the appropriate arguments.
        :param prev:
        :return:
        """
        self.log_win.clear()
        if not self.ff_path:
            self.set_ff_path()
        if self.ff_path:
            if not self.font_info.out_dir:
                save_dir = os.path.normpath(
                    QFileDialog.getExistingDirectory(
                        self,
                        'Select save directory...',
                        options=QFileDialog.ShowDirsOnly))
                if save_dir == '.' or save_dir == '':
                    return
                else:
                    self.font_info.out_dir = save_dir
            else:
                save_dir = os.path.normpath(
                    QFileDialog.getExistingDirectory(
                        self,
                        'Select Save directory...',
                        self.font_info.out_dir,
                        options=QFileDialog.ShowDirsOnly))
                if save_dir == '.' or save_dir == '':
                    return
                else:
                    self.font_info.out_dir = save_dir

            for file, style in zip(self.font_files, self.fnt_sty_combo_list):
                if style.currentIndex() == SEL_REGULAR:
                    self.font_info.font_file_reg = file
                elif style.currentIndex() == SEL_BOLDITALIC:
                    self.font_info.font_file_bi = file
                elif style.currentIndex() == SEL_BOLD:
                    self.font_info.font_file_bd = file
                elif style.currentIndex() == SEL_ITALIC:
                    self.font_info.font_file_it = file

            cli_opt_list = self.font_info.gen_cli_command()
            self.cli_process.start(self.ff_path, cli_opt_list)

    def closeEvent(self, event):
        """
        Cleaning up...
        :param event:
        :return:
        """
        self.cli_process.close()
        event.accept()
Ejemplo n.º 12
0
class PipManager(QObject):
    """
    Manage `pip` processes.
    """
    started = pyqtSignal()
    finished = pyqtSignal()
    failed = pyqtSignal()
    textChanged = pyqtSignal(str)

    def __init__(self, venv_dir, venv_name, parent=None):
        super().__init__(parent)

        self._venv_dir = venv_dir
        self._venv_name = venv_name

        self._process = QProcess(self)
        self._process.setWorkingDirectory(venv_dir)

        self._process.readyReadStandardOutput.connect(
            self.on_ready_read_stdout)
        self._process.readyReadStandardError.connect(self.on_ready_read_stderr)

        # started
        self._process.started.connect(self.started)

        # updated
        self._process.stateChanged.connect(self.on_state_changed)

        # finished
        self._process.finished.connect(self.finished)
        self._process.finished.connect(self.on_finished)

    def run_pip(self, command="", options=None):
        """
        Activate the virtual environment and run pip commands.
        """
        if has_bash():
            if options is None:
                options = []

            venv_path = os.path.join(self._venv_dir, self._venv_name)
            pip = f"pip {command} {' '.join(options)};"
            pipdeptree = f"pipdeptree {' '.join(options)};"
            task = pipdeptree if command == "pipdeptree" else pip

            script = (f"source {venv_path}/bin/activate;"
                      f"{task}"
                      "deactivate;")
            self._process.start("bash", ["-c", script])

    def process_stop(self):
        """Stop the process."""
        self._process.close()

    @pyqtSlot(QProcess.ProcessState)
    def on_state_changed(self, state):
        """Show the current process state.
        """
        if state == QProcess.Starting:
            #print("[PROCESS]: Started")
            logger.debug("Started")
        elif state == QProcess.Running:
            #print("[PROCESS]: Running")
            logger.debug("Running")
        elif state == QProcess.NotRunning:
            #print("[PROCESS]: Stopped")
            logger.info("Done.")
            self.textChanged.emit("\n\nPress [ESC] to continue...")

    @pyqtSlot(int, QProcess.ExitStatus)
    def on_finished(self, exitCode):
        """Show exit code when finished.
        """
        #print(f"[PROCESS]: Exit code: {exitCode}")
        logger.debug(f"Exit code: {exitCode}")
        self._process.kill()

    @pyqtSlot()
    def on_ready_read_stdout(self):
        """Read from `stdout` and send the output to `update_status()`.
        """
        message = self._process.readAllStandardOutput().data().decode().strip()
        #print(f"[PIP]: {message}")
        logger.debug(message)
        self.textChanged.emit(message)

    @pyqtSlot()
    def on_ready_read_stderr(self):
        """Read from `stderr`, then kill the process.
        """
        message = self._process.readAllStandardError().data().decode().strip()
        #print(f"[ERROR]: {message}")
        logger.error(message)
        self.textChanged.emit(message)
        self.failed.emit()
        self._process.kill()
Ejemplo n.º 13
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # Set up the user interface from Designer.
        self.setupUi(self)

        # Make some local modifications.
        # self.colorDepthCombo.addItem("2 colors (1 bit per pixel)")
        # self.setWindowIcon(QIcon('images/icons8-google-news-24.png'))
        self.crawlNone = 'Nothing had been crawled, try another url.'
        self.exit.triggered.connect(self.close)
        if self.stackedWidget.currentIndex() == 0:
            self.simiBtn.setStyleSheet("""
                                    color: #fff;
                                    text-decoration: none;
                                    background-color: #28a745;
                                    border-color:#28a745;
                                    text-decoration: none;""")
        elif self.stackedWidget.currentIndex() == 1:
            self.kwBtn.setStyleSheet("""
                                    color: #fff;
                                    text-decoration: none;
                                    background-color: #28a745;
                                    border-color:#28a745;
                                    text-decoration: none;""")
        else:
            self.settingsBtn.setStyleSheet("""
                                    color: #fff;
                                    text-decoration: none;
                                    background-color: #28a745;
                                    border-color:#28a745;
                                    text-decoration: none;""")
        # Connect up the buttons.
        self.url1CrawlBtn.clicked.connect(self.url1CrawlBtn_on_click)
        self.url2CrawlBtn.clicked.connect(self.url2CrawlBtn_on_click)
        self.detectBtn.clicked.connect(self.detectBtn_on_click)
        # self.simiBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(0))
        # self.kwBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(1))
        # self.settingsBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(2))

        self.simiBtn.clicked.connect(lambda: self.from_to(0, self.simiBtn))
        self.kwBtn.clicked.connect(lambda: self.from_to(1, self.kwBtn))
        self.settingsBtn.clicked.connect(lambda: self.from_to(2, self.settingsBtn))
        self.kwDetectBtn.clicked.connect(self.kwDetectBtn_on_click)
        self.getlistBtn.clicked.connect(self.getlistBtn_on_click)

        self.searchBtn.clicked.connect(self.searchBtn_on_click)
        self.closeSearchBtn.clicked.connect(self.closeSearchBtn_on_click)

        self.mpComfirmBtn.clicked.connect(self.mpComfirmBtn_on_click)

        # 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: self.searchBtn.setEnabled(False))
        self.process.finished.connect(lambda: self.searchBtn.setEnabled(True))

        self.coll_name = None


        output = subprocess.Popen(["sed -n '74p' ../news_spider/settings.py"], stdout=subprocess.PIPE,
                                  shell=True).communicate()
        self.SERVERTEXT = output[0].decode('utf-8')
        output = subprocess.Popen(["sed -n '75p' ../news_spider/settings.py"], stdout=subprocess.PIPE,
                                  shell=True).communicate()
        self.PORTTEXT = output[0].decode('utf-8')
        self.configList.setText(self.SERVERTEXT+self.PORTTEXT)
        self.PORT = self.PORTTEXT.split('=')[1]


        # self.threadpool = QThreadPool()
        # print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

    def mpComfirmBtn_on_click(self):
        mpValue = self.mpValue.text()
        print(mpValue)
        if mpValue:
            self.process.start('sed',
                               ['-i', 's/^MONGODB_PORT=.*/MONGODB_PORT={0}/'.format(mpValue),
                                '../news_spider/settings.py'])
            output = subprocess.Popen(["sed -n '74p' ../news_spider/settings.py"], stdout=subprocess.PIPE,
                                      shell=True).communicate()
            self.SERVERTEXT = output[0].decode('utf-8')
            output = subprocess.Popen(["sed -n '75p' ../news_spider/settings.py"], stdout=subprocess.PIPE,
                                      shell=True).communicate()
            self.PORTTEXT = output[0].decode('utf-8')
            self.configList.clear()
            self.configList.setText(self.SERVERTEXT + self.PORTTEXT)
            self.PORT = self.PORTTEXT.split('=')[1]

    def from_to(self, to, toBtn):
        if self.stackedWidget.currentIndex() == to:
            pass
        else:
            if self.stackedWidget.currentIndex() == 0:
                self.simiBtn.setStyleSheet("""""")
            elif self.stackedWidget.currentIndex() == 1:
                self.kwBtn.setStyleSheet("""""")
            else:
                self.settingsBtn.setStyleSheet("""""")
            self.stackedWidget.setCurrentIndex(to)
            toBtn.setStyleSheet(""" color: #fff;
                                     text-decoration: none;
                                     background-color: #28a745;
                                     border-color:#28a745;
                                     text-decoration: none;
                                 """)

    # def closeStatBtn_on_click(self):
    #     print('stopStat button clicked')
    #     self.statBtn.setEnabled(True)
    #     print(self.p.returncode)
    #     # if self.process.isOpen():
    #     #     self.process.close()
    #     #     print('close process')
    #
    # def statBtn_on_click(self):
    #     print('stat button clicked')
    #     self.statBtn.setEnabled(False)
    #     self.output.clear()
    #     # self.process.start('ping', ['127.0.0.1'])
    #     self.p = subprocess.Popen("scrapy crawl news_spider", cwd='/home/watmel/PycharmProjects/news_similarity_detection/nnorder/news_spider/news_spider/spiders',shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    #     cursor = self.output.textCursor()
    #     for line in self.p.stdout.readlines():
    #         cursor.insertText(str(line, 'utf-8'))
    #     if self.p.returncode != 0:
    #         cursor.insertText("error")

    def closeSearchBtn_on_click(self):
        print('stopStat button clicked')
        if self.process.isOpen():
            self.process.close()
            print('close process')

    # def execute_this_fn(self):
    #     pc = post_crawl()
    #     tag_rank = pc.get_tag_rank()
    #     return tag_rank
    def print_output(self, s):
        tag_to_str = """<table><tr><td>{0}</td><td>{1}</td></tr>""".format('关键词', '热度值')
        for t in s:
            tag_to_str += '<tr><td><b>{0}:</b></td><td>{1}</td></tr>'.format(t['_id'], t['value'])
        tag_to_str += """</table>"""
        self.result.setText(tag_to_str)

    def thread_start(self):
        self.statBtn.setEnabled(False)
        print("THREAD START!")

    def thread_complete(self):
        self.statBtn.setEnabled(True)
        print("THREAD COMPLETE!")

    def searchBtn_on_click(self):
        # worker = Worker(self.execute_this_fn)
        # worker.signals.started.connect(self.thread_start)
        # worker.signals.result.connect(self.print_output)
        # worker.signals.finished.connect(self.thread_complete)
        # self.threadpool.start(worker)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        # self.process.start('ping', ['127.0.0.1'])
        kw_urlstyle = self.keyword.text()
        print(kw_urlstyle)
        if kw_urlstyle:
            self.output.clear()
            ck = check_kw(kw_urlstyle, self.PORT)
            flag, coll_name = ck.is_crawled()
            self.coll_name = coll_name
            if not flag:
                self.process.start('sed', ['-i', 's/^\s*kw=".*"/    kw="{0}"/'.format(kw_urlstyle),
                                           '../news_spider/spiders/news_spider.py'])

                self.process.waitForFinished(10)
                # self.process.start('sed',['-i','s/^MONGODB_COLLECTION=".*"/MONGODB_COLLECTION="{0}"/'.format(self.coll_name),'../news_spider/spiders/news_spider.py'])
                self.process.start('sed',
                                   ['-i', 's/^MONGODB_COLLECTION=".*"/MONGODB_COLLECTION="{0}"/'.format(self.coll_name),
                                    '../news_spider/settings.py'])
                self.process.waitForFinished(10)

                self.process.start('scrapy crawl news_spider')
            else:
                self.output.setPlainText('该关键词已抓取,可以直接点击获取列表按钮')

        else:
            pass

    def getlistBtn_on_click(self):
        pc = post_crawl(self.coll_name, self.PORT)
        title_list = pc.get_title_list()
        print("title_list:" + str(title_list))
        self.leftnews.addItems(title_list)
        self.rightnews.addItems(title_list)
        self.leftnews.activated.connect(self.left_combox_on_activate)
        self.rightnews.activated.connect(self.right_combox_on_activate)

    def kwDetectBtn_on_click(self):
        text1 = self.leftnewsContent.toPlainText()
        text2 = self.rightnewsContent.toPlainText()
        if text1 and text2:
            s1, t1 = self.get_simhash(text=text1)
            s2, t2 = self.get_simhash(text=text2)
            dis = self.get_distance(s1, s2)
            self.searchDistance.setText('{0}%, 在32位哈希值有{1}位不同'.format(str((1 - dis / 32) * 100), dis.__str__()))

    def left_combox_on_activate(self):
        title = self.leftnews.currentText()
        pc = post_crawl(self.coll_name, self.PORT)
        text = pc.get_text_by_title(title)
        self.leftnewsContent.setText(text)

    def right_combox_on_activate(self):
        title = self.rightnews.currentText()
        pc = post_crawl(self.coll_name, self.PORT)
        text = pc.get_text_by_title(title)
        self.rightnewsContent.setText(text)

    def dataReady(self):
        cursor = self.output.textCursor()
        cursor.movePosition(cursor.End)
        cursor.insertText(str(self.process.readAll(), 'utf-8'))
        self.output.ensureCursorVisible()

    def url1CrawlBtn_on_click(self):
        # print('url1CrawlBtn clicked')
        self.url1.setText('http://news.sina.com.cn/c/2018-06-07/doc-ihcscwwz9278602.shtml')
        if self.url1.text():
            clean_text, title = myGoose(url=self.url1.text()).get_cleaned_text()
            # clean_text = '123'
            if clean_text:
                self.news1.setPlainText(clean_text)
                self.title1.setText(title)
            else:
                self.news1.setPlainText(self.crawlNone)

    def url2CrawlBtn_on_click(self):
        # print('url2CrawlBtn clicked')
        self.url2.setText('http://china.chinadaily.com.cn/2018-06/08/content_36349835.htm')
        if self.url2.text():
            clean_text, title = myGoose(url=self.url2.text()).get_cleaned_text()
            # clean_text = '123'
            if clean_text:
                self.news2.setPlainText(clean_text)
                self.title2.setText(title)
            else:
                self.news2.setPlainText(self.crawlNone)

    def tag_to_str(self, tag):
        # tag_to_str=''
        # for t in tag:
        #     tag_to_str+='<b>{0}</b>: {1}<br/>'.format(t[0],t[1])
        tag_to_str = """<table><tr><td>{0}</td><td>{1}</td></tr>""".format('关键词', '权重')
        for t in tag:
            tag_to_str += '<tr><td><b>{0}:</b></td><td>{1}</td></tr>'.format(t[0], t[1])
        tag_to_str += """</table>"""
        return tag_to_str

    def detectBtn_on_click(self):
        print('detectBtn clicked')
        text1 = self.news1.toPlainText()
        text2 = self.news2.toPlainText()
        if text1 and text2:
            s1, t1 = self.get_simhash(text=text1)
            s2, t2 = self.get_simhash(text=text2)
            self.tag1.setText(self.tag_to_str(t1))
            self.tag2.setText(self.tag_to_str(t2))
            dis = self.get_distance(s1, s2)
            self.distance.setText('{0}%, 在32位哈希值有{1}位不同'.format(str((1 - dis / 32) * 100), dis.__str__()))

    def get_simhash(self, text):
        pair = jieba.analyse.extract_tags(text, topK=20, withWeight=True)
        return MySimHash().get_simhash(pair), pair

    def get_distance(self, s1, s2):
        return MySimHash().get_distance(s1, s2)
Ejemplo n.º 14
0
class MainWindow(QMainWindow, Ui_MainWindow):

    # Create settings for the software
    settings = QSettings('GGGG', 'Game Genie Good Guy')
    settings.setFallbacksEnabled(False)
    version = '1.0.0'

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        # Load the ui
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # Set the MainWindow Title
        self.setWindowTitle('Game Genie Good Guy - ' + self.version)
        # When the software are closed on console the software are closed
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        self.ui.system.addItem("Game Boy/Gear/Master System")
        self.ui.system.addItem("Genesis/Mega Drive (no SMD roms)")
        self.ui.system.addItem("Nintendo")
        self.ui.system.addItem("Super Nintendo")
        self.ui.browse.clicked.connect(self.browse)
        self.ui.patch.clicked.connect(self.generateRom)
        self.ui.ips.clicked.connect(self.generateIPS)
        self.ui.log.setReadOnly(True)
        self.process = QProcess()
        self.process.readyReadStandardOutput.connect(self.printLog)
        if self.settings.value("rom"):
            self.ui.rom.setText(self.settings.value("rom"))
        if self.settings.value("system"):
            self.ui.system.setCurrentIndex(int(self.settings.value("system")))
        # Show the form
        self.show()

    def browse(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(
            self,
            "QFileDialog.getOpenFileName()",
            "",
            "All Files (*);;",
            options=options)
        self.ui.rom.setText(str(fileName))
        self.settings.setValue("rom", str(fileName))

    def printLog(self):
        self.ui.log.clear()
        result = self.process.readAllStandardOutput().data().decode()
        self.ui.log.appendPlainText(result)

    def generateRom(self):
        self.rom = str(self.ui.rom.text())
        name, ext = os.path.splitext(self.rom)
        self.newrom = "{name}-{uid}{ext}".format(name=name, uid='new', ext=ext)
        system = int(self.ui.system.currentIndex()) + 1
        codes = str(self.ui.codes.toPlainText())
        self.settings.setValue("system", system - 1)
        system = str(system)
        if self.rom != '':
            self.process.start('./GGGG "' + codes + '" ' + system + ' "' +
                               self.rom + '" "' + self.newrom + '"')
            self.process.waitForFinished()
            self.process.close()

    # Based on https://github.com/fbeaudet/ips.py/blob/master/ips.py
    def generateIPS(self):
        self.generateRom()
        FILE_LIMIT = 0x1000000  #16MB
        PATCH_ASCII = bytes((0x50, 0x41, 0x54, 0x43, 0x48))
        EOF_ASCII = bytes((0x45, 0x4f, 0x46))
        name, ext = os.path.splitext(self.rom)
        ipsfile = "{name}{uid}{ext}".format(name=name, uid='', ext='.ips')
        original = open(self.rom, 'rb').read()
        modified = open(self.newrom, 'rb').read()
        patch = open(ipsfile, 'wb')
        recording = False
        record = bytearray()
        size = bytearray(2)

        if len(modified) > FILE_LIMIT:
            self.ui.log.appendPlainText(
                "\nModified file is too large for IPS format. Max: 16MB.")

        patch.write(PATCH_ASCII)
        for a in range(len(modified)):
            if not recording:
                if len(original) <= a or modified[a] != original[a]:
                    recording = True
                    record = bytearray()
                    offset = bytearray(3)

                    if a == EOF_ASCII:
                        record.append(modified[a - 1])

                    record.append(modified[a])
                    for x in range(3):
                        offset[x] = (a >> (16 - x * 8)) % 256

                    patch.write(offset)

                    if a == len(modified) - 1:
                        recording = False
                        patch.write(b'\x00\x01')
                        patch.write(record)
            else:
                if len(original) <= a or modified[a] != original[a]:
                    record.append(modified[a])

                    if a == len(modified) - 1:
                        recording = False
                        for x in range(2):
                            size[x] = len(record) >> (8 - x * 8)

                        patch.write(size)
                        patch.write(record)
                else:
                    recording = False
                    for x in range(2):
                        size[x] = len(record) >> (8 - x * 8)

                    patch.write(size)
                    patch.write(record)

        patch.write(EOF_ASCII)
        patch.close()
        self.ui.log.appendPlainText("\nIPS file generated")
Ejemplo n.º 15
0
class runV2raycore(QObject):
    """
    you should emit a signal to start or stop a program.
    """
    start = pyqtSignal()
    stop = pyqtSignal()

    def __init__(self,
                 outputTextEdit,
                 v2rayPath="v2ray",
                 v2rayOption="",
                 bridgetreasureChest=False):
        super().__init__()
        self.outputTextEdit = outputTextEdit
        self.v2rayPath = v2rayPath
        self.v2rayOption = v2rayOption
        self.bridgetreasureChest = bridgetreasureChest
        if not self.bridgetreasureChest:
            from bridgehouse.extension import bridgetreasureChest
            self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest(
            )

        self.v2rayProcess = QProcess()
        self.v2rayProcess.setProcessChannelMode(QProcess.MergedChannels)
        self.v2rayProcess.setProcessEnvironment(
            QProcessEnvironment.systemEnvironment())

        self.v2rayProcess.readyRead.connect(self.setoutputTextEdit)
        self.v2rayProcess.started.connect(self.oncreatePIDFile)
        self.start.connect(self.onstart)
        self.stop.connect(self.onstop)
        self.translate = QCoreApplication.translate
        self.pidFile = ".v2rayPID"

    def onstart(self):
        if (self.v2rayProcess.state() == QProcess.NotRunning):
            self.outputTextEdit.clear()
            command = self.translate("runV2raycore",
                                     "v2ray file path had no seted.")
            if (self.v2rayPath):
                checkSpaces = re.search(" ", self.v2rayPath)
                if checkSpaces:
                    # in fact, you can just keep this line.
                    # do not need check spaces
                    command = '"' + self.v2rayPath + '" ' + self.v2rayOption
                else:
                    command = "{} {}".format(self.v2rayPath, self.v2rayOption)
                self.killOrphanProcess()
                self.v2rayProcess.start(command, QIODevice.ReadWrite)
                self.outputTextEdit.insertPlainText("{}\n\n".format(command))

            if (self.v2rayProcess.state() == QProcess.NotRunning):
                self.outputTextEdit.moveCursor(QTextCursor.End)
                self.outputTextEdit.append("\n")
                self.outputTextEdit.insertPlainText(str(
                    "{}\n".format(command)))
                self.outputTextEdit.insertPlainText(
                    str(
                        self.translate("runV2raycore",
                                       "{}   Error Code:{}").format(
                                           self.v2rayProcess.errorString(),
                                           self.v2rayProcess.error())))
                self.outputTextEdit.moveCursor(QTextCursor.End)

            self.outputTextEdit.textChanged.connect(self.getV2raycoreVersion)

    def killOrphanProcess(self):
        openFile = QFileInfo(self.pidFile)

        fileName = openFile.fileName()
        if QFile.exists(fileName):
            openFile = QFile(fileName)
        else:
            return
        v2rayPID = None

        try:
            openFile.open(QIODevice.ReadOnly | QIODevice.Text)
            v2rayPID = str(openFile.readAll(), "utf-8")
        except Exception:
            pass

        try:
            os.kill(int(v2rayPID), signal.SIGTERM)
        except Exception:
            pass

    def oncreatePIDFile(self):
        if self.v2rayProcess.state() == QProcess.NotRunning:
            return

        outFile = QFileInfo(self.pidFile)
        fileName = outFile.fileName()
        if QFile.exists(fileName):
            QFile.remove(fileName)
        outFile = QFile(fileName)

        v2rayPID = str(self.v2rayProcess.processId())
        qDebug("process ID is: {}".format(v2rayPID))
        try:
            outFile.open(QIODevice.WriteOnly | QIODevice.Text)
            outFile.write(codecs.encode(v2rayPID, "utf-8"))
        except Exception:
            pass
        outFile.close()

    def getV2raycoreVersion(self):
        text = self.outputTextEdit.toPlainText()
        version = re.findall("V2Ray v\d\.\d{1,2}", text)
        failtostart = re.findall("Failed to start App", text)
        if (version):
            version = version[0].split(" ")[1]
            self.bridgetreasureChest.setV2raycoreVersion(version)
        if (failtostart):
            self.outputTextEdit.textChanged.disconnect(
                self.getV2raycoreVersion)
            self.onstop()

    def onstop(self):
        if (self.v2rayProcess.state() == QProcess.Running):
            self.v2rayProcess.close()
            self.v2rayProcess.kill()
            self.outputTextEdit.moveCursor(QTextCursor.End)
            self.outputTextEdit.append("\n\n")
            self.outputTextEdit.insertPlainText(
                str(
                    self.translate("runV2raycore", "{} is stop now...").format(
                        self.v2rayPath)))
            self.outputTextEdit.insertPlainText(
                str(
                    self.translate("runV2raycore",
                                   "\n{} is ready to run...").format(
                                       self.v2rayPath)))
            self.outputTextEdit.moveCursor(QTextCursor.End)

    def setoutputTextEdit(self):
        self.outputTextEdit.moveCursor(QTextCursor.End)
        self.outputTextEdit.insertPlainText(
            str(self.v2rayProcess.readAllStandardOutput(), "utf-8"))
        self.outputTextEdit.insertPlainText(
            str(self.v2rayProcess.readAllStandardError(), "utf-8"))
        self.outputTextEdit.moveCursor(QTextCursor.End)
Ejemplo n.º 16
0
class Heimdall(QObject):
    """Heimdall service

    This service maintains a Raspberry Pi or similar device as an automatic display that works without user interaction.

    Connection timeline:
        0. _setup_reestablish_tunnel - Set up a timer to try to reestablish the tunnel. This step is only performed
           if the tunnel was previously active and failed, and exist to provide a reconnection delay.

        1. start_tunnel - Run SSH to the Raspberry pi

        2. try_connect - Attempt to use the SSH connection to contact i3/Sway.
           If this fails, an automatic retry after a timeout is done.

        3. setup - Take over the i3/Sway setup

    """
    def __init__(self, parent=None):
        super().__init__(parent)

        self.reconnect_timer = QTimer()
        self.ssh_proc = QProcess()

        self.bus = QtDBus.QDBusConnection.sessionBus()
        self.dbus_adaptor = DBusAdaptor(self)
        self.contextual_executor = ContextualExecutor(self)

        if not self.bus.isConnected():
            raise Exception("Failed to connect to dbus!")

        self.bus.registerObject("/heimdall", self)
        self.bus.registerService("com.troshchinskiy.Heimdall")

        self.homedir = os.environ['HOME'] + "/.heimdall"
        self.read_config()
        self.start_tunnel()

    def echo(self, text):
        return text

    def version(self):
        return "0.1"

    def connect(self):
        self.ssh = Popen(["ssh"], stdout=PIPE)

    def read_config(self):
        filename = self.homedir + '/config.json'

        print("Loading config file {}...\n".format(filename))

        with open(filename, 'r') as conf_h:
            self.config = json.load(conf_h)

    def start_tunnel(self):
        if self.ssh_proc and self.ssh_proc.isOpen():
            print("Tunnel already running")
            return

        print("Starting tunnel...\n")

        sway_pid = self._run_remote(["pidof", "sway"])
        if sway_pid is None:
            raise Exception('Sway is not running!')

        home_dir = self._run_remote(["echo", '$HOME'])
        uid = self._run_remote(["echo", '$UID'])

        self.remote_socket = "/run/user/" + uid + "/sway-ipc." + uid + "." + sway_pid + ".sock"
        self.local_socket = self.homedir + "/sway.sock"

        print("Sway pid: '{}'".format(sway_pid))
        print("Home dir: '{}'".format(home_dir))
        print("UID     : '{}'".format(uid))
        print("Socket  : '{}'".format(self.remote_socket))

        if os.path.exists(self.local_socket):
            os.remove(self.local_socket)

        r = self.config['remote']

        command_args = [
            "-i", r['ssh-key'], "-p", r['port'], "-l", r['user'], "-R",
            r['backwards-port'] + ":127.0.0.1:" + r['local-ssh-port'], "-L",
            self.local_socket + ':' + self.remote_socket, r['server']
        ]

        print("Running command: ssh {}".format(command_args))

        self.ssh_proc.started.connect(self._ssh_process_started)
        self.ssh_proc.errorOccurred.connect(self._ssh_process_error)
        self.ssh_proc.finished.connect(self._ssh_process_finished)

        self.ssh_proc.start(self.config['commands']['ssh'], command_args)

    def try_connect(self):
        """Try to connect to i3/Sway.

        SSH takes a while to perform the port forwarding, so we may do this several times, until it starts
        working.
        """
        print("Trying to connect to Sway/i3 at socket {}...".format(
            self.local_socket))
        try:
            self.i3 = Connection(socket_path=self.local_socket)
        except ConnectionRefusedError:
            print("Not connected yet!")
            return
        except FileNotFoundError:
            print("Socket doesn't exist yet!!")
            return

        self.connect_timer.stop()
        self.setup()

    def setup(self):
        try:
            print("Setting up Sway/i3...")
            self.wm_version = self.i3.get_version()
            print("Connected to Sway/i3 version {}".format(self.wm_version))

            print("Resetting workspace...")
            for workspace in self.i3.get_workspaces():
                print("Deleting workspace {}".format(workspace.name))
                self.i3.command('[workspace="{}"] kill'.format(workspace.name))

            print("Executing commands...")
            for cmd in self.config['startup']['remote-run']:
                print("\tExecuting: {}".format(cmd))
                self._run_remote(cmd)

            print("Setting up workspaces...")
            wsnum = 0
            for wsconf in self.config['startup']['workspaces']:
                wsnum += 1
                self.i3.command("workspace {}".format(wsnum))
                self.i3.command('rename workspace "{}" to "{}"'.format(
                    wsnum, wsconf['name']))

                for wscmd in wsconf['commands']:
                    self.i3_command(wscmd)

        except (ConnectionRefusedError, FileNotFoundError):
            self._setup_reestablish_tunnel()

    def i3_command(self, command):

        command = command.replace('$TERM_EXEC_KEEP',
                                  self.config['remote']['terminal-exec-keep'])
        command = command.replace('$TERM_EXEC',
                                  self.config['remote']['terminal-exec'])
        command = command.replace('$TERM', self.config['remote']['terminal'])
        command = command.replace(
            '$SSH_TO_HOST', self.config['commands']['ssh'] + " -p " +
            self.config['remote']['backwards-port'] + " -t " +
            os.environ['USER'] + '@localhost ')

        print("Executing command: " + command)
        self.i3.command(command)

    def contextual_action(self, environment, path, command):
        self.contextual_executor.execute(environment, path, command)

    def stop_tunnel(self):
        """Stop the tunnel, if it's running"""

        if self.ssh_proc and self.ssh_proc.isOpen():
            print("Stopping ssh\n")
            self.ssh_proc.kill()
            self.ssh_proc.close()

        if os.path.exists(self.local_socket):
            os.remove(self.local_socket)

    def _setup_reestablish_tunnel(self):
        """Re-establish the SSH tunnel and begin again the process of syncing up"""

        self.stop_tunnel()
        self.reconnect_timer.timeout.connect(self.start_tunnel())
        self.reconnect_timer.singleShot(True)
        self.reconnect_timer.start(100)

    def _ssh_process_started(self):
        print("SSH process started!")
        self.connect_timer = QTimer()
        self.connect_timer.timeout.connect(self.try_connect)
        self.connect_timer.start(50)

    def _ssh_process_error(self, error):
        print("SSH process failed with error {}!".format(error))

    def _ssh_process_finished(self, exit_code, exit_status):
        print("SSH process exited with code {}, status {}!".format(
            exit_code, exit_status))

    def _run_remote(self, command):
        r = self.config['remote']

        ssh_command = [
            self.config['commands']['ssh'], "-i", r['ssh-key'], "-p",
            r['port'], "-l", r['user'], r['server']
        ]
        ssh_command += command

        print("Running: {}".format(ssh_command))
        result_raw = subprocess.run(ssh_command, stdout=subprocess.PIPE)
        result = result_raw.stdout.decode('utf-8').strip()
        return result