settings.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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 urllib.parse import urlparse
  22. from PyQt5.QtWidgets import (
  23. QWidget,
  24. QFrame,
  25. QVBoxLayout,
  26. QFormLayout,
  27. QCheckBox,
  28. QLabel,
  29. QDoubleSpinBox,
  30. QLineEdit,
  31. QComboBox,
  32. QHBoxLayout,
  33. QSizePolicy,
  34. QDialog,
  35. QSpacerItem
  36. )
  37. from PyQt5.QtCore import Qt, pyqtSignal
  38. from searxqt.widgets.groupBox import CollapsableGroupBox
  39. from searxqt.widgets.buttons import Button
  40. HAVE_SOCKS = False
  41. try:
  42. import socks
  43. HAVE_SOCKS = True
  44. except ImportError:
  45. print("pysocks not installed! No socks proxy support.")
  46. class ProxyWidget(QWidget):
  47. changed = pyqtSignal(str) # self.str()
  48. def __init__(self, parent=None):
  49. QWidget.__init__(self, parent=parent)
  50. l = QVBoxLayout(self)
  51. hLayout = QHBoxLayout()
  52. self._proxyType = QComboBox(self)
  53. self._proxyType.setSizePolicy(QSizePolicy(
  54. QSizePolicy.Maximum,
  55. QSizePolicy.Fixed))
  56. typeList = ['http']
  57. if HAVE_SOCKS:
  58. typeList += ['socks4', 'socks5']
  59. for item in typeList:
  60. self._proxyType.addItem(item)
  61. self._proxyStr = QLineEdit(self)
  62. self._proxyStr.setPlaceholderText("user:pass@host:port")
  63. hLayout.addWidget(self._proxyType)
  64. hLayout.addWidget(self._proxyStr)
  65. l.addLayout(hLayout)
  66. self._proxyDns = QCheckBox("Proxy DNS", self)
  67. l.addWidget(self._proxyDns)
  68. if not HAVE_SOCKS:
  69. self._proxyDns.setToolTip("Install pysocks for socks support.")
  70. self._proxyDns.setEnabled(False)
  71. self._proxyStr.textChanged.connect(self.__changed)
  72. self._proxyType.currentIndexChanged.connect(self.__typeChanged)
  73. self._proxyDns.toggled.connect(self.__dnsChanged)
  74. def __changed(self):
  75. self.changed.emit(self.str())
  76. def __typeChanged(self, index):
  77. """ From proxy type combobox
  78. """
  79. if index == 0:
  80. self._proxyDns.setEnabled(False)
  81. self._proxyDns.setToolTip("Not available for http proxy.")
  82. else:
  83. self._proxyDns.setEnabled(True)
  84. if self.str(): self.__changed()
  85. def __dnsChanged(self, state):
  86. """ From proxy dns checkbox
  87. """
  88. if self.str(): self.__changed()
  89. def reset(self):
  90. self._proxyDns.setChecked(True)
  91. self._proxyStr.setText("")
  92. self._proxyType.setCurrentIndex(0)
  93. self.__typeChanged(0)
  94. def setStr(self, _str):
  95. self.reset()
  96. seq = _str.split(':')
  97. if len(seq) > 1:
  98. index = self._proxyType.findText(seq[0].rstrip('h'))
  99. if index != -1:
  100. self._proxyType.setCurrentIndex(index)
  101. self._proxyStr.setText(_str[len(seq[0]) + 3:])
  102. if seq[0] not in ['socks5h', 'socks4h']:
  103. self._proxyDns.setChecked(False)
  104. def str(self):
  105. if self._proxyStr.text():
  106. return "{0}://{1}".format(
  107. self.protocolStr(),
  108. self._proxyStr.text())
  109. return ""
  110. def protocol(self): return self._proxyType.currentText()
  111. def protocolStr(self):
  112. if (self.protocol() in ['socks4', 'socks5']
  113. and self._proxyDns.isChecked()):
  114. return "{0}h".format(self.protocol())
  115. return self.protocol()
  116. class UrlDialog(QDialog):
  117. def __init__(self, url='', parent=None):
  118. QDialog.__init__(self, parent=parent)
  119. layout = QFormLayout(self)
  120. label = QLabel("URL:")
  121. self._urlEdit = QLineEdit(self)
  122. self._urlEdit.setToolTip(
  123. "Make sure the URL is valid and has a valid scheme! Valid"
  124. " schemes are http:// and https://"
  125. )
  126. self._urlEdit.setText(url)
  127. layout.addRow(label, self._urlEdit)
  128. self._cancelButton = Button("Cancel", self)
  129. self._saveButton = Button("Save", self)
  130. layout.addRow(self._cancelButton, self._saveButton)
  131. self._urlEdit.textChanged.connect(self.__inputChanged)
  132. self._saveButton.clicked.connect(self.accept)
  133. self._cancelButton.clicked.connect(self.reject)
  134. def __inputChanged(self, text):
  135. if self.isValid():
  136. self._saveButton.setEnabled(True)
  137. else:
  138. self._saveButton.setEnabled(False)
  139. def isValid(self):
  140. parsedUrl = urlparse(self._urlEdit.text())
  141. if parsedUrl.scheme in ['http', 'https'] and parsedUrl.netloc:
  142. return True
  143. return False
  144. @property
  145. def url(self):
  146. if self.isValid():
  147. return self._urlEdit.text()
  148. return ''
  149. class RequestsSettings(CollapsableGroupBox):
  150. def __init__(self, model, parent=None):
  151. CollapsableGroupBox.__init__(self, txt="Connection", parent=parent)
  152. self._model = model
  153. l = QFormLayout(self.contentWidget)
  154. # Verify checkbox
  155. self._verifyCheck = QCheckBox(self.contentWidget)
  156. l.addRow(QLabel("Verify (SSL):"), self._verifyCheck)
  157. # Timeout double spinbox
  158. self._timeoutSpin = QDoubleSpinBox(self.contentWidget)
  159. self._timeoutSpin.setSuffix(" sec")
  160. self._timeoutSpin.setMinimum(3)
  161. self._timeoutSpin.setMaximum(300)
  162. l.addRow(QLabel("Timeout:"), self._timeoutSpin)
  163. # Proxy group box
  164. self._proxyBox = CollapsableGroupBox(
  165. txt="Proxy", parent=self.contentWidget)
  166. l.addRow(QLabel("Proxy:"), self._proxyBox)
  167. proxyL = QFormLayout(self._proxyBox.contentWidget)
  168. self._httpProxy = ProxyWidget(self._proxyBox)
  169. self._httpsProxy = ProxyWidget(self._proxyBox)
  170. proxyL.addRow(QLabel("Http:"), self._httpProxy)
  171. proxyL.addRow(QLabel("Https:"), self._httpsProxy)
  172. self._changed()
  173. self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
  174. self._verifyCheck.stateChanged.connect(self.__verifyEdited)
  175. self._httpProxy.changed.connect(self.__proxyEdited)
  176. self._httpsProxy.changed.connect(self.__proxyEdited)
  177. def __timeoutEdited(self, value):
  178. self._model.timeout = value
  179. def __verifyEdited(self, state):
  180. self._model.verify = bool(state)
  181. def __proxyEdited(self, text):
  182. self._model.proxies = {
  183. 'http': self._httpProxy.str(), 'https': self._httpsProxy.str()}
  184. def _changed(self):
  185. self._verifyCheck.setChecked(self._model.verify)
  186. self._timeoutSpin.setValue(self._model.timeout)
  187. self._httpProxy.setStr(self._model.proxies.get('http', 'socks5h://'))
  188. self._httpsProxy.setStr(self._model.proxies.get('https', 'socks5h://'))
  189. class Stats2Settings(CollapsableGroupBox):
  190. def __init__(self, model, parent=None):
  191. """
  192. @type model: SearxStats2Model
  193. """
  194. CollapsableGroupBox.__init__(self, txt="searx-stats2", parent=parent)
  195. self._model = model
  196. layout = QVBoxLayout(self.contentWidget)
  197. infoLabel = QLabel(
  198. "The searx-stats2 project lists public searx instances with"
  199. " statistics. The original instance is running at"
  200. " https://searx.space/. This is where searx-qt will request"
  201. " a list with instances when the update button is pressed."
  202. , self
  203. )
  204. infoLabel.setWordWrap(True)
  205. layout.addWidget(infoLabel)
  206. hLayout = QHBoxLayout(self.contentWidget)
  207. label = QLabel("URL:", self)
  208. label.setSizePolicy(
  209. QSizePolicy(
  210. QSizePolicy.Maximum, QSizePolicy.Maximum
  211. )
  212. )
  213. self._urlLabel = QLabel(model.url, self)
  214. self._urlEditButton = Button("Edit", self)
  215. self._urlResetButton = Button("Reset", self)
  216. hLayout.addWidget(label)
  217. hLayout.addWidget(self._urlLabel)
  218. hLayout.addWidget(self._urlEditButton)
  219. hLayout.addWidget(self._urlResetButton)
  220. layout.addLayout(hLayout)
  221. self._urlEditButton.clicked.connect(self.__urlEditClicked)
  222. self._urlResetButton.clicked.connect(self.__urlResetClicked)
  223. model.changed.connect(self.__modelChanged)
  224. def __modelChanged(self):
  225. self._urlLabel.setText(self._model.url)
  226. def __urlEditClicked(self):
  227. dialog = UrlDialog(self._model.url)
  228. if dialog.exec():
  229. self._model.url = dialog.url
  230. def __urlResetClicked(self):
  231. self._model.reset()
  232. class SettingsWindow(QFrame):
  233. def __init__(self, model, parent=None):
  234. """
  235. @type model: SettingsModel
  236. """
  237. QFrame.__init__(self, parent=parent)
  238. layout = QVBoxLayout(self)
  239. self.setWindowTitle("Settings")
  240. self._requestsView = RequestsSettings(model.requests, self)
  241. layout.addWidget(self._requestsView, 0, Qt.AlignTop)
  242. self._stats2View = Stats2Settings(model.stats2, self)
  243. layout.addWidget(self._stats2View, 0, Qt.AlignTop)
  244. # keep widgets aligned on top
  245. spacer = QSpacerItem(
  246. 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
  247. )
  248. layout.addItem(spacer)
  249. self.resize(480, 400)