main.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. """
  2. # Razer device configuration
  3. # High level user interface library
  4. #
  5. # This library connects to the lowlevel 'razerd' system daemon.
  6. #
  7. # Copyright (C) 2008-2018 Michael Buesch <m@bues.ch>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. """
  19. import sys
  20. if sys.version_info[0] != 3:
  21. print("Python %d is not supported by razercfg." % sys.version_info[0])
  22. print("Please install Python 3.x")
  23. sys.exit(1)
  24. import socket
  25. import select
  26. import hashlib
  27. import struct
  28. RAZER_VERSION = "0.43"
  29. class RazerEx(Exception):
  30. "Exception thrown by pyrazer code."
  31. __be32_struct = struct.Struct(">I")
  32. __be16_struct = struct.Struct(">H")
  33. def razer_be32_to_int(be32, offset=0):
  34. return __be32_struct.unpack_from(be32, offset)[0]
  35. def razer_be16_to_int(be16, offset=0):
  36. return __be16_struct.unpack_from(be16, offset)[0]
  37. def razer_int_to_be32(integer):
  38. return __be32_struct.pack(integer)
  39. def razer_int_to_be16(integer):
  40. return __be16_struct.pack(integer)
  41. def razer_str2bool(string):
  42. string = string.lower().strip()
  43. if string in ["no", "off", "false"]:
  44. return False
  45. if string in ["yes", "on", "true"]:
  46. return True
  47. try:
  48. return bool(int(string))
  49. except ValueError as e:
  50. pass
  51. raise ValueError
  52. class RazerDevId(object):
  53. "devid parser"
  54. DEVTYPE_UNKNOWN = "Unknown"
  55. DEVTYPE_MOUSE = "Mouse"
  56. BUSTYPE_UNKNOWN = "Unknown"
  57. BUSTYPE_USB = "USB"
  58. def __init__(self, devid):
  59. self.devtype = self.DEVTYPE_UNKNOWN
  60. self.bustype = self.BUSTYPE_UNKNOWN
  61. self.buspos = ""
  62. self.devname = ""
  63. self.devid = ""
  64. try:
  65. id = devid.split(':')
  66. self.devtype = id[0]
  67. self.devname = id[1]
  68. bus = id[2].split('-')
  69. self.bustype = bus[0]
  70. self.buspos = bus[1]
  71. if len(bus) >= 3:
  72. self.buspos += "-" + bus[2]
  73. self.devid = id[3]
  74. except IndexError:
  75. pass
  76. def getDevType(self):
  77. "Returns DEVTYPE_..."
  78. return self.devtype
  79. def getBusType(self):
  80. "Returns BUSTYPE_..."
  81. return self.bustype
  82. def getBusPosition(self):
  83. "Returns the bus position ID string"
  84. return self.buspos
  85. def getDevName(self):
  86. "Returns the device name string"
  87. return self.devname
  88. def getDevId(self):
  89. "Returns the device ID string"
  90. return self.devid
  91. class RazerRGB(object):
  92. "An RGB color"
  93. def __init__(self, r, g, b):
  94. self.r = r
  95. self.g = g
  96. self.b = b
  97. @classmethod
  98. def fromU32(cls, u32):
  99. return cls(r=(u32 >> 16) & 0xFF,
  100. g=(u32 >> 8) & 0xFF,
  101. b=(u32 >> 0) & 0xFF)
  102. def toU32(self):
  103. return ((self.r & 0xFF) << 16) |\
  104. ((self.g & 0xFF) << 8) |\
  105. ((self.b & 0xFF) << 0)
  106. @classmethod
  107. def fromString(cls, string):
  108. string = string.strip().lstrip("#")
  109. if len(string) != 6:
  110. raise ValueError
  111. return cls(r=int(string[0:2], 16),
  112. g=int(string[2:4], 16),
  113. b=int(string[4:6], 16))
  114. class RazerLEDMode(object):
  115. "Representation of LED mode"
  116. LED_MODE_STATIC = 0
  117. LED_MODE_SPECTRUM = 1
  118. LED_MODE_BREATHING = 2
  119. LED_MODE_WAVE = 3
  120. LED_MODE_REACTION = 4
  121. def __init__(self, val):
  122. self.val = val
  123. def toString(self):
  124. return {
  125. self.LED_MODE_STATIC: 'static',
  126. self.LED_MODE_SPECTRUM: 'spectrum',
  127. self.LED_MODE_BREATHING: 'breathing',
  128. self.LED_MODE_WAVE: 'wave',
  129. self.LED_MODE_REACTION: 'reaction'
  130. }[self.val]
  131. @classmethod
  132. def listFromSupportedModes(cls, mask):
  133. modes = []
  134. for mode in (cls.LED_MODE_STATIC, cls.LED_MODE_SPECTRUM, cls.LED_MODE_BREATHING, cls.LED_MODE_WAVE, cls.LED_MODE_REACTION):
  135. if mask & (1 << mode):
  136. modes.append(cls(mode))
  137. return modes
  138. @classmethod
  139. def fromString(cls, string):
  140. return {
  141. 'static': cls(cls.LED_MODE_STATIC),
  142. 'spectrum': cls(cls.LED_MODE_SPECTRUM),
  143. 'breathing': cls(cls.LED_MODE_BREATHING),
  144. 'wave': cls(cls.LED_MODE_WAVE),
  145. 'reaction': cls(cls.LED_MODE_REACTION)
  146. }[string]
  147. class RazerLED(object):
  148. "LED representation"
  149. def __init__(self, profileId, name, state, mode, supported_modes, color, canChangeColor):
  150. self.profileId = profileId
  151. self.name = name
  152. self.state = state
  153. self.mode = mode
  154. self.supported_modes = supported_modes
  155. self.color = color
  156. self.canChangeColor = canChangeColor
  157. class RazerDpiMapping(object):
  158. "DPI mapping"
  159. def __init__(self, id, res, profileMask, mutable):
  160. self.id = id
  161. self.res = res
  162. self.profileMask = profileMask
  163. self.mutable = mutable
  164. class Razer(object):
  165. SOCKET_PATH = "/run/razerd/socket"
  166. PRIVSOCKET_PATH = "/run/razerd/socket.privileged"
  167. INTERFACE_REVISION = 6
  168. COMMAND_MAX_SIZE = 512
  169. COMMAND_HDR_SIZE = 1
  170. BULK_CHUNK_SIZE = 128
  171. RAZER_IDSTR_MAX_SIZE = 128
  172. RAZER_LEDNAME_MAX_SIZE = 64
  173. RAZER_NR_DIMS = 3
  174. COMMAND_ID_GETREV = 0 # Get the revision number of the socket interface.
  175. COMMAND_ID_RESCANMICE = 1 # Rescan mice.
  176. COMMAND_ID_GETMICE = 2 # Get a list of detected mice.
  177. COMMAND_ID_GETFWVER = 3 # Get the firmware rev of a mouse.
  178. COMMAND_ID_SUPPFREQS = 4 # Get a list of supported frequencies.
  179. COMMAND_ID_SUPPRESOL = 5 # Get a list of supported resolutions.
  180. COMMAND_ID_SUPPDPIMAPPINGS = 6 # Get a list of supported DPI mappings.
  181. COMMAND_ID_CHANGEDPIMAPPING = 7 # Modify a DPI mapping.
  182. COMMAND_ID_GETDPIMAPPING = 8 # Get the active DPI mapping for a profile.
  183. COMMAND_ID_SETDPIMAPPING = 9 # Set the active DPI mapping for a profile.
  184. COMMAND_ID_GETLEDS = 10 # Get a list of LEDs on the device.
  185. COMMAND_ID_SETLED = 11 # Set the state of a LED.
  186. COMMAND_ID_GETFREQ = 12 # Get the current frequency.
  187. COMMAND_ID_SETFREQ = 13 # Set the frequency.
  188. COMMAND_ID_GETPROFILES = 14 # Get a list of supported profiles.
  189. COMMAND_ID_GETACTIVEPROF = 15 # Get the active profile.
  190. COMMAND_ID_SETACTIVEPROF = 16 # Set the active profile.
  191. COMMAND_ID_SUPPBUTTONS = 17 # Get a list of physical buttons.
  192. COMMAND_ID_SUPPBUTFUNCS = 18 # Get a list of supported button functions.
  193. COMMAND_ID_GETBUTFUNC = 19 # Get the current function of a button.
  194. COMMAND_ID_SETBUTFUNC = 20 # Set the current function of a button.
  195. COMMAND_ID_SUPPAXES = 21 # Get a list of supported axes.
  196. COMMAND_ID_RECONFIGMICE = 22 # Reconfigure all mice
  197. COMMAND_ID_GETMOUSEINFO = 23 # Get detailed information about a mouse
  198. COMMAND_ID_GETPROFNAME = 24 # Get a profile name.
  199. COMMAND_ID_SETPROFNAME = 25 # Set a profile name.
  200. COMMAND_PRIV_FLASHFW = 128 # Upload and flash a firmware image
  201. COMMAND_PRIV_CLAIM = 129 # Claim the device.
  202. COMMAND_PRIV_RELEASE = 130 # Release the device.
  203. # Replies to commands
  204. REPLY_ID_U32 = 0 # An unsigned 32bit integer.
  205. REPLY_ID_STR = 1 # A string
  206. # Notifications. These go through the reply channel.
  207. __NOTIFY_ID_FIRST = 128
  208. NOTIFY_ID_NEWMOUSE = 128 # New mouse was connected.
  209. NOTIFY_ID_DELMOUSE = 129 # A mouse was removed.
  210. # String encodings
  211. STRING_ENC_ASCII = 0
  212. STRING_ENC_UTF8 = 1
  213. STRING_ENC_UTF16BE = 2
  214. ERR_NONE = 0
  215. ERR_CMDSIZE = 1
  216. ERR_NOMEM = 2
  217. ERR_NOMOUSE = 3
  218. ERR_NOLED = 4
  219. ERR_CLAIM = 5
  220. ERR_FAIL = 6
  221. ERR_PAYLOAD = 7
  222. ERR_NOTSUPP = 8
  223. errorToStringMap = {
  224. ERR_NONE : "Success",
  225. ERR_CMDSIZE : "Invalid command size",
  226. ERR_NOMEM : "Out of memory",
  227. ERR_NOMOUSE : "Could not find mouse",
  228. ERR_NOLED : "Could not find LED",
  229. ERR_CLAIM : "Failed to claim device",
  230. ERR_FAIL : "Failure",
  231. ERR_PAYLOAD : "Payload error",
  232. ERR_NOTSUPP : "Operation not supported",
  233. }
  234. # Axis flags
  235. RAZER_AXIS_INDEPENDENT_DPIMAPPING = (1 << 0)
  236. # Mouseinfo flags
  237. MOUSEINFOFLG_RESULTOK = (1 << 0) # Other flags are ok, if this is set.
  238. MOUSEINFOFLG_GLOBAL_LEDS = (1 << 1) # The device has global LEDs.
  239. MOUSEINFOFLG_PROFILE_LEDS = (1 << 2) # The device has per-profile LEDs.
  240. MOUSEINFOFLG_GLOBAL_FREQ = (1 << 3) # The device has global frequency settings.
  241. MOUSEINFOFLG_PROFILE_FREQ = (1 << 4) # The device has per-profile frequency settings.
  242. MOUSEINFOFLG_PROFNAMEMUTABLE = (1 << 5) # Profile names can be changed.
  243. MOUSEINFOFLG_SUGGESTFWUP = (1 << 6) # A firmware update is suggested.
  244. # LED flags
  245. LED_FLAG_HAVECOLOR = (1 << 0)
  246. LED_FLAG_CHANGECOLOR = (1 << 1)
  247. # Special profile ID
  248. PROFILE_INVALID = 0xFFFFFFFF
  249. @staticmethod
  250. def strerror(errno):
  251. try:
  252. errstr = Razer.errorToStringMap[errno]
  253. except KeyError:
  254. errstr = "Unknown error"
  255. return "Errorcode %d: %s" % (errno, errstr)
  256. def __init__(self, enableNotifications=False):
  257. "Connect to razerd."
  258. self.enableNotifications = enableNotifications
  259. self.notifications = []
  260. try:
  261. self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  262. self.sock.connect(self.SOCKET_PATH)
  263. except socket.error as e:
  264. raise RazerEx("Failed to connect to razerd socket: %s" % e)
  265. try:
  266. self.privsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  267. self.privsock.connect(self.PRIVSOCKET_PATH)
  268. except socket.error as e:
  269. self.privsock = None # No privileged access
  270. self.__sendCommand(self.COMMAND_ID_GETREV)
  271. rev = self.__recvU32()
  272. if (rev != self.INTERFACE_REVISION):
  273. additional = ""
  274. if rev < self.INTERFACE_REVISION:
  275. additional = "\nThe running razerd is too old. " \
  276. "Try to delete all razerd binaries and " \
  277. "re-install the razercfg package."
  278. raise RazerEx("Incompatible razerd daemon socket interface revision.\n"
  279. "razerd reported revision %u, but we expected revision %u."
  280. "%s" %\
  281. (rev, self.INTERFACE_REVISION, additional))
  282. def __constructCommand(self, commandId, idstr, payload):
  283. cmd = bytes((commandId,))
  284. idstr = idstr.encode("UTF-8")
  285. idstr += b'\0' * (self.RAZER_IDSTR_MAX_SIZE - len(idstr))
  286. cmd += idstr
  287. cmd += payload
  288. cmd += b'\0' * (self.COMMAND_MAX_SIZE - len(cmd))
  289. return cmd
  290. def __send(self, data):
  291. self.sock.sendall(data)
  292. def __sendPrivileged(self, data):
  293. try:
  294. self.privsock.sendall(data)
  295. except (socket.error, AttributeError) as e:
  296. raise RazerEx("Privileged command failed. Do you have permission?")
  297. def __sendBulkPrivileged(self, data):
  298. for i in range(0, len(data), self.BULK_CHUNK_SIZE):
  299. chunk = data[i : i + self.BULK_CHUNK_SIZE]
  300. self.__sendPrivileged(chunk)
  301. result = self.__recvU32Privileged()
  302. if result != 0:
  303. raise RazerEx("Privileged bulk write failed. %u" % result)
  304. def __sendCommand(self, commandId, idstr="", payload=b""):
  305. cmd = self.__constructCommand(commandId, idstr, payload)
  306. self.__send(cmd)
  307. def __sendPrivilegedCommand(self, commandId, idstr="", payload=b""):
  308. cmd = self.__constructCommand(commandId, idstr, payload)
  309. self.__sendPrivileged(cmd)
  310. def __handleReceivedMessage(self, packet):
  311. id = packet[0]
  312. if id < self.__NOTIFY_ID_FIRST:
  313. raise RazerEx("Received unhandled packet %u" % id)
  314. if self.enableNotifications:
  315. self.notifications.append(packet)
  316. def __receive(self, sock):
  317. "Receive the next message. This will block until a message arrives."
  318. hdrlen = 1
  319. hdr = sock.recv(hdrlen)
  320. id = hdr[0]
  321. payload = None
  322. if id == self.REPLY_ID_U32:
  323. payload = razer_be32_to_int(sock.recv(4))
  324. elif id == self.REPLY_ID_STR:
  325. encoding = sock.recv(1)[0]
  326. strlen = razer_be16_to_int(sock.recv(2))
  327. if encoding == self.STRING_ENC_ASCII:
  328. nrbytes = strlen
  329. decode = lambda pl: pl.decode("ASCII")
  330. elif encoding == self.STRING_ENC_UTF8:
  331. nrbytes = strlen
  332. decode = lambda pl: pl.decode("UTF-8")
  333. elif encoding == self.STRING_ENC_UTF16BE:
  334. nrbytes = strlen * 2
  335. decode = lambda pl: pl.decode("UTF-16-BE")
  336. else:
  337. raise RazerEx("Received invalid string encoding %d" %\
  338. encoding)
  339. payload = sock.recv(nrbytes) if nrbytes else b""
  340. try:
  341. payload = decode(payload)
  342. except UnicodeError as e:
  343. raise RazerEx("Unicode decode error in received payload")
  344. elif id == self.NOTIFY_ID_NEWMOUSE:
  345. pass
  346. elif id == self.NOTIFY_ID_DELMOUSE:
  347. pass
  348. else:
  349. raise RazerEx("Received unknown message (id=%u)" % id)
  350. return (id, payload)
  351. def __receiveExpectedMessage(self, sock, expectedId):
  352. """Receive messages until the expected one appears.
  353. Unexpected messages will be handled by __handleReceivedMessage.
  354. This function returns the payload of the expected message."""
  355. while 1:
  356. id, payload = self.__receive(sock)
  357. if id == expectedId:
  358. break
  359. else:
  360. self.__handleReceivedMessage((id, payload))
  361. return payload
  362. def __recvU32(self):
  363. "Receive an expected REPLY_ID_U32"
  364. return self.__receiveExpectedMessage(self.sock, self.REPLY_ID_U32)
  365. def __recvU32Privileged(self):
  366. "Receive an expected REPLY_ID_U32 on the privileged socket"
  367. try:
  368. return self.__receiveExpectedMessage(self.privsock, self.REPLY_ID_U32)
  369. except (socket.error, AttributeError) as e:
  370. raise RazerEx("Privileged recvU32 failed. Do you have permission?")
  371. def __recvString(self):
  372. "Receive an expected REPLY_ID_STR"
  373. return self.__receiveExpectedMessage(self.sock, self.REPLY_ID_STR)
  374. def pollNotifications(self):
  375. "Returns a list of pending notifications (id, payload)"
  376. if not self.enableNotifications:
  377. raise RazerEx("Polled notifications while notifications were disabled")
  378. while 1:
  379. res = select.select([self.sock], [], [], 0.001)
  380. if not res[0]:
  381. break
  382. pack = self.__receive(self.sock)
  383. self.__handleReceivedMessage(pack)
  384. notifications = self.notifications
  385. self.notifications = []
  386. return notifications
  387. def rescanMice(self):
  388. "Send the command to rescan for mice to the daemon."
  389. self.__sendCommand(self.COMMAND_ID_RESCANMICE)
  390. def rescanDevices(self):
  391. "Rescan for new devices."
  392. self.rescanMice()
  393. def getMice(self):
  394. "Returns a list of ID-strings for the detected mice."
  395. self.__sendCommand(self.COMMAND_ID_GETMICE)
  396. count = self.__recvU32()
  397. mice = []
  398. for i in range(0, count):
  399. mice.append(self.__recvString())
  400. return mice
  401. def getMouseInfo(self, idstr):
  402. "Get detailed information about a mouse"
  403. self.__sendCommand(self.COMMAND_ID_GETMOUSEINFO, idstr)
  404. flags = self.__recvU32()
  405. if (flags & self.MOUSEINFOFLG_RESULTOK) == 0:
  406. raise RazerEx("Failed to get mouseinfo for " + idstr)
  407. return flags
  408. def reconfigureMice(self):
  409. "Reconfigure all mice."
  410. self.__sendCommand(self.COMMAND_ID_RECONFIGMICE)
  411. def reconfigureDevices(self):
  412. "Reconfigure all devices."
  413. self.reconfigureMice()
  414. def getFwVer(self, idstr):
  415. "Returns the firmware version. The returned value is a tuple (major, minor)."
  416. self.__sendCommand(self.COMMAND_ID_GETFWVER, idstr)
  417. rawVer = self.__recvU32()
  418. return ((rawVer >> 8) & 0xFF, rawVer & 0xFF)
  419. def getSupportedFreqs(self, idstr):
  420. "Returns a list of supported frequencies for a mouse."
  421. self.__sendCommand(self.COMMAND_ID_SUPPFREQS, idstr)
  422. count = self.__recvU32()
  423. freqs = []
  424. for i in range(0, count):
  425. freqs.append(self.__recvU32())
  426. return freqs
  427. def getCurrentFreq(self, idstr, profileId=PROFILE_INVALID):
  428. "Returns the currently selected frequency for a mouse."
  429. payload = razer_int_to_be32(profileId)
  430. self.__sendCommand(self.COMMAND_ID_GETFREQ, idstr, payload)
  431. return self.__recvU32()
  432. def getSupportedRes(self, idstr):
  433. "Returns a list of supported resolutions for a mouse."
  434. self.__sendCommand(self.COMMAND_ID_SUPPRESOL, idstr)
  435. count = self.__recvU32()
  436. res = []
  437. for i in range(0, count):
  438. res.append(self.__recvU32())
  439. return res
  440. def getLeds(self, idstr, profileId=PROFILE_INVALID):
  441. """Returns a list of RazerLED instances for the given profile,
  442. or the global LEDs, if no profile given"""
  443. payload = razer_int_to_be32(profileId)
  444. self.__sendCommand(self.COMMAND_ID_GETLEDS, idstr, payload)
  445. count = self.__recvU32()
  446. leds = []
  447. for i in range(0, count):
  448. flags = self.__recvU32()
  449. name = self.__recvString()
  450. state = self.__recvU32()
  451. mode = RazerLEDMode(self.__recvU32())
  452. supported_modes = RazerLEDMode.listFromSupportedModes(self.__recvU32())
  453. color = self.__recvU32()
  454. if (flags & self.LED_FLAG_HAVECOLOR) == 0:
  455. color = None
  456. else:
  457. color = RazerRGB.fromU32(color)
  458. canChangeColor = bool(flags & self.LED_FLAG_CHANGECOLOR)
  459. leds.append(RazerLED(profileId, name, state, mode, supported_modes, color, canChangeColor))
  460. return leds
  461. def setLed(self, idstr, led):
  462. "Set a LED to a new state."
  463. if len(led.name) > self.RAZER_LEDNAME_MAX_SIZE:
  464. raise RazerEx("LED name string too long")
  465. payload = razer_int_to_be32(led.profileId)
  466. led_name = led.name.encode("UTF-8")
  467. payload += led_name
  468. payload += b'\0' * (self.RAZER_LEDNAME_MAX_SIZE - len(led_name))
  469. payload += b'\x01' if led.state else b'\x00'
  470. payload += bytes([led.mode.val])
  471. if led.color:
  472. payload += razer_int_to_be32(led.color.toU32())
  473. else:
  474. payload += razer_int_to_be32(0)
  475. self.__sendCommand(self.COMMAND_ID_SETLED, idstr, payload)
  476. return self.__recvU32()
  477. def setFrequency(self, idstr, profileId, newFrequency):
  478. "Set a new scan frequency (in Hz)."
  479. payload = razer_int_to_be32(profileId) + razer_int_to_be32(newFrequency)
  480. self.__sendCommand(self.COMMAND_ID_SETFREQ, idstr, payload)
  481. return self.__recvU32()
  482. def getSupportedDpiMappings(self, idstr):
  483. "Returns a list of supported DPI mappings. Each entry is a RazerDpiMapping() instance."
  484. self.__sendCommand(self.COMMAND_ID_SUPPDPIMAPPINGS, idstr)
  485. count = self.__recvU32()
  486. mappings = []
  487. for i in range(0, count):
  488. id = self.__recvU32()
  489. dimMask = self.__recvU32()
  490. res = []
  491. for i in range(0, self.RAZER_NR_DIMS):
  492. rVal = self.__recvU32()
  493. if (dimMask & (1 << i)) == 0:
  494. rVal = None
  495. res.append(rVal)
  496. profileMaskHigh = self.__recvU32()
  497. profileMaskLow = self.__recvU32()
  498. profileMask = (profileMaskHigh << 32) | profileMaskLow
  499. mutable = self.__recvU32()
  500. mappings.append(RazerDpiMapping(
  501. id, res, profileMask, mutable))
  502. return mappings
  503. def changeDpiMapping(self, idstr, mappingId, dimensionId, newResolution):
  504. "Changes the resolution value of a DPI mapping."
  505. payload = razer_int_to_be32(mappingId) +\
  506. razer_int_to_be32(dimensionId) +\
  507. razer_int_to_be32(newResolution)
  508. self.__sendCommand(self.COMMAND_ID_CHANGEDPIMAPPING, idstr, payload)
  509. return self.__recvU32()
  510. def getDpiMapping(self, idstr, profileId, axisId=None):
  511. "Gets the resolution mapping of a profile."
  512. if axisId is None:
  513. axisId = 0xFFFFFFFF
  514. payload = razer_int_to_be32(profileId) +\
  515. razer_int_to_be32(axisId)
  516. self.__sendCommand(self.COMMAND_ID_GETDPIMAPPING, idstr, payload)
  517. return self.__recvU32()
  518. def setDpiMapping(self, idstr, profileId, mappingId, axisId=None):
  519. "Sets the resolution mapping of a profile."
  520. if axisId is None:
  521. axisId = 0xFFFFFFFF
  522. payload = razer_int_to_be32(profileId) +\
  523. razer_int_to_be32(axisId) +\
  524. razer_int_to_be32(mappingId)
  525. self.__sendCommand(self.COMMAND_ID_SETDPIMAPPING, idstr, payload)
  526. return self.__recvU32()
  527. def getProfiles(self, idstr):
  528. "Returns a list of profiles. Each entry is the profile ID."
  529. self.__sendCommand(self.COMMAND_ID_GETPROFILES, idstr)
  530. count = self.__recvU32()
  531. profiles = []
  532. for i in range(0, count):
  533. profiles.append(self.__recvU32())
  534. return profiles
  535. def getActiveProfile(self, idstr):
  536. "Returns the ID of the active profile."
  537. self.__sendCommand(self.COMMAND_ID_GETACTIVEPROF, idstr)
  538. return self.__recvU32()
  539. def setActiveProfile(self, idstr, profileId):
  540. "Selects the active profile."
  541. payload = razer_int_to_be32(profileId)
  542. self.__sendCommand(self.COMMAND_ID_SETACTIVEPROF, idstr, payload)
  543. return self.__recvU32()
  544. def getProfileName(self, idstr, profileId):
  545. "Get a profile name."
  546. payload = razer_int_to_be32(profileId)
  547. self.__sendCommand(self.COMMAND_ID_GETPROFNAME, idstr, payload)
  548. return self.__recvString()
  549. def setProfileName(self, idstr, profileId, newName):
  550. "Set a profile name. newName is expected to be unicode."
  551. payload = razer_int_to_be32(profileId)
  552. rawstr = newName.encode("UTF-16-BE")
  553. rawstr = rawstr[:min(len(rawstr), 64 * 2)]
  554. rawstr += b'\0' * (64 * 2 - len(rawstr))
  555. payload += rawstr
  556. self.__sendCommand(self.COMMAND_ID_SETPROFNAME, idstr, payload)
  557. return self.__recvU32()
  558. def flashFirmware(self, idstr, image):
  559. "Flash a new firmware on the device. Needs high privileges!"
  560. payload = razer_int_to_be32(len(image))
  561. self.__sendPrivilegedCommand(self.COMMAND_PRIV_FLASHFW, idstr, payload)
  562. self.__sendBulkPrivileged(image)
  563. return self.__recvU32Privileged()
  564. def getSupportedButtons(self, idstr):
  565. "Get a list of supported buttons. Each entry is a tuple (id, name)."
  566. self.__sendCommand(self.COMMAND_ID_SUPPBUTTONS, idstr)
  567. buttons = []
  568. count = self.__recvU32()
  569. for i in range(0, count):
  570. id = self.__recvU32()
  571. name = self.__recvString()
  572. buttons.append( (id, name) )
  573. return buttons
  574. def getSupportedButtonFunctions(self, idstr):
  575. "Get a list of possible button functions. Each entry is a tuple (id, name)."
  576. self.__sendCommand(self.COMMAND_ID_SUPPBUTFUNCS, idstr)
  577. funcs = []
  578. count = self.__recvU32()
  579. for i in range(0, count):
  580. id = self.__recvU32()
  581. name = self.__recvString()
  582. funcs.append( (id, name) )
  583. return funcs
  584. def getButtonFunction(self, idstr, profileId, buttonId):
  585. "Get a button function. Returns a tuple (id, name)."
  586. payload = razer_int_to_be32(profileId) + razer_int_to_be32(buttonId)
  587. self.__sendCommand(self.COMMAND_ID_GETBUTFUNC, idstr, payload)
  588. id = self.__recvU32()
  589. name = self.__recvString()
  590. return (id, name)
  591. def setButtonFunction(self, idstr, profileId, buttonId, functionId):
  592. "Set a button function."
  593. payload = razer_int_to_be32(profileId) +\
  594. razer_int_to_be32(buttonId) +\
  595. razer_int_to_be32(functionId)
  596. self.__sendCommand(self.COMMAND_ID_SETBUTFUNC, idstr, payload)
  597. return self.__recvU32()
  598. def getSupportedAxes(self, idstr):
  599. "Get a list of axes on the device. Each entry is a tuple (id, name, flags)."
  600. self.__sendCommand(self.COMMAND_ID_SUPPAXES, idstr)
  601. axes = []
  602. count = self.__recvU32()
  603. for i in range(0, count):
  604. id = self.__recvU32()
  605. name = self.__recvString()
  606. flags = self.__recvU32()
  607. axes.append( (id, name, flags) )
  608. return axes
  609. class IHEXParser(object):
  610. TYPE_DATA = 0
  611. TYPE_EOF = 1
  612. TYPE_ESAR = 2
  613. TYPE_SSAR = 3
  614. TYPE_ELAR = 4
  615. TYPE_SLAR = 5
  616. def __init__(self, ihex):
  617. self.ihex = ihex
  618. def parse(self):
  619. bin = []
  620. try:
  621. lines = self.ihex.decode("ASCII").splitlines()
  622. hiAddr = 0
  623. for line in lines:
  624. line = line.strip()
  625. if len(line) == 0:
  626. continue
  627. if len(line) < 11 or (len(line) - 1) % 2 != 0:
  628. raise RazerEx("Invalid firmware file format (IHEX length error)")
  629. if line[0] != ':':
  630. raise RazerEx("Invalid firmware file format (IHEX magic error)")
  631. count = int(line[1:3], 16)
  632. if len(line) != count * 2 + 11:
  633. raise RazerEx("Invalid firmware file format (IHEX count error)")
  634. addr = (int(line[3:5], 16) << 8) | int(line[5:7], 16)
  635. addr |= hiAddr << 16
  636. type = int(line[7:9], 16)
  637. checksum = 0
  638. for i in range(1, len(line), 2):
  639. byte = int(line[i:i+2], 16)
  640. checksum = (checksum + byte) & 0xFF
  641. checksum = checksum & 0xFF
  642. if checksum != 0:
  643. raise RazerEx("Invalid firmware file format (IHEX checksum error)")
  644. if type == self.TYPE_EOF:
  645. break
  646. if type == self.TYPE_ELAR:
  647. if count != 2:
  648. raise RazerEx("Invalid firmware file format (IHEX inval ELAR)")
  649. hiAddr = (int(line[9:11], 16) << 8) | int(line[11:13], 16)
  650. continue
  651. if type == self.TYPE_DATA:
  652. if len(bin) < addr + count: # Reallocate
  653. bin += [b'\0'] * (addr + count - len(bin))
  654. for i in range(9, 9 + count * 2, 2):
  655. byte = bytes( (int(line[i:i+2], 16), ) )
  656. if bin[(i - 9) // 2 + addr] != b'\0':
  657. raise RazerEx("Invalid firmware file format (IHEX corruption)")
  658. bin[(i - 9) // 2 + addr] = byte
  659. continue
  660. raise RazerEx("Invalid firmware file format (IHEX unsup type %d)" % type)
  661. except (ValueError, UnicodeError) as e:
  662. raise RazerEx("Invalid firmware file format (IHEX digit format)")
  663. return b"".join(bin)
  664. class RazerFirmwareParser(object):
  665. class Descriptor:
  666. def __init__(self, startOffset, endOffset, parser, binTruncate):
  667. # startOffset: The offset where the ihex/srec/etc starts
  668. # endOffset: The offset where the ihex/srec/etc ends
  669. # parser: ihex/srec/etc parser
  670. # binTruncate: Number of bytes to truncate the binary to
  671. self.start = startOffset
  672. self.len = endOffset - startOffset + 1
  673. self.parser = parser
  674. self.binTruncate = binTruncate
  675. DUMP = 0 # Set to 1 to dump all images to /tmp
  676. FWLIST = {
  677. # Deathadder 1.27
  678. "92d7f44637858405a83c0f192c61388c" : Descriptor(0x14B28, 0x1D8F4, IHEXParser, 0x4000)
  679. }
  680. def __init__(self, filepath):
  681. try:
  682. self.data = open(filepath, "rb").read()
  683. except IOError as e:
  684. raise RazerEx("Could not read file: %s" % e.strerror)
  685. md5sum = hashlib.md5(self.data).hexdigest().lower()
  686. try:
  687. descriptor = self.FWLIST[md5sum]
  688. except KeyError:
  689. raise RazerEx("Unsupported firmware file")
  690. try:
  691. rawFwData = self.data[descriptor.start : descriptor.start+descriptor.len]
  692. if self.DUMP:
  693. open("/tmp/razer.dump", "wb").write(rawFwData)
  694. fwImage = descriptor.parser(rawFwData).parse()
  695. if self.DUMP:
  696. open("/tmp/razer.dump.image", "wb").write(fwImage)
  697. if descriptor.binTruncate:
  698. fwImage = fwImage[:descriptor.binTruncate]
  699. if self.DUMP:
  700. open("/tmp/razer.dump.image.trunc", "wb").write(fwImage)
  701. except IndexError:
  702. raise RazerEx("Invalid firmware file format")
  703. self.fwImage = fwImage
  704. def getImage(self):
  705. return self.fwImage