main.py 25 KB

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