123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- ########################################################################
- # Searx-Qt - Lightweight desktop application for Searx.
- # Copyright (C) 2020-2022 CYBERDEViL
- #
- # This file is part of Searx-Qt.
- #
- # Searx-Qt is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # Searx-Qt is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
- #
- ########################################################################
- from PyQt5.QtWidgets import (
- QWidget,
- QVBoxLayout,
- QFormLayout,
- QCheckBox,
- QLabel,
- QDoubleSpinBox,
- QLineEdit,
- QComboBox,
- QHBoxLayout,
- QSizePolicy,
- QTabWidget,
- QPlainTextEdit,
- QSpacerItem
- )
- from PyQt5.QtCore import Qt, pyqtSignal, QVariant
- from searxqt.views.guard import GuardSettings
- from searxqt.widgets.buttons import Button
- from searxqt.widgets.dialogs import UrlDialog
- from searxqt.translations import _
- from searxqt.themes import Themes
- from searxqt.core import log
- HAVE_SOCKS = False
- try:
- import socks
- HAVE_SOCKS = True
- del socks
- except ImportError:
- log.debug("pysocks not installed! No socks proxy support.")
- class ProxyWidget(QWidget):
- changed = pyqtSignal(str) # self.str()
- def __init__(self, parent=None):
- QWidget.__init__(self, parent=parent)
- layout = QVBoxLayout(self)
- hLayout = QHBoxLayout()
- self._proxyType = QComboBox(self)
- self._proxyType.setSizePolicy(QSizePolicy(
- QSizePolicy.Maximum,
- QSizePolicy.Fixed))
- typeList = ['http']
- if HAVE_SOCKS:
- typeList += ['socks4', 'socks5']
- for item in typeList:
- self._proxyType.addItem(item)
- self._proxyStr = QLineEdit(self)
- self._proxyStr.setPlaceholderText("user:pass@host:port")
- hLayout.addWidget(self._proxyType)
- hLayout.addWidget(self._proxyStr)
- layout.addLayout(hLayout)
- self._proxyDns = QCheckBox(_("Proxy DNS"), self)
- layout.addWidget(self._proxyDns)
- if not HAVE_SOCKS:
- self._proxyDns.setToolTip(_("Install pysocks for socks support."))
- self._proxyDns.setEnabled(False)
- self._proxyStr.textChanged.connect(self.__changed)
- self._proxyType.currentIndexChanged.connect(self.__typeChanged)
- self._proxyDns.toggled.connect(self.__dnsChanged)
- def __changed(self):
- self.changed.emit(self.str())
- def __typeChanged(self, index):
- """ From proxy type combobox
- """
- if index == 0:
- self._proxyDns.setEnabled(False)
- self._proxyDns.setToolTip(_("Not available for http proxy."))
- else:
- self._proxyDns.setEnabled(True)
- if self.str():
- self.__changed()
- def __dnsChanged(self, state):
- """ From proxy dns checkbox
- """
- if self.str():
- self.__changed()
- def reset(self):
- self._proxyDns.setChecked(True)
- self._proxyStr.setText("")
- self._proxyType.setCurrentIndex(0)
- self.__typeChanged(0)
- def setStr(self, _str):
- self.reset()
- seq = _str.split(':')
- if len(seq) > 1:
- index = self._proxyType.findText(seq[0].rstrip('h'))
- if index != -1:
- self._proxyType.setCurrentIndex(index)
- self._proxyStr.setText(_str[len(seq[0]) + 3:])
- if seq[0] not in ['socks5h', 'socks4h']:
- self._proxyDns.setChecked(False)
- def str(self):
- if self._proxyStr.text():
- return "{0}://{1}".format(
- self.protocolStr(),
- self._proxyStr.text())
- return ""
- def protocol(self): return self._proxyType.currentText()
- def protocolStr(self):
- if (self.protocol() in ['socks4', 'socks5']
- and self._proxyDns.isChecked()):
- return "{0}h".format(self.protocol())
- return self.protocol()
- class RequestsSettings(QWidget):
- def __init__(self, model, parent=None):
- """
- @param model:
- @type model: searxqt.models.RequestSettingsModel
- """
- QWidget.__init__(self, parent=parent)
- self._model = model
- layout = QFormLayout(self)
- # Verify checkbox
- self._verifyCheck = QCheckBox(self)
- layout.addRow(QLabel(_("Verify") + " (SSL):"), self._verifyCheck)
- # Timeout double spinbox
- self._timeoutSpin = QDoubleSpinBox(self)
- self._timeoutSpin.setSuffix(" sec")
- self._timeoutSpin.setMinimum(3)
- self._timeoutSpin.setMaximum(300)
- layout.addRow(QLabel(_("Timeout") + ":"), self._timeoutSpin)
- # Proxy
- proxyLayout = QFormLayout()
- layout.addRow(QLabel(_("Proxy") + ":"), proxyLayout)
- self._httpProxy = ProxyWidget(self)
- self._httpsProxy = ProxyWidget(self)
- proxyLayout.addRow(QLabel("Http:"), self._httpProxy)
- proxyLayout.addRow(QLabel("Https:"), self._httpsProxy)
- # Headers
- # User-agent
- userAgentLayout = QFormLayout()
- layout.addRow(QLabel(_("User-agents") + ":"), userAgentLayout)
- self._userAgentStringsEdit = QPlainTextEdit(self)
- self._userAgentStringsEdit.setToolTip(
- """- One user-agent string per line.
- - Default user-agent string is the first (top) line.
- - Empty lines will be removed.
- - Leave empty to not send any user-agent string."""
- )
- userAgentLayout.addWidget(self._userAgentStringsEdit)
- self._userAgentEditButton = Button("", self)
- self._userAgentEditButton.setCheckable(True)
- userAgentLayout.addWidget(self._userAgentEditButton)
- self._randomUserAgent = QCheckBox(_("Random"), self)
- self._randomUserAgent.setToolTip(
- """When checked it will pick a random
- user-agent from the list for each request."""
- )
- userAgentLayout.addWidget(self._randomUserAgent)
- # Init values for view
- self._changed()
- # Connections
- self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
- self._verifyCheck.stateChanged.connect(self.__verifyEdited)
- self._httpProxy.changed.connect(self.__proxyEdited)
- self._httpsProxy.changed.connect(self.__proxyEdited)
- self._userAgentEditButton.toggled.connect(self._toggleUserAgentEdit)
- self._randomUserAgent.stateChanged.connect(self._randomUserAgentEdited)
- self.__unsetWidgetsEditMode()
- def _randomUserAgentEdited(self, state):
- self._model.randomUserAgent = bool(state)
- def __setWidgetsEditMode(self):
- self._userAgentEditButton.setText(_("Save"))
- self._userAgentStringsEdit.setReadOnly(False)
- self._userAgentStringsEdit.setFocus()
- def __unsetWidgetsEditMode(self):
- self._userAgentEditButton.setText(_("Edit"))
- self._userAgentStringsEdit.setReadOnly(True)
- def _toggleUserAgentEdit(self, state):
- if state:
- self.__setWidgetsEditMode()
- else:
- self.__unsetWidgetsEditMode()
- self.__UserAgentStringsEdited(
- self._userAgentStringsEdit.toPlainText()
- )
- def __UserAgentStringsEdited(self, value):
- """
- @param value: String with the ?user-agent(s)
- @type value: str
- """
- self._model.useragents = [s for s in value.split('\n') if s]
- self._userAgentListChanged()
- def __timeoutEdited(self, value):
- self._model.timeout = value
- def __verifyEdited(self, state):
- self._model.verify = bool(state)
- def __proxyEdited(self, text):
- self._model.proxies = {
- 'http': self._httpProxy.str(), 'https': self._httpsProxy.str()}
- def _userAgentListChanged(self):
- txt = ""
- for userAgentStr in self._model.useragents:
- if not txt:
- txt = userAgentStr
- else:
- txt += "\n{}".format(userAgentStr)
- self._userAgentStringsEdit.setPlainText(txt)
- def _changed(self):
- self._verifyCheck.setChecked(self._model.verify)
- self._timeoutSpin.setValue(self._model.timeout)
- self._httpProxy.setStr(self._model.proxies.get('http', 'socks5h://'))
- self._httpsProxy.setStr(self._model.proxies.get('https', 'socks5h://'))
- self._userAgentListChanged()
- self._randomUserAgent.setChecked(self._model.randomUserAgent)
- class Stats2Settings(QWidget):
- def __init__(self, model, parent=None):
- """
- @type model: SearxStats2Model
- """
- QWidget.__init__(self, parent=parent)
- self._model = model
- layout = QVBoxLayout(self)
- infoLabel = QLabel(_(
- "The Searx-Stats2 project lists public Searx instances with"
- " statistics. The original instance is running at"
- " https://searx.space/. This is where Searx-Qt will request"
- " a list with instances when the update button is pressed."),
- self
- )
- infoLabel.setWordWrap(True)
- layout.addWidget(infoLabel, 0, Qt.AlignTop)
- hLayout = QHBoxLayout()
- label = QLabel("URL:", self)
- label.setSizePolicy(
- QSizePolicy(
- QSizePolicy.Maximum, QSizePolicy.Maximum
- )
- )
- self._urlLabel = QLabel(model.url, self)
- self._urlEditButton = Button(_("Edit"), self)
- self._urlResetButton = Button(_("Reset"), self)
- hLayout.addWidget(label, 0, Qt.AlignTop)
- hLayout.addWidget(self._urlLabel, 0, Qt.AlignTop)
- hLayout.addWidget(self._urlEditButton, 0, Qt.AlignTop)
- hLayout.addWidget(self._urlResetButton, 0, Qt.AlignTop)
- spacer = QSpacerItem(
- 20, 40, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding
- )
- layout.addLayout(hLayout)
- layout.addItem(spacer)
- self._urlEditButton.clicked.connect(self.__urlEditClicked)
- self._urlResetButton.clicked.connect(self.__urlResetClicked)
- model.changed.connect(self.__modelChanged)
- def __modelChanged(self):
- self._urlLabel.setText(self._model.url)
- def __urlEditClicked(self):
- dialog = UrlDialog(self._model.url)
- if dialog.exec():
- self._model.url = dialog.url
- def __urlResetClicked(self):
- self._model.reset()
- class LogLevelSettings(QWidget):
- def __init__(self, parent=None):
- QWidget.__init__(self, parent=parent)
- layout = QVBoxLayout(self)
- label = QLabel(_("<h2>CLI output level</h2>"), self)
- self.__cbInfo = QCheckBox(_("Info"), self)
- self.__cbWarning = QCheckBox(_("Warning"), self)
- self.__cbDebug = QCheckBox(_("Debug"), self)
- self.__cbError = QCheckBox(_("Error"), self)
- if log.LogLevel & log.LogLevels.INFO:
- self.__cbInfo.setChecked(True)
- if log.LogLevel & log.LogLevels.WARNING:
- self.__cbWarning.setChecked(True)
- if log.LogLevel & log.LogLevels.DEBUG:
- self.__cbDebug.setChecked(True)
- if log.LogLevel & log.LogLevels.ERROR:
- self.__cbError.setChecked(True)
- layout.addWidget(label)
- if log.DebugMode == True:
- label = QLabel(
- _("Debug mode enabled via environment variable"
- " 'SEARXQT_DEBUG'. The settings below are ignored,"
- " unset 'SEARXQT_DEBUG' and restart Searx-Qt to disable"
- " debug mode."),
- parent=self
- )
- label.setWordWrap(True)
- layout.addWidget(label)
- layout.addWidget(self.__cbInfo)
- layout.addWidget(self.__cbWarning)
- layout.addWidget(self.__cbDebug)
- layout.addWidget(self.__cbError)
- self.__cbInfo.stateChanged.connect(self.__stateChangedInfo)
- self.__cbWarning.stateChanged.connect(self.__stateChangedWarning)
- self.__cbDebug.stateChanged.connect(self.__stateChangedDebug)
- self.__cbError.stateChanged.connect(self.__stateChangedError)
- def __stateChanged(self, logLevel, state):
- if state:
- log.LogLevel |= logLevel
- else:
- log.LogLevel &= ~logLevel
- def __stateChangedInfo(self, state):
- self.__stateChanged(log.LogLevels.INFO, state)
- def __stateChangedWarning(self, state):
- self.__stateChanged(log.LogLevels.WARNING, state)
- def __stateChangedDebug(self, state):
- self.__stateChanged(log.LogLevels.DEBUG, state)
- def __stateChangedError(self, state):
- self.__stateChanged(log.LogLevels.ERROR, state)
- class GeneralSettings(QWidget):
- def __init__(self, parent=None):
- QWidget.__init__(self, parent=parent)
- layout = QVBoxLayout(self)
- # Theme
- label = QLabel("<h2>{0}</h2>".format(_("Theme")), self)
- layout.addWidget(label, 0, Qt.AlignTop)
- formLayout = QFormLayout()
- layout.addLayout(formLayout)
- self.__themesCombo = QComboBox(self)
- currentTheme = Themes.currentTheme
- indexOfCurrentTheme = 0
- index = 1
- self.__themesCombo.addItem("None", QVariant(None))
- for theme in Themes.themes:
- data = QVariant(theme)
- self.__themesCombo.addItem(theme.name, data)
- if theme.key == currentTheme:
- indexOfCurrentTheme = index
- index += 1
- self.__themesCombo.setCurrentIndex(indexOfCurrentTheme)
- formLayout.addRow(
- QLabel(_("Theme:"), self),
- self.__themesCombo
- )
- self.__stylesCombo = QComboBox(self)
- currentStyle = Themes.currentStyle
- indexOfCurrentStyle = 0
- index = 0
- for style in Themes.styles:
- self.__stylesCombo.addItem(style, QVariant(style))
- if style == currentStyle:
- indexOfCurrentStyle = index
- index += 1
- self.__stylesCombo.setCurrentIndex(indexOfCurrentStyle)
- formLayout.addRow(
- QLabel(_("Base style:"), self),
- self.__stylesCombo
- )
- applyButton = Button("Apply", self)
- applyButton.clicked.connect(self.__applyTheme)
- layout.addWidget(applyButton, 0, Qt.AlignTop)
- # Log level
- logLevelSettings = LogLevelSettings(self)
- layout.addWidget(logLevelSettings, 1, Qt.AlignTop)
- def __applyTheme(self):
- index = self.__stylesCombo.currentIndex()
- style = self.__stylesCombo.itemData(index, Qt.UserRole)
- Themes.setStyle(style)
- index = self.__themesCombo.currentIndex()
- theme = self.__themesCombo.itemData(index, Qt.UserRole)
- Themes.setTheme(theme.key if theme is not None else "")
- Themes.repolishAllWidgets()
- class SettingsWindow(QTabWidget):
- closed = pyqtSignal()
- def __init__(self, model, guard, parent=None):
- """
- @type model: SettingsModel
- """
- QTabWidget.__init__(self, parent=parent)
- self.setWindowTitle(_("Settings"))
- # General settings
- self._generalView = GeneralSettings(self)
- self.addTab(self._generalView, _("General"))
- # Requests settings
- self._requestsView = RequestsSettings(model.requests, self)
- self.addTab(self._requestsView, _("Connection"))
- # Stats2 settings
- if model.stats2:
- self._stats2View = Stats2Settings(model.stats2, self)
- self.addTab(self._stats2View, "Searx-Stats2")
- # Guard settings
- self._guardView = GuardSettings(guard, self)
- self.addTab(self._guardView, _("Guard"))
- def closeEvent(self, event):
- QTabWidget.closeEvent(self, event)
- self.closed.emit()
|