ui.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. # Razer device QT configuration tool
  2. #
  3. # Copyright (C) 2007-2018 Michael Buesch <m@bues.ch>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. import sys
  15. from PyQt5.QtCore import *
  16. from PyQt5.QtGui import *
  17. from PyQt5.QtWidgets import *
  18. Signal = pyqtSignal
  19. from functools import partial
  20. from pyrazer import *
  21. try:
  22. PYRAZER_SETUP_PY == True
  23. except:
  24. print("ERROR: Found an old 'pyrazer' module.")
  25. print("You should uninstall razercfg from the system (see README)")
  26. print("and re-install it properly.")
  27. sys.exit(1)
  28. razer = None
  29. class Wrapper(object):
  30. def __init__(self, obj):
  31. self.obj = obj
  32. def __eq__(self, other):
  33. return self.obj == other.obj
  34. def __ne__(self, other):
  35. return self.obj != other.obj
  36. class WrappedComboBox(QComboBox):
  37. def addItem(self, text, dataObj=None):
  38. QComboBox.addItem(self, text, Wrapper(dataObj))
  39. def itemData(self, index):
  40. return QComboBox.itemData(self, index).obj
  41. def findData(self, dataObj):
  42. for i in range(0, self.count()):
  43. if self.itemData(i) == dataObj:
  44. return i
  45. return -1
  46. class OneButtonConfig(QWidget):
  47. def __init__(self, id, name, supportedFunctions, buttonConfWidget):
  48. QWidget.__init__(self, buttonConfWidget)
  49. self.setContentsMargins(QMargins())
  50. self.buttonConfWidget = buttonConfWidget
  51. self.id = id
  52. self.name = name
  53. self.setLayout(QHBoxLayout(self))
  54. self.layout().setContentsMargins(QMargins())
  55. self.nameLabel = QLabel(name, self)
  56. self.layout().addWidget(self.nameLabel)
  57. self.layout().addStretch()
  58. l = QLabel(self.tr("button is assigned to function"), self)
  59. self.layout().addWidget(l)
  60. self.funcCombo = WrappedComboBox(self)
  61. for func in supportedFunctions:
  62. self.funcCombo.addItem(func[1], func[0])
  63. curFunc = razer.getButtonFunction(buttonConfWidget.profileWidget.mouseWidget.mouse,
  64. buttonConfWidget.profileWidget.profileId,
  65. id)
  66. self.initialFunc = curFunc[0]
  67. index = self.funcCombo.findData(curFunc[0])
  68. if index >= 0:
  69. self.funcCombo.setCurrentIndex(index)
  70. self.layout().addWidget(self.funcCombo)
  71. def getId(self):
  72. return self.id
  73. def getSelectedFunction(self):
  74. index = self.funcCombo.currentIndex()
  75. return self.funcCombo.itemData(index)
  76. def getInitialFunction(self):
  77. return self.initialFunc
  78. class ButtonConfDialog(QDialog):
  79. def __init__(self, profileWidget):
  80. QDialog.__init__(self, profileWidget)
  81. self.profileWidget = profileWidget
  82. self.setWindowTitle(self.tr("Configure buttons"))
  83. self.setLayout(QVBoxLayout(self))
  84. h = QHBoxLayout()
  85. l = QLabel(self.tr("Physical button"), self)
  86. h.addWidget(l)
  87. h.addStretch()
  88. l = QLabel(self.tr("Assigned function"), self)
  89. h.addWidget(l)
  90. self.layout().addLayout(h)
  91. funcs = razer.getSupportedButtonFunctions(profileWidget.mouseWidget.mouse)
  92. self.buttons = []
  93. for b in razer.getSupportedButtons(profileWidget.mouseWidget.mouse):
  94. button = OneButtonConfig(b[0], b[1], funcs, self)
  95. self.layout().addWidget(button)
  96. self.buttons.append(button)
  97. h = QHBoxLayout()
  98. self.applyButton = QPushButton(self.tr("Apply"), self)
  99. self.applyButton.clicked.connect(self.apply)
  100. h.addWidget(self.applyButton)
  101. self.cancelButton = QPushButton(self.tr("Cancel"), self)
  102. self.cancelButton.clicked.connect(self.cancel)
  103. h.addWidget(self.cancelButton)
  104. self.layout().addLayout(h)
  105. def cancel(self):
  106. self.done(0)
  107. def apply(self):
  108. for button in self.buttons:
  109. func = button.getSelectedFunction()
  110. if func != button.getInitialFunction():
  111. razer.setButtonFunction(self.profileWidget.mouseWidget.mouse,
  112. self.profileWidget.profileId,
  113. button.getId(),
  114. func)
  115. self.done(1)
  116. class OneLedConfig(QWidget):
  117. def __init__(self, ledsWidget, led):
  118. QWidget.__init__(self, ledsWidget)
  119. self.setContentsMargins(QMargins())
  120. self.ledsWidget = ledsWidget
  121. self.led = led
  122. self.setLayout(QGridLayout(self))
  123. self.layout().setContentsMargins(QMargins())
  124. self.stateCb = QCheckBox(led.name + " LED", self)
  125. self.layout().addWidget(self.stateCb, 0, 0)
  126. self.stateCb.setCheckState(Qt.Checked if led.state else Qt.Unchecked)
  127. if led.supported_modes:
  128. self.modeComBox = WrappedComboBox(self)
  129. self.layout().addWidget(self.modeComBox, 0, 1)
  130. for mode in led.supported_modes:
  131. self.modeComBox.addItem(mode.toString(), mode.val)
  132. self.modeComBox.currentIndexChanged.connect(self.modeChanged)
  133. index = self.modeComBox.findData(led.mode.val)
  134. if index >= 0:
  135. self.modeComBox.setCurrentIndex(index)
  136. if led.color is not None and led.canChangeColor:
  137. self.colorPb = QPushButton(self.tr("change color..."), self)
  138. self.layout().addWidget(self.colorPb, 0, 2)
  139. self.colorPb.released.connect(self.colorChangePressed)
  140. self.stateCb.stateChanged.connect(self.toggled)
  141. def toggled(self, state):
  142. self.led.state = bool(state)
  143. razer.setLed(self.ledsWidget.mouseWidget.mouse, self.led)
  144. def colorChangePressed(self):
  145. c = QColor(self.led.color.r, self.led.color.g, self.led.color.b)
  146. c = QColorDialog.getColor(c, self, self.led.name + self.tr(" color"))
  147. if not c.isValid():
  148. return
  149. self.led.color.r = c.red()
  150. self.led.color.g = c.green()
  151. self.led.color.b = c.blue()
  152. razer.setLed(self.ledsWidget.mouseWidget.mouse, self.led)
  153. def modeChanged(self, currentIndex):
  154. self.led.mode.val = self.modeComBox.itemData(currentIndex)
  155. razer.setLed(self.ledsWidget.mouseWidget.mouse, self.led)
  156. class LedsWidget(QGroupBox):
  157. def __init__(self, parent, mouseWidget):
  158. QGroupBox.__init__(self, "LEDs", parent)
  159. self.mouseWidget = mouseWidget
  160. self.setLayout(QVBoxLayout(self))
  161. self.leds = []
  162. def clear(self):
  163. for led in self.leds:
  164. led.deleteLater()
  165. self.leds = []
  166. self.setEnabled(False)
  167. self.hide()
  168. def add(self, led):
  169. oneLed = OneLedConfig(self, led)
  170. self.layout().addWidget(oneLed)
  171. self.leds.append(oneLed)
  172. self.setEnabled(True)
  173. self.show()
  174. def updateContent(self, profileId=Razer.PROFILE_INVALID):
  175. for led in razer.getLeds(self.mouseWidget.mouse, profileId):
  176. self.add(led)
  177. self.show()
  178. #TODO profile name
  179. class MouseProfileWidget(QWidget):
  180. def __init__(self, mouseWidget, profileId):
  181. QWidget.__init__(self, mouseWidget)
  182. self.mouseWidget = mouseWidget
  183. self.profileId = profileId
  184. minfo = razer.getMouseInfo(mouseWidget.mouse)
  185. self.setLayout(QGridLayout(self))
  186. yoff = 0
  187. self.profileActive = QRadioButton(self.tr("Profile active"), self)
  188. self.profileActive.toggled.connect(self.activeChanged)
  189. self.layout().addWidget(self.profileActive, yoff, 0)
  190. yoff += 1
  191. self.freqSel = None
  192. if minfo & Razer.MOUSEINFOFLG_PROFILE_FREQ:
  193. self.freqSel = MouseScanFreqWidget(self, mouseWidget, profileId)
  194. self.layout().addWidget(self.freqSel, yoff, 0, 1, 2)
  195. yoff += 1
  196. self.resSel = []
  197. axes = self.__getIndependentAxes()
  198. for axis in axes:
  199. axisName = axis[1] + " " if axis[1] else ""
  200. self.layout().addWidget(QLabel(self.tr("%sScan resolution:" % axisName), self), yoff, 0)
  201. resSel = WrappedComboBox(self)
  202. resSel.currentIndexChanged.connect(self.resChanged)
  203. self.layout().addWidget(resSel, yoff, 1)
  204. self.resSel.append(resSel)
  205. yoff += 1
  206. self.resIndependent = QCheckBox(self.tr("Independent resolutions"), self)
  207. self.resIndependent.stateChanged.connect(self.resIndependentChanged)
  208. self.layout().addWidget(self.resIndependent, yoff, 1)
  209. yoff += 1
  210. if len(axes) <= 1:
  211. self.resIndependent.hide()
  212. funcs = razer.getSupportedButtonFunctions(self.mouseWidget.mouse)
  213. if funcs:
  214. self.buttonConf = QPushButton(self.tr("Configure buttons"), self)
  215. self.buttonConf.clicked.connect(self.showButtonConf)
  216. self.layout().addWidget(self.buttonConf, yoff, 0, 1, 2)
  217. yoff += 1
  218. if minfo & Razer.MOUSEINFOFLG_PROFNAMEMUTABLE:
  219. self.buttonName = QPushButton(self.tr("Change profile name"), self)
  220. self.buttonName.clicked.connect(self.nameChange)
  221. self.layout().addWidget(self.buttonName, yoff, 0, 1, 2)
  222. yoff += 1
  223. self.dpimappings = MouseDpiMappingsWidget(self, mouseWidget)
  224. self.layout().addWidget(self.dpimappings, yoff, 0, 1, 2)
  225. yoff += 1
  226. self.leds = LedsWidget(self, mouseWidget)
  227. self.layout().addWidget(self.leds, yoff, 0, 1, 2)
  228. yoff += 1
  229. def __getIndependentAxes(self):
  230. axes = razer.getSupportedAxes(self.mouseWidget.mouse)
  231. axes = [axis for axis in axes if (axis[2] & Razer.RAZER_AXIS_INDEPENDENT_DPIMAPPING)]
  232. if not axes:
  233. axes = [ (0, "", 0) ]
  234. return axes
  235. def reload(self):
  236. # Refetch the data from the daemon
  237. self.mouseWidget.recurseProtect += 1
  238. # Frequency selection (if any)
  239. if self.freqSel:
  240. self.freqSel.updateContent()
  241. # Resolution selection
  242. for resSel in self.resSel:
  243. resSel.clear()
  244. supportedMappings = razer.getSupportedDpiMappings(self.mouseWidget.mouse)
  245. supportedMappings = [m for m in supportedMappings if (m.profileMask == 0) or\
  246. (m.profileMask & (1 << self.profileId))]
  247. axisMappings = []
  248. for axis in self.__getIndependentAxes():
  249. axisMappings.append(razer.getDpiMapping(self.mouseWidget.mouse,
  250. self.profileId,
  251. axis[0]))
  252. for i, resSel in enumerate(self.resSel):
  253. resSel.addItem(self.tr("Unknown mapping"), 0xFFFFFFFF)
  254. for mapping in supportedMappings:
  255. r = [ r for r in mapping.res if r is not None ]
  256. r = [ ("%u" % x) if x else self.tr("Unknown") for x in r]
  257. rStr = "/".join(r)
  258. resSel.addItem(self.tr("Scan resolution %u (%s DPI)" %\
  259. (mapping.id + 1, rStr)),
  260. mapping.id)
  261. index = resSel.findData(axisMappings[i])
  262. if index >= 0:
  263. resSel.setCurrentIndex(index)
  264. independent = bool([x for x in axisMappings if x != axisMappings[0]])
  265. if independent:
  266. self.resIndependent.setCheckState(Qt.Checked)
  267. else:
  268. self.resIndependent.setCheckState(Qt.Unchecked)
  269. self.resIndependentChanged(self.resIndependent.checkState())
  270. # Profile selection
  271. activeProf = razer.getActiveProfile(self.mouseWidget.mouse)
  272. self.profileActive.setChecked(activeProf == self.profileId)
  273. # Per-profile DPI mappings (if any)
  274. self.dpimappings.clear()
  275. self.dpimappings.updateContent(self.profileId)
  276. # Per-profile LEDs (if any)
  277. self.leds.clear()
  278. self.leds.updateContent(self.profileId)
  279. self.mouseWidget.recurseProtect -= 1
  280. def activeChanged(self, checked):
  281. if self.mouseWidget.recurseProtect:
  282. return
  283. if not checked:
  284. # Cannot disable
  285. self.mouseWidget.recurseProtect += 1
  286. self.profileActive.setChecked(1)
  287. self.mouseWidget.recurseProtect -= 1
  288. return
  289. razer.setActiveProfile(self.mouseWidget.mouse, self.profileId)
  290. self.mouseWidget.reloadProfiles()
  291. def resChanged(self, unused=None):
  292. if self.mouseWidget.recurseProtect:
  293. return
  294. if self.resIndependent.checkState() == Qt.Checked:
  295. axisId = 0
  296. for resSel in self.resSel:
  297. index = resSel.currentIndex()
  298. res = resSel.itemData(index)
  299. razer.setDpiMapping(self.mouseWidget.mouse, self.profileId,
  300. res, axisId)
  301. axisId += 1
  302. else:
  303. index = self.resSel[0].currentIndex()
  304. res = self.resSel[0].itemData(index)
  305. razer.setDpiMapping(self.mouseWidget.mouse, self.profileId, res)
  306. for resSel in self.resSel[1:]:
  307. self.mouseWidget.recurseProtect += 1
  308. resSel.setCurrentIndex(index)
  309. self.mouseWidget.recurseProtect -= 1
  310. def resIndependentChanged(self, newState):
  311. if newState == Qt.Checked:
  312. for resSel in self.resSel[1:]:
  313. resSel.setEnabled(True)
  314. else:
  315. for resSel in self.resSel[1:]:
  316. resSel.setEnabled(False)
  317. self.resChanged()
  318. def showButtonConf(self, checked):
  319. bconf = ButtonConfDialog(self)
  320. bconf.exec_()
  321. def nameChange(self, unused):
  322. name = razer.getProfileName(self.mouseWidget.mouse, self.profileId)
  323. (newName, ok) = QInputDialog.getText(self, self.tr("New profile name"),
  324. self.tr("New profile name:"),
  325. QLineEdit.Normal,
  326. name)
  327. if not ok:
  328. return
  329. razer.setProfileName(self.mouseWidget.mouse, self.profileId, newName)
  330. self.mouseWidget.reloadProfiles()
  331. class OneDpiMapping(QWidget):
  332. def __init__(self, dpiMappingsWidget, dpimapping):
  333. QWidget.__init__(self, dpiMappingsWidget)
  334. self.setContentsMargins(QMargins())
  335. self.dpiMappingsWidget = dpiMappingsWidget
  336. self.dpimapping = dpimapping
  337. self.setLayout(QHBoxLayout(self))
  338. self.layout().setContentsMargins(QMargins())
  339. self.layout().addWidget(QLabel(self.tr("Scan resolution %u:" % (dpimapping.id + 1)),
  340. self))
  341. supportedRes = razer.getSupportedRes(self.dpiMappingsWidget.mouseWidget.mouse)
  342. haveMultipleDims = len([r for r in dpimapping.res if r is not None]) > 1
  343. dimNames = ( "X", "Y", "Z" )
  344. changeSlots = ( self.changedDim0, self.changedDim1, self.changedDim2 )
  345. for dimIdx, thisRes in enumerate([r for r in dpimapping.res if r is not None]):
  346. self.mappingSel = WrappedComboBox(self)
  347. name = self.tr("Unknown DPI")
  348. if haveMultipleDims:
  349. name = dimNames[dimIdx] + ": " + name
  350. self.mappingSel.addItem(name, 0)
  351. for res in supportedRes:
  352. name = self.tr("%u DPI" % res)
  353. if haveMultipleDims:
  354. name = dimNames[dimIdx] + ": " + name
  355. self.mappingSel.addItem(name, res)
  356. index = self.mappingSel.findData(thisRes)
  357. if index >= 0:
  358. self.mappingSel.setCurrentIndex(index)
  359. self.mappingSel.currentIndexChanged.connect(changeSlots[dimIdx])
  360. self.mappingSel.setEnabled(dpimapping.mutable)
  361. self.layout().addWidget(self.mappingSel)
  362. self.layout().addStretch()
  363. def changedDim0(self, index):
  364. self.changed(index, 0)
  365. def changedDim1(self, index):
  366. self.changed(index, 1)
  367. def changedDim2(self, index):
  368. self.changed(index, 2)
  369. def changed(self, index, dimIdx):
  370. if index <= 0:
  371. return
  372. resolution = self.mappingSel.itemData(index)
  373. razer.changeDpiMapping(self.dpiMappingsWidget.mouseWidget.mouse,
  374. self.dpimapping.id,
  375. dimIdx,
  376. resolution)
  377. self.dpiMappingsWidget.mouseWidget.reloadProfiles()
  378. class MouseDpiMappingsWidget(QGroupBox):
  379. def __init__(self, parent, mouseWidget):
  380. QGroupBox.__init__(self, parent.tr("Possible scan resolutions"), parent)
  381. self.mouseWidget = mouseWidget
  382. self.setLayout(QVBoxLayout(self))
  383. self.mappings = []
  384. self.clear()
  385. def clear(self):
  386. for mapping in self.mappings:
  387. mapping.deleteLater()
  388. self.mappings = []
  389. self.setEnabled(False)
  390. self.hide()
  391. def add(self, dpimapping):
  392. mapping = OneDpiMapping(self, dpimapping)
  393. self.mappings.append(mapping)
  394. self.layout().addWidget(mapping)
  395. if dpimapping.mutable:
  396. self.setEnabled(True)
  397. self.show()
  398. def updateContent(self, profileId=Razer.PROFILE_INVALID):
  399. for dpimapping in razer.getSupportedDpiMappings(self.mouseWidget.mouse):
  400. if (profileId == Razer.PROFILE_INVALID and dpimapping.profileMask == 0) or\
  401. (profileId != Razer.PROFILE_INVALID and dpimapping.profileMask & (1 << profileId)):
  402. self.add(dpimapping)
  403. class MouseScanFreqWidget(QWidget):
  404. def __init__(self, parent, mouseWidget, profileId=Razer.PROFILE_INVALID):
  405. QWidget.__init__(self, parent)
  406. self.setContentsMargins(QMargins())
  407. self.mouseWidget = mouseWidget
  408. self.profileId = profileId
  409. self.setLayout(QGridLayout(self))
  410. self.layout().setContentsMargins(QMargins())
  411. self.layout().addWidget(QLabel(self.tr("Scan frequency:"), self), 0, 0)
  412. self.freqSel = WrappedComboBox(self)
  413. self.layout().addWidget(self.freqSel, 0, 1)
  414. self.freqSel.currentIndexChanged.connect(self.freqChanged)
  415. def freqChanged(self, index):
  416. if self.mouseWidget.recurseProtect or index <= 0:
  417. return
  418. freq = self.freqSel.itemData(index)
  419. razer.setFrequency(self.mouseWidget.mouse, self.profileId, freq)
  420. def updateContent(self):
  421. self.mouseWidget.recurseProtect += 1
  422. self.freqSel.clear()
  423. supportedFreqs = razer.getSupportedFreqs(self.mouseWidget.mouse)
  424. curFreq = razer.getCurrentFreq(self.mouseWidget.mouse, self.profileId)
  425. self.freqSel.addItem(self.tr("Unknown Hz"), 0)
  426. for freq in supportedFreqs:
  427. self.freqSel.addItem(self.tr("%u Hz" % freq), freq)
  428. index = self.freqSel.findData(curFreq)
  429. if index >= 0:
  430. self.freqSel.setCurrentIndex(index)
  431. self.mouseWidget.recurseProtect -= 1
  432. class MouseWidget(QWidget):
  433. def __init__(self, parent=None):
  434. QWidget.__init__(self, parent)
  435. self.recurseProtect = 0
  436. self.mainwnd = parent
  437. self.setLayout(QVBoxLayout(self))
  438. self.mousesel = WrappedComboBox(self)
  439. self.mousesel.currentIndexChanged.connect(self.mouseChanged)
  440. self.layout().addWidget(self.mousesel)
  441. self.layout().addSpacing(15)
  442. self.profiletab = QTabWidget(self)
  443. self.layout().addWidget(self.profiletab)
  444. self.profileWidgets = []
  445. self.freqSel = MouseScanFreqWidget(self, self)
  446. self.layout().addWidget(self.freqSel)
  447. self.dpimappings = MouseDpiMappingsWidget(self, self)
  448. self.layout().addWidget(self.dpimappings)
  449. self.leds = LedsWidget(self, self)
  450. self.layout().addWidget(self.leds)
  451. self.layout().addStretch()
  452. self.fwVer = QLabel(self)
  453. self.layout().addWidget(self.fwVer)
  454. def updateContent(self, mice):
  455. self.mice = mice
  456. self.mousesel.clear()
  457. for mouse in mice:
  458. id = RazerDevId(mouse)
  459. self.mousesel.addItem("%s %s-%s %s" % \
  460. (id.getDevName(), id.getBusType(),
  461. id.getBusPosition(), id.getDevId()))
  462. def mouseChanged(self, index):
  463. self.profiletab.clear()
  464. self.profileWidgets = []
  465. self.dpimappings.clear()
  466. self.leds.clear()
  467. self.profiletab.setEnabled(index > -1)
  468. if index == -1:
  469. self.fwVer.clear()
  470. return
  471. self.mouse = self.mice[index]
  472. minfo = razer.getMouseInfo(self.mouse)
  473. profileIds = razer.getProfiles(self.mouse)
  474. activeProfileId = razer.getActiveProfile(self.mouse)
  475. activeWidget = None
  476. for profileId in profileIds:
  477. widget = MouseProfileWidget(self, profileId)
  478. if profileId == activeProfileId:
  479. activeWidget = widget
  480. self.profiletab.addTab(widget, str(profileId + 1))
  481. self.profileWidgets.append(widget)
  482. self.reloadProfiles()
  483. if activeWidget:
  484. self.profiletab.setCurrentWidget(activeWidget)
  485. # Update global frequency selection (if any)
  486. if minfo & Razer.MOUSEINFOFLG_GLOBAL_FREQ:
  487. self.freqSel.updateContent()
  488. self.freqSel.show()
  489. else:
  490. self.freqSel.hide()
  491. # Update global DPI mappings (if any)
  492. self.dpimappings.updateContent()
  493. # Update global LEDs (if any)
  494. self.leds.updateContent()
  495. ver = razer.getFwVer(self.mouse)
  496. if (ver[0] == 0xFF and ver[1] == 0xFF) or\
  497. (ver[0] == 0 and ver[1] == 0):
  498. self.fwVer.hide()
  499. else:
  500. extra = ""
  501. if minfo & Razer.MOUSEINFOFLG_SUGGESTFWUP:
  502. extra = self.tr("\nDue to known bugs in this firmware version, a "
  503. "firmware update is STRONGLY suggested!")
  504. self.fwVer.setText(self.tr("Firmware version: %u.%02u%s" % (ver[0], ver[1], extra)))
  505. self.fwVer.show()
  506. def reloadProfiles(self):
  507. for prof in self.profileWidgets:
  508. prof.reload()
  509. activeProf = razer.getActiveProfile(self.mouse)
  510. for i in range(0, self.profiletab.count()):
  511. profileId = self.profiletab.widget(i).profileId
  512. name = razer.getProfileName(self.mouse, profileId)
  513. if activeProf == profileId:
  514. name = ">" + name + "<"
  515. self.profiletab.setTabText(i, name)
  516. class StatusBar(QStatusBar):
  517. def showMessage(self, msg):
  518. QStatusBar.showMessage(self, msg, 10000)
  519. class MainWindow(QMainWindow):
  520. shown = Signal(QWidget)
  521. hidden = Signal(QWidget)
  522. def __init__(self, parent = None, enableNotificationPolling = True):
  523. QMainWindow.__init__(self, parent)
  524. self.setWindowTitle(self.tr("Razer device configuration"))
  525. mb = QMenuBar(self)
  526. rzrmen = QMenu(self.tr("Razer"), mb)
  527. rzrmen.addAction(self.tr("Scan system for devices"), self.scan)
  528. rzrmen.addAction(self.tr("Re-apply all hardware settings"), self.reconfig)
  529. rzrmen.addSeparator()
  530. rzrmen.addAction(self.tr("Exit"), self.close)
  531. mb.addMenu(rzrmen)
  532. helpmen = QMenu(self.tr("Help"), mb)
  533. helpmen.addAction(self.tr("About"), self.about)
  534. mb.addMenu(helpmen)
  535. self.setMenuBar(mb)
  536. tab = QTabWidget(self)
  537. self.mousewidget = MouseWidget(self)
  538. tab.addTab(self.mousewidget, self.tr("Mice"))
  539. self.setCentralWidget(tab)
  540. self.setStatusBar(StatusBar())
  541. self.mice = []
  542. self.scan()
  543. if enableNotificationPolling:
  544. self.__notifyPollTimer = QTimer(self)
  545. self.__notifyPollTimer.timeout.connect(self.pollNotifications)
  546. self.__notifyPollTimer.start(300)
  547. def pollNotifications(self):
  548. n = razer.pollNotifications()
  549. if n:
  550. self.scan()
  551. # Rescan for new devices
  552. def scan(self):
  553. razer.rescanMice()
  554. mice = razer.getMice()
  555. if len(mice) != len(self.mice):
  556. if (len(mice) == 1):
  557. self.statusBar().showMessage(self.tr("Found one Razer mouse"))
  558. elif (len(mice) > 1):
  559. self.statusBar().showMessage(self.tr("Found %d Razer mice" % len(mice)))
  560. self.mice = mice
  561. self.mousewidget.updateContent(mice)
  562. def reconfig(self):
  563. razer.rescanDevices()
  564. razer.reconfigureDevices()
  565. def about(self):
  566. QMessageBox.information(self, self.tr("About"),
  567. self.tr("Razer device configuration tool\n"
  568. "Version %s\n"
  569. "Copyright (c) 2007-2020 Michael Buesch et al."
  570. % RAZER_VERSION))
  571. def showEvent(self, ev):
  572. super(MainWindow, self).showEvent(ev)
  573. self.shown.emit(self)
  574. def hideEvent(self, ev):
  575. super(MainWindow, self).hideEvent(ev)
  576. self.hidden.emit(self)
  577. class AppletMainWindow(MainWindow):
  578. def __init__(self, parent=None):
  579. super().__init__(parent, enableNotificationPolling = False)
  580. def updateContent(self):
  581. self.mousewidget.reloadProfiles()
  582. def closeEvent(self, event):
  583. event.ignore()
  584. self.hide()
  585. class RazerApplet(QSystemTrayIcon):
  586. def __init__(self):
  587. QSystemTrayIcon.__init__(self)
  588. self.__contextMenuIsShown = False
  589. self.__mainwndIsShown = False
  590. icon = QIcon.fromTheme("razercfg")
  591. if icon.isNull():
  592. for prefix in ("/usr/local", "/usr"):
  593. icon.addFile(prefix + "/share/icons/hicolor/" +\
  594. "scalable/apps/razercfg.svg")
  595. icon.addFile("razercfg.svg")
  596. self.setIcon(icon)
  597. self.menu = QMenu()
  598. self.mainwnd = AppletMainWindow()
  599. self.__pollTimer = QTimer(self)
  600. self.__pollTimer.setInterval(300)
  601. self.__pollTimer.stop()
  602. self.mainwnd.scan()
  603. self.mice = razer.getMice();
  604. self.buildMenu()
  605. self.setContextMenu(self.menu)
  606. self.__pollTimer.timeout.connect(self.updateContent)
  607. self.contextMenu().aboutToShow.connect(self.__contextAboutToShow)
  608. self.contextMenu().aboutToHide.connect(self.__contextAboutToHide)
  609. self.activated.connect(self.__handleActivate)
  610. self.mainwnd.shown.connect(self.__mainwndShown)
  611. self.mainwnd.hidden.connect(self.__mainwndHidden)
  612. def __handleActivate(self, reason):
  613. if reason in {QSystemTrayIcon.Trigger,
  614. QSystemTrayIcon.DoubleClick,
  615. QSystemTrayIcon.MiddleClick}:
  616. self.contextMenu().popup(QCursor.pos())
  617. def __contextAboutToShow(self):
  618. self.__contextMenuIsShown = True
  619. self.__setPolling()
  620. self.updateContent()
  621. def __contextAboutToHide(self):
  622. self.__contextMenuIsShown = False
  623. self.__setPolling()
  624. def __mainwndShown(self, widget):
  625. self.__mainwndIsShown = True
  626. self.__setPolling()
  627. self.updateContent()
  628. def __mainwndHidden(self, widget):
  629. self.__mainwndIsShown = False
  630. self.__setPolling()
  631. def __setPolling(self):
  632. if self.__contextMenuIsShown or self.__mainwndIsShown:
  633. if not self.__pollTimer.isActive():
  634. self.__pollTimer.start()
  635. else:
  636. if self.__pollTimer.isActive():
  637. self.__pollTimer.stop()
  638. def updateContent(self):
  639. if razer.pollNotifications():
  640. self.mainwnd.scan()
  641. mice = razer.getMice()
  642. if mice != self.mice:
  643. self.mice = mice
  644. self.buildMenu()
  645. def buildMenu(self):
  646. # clear the menu
  647. self.menu.clear()
  648. # add mices and profiles to menu
  649. for mouse in self.mice:
  650. mouse_id = RazerDevId(mouse)
  651. mouse_menu = self.menu.addMenu("&" + mouse_id.getDevName() + " mouse")
  652. self.getMouseProfiles(mouse, mouse_menu)
  653. if not self.mice:
  654. act = self.menu.addAction("No Razer devices found")
  655. act.setEnabled(False)
  656. # add buttons
  657. self.menu.addSeparator()
  658. self.menu.addAction("&Open main window...", self.mainwnd.show)
  659. self.menu.addAction("&Exit", sys.exit)
  660. def selectProfile(self, mouse, profileId):
  661. razer.setActiveProfile(mouse, profileId)
  662. self.mainwnd.updateContent()
  663. def getMouseProfiles(self, mouse, mouse_menu):
  664. mouse_menu.clear()
  665. profileIds = razer.getProfiles(mouse)
  666. activeProfileId = razer.getActiveProfile(mouse)
  667. group = QActionGroup(self)
  668. for profileId in profileIds:
  669. name = razer.getProfileName(mouse, profileId)
  670. action = QAction(name, mouse_menu)
  671. action.setCheckable(True)
  672. action.triggered.connect( partial(self.selectProfile, mouse, profileId))
  673. group.addAction(action)
  674. mouse_menu.addAction(action)
  675. if profileId == activeProfileId:
  676. action.setChecked(True)