ui.py 25 KB

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