|
|
- # Gnome15 - Suite of tools for the Logitech G series keyboards and headsets
- # Copyright (C) 2010 Brett Smith <tanktarta@blueyonder.co.uk>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- from .g19_receivers import G19Receiver
-
- import sys
- import threading
- import time
- import usb
- from PIL import Image as Img
- import logging
- import array
- import math
- logger = logging.getLogger(__name__)
-
- class G19(object):
- '''Simple access to Logitech G19 features.
-
- All methods are thread-safe if not denoted otherwise.
-
- '''
-
- def __init__(self, resetOnStart=False, enable_mm_keys=False, write_timeout = 10000, reset_wait = 0):
- '''Initializes and opens the USB device.'''
-
- logger.info("Setting up G19 with write timeout of %d", write_timeout)
- self.enable_mm_keys = enable_mm_keys
- self.__write_timeout = write_timeout
- self.__usbDevice = G19UsbController(resetOnStart, enable_mm_keys, reset_wait)
- self.__usbDeviceMutex = threading.Lock()
- self.__keyReceiver = G19Receiver(self)
- self.__threadDisplay = None
-
- self.__frame_content = [0x10, 0x0F, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x3F, 0x01, 0xEF, 0x00, 0x0F]
- for i in range(16, 256):
- self.__frame_content.append(i)
- for i in range(256):
- self.__frame_content.append(i)
-
- @staticmethod
- def convert_image_to_frame(filename):
- '''Loads image from given file.
-
- Format will be auto-detected. If neccessary, the image will be resized
- to 320x240.
-
- @return Frame data to be used with send_frame().
-
- '''
- img = Img.open(filename)
- access = img.load()
- if img.size != (320, 240):
- img = img.resize((320, 240), Img.CUBIC)
- access = img.load()
- data = []
- for x in range(320):
- for y in range(240):
- ax = access[x, y]
- if len(ax) == 3:
- r, g, b = ax
- else:
- r, g, b, a = ax
- val = G19.rgb_to_uint16(r, g, b)
- data.append(val & 0xff)
- data.append(val >> 8)
- return data
-
- @staticmethod
- def rgb_to_uint16(r, g, b):
- '''Converts a RGB value to 16bit highcolor (5-6-5).
-
- @return 16bit highcolor value in little-endian.
-
- '''
- rBits = math.trunc(r * 2**5 / 255)
- gBits = math.trunc(g * 2**6 / 255)
- bBits = math.trunc(b * 2**5 / 255)
-
- rBits = rBits if rBits <= 0b00011111 else 0b00011111
- gBits = gBits if gBits <= 0b00111111 else 0b00111111
- bBits = bBits if bBits <= 0b00011111 else 0b00011111
-
- # print(rBits)
- # print(gBits)
- # print(bBits)
- # print("........")
- # print("{0:b}".format(rBits))
- # print("{0:b}".format(gBits))
- # print("{0:b}".format(bBits))
- # print(";;;;;;;;")
- # print("{0:b}".format(rBits << 3))
- # print("{0:b}".format(gBits >> 3))
- # print("{0:b}".format(gBits << 5))
- # print("{0:b}".format(bBits))
- # #print("{0:b}".format(bBits))
- valueH = (gBits << 5) | bBits
- valueL = (rBits << 3) | (gBits >> 3)
- value = (rBits << 11) | (gBits << 5) | bBits
-
- # print("=======")
- # print(valueL)
- # print(valueH)
- # print(value)
- # print("****")
- # print("{0:b}".format(valueL))
- # print("{0:b}".format(valueH))
- # print("{0:b}".format(r))
- # print("{0:b}".format(value))
- return value
-
- def add_input_processor(self, input_processor):
- self.__keyReceiver.add_input_processor(input_processor)
-
- def add_applet(self, applet):
- '''Starts an applet.'''
- self.add_input_processor(applet.get_input_processor())
-
- def fill_display_with_color(self, r, g, b):
- '''Fills display with given color.'''
- # 16bit highcolor format: 5 red, 6 gree, 5 blue
- # saved in little-endian, because USB is little-endian
- value = self.rgb_to_uint16(r, g, b)
- valueH = value & 0xff
- valueL = value >> 8
- frame = [valueH, valueL] * (320 * 240)
- self.send_frame(frame)
-
- def load_image(self, filename):
- '''Loads image from given file.
-
- Format will be auto-detected. If neccessary, the image will be resized
- to 320x240.
-
- '''
- self.send_frame(self.convert_image_to_frame(filename))
-
- def read_g_and_m_keys(self, maxLen=20):
- '''Reads interrupt data from G, M and light switch keys.
-
- @return maxLen Maximum number of bytes to read.
- @return Read data or empty list.
-
- '''
- self.__usbDeviceMutex.acquire()
- val = []
- try:
- val = list(self.__usbDevice.handleIf1.interruptRead(
- 0x83, maxLen, 10))
- except usb.USBError as e:
- if e.message != "Connection timed out":
- logger.debug("Error reading g and m keys", exc_info = e)
- pass
- finally:
- self.__usbDeviceMutex.release()
- return val
-
- def read_display_menu_keys(self):
- '''Reads interrupt data from display keys.
-
- @return Read data or empty list.
-
- '''
- self.__usbDeviceMutex.acquire()
- val = []
- try:
- val = list(self.__usbDevice.handleIf0.interruptRead(0x81, 2, 10))
- except usb.USBError as e:
- if e.message != "Connection timed out":
- logger.debug("Error reading display menu keys", exc_info = e)
- pass
- finally:
- self.__usbDeviceMutex.release()
- return val
-
- def read_multimedia_keys(self):
- '''Reads interrupt data from multimedia keys.
-
- @return Read data or empty list.
-
- '''
- if not self.enable_mm_keys:
- return False
-
- self.__usbDeviceMutex.acquire()
- val = []
- try:
- val = list(self.__usbDevice.handleIfMM.interruptRead(0x82, 2, 10))
- except usb.USBError as e:
- if e.message != "Connection timed out":
- logger.debug("Error reading multimedia keys", exc_info = e)
- pass
- finally:
- self.__usbDeviceMutex.release()
- return val
-
- def reset(self):
- '''Initiates a bus reset to USB device.'''
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.reset()
- finally:
- self.__usbDeviceMutex.release()
-
- def save_default_bg_color(self, r, g, b):
- '''This stores given color permanently to keyboard.
-
- After a reset this will be color used by default.
-
- '''
- rtype = usb.TYPE_CLASS | usb.RECIP_INTERFACE
- colorData = [7, r, g, b]
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.handleIf1.controlMsg(
- rtype, 0x09, colorData, 0x308, 0x01, self.__write_timeout)
- finally:
- self.__usbDeviceMutex.release()
-
- def set_display_colorful(self):
- '''This is an example how to create an image having a green to red
- transition from left to right and a black to blue from top to bottom.
-
- '''
- data = []
- for i in range(320 * 240 * 2):
- data.append(0)
- for x in range(320):
- for y in range(240):
- data[2*(x*240+y)] = self.rgb_to_uint16(
- 255 * x / 320, 255 * (320 - x) / 320, 255 * y / 240) >> 8
- data[2*(x*240+y)+1] = self.rgb_to_uint16(
- 255 * x / 320, 255 * (320 - x) / 320, 255 * y / 240) & 0xff
- self.send_frame(data)
-
- def send_frame(self, data):
- '''Sends a frame to display.
-
- @param data 320x240x2 bytes, containing the frame in little-endian
- 16bit highcolor (5-6-5) format.
- Image must be row-wise, starting at upper left corner and ending at
- lower right. This means (data[0], data[1]) is the first pixel and
- (data[239 * 2], data[239 * 2 + 1]) the lower left one.
-
- '''
- if len(data) != (320 * 240 * 2):
- raise ValueError("illegal frame size: " + str(len(data))
- + " should be 320x240x2=" + str(320 * 240 * 2))
-
- frame = list(self.__frame_content)
- frame += data
-
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.handleIf0.bulkWrite(2, frame, self.__write_timeout)
- finally:
- self.__usbDeviceMutex.release()
-
- def set_bg_color(self, r, g, b):
- '''Sets backlight to given color.'''
- rtype = usb.TYPE_CLASS | usb.RECIP_INTERFACE
- colorData = [7, r, g, b]
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.handleIf1.controlMsg(
- rtype, 0x09, colorData, 0x307, 0x01, 10000)
- finally:
- self.__usbDeviceMutex.release()
-
- def set_enabled_m_keys(self, keys):
- '''Sets currently lit keys as an OR-combination of LIGHT_KEY_M1..3,R.
-
- example:
- from logitech.g19_keys import Data
- lg19 = G19()
- lg19.set_enabled_m_keys(Data.LIGHT_KEY_M1 | Data.LIGHT_KEY_MR)
-
- '''
- rtype = usb.TYPE_CLASS | usb.RECIP_INTERFACE
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.handleIf1.controlMsg(
- rtype, 0x09, [5, keys], 0x305, 0x01, self.__write_timeout)
- finally:
- self.__usbDeviceMutex.release()
-
- def set_display_brightness(self, val):
- '''Sets display brightness.
-
- @param val in [0,100] (off..maximum).
-
- '''
- data = [val, 0xe2, 0x12, 0x00, 0x8c, 0x11, 0x00, 0x10, 0x00]
- rtype = usb.TYPE_VENDOR | usb.RECIP_INTERFACE
- self.__usbDeviceMutex.acquire()
- try:
- self.__usbDevice.handleIf1.controlMsg(rtype, 0x0a, data, 0x0, 0x0, self.__write_timeout)
- finally:
- self.__usbDeviceMutex.release()
-
- def start_event_handling(self):
- '''Start event processing (aka keyboard driver).
-
- This method is NOT thread-safe.
-
- '''
- self.stop_event_handling()
- self.__threadDisplay = threading.Thread(
- target=self.__keyReceiver.run)
- self.__keyReceiver.start()
- self.__threadDisplay.name = "EventThread"
- self.__threadDisplay.setDaemon(True)
- self.__threadDisplay.start()
-
- def stop_event_handling(self):
- '''Stops event processing (aka keyboard driver).
-
- This method is NOT thread-safe.
-
- '''
- self.__keyReceiver.stop()
- if self.__threadDisplay:
- self.__threadDisplay.join()
- self.__threadDisplay = None
-
- def close(self):
- logger.info("Closing G19")
- self.stop_event_handling()
- self.__usbDevice.close()
-
-
- class G19UsbController(object):
- '''Controller for accessing the G19 USB device.
-
- The G19 consists of two composite USB devices:
- * 046d:c228
- The keyboard consisting of two interfaces:
- MI00: keyboard
- EP 0x81(in) - INT the keyboard itself
- MI01: (ifacMM)
- EP 0x82(in) - multimedia keys, incl. scroll and Winkey-switch
-
- * 046d:c229
- LCD display with two interfaces:
- MI00 (0x05): (iface0) via control data in: display keys
- EP 0x81(in) - INT
- EP 0x02(out) - BULK display itself
- MI01 (0x06): (iface1) backlight
- EP 0x83(in) - INT G-keys, M1..3/MR key, light key
-
- '''
-
- def __init__(self, resetOnStart=False, enable_mm_keys=False, resetWait = 0):
- self.enable_mm_keys = enable_mm_keys
- logger.info("Looking for LCD device")
- self.__lcd_device = self._find_device(0x046d, 0xc229)
- if not self.__lcd_device:
- raise usb.USBError("G19 LCD not found on USB bus")
-
- # Reset
- self.handleIf0 = self.__lcd_device.open()
- if resetOnStart:
- logger.info("Resetting LCD device")
- self.handleIf0.reset()
- time.sleep(float(resetWait) / 1000.0)
- logger.info("Re-opening LCD device")
- self.handleIf0 = self.__lcd_device.open()
- logger.info("Re-opened LCD device")
-
- self.handleIf1 = self.__lcd_device.open()
-
- config = self.__lcd_device.configurations[0]
- display_interface = config.interfaces[0][0]
-
- # This is to cope with a difference in pyusb 1.0 compatibility layer
- if len(config.interfaces) > 1:
- macro_and_backlight_interface = config.interfaces[1][0]
- else:
- macro_and_backlight_interface = config.interfaces[0][1]
-
- try:
- logger.debug("Detaching kernel driver for LCD device")
- # Use .interfaceNumber for pyusb 1.0 compatibility layer
- self.handleIf0.detachKernelDriver(display_interface.interfaceNumber)
- logger.debug("Detached kernel driver for LCD device")
- except usb.USBError as e:
- logger.debug("Detaching kernel driver for LCD device failed.", exc_info = e)
-
- try:
- logger.debug("Detaching kernel driver for macro / backlight device")
- # Use .interfaceNumber for pyusb 1.0 compatibility layer
- self.handleIf1.detachKernelDriver(macro_and_backlight_interface.interfaceNumber)
- logger.debug("Detached kernel driver for macro / backlight device")
- except usb.USBError as e:
- logger.debug("Detaching kernel driver for macro / backlight device failed.", exc_info = e)
-
- logger.debug("Setting configuration")
-
- #self.handleIf0.setConfiguration(1)
- #self.handleIf1.setConfiguration(1)
-
- logger.debug("Claiming LCD interface")
- self.handleIf0.claimInterface(display_interface.interfaceNumber)
- logger.info("Claimed LCD interface")
- logger.debug("Claiming macro interface")
- self.handleIf1.claimInterface(macro_and_backlight_interface.interfaceNumber)
- logger.info("Claimed macro interface")
-
- if self.enable_mm_keys:
- logger.debug("Looking for multimedia keys device")
- self.__kbd_device = self._find_device(0x046d, 0xc228)
- if not self.__kbd_device:
- raise usb.USBError("G19 keyboard not found on USB bus")
- self.handleIfMM = self.__kbd_device.open()
-
- if resetOnStart:
- logger.debug("Resetting multimedia keys device")
- self.handleIfMM.reset()
- logger.debug("Re-opening multimedia keys device")
- self.handleIfMM = self.__kbd_device.open()
- logger.debug("Re-opened multimedia keys device")
-
-
- config = self.__kbd_device.configurations[0]
- ifacMM = config.interfaces[1][0]
-
- try:
- self.handleIfMM.setConfiguration(1)
- except usb.USBError as e:
- logger.debug("Error when trying to set configuration", exc_info = e)
- pass
- try:
- logger.debug("Detaching kernel driver for multimedia keys device")
- self.handleIfMM.detachKernelDriver(ifacMM)
- logger.debug("Detached kernel driver for multimedia keys device")
- except usb.USBError as e:
- logger.debug("Detaching kernel driver for multimedia keys device failed.", exc_info = e)
-
- logger.debug("Claiming multimedia interface")
- self.handleIfMM.claimInterface(1)
- logger.info("Claimed multimedia keys interface")
-
-
- def close(self):
- if self.enable_mm_keys:
- self.handleIfMM.releaseInterface()
- self.handleIf1.releaseInterface()
- self.handleIf0.releaseInterface()
-
- @staticmethod
- def _find_device(idVendor, idProduct):
- for bus in usb.busses():
- for dev in bus.devices:
- if dev.idVendor == idVendor and \
- dev.idProduct == idProduct:
- return dev
- return None
-
- def reset(self):
- '''Resets the device on the USB.'''
- self.handleIf0.reset()
- self.handleIf1.reset()
-
- def main():
- lg19 = G19()
- lg19.start_event_handling()
- time.sleep(20)
- lg19.stop_event_handling()
-
- if __name__ == '__main__':
- main()
|