settings.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. ########################################################################
  2. # Searx-qt - Lightweight desktop application for SearX.
  3. # Copyright (C) 2020 CYBERDEViL
  4. #
  5. # This file is part of Searx-qt.
  6. #
  7. # Searx-qt is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Searx-qt is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. from PyQt5.QtWidgets import (
  22. QWidget,
  23. QVBoxLayout,
  24. QFormLayout,
  25. QCheckBox,
  26. QLabel,
  27. QDoubleSpinBox,
  28. QLineEdit,
  29. QComboBox,
  30. QHBoxLayout,
  31. QSizePolicy,
  32. QTabWidget,
  33. QPlainTextEdit,
  34. QSpacerItem
  35. )
  36. from PyQt5.QtCore import Qt, pyqtSignal
  37. from searxqt.widgets.buttons import Button
  38. from searxqt.widgets.dialogs import UrlDialog
  39. from searxqt.translations import _
  40. HAVE_SOCKS = False
  41. try:
  42. import socks
  43. HAVE_SOCKS = True
  44. del socks
  45. except ImportError:
  46. print("pysocks not installed! No socks proxy support.")
  47. class ProxyWidget(QWidget):
  48. changed = pyqtSignal(str) # self.str()
  49. def __init__(self, parent=None):
  50. QWidget.__init__(self, parent=parent)
  51. layout = QVBoxLayout(self)
  52. hLayout = QHBoxLayout()
  53. self._proxyType = QComboBox(self)
  54. self._proxyType.setSizePolicy(QSizePolicy(
  55. QSizePolicy.Maximum,
  56. QSizePolicy.Fixed))
  57. typeList = ['http']
  58. if HAVE_SOCKS:
  59. typeList += ['socks4', 'socks5']
  60. for item in typeList:
  61. self._proxyType.addItem(item)
  62. self._proxyStr = QLineEdit(self)
  63. self._proxyStr.setPlaceholderText("user:pass@host:port")
  64. hLayout.addWidget(self._proxyType)
  65. hLayout.addWidget(self._proxyStr)
  66. layout.addLayout(hLayout)
  67. self._proxyDns = QCheckBox(_("Proxy DNS"), self)
  68. layout.addWidget(self._proxyDns)
  69. if not HAVE_SOCKS:
  70. self._proxyDns.setToolTip(_("Install pysocks for socks support."))
  71. self._proxyDns.setEnabled(False)
  72. self._proxyStr.textChanged.connect(self.__changed)
  73. self._proxyType.currentIndexChanged.connect(self.__typeChanged)
  74. self._proxyDns.toggled.connect(self.__dnsChanged)
  75. def __changed(self):
  76. self.changed.emit(self.str())
  77. def __typeChanged(self, index):
  78. """ From proxy type combobox
  79. """
  80. if index == 0:
  81. self._proxyDns.setEnabled(False)
  82. self._proxyDns.setToolTip(_("Not available for http proxy."))
  83. else:
  84. self._proxyDns.setEnabled(True)
  85. if self.str():
  86. self.__changed()
  87. def __dnsChanged(self, state):
  88. """ From proxy dns checkbox
  89. """
  90. if self.str():
  91. self.__changed()
  92. def reset(self):
  93. self._proxyDns.setChecked(True)
  94. self._proxyStr.setText("")
  95. self._proxyType.setCurrentIndex(0)
  96. self.__typeChanged(0)
  97. def setStr(self, _str):
  98. self.reset()
  99. seq = _str.split(':')
  100. if len(seq) > 1:
  101. index = self._proxyType.findText(seq[0].rstrip('h'))
  102. if index != -1:
  103. self._proxyType.setCurrentIndex(index)
  104. self._proxyStr.setText(_str[len(seq[0]) + 3:])
  105. if seq[0] not in ['socks5h', 'socks4h']:
  106. self._proxyDns.setChecked(False)
  107. def str(self):
  108. if self._proxyStr.text():
  109. return "{0}://{1}".format(
  110. self.protocolStr(),
  111. self._proxyStr.text())
  112. return ""
  113. def protocol(self): return self._proxyType.currentText()
  114. def protocolStr(self):
  115. if (self.protocol() in ['socks4', 'socks5']
  116. and self._proxyDns.isChecked()):
  117. return "{0}h".format(self.protocol())
  118. return self.protocol()
  119. class RequestsSettings(QWidget):
  120. def __init__(self, model, parent=None):
  121. """
  122. @param model:
  123. @type model: searxqt.models.RequestSettingsModel
  124. """
  125. QWidget.__init__(self, parent=parent)
  126. self._model = model
  127. layout = QFormLayout(self)
  128. # Verify checkbox
  129. self._verifyCheck = QCheckBox(self)
  130. layout.addRow(QLabel(_("Verify") + " (SSL):"), self._verifyCheck)
  131. # Timeout double spinbox
  132. self._timeoutSpin = QDoubleSpinBox(self)
  133. self._timeoutSpin.setSuffix(" sec")
  134. self._timeoutSpin.setMinimum(3)
  135. self._timeoutSpin.setMaximum(300)
  136. layout.addRow(QLabel(_("Timeout") + ":"), self._timeoutSpin)
  137. # Proxy
  138. proxyLayout = QFormLayout()
  139. layout.addRow(QLabel(_("Proxy") + ":"), proxyLayout)
  140. self._httpProxy = ProxyWidget(self)
  141. self._httpsProxy = ProxyWidget(self)
  142. proxyLayout.addRow(QLabel("Http:"), self._httpProxy)
  143. proxyLayout.addRow(QLabel("Https:"), self._httpsProxy)
  144. # Headers
  145. # User-agent
  146. userAgentLayout = QFormLayout()
  147. layout.addRow(QLabel(_("User-agents") + ":"), userAgentLayout)
  148. self._userAgentStringsEdit = QPlainTextEdit(self)
  149. self._userAgentStringsEdit.setToolTip(
  150. """- One user-agent string per line.
  151. - Default user-agent string is the first (top) line.
  152. - Empty lines will be removed.
  153. - Leave empty to not send any user-agent string."""
  154. )
  155. userAgentLayout.addWidget(self._userAgentStringsEdit)
  156. self._userAgentEditButton = Button("", self)
  157. self._userAgentEditButton.setCheckable(True)
  158. userAgentLayout.addWidget(self._userAgentEditButton)
  159. self._randomUserAgent = QCheckBox(_("Random"), self)
  160. self._randomUserAgent.setToolTip(
  161. """When checked it will pick a random
  162. user-agent from the list for each request."""
  163. )
  164. userAgentLayout.addWidget(self._randomUserAgent)
  165. # Init values for view
  166. self._changed()
  167. # Connections
  168. self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
  169. self._verifyCheck.stateChanged.connect(self.__verifyEdited)
  170. self._httpProxy.changed.connect(self.__proxyEdited)
  171. self._httpsProxy.changed.connect(self.__proxyEdited)
  172. self._userAgentEditButton.toggled.connect(self._toggleUserAgentEdit)
  173. self._randomUserAgent.stateChanged.connect(self._randomUserAgentEdited)
  174. self.__unsetWidgetsEditMode()
  175. def _randomUserAgentEdited(self, state):
  176. self._model.randomUserAgent = bool(state)
  177. def __setWidgetsEditMode(self):
  178. self._userAgentEditButton.setText(_("Save"))
  179. self._userAgentStringsEdit.setReadOnly(False)
  180. self._userAgentStringsEdit.setFocus()
  181. def __unsetWidgetsEditMode(self):
  182. self._userAgentEditButton.setText(_("Edit"))
  183. self._userAgentStringsEdit.setReadOnly(True)
  184. def _toggleUserAgentEdit(self, state):
  185. if state:
  186. self.__setWidgetsEditMode()
  187. else:
  188. self.__unsetWidgetsEditMode()
  189. self.__UserAgentStringsEdited(
  190. self._userAgentStringsEdit.toPlainText()
  191. )
  192. def __UserAgentStringsEdited(self, value):
  193. """
  194. @param value: String with the ?user-agent(s)
  195. @type value: str
  196. """
  197. self._model.useragents = [s for s in value.split('\n') if s]
  198. self._userAgentListChanged()
  199. def __timeoutEdited(self, value):
  200. self._model.timeout = value
  201. def __verifyEdited(self, state):
  202. self._model.verify = bool(state)
  203. def __proxyEdited(self, text):
  204. self._model.proxies = {
  205. 'http': self._httpProxy.str(), 'https': self._httpsProxy.str()}
  206. def _userAgentListChanged(self):
  207. txt = ""
  208. for userAgentStr in self._model.useragents:
  209. if not txt:
  210. txt = userAgentStr
  211. else:
  212. txt += "\n{}".format(userAgentStr)
  213. self._userAgentStringsEdit.setPlainText(txt)
  214. def _changed(self):
  215. self._verifyCheck.setChecked(self._model.verify)
  216. self._timeoutSpin.setValue(self._model.timeout)
  217. self._httpProxy.setStr(self._model.proxies.get('http', 'socks5h://'))
  218. self._httpsProxy.setStr(self._model.proxies.get('https', 'socks5h://'))
  219. self._userAgentListChanged()
  220. self._randomUserAgent.setChecked(self._model.randomUserAgent)
  221. class Stats2Settings(QWidget):
  222. def __init__(self, model, parent=None):
  223. """
  224. @type model: SearxStats2Model
  225. """
  226. QWidget.__init__(self, parent=parent)
  227. self._model = model
  228. layout = QVBoxLayout(self)
  229. infoLabel = QLabel(_(
  230. "The searx-stats2 project lists public searx instances with"
  231. " statistics. The original instance is running at"
  232. " https://searx.space/. This is where searx-qt will request"
  233. " a list with instances when the update button is pressed."),
  234. self
  235. )
  236. infoLabel.setWordWrap(True)
  237. layout.addWidget(infoLabel, 0, Qt.AlignTop)
  238. hLayout = QHBoxLayout()
  239. label = QLabel("URL:", self)
  240. label.setSizePolicy(
  241. QSizePolicy(
  242. QSizePolicy.Maximum, QSizePolicy.Maximum
  243. )
  244. )
  245. self._urlLabel = QLabel(model.url, self)
  246. self._urlEditButton = Button(_("Edit"), self)
  247. self._urlResetButton = Button(_("Reset"), self)
  248. hLayout.addWidget(label, 0, Qt.AlignTop)
  249. hLayout.addWidget(self._urlLabel, 0, Qt.AlignTop)
  250. hLayout.addWidget(self._urlEditButton, 0, Qt.AlignTop)
  251. hLayout.addWidget(self._urlResetButton, 0, Qt.AlignTop)
  252. spacer = QSpacerItem(
  253. 20, 40, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding
  254. )
  255. layout.addLayout(hLayout)
  256. layout.addItem(spacer)
  257. self._urlEditButton.clicked.connect(self.__urlEditClicked)
  258. self._urlResetButton.clicked.connect(self.__urlResetClicked)
  259. model.changed.connect(self.__modelChanged)
  260. def __modelChanged(self):
  261. self._urlLabel.setText(self._model.url)
  262. def __urlEditClicked(self):
  263. dialog = UrlDialog(self._model.url)
  264. if dialog.exec():
  265. self._model.url = dialog.url
  266. def __urlResetClicked(self):
  267. self._model.reset()
  268. class SettingsWindow(QTabWidget):
  269. def __init__(self, model, parent=None):
  270. """
  271. @type model: SettingsModel
  272. """
  273. QTabWidget.__init__(self, parent=parent)
  274. self.setWindowTitle(_("Settings"))
  275. self._requestsView = RequestsSettings(model.requests, self)
  276. self.addTab(self._requestsView, _("Connection"))
  277. if model.stats2:
  278. self._stats2View = Stats2Settings(model.stats2, self)
  279. self.addTab(self._stats2View, "searx-stats2")