@ -0,0 +1,3 @@ | |||
*.pyc | |||
*.swp | |||
test.* |
@ -0,0 +1,20 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 1500 500" style="enable-background:new 0 0 1500 500;"> | |||
<style type="text/css"> | |||
.st0{fill:url(#SVGID_1_);} | |||
.st1{fill:#FFD94A;} | |||
.st2{fill:#3771A2;} | |||
</style> | |||
<g transform="translate(0, -500)"> | |||
<path class="st1" d="M123.34,605.23h115.2c62.21,0,100.67,28.8,90.17,95.23c-10.8,68.35-52.4,98.31-118.06,98.31h-42.62l-15.17,96 H77.6L123.34,605.23z M177.62,738.1h12.67c27.26,0,54.14,0,59.73-35.33c5.76-36.48-19.14-36.86-48.32-36.86h-12.67L177.62,738.1z"/> | |||
<path class="st1" d="M326.09,605.23h90.24l41.71,78.72l66.58-78.72h90.24L484.33,755.76l-21.96,139.01H387.1l21.96-139.01 L326.09,605.23z"/> | |||
<path class="st2" d="M586.39,894.77l45.74-289.54h107.52c51.46,0,82.86,19.97,74.13,75.26c-4.13,26.11-15.91,47.23-41.21,59.14 l-0.12,0.77c40.62,5.38,53.86,33.41,47.49,73.73c-9.59,60.67-64.58,80.64-117.57,80.64H586.39z M670.76,837.17h9.6 c22.27,0,59.72,1.15,64.76-30.72c5.52-34.94-32.27-31.49-56.85-31.49h-7.68L670.76,837.17z M689.44,718.9h8.45 c19.97,0,41.25-3.46,45.13-28.03c4.19-26.5-19-28.03-39.35-28.03h-5.38L689.44,718.9z"/> | |||
<path class="st2" d="M1156.54,744.62c-14.8,93.7-92.44,159.74-185.37,159.74s-149.69-66.05-134.89-159.74 c13.83-87.55,100.34-148.99,183.67-148.99C1103.28,595.63,1170.37,657.07,1156.54,744.62z M914.56,745.01 c-7.89,49.92,23.21,86.4,68.14,86.4c44.93,0,87.56-36.48,95.44-86.4c6.31-39.94-24.79-76.42-69.72-76.42 C963.5,668.59,920.87,705.07,914.56,745.01z"/> | |||
<path class="st2" d="M1133.63,605.23h90.24l41.71,78.72l66.58-78.72h90.24l-130.54,150.53l-21.96,139.01h-75.26l21.96-139.01 L1133.63,605.23z"/> | |||
</g> | |||
</svg> |
@ -0,0 +1,84 @@ | |||
from logitech.g19_keys import (Data, Key) | |||
from logitech.g19_receivers import InputProcessor | |||
class SimpleBgLight(object): | |||
'''Simple color changing. | |||
Enable M1..3 for red/green/blue and use the scroll to change the intensity | |||
for the currently selected colors. | |||
''' | |||
def __init__(self, lg19): | |||
self.__lg19 = lg19 | |||
self.__redEnabled = False | |||
self.__greenEnabled = False | |||
self.__blueEnabled = False | |||
self.__curColor = [255, 255, 255] | |||
def _clamp_current_color(self): | |||
'''Assures that all color components are in [0, 255].''' | |||
for i in range(3): | |||
val = self.__curColor[i] | |||
self.__curColor[i] = val if val >= 0 else 0 | |||
val = self.__curColor[i] | |||
self.__curColor[i] = val if val <= 255 else 255 | |||
def _update_leds(self): | |||
'''Updates M-leds according to enabled state.''' | |||
val = 0 | |||
if self.__redEnabled: | |||
val |= Data.LIGHT_KEY_M1 | |||
if self.__greenEnabled: | |||
val |= Data.LIGHT_KEY_M2 | |||
if self.__blueEnabled: | |||
val |= Data.LIGHT_KEY_M3 | |||
self.__lg19.set_enabled_m_keys(val) | |||
def get_input_processor(self): | |||
return self | |||
def process_input(self, evt): | |||
processed = False | |||
if Key.M1 in evt.keysDown: | |||
self.__redEnabled = not self.__redEnabled | |||
self._update_leds() | |||
processed = True | |||
if Key.M2 in evt.keysDown: | |||
self.__greenEnabled = not self.__greenEnabled | |||
self._update_leds() | |||
processed = True | |||
if Key.M3 in evt.keysDown: | |||
self.__blueEnabled = not self.__blueEnabled | |||
self._update_leds() | |||
processed = True | |||
oldColor = list(self.__curColor) | |||
diffVal = 0 | |||
scrollUsed = False | |||
if Key.SCROLL_UP in evt.keysDown: | |||
diffVal = 10 | |||
scrollUsed = True | |||
if Key.SCROLL_DOWN in evt.keysDown: | |||
diffVal = -10 | |||
scrollUsed = True | |||
atLeastOneColorIsEnabled = False | |||
if self.__redEnabled: | |||
self.__curColor[0] += diffVal | |||
atLeastOneColorIsEnabled = True | |||
if self.__greenEnabled: | |||
self.__curColor[1] += diffVal | |||
atLeastOneColorIsEnabled = True | |||
if self.__blueEnabled: | |||
self.__curColor[2] += diffVal | |||
atLeastOneColorIsEnabled = True | |||
self._clamp_current_color() | |||
processed = processed or atLeastOneColorIsEnabled and scrollUsed | |||
if oldColor != self.__curColor: | |||
self.__lg19.set_bg_color(*self.__curColor) | |||
return processed |
@ -0,0 +1,42 @@ | |||
from logitech.g19_keys import (Data, Key) | |||
from logitech.g19_receivers import InputProcessor | |||
class SimpleDisplayBrightness(object): | |||
'''Simple adjustment of display brightness. | |||
Uses scroll to adjust display brightness. | |||
''' | |||
def __init__(self, lg19): | |||
self.__lg19 = lg19 | |||
self.__curBrightness = 100 | |||
@staticmethod | |||
def _clamp_brightness(val): | |||
'''Clamps given value to [0, 100].''' | |||
val = val if val >= 0 else 0 | |||
val = val if val <= 100 else 100 | |||
return val | |||
def get_input_processor(self): | |||
return self | |||
def process_input(self, evt): | |||
usedInput = False | |||
diffVal = 0 | |||
if Key.SCROLL_UP in evt.keysDown: | |||
diffVal = 5 | |||
usedInput = True | |||
if Key.SCROLL_DOWN in evt.keysDown: | |||
diffVal = -5 | |||
usedInput = True | |||
oldVal = self.__curBrightness | |||
newVal = self._clamp_brightness(oldVal + diffVal) | |||
if oldVal != newVal: | |||
self.__lg19.set_display_brightness(newVal) | |||
self.__curBrightness = newVal | |||
return usedInput |
@ -0,0 +1,205 @@ | |||
from logitech.g19 import * | |||
from logitech.g19_keys import Key | |||
from logitech.g19_receivers import * | |||
from logitech.runnable import Runnable | |||
import multiprocessing | |||
import os | |||
import tempfile | |||
import threading | |||
import time | |||
class EarthImageCreator(Runnable): | |||
'''Thread for calling xplanet for specific angles.''' | |||
def __init__(self, lg19, angleStart, angleStop, slot, dataStore): | |||
'''Creates images for angles [angleStart, angleStop) and stores a the | |||
list of frames via dataStore.store(slot, frames). | |||
After completion of each frame, dataStore.signal_frame_done() will be | |||
called. | |||
''' | |||
Runnable.__init__(self) | |||
self.__angleStart = angleStart | |||
self.__angleStop = angleStop | |||
self.__dataStore = dataStore | |||
self.__lg19 = lg19 | |||
self.__slot = slot | |||
def run(self): | |||
frames = [] | |||
try: | |||
handle, filename = tempfile.mkstemp('.bmp') | |||
os.close(handle) | |||
for i in range(self.__angleStart, self.__angleStop): | |||
if self.is_about_to_stop(): | |||
break | |||
cmdline = "xplanet -geometry 320x240 -output " | |||
cmdline += filename | |||
cmdline += " -num_times 1 -latitude 40 -longitude " | |||
cmdline += str(i) | |||
os.system(cmdline) | |||
frames.append( self.__lg19.convert_image_to_frame(filename) ); | |||
self.__dataStore.signal_frame_done() | |||
finally: | |||
os.remove(filename) | |||
self.__dataStore.store(self.__slot, frames) | |||
class DataStore(object): | |||
'''Maintains all xplanet generated frames.''' | |||
def __init__(self, lg19): | |||
self.__allFrames = [] | |||
self.__creators = [] | |||
self.__numThreads = multiprocessing.cpu_count() | |||
self.__lg19 = lg19 | |||
self.__data = [[]] * self.__numThreads | |||
self.__lock = threading.Lock() | |||
self.__framesDone = 0 | |||
def abort_update(self): | |||
self.__lock.acquire() | |||
for creator in self.__creators: | |||
creator.stop() | |||
self.__lock.release() | |||
def get_data(self): | |||
'''Returns all currently available data. | |||
@return List of all frames. If no frames are calculated, an empty list | |||
will be returned. | |||
''' | |||
self.__lock.acquire() | |||
frames = self.__allFrames | |||
self.__lock.release() | |||
return frames | |||
def signal_frame_done(self): | |||
self.__lock.acquire() | |||
self.__framesDone += 1 | |||
print "frames done: {0}".format(self.__framesDone) | |||
self.__lock.release() | |||
def update(self): | |||
'''Regenerates all data.''' | |||
self.__lock.acquire() | |||
self.__framesDone = 0 | |||
self.__data = [[]] * self.__numThreads | |||
self.__lock.release() | |||
threads = [] | |||
for i in range(self.__numThreads): | |||
perCpu = 360 / self.__numThreads | |||
angleStart = i * perCpu | |||
if i == self.__numThreads - 1: | |||
angleStop = 360 | |||
else: | |||
angleStop = angleStart + perCpu | |||
c = EarthImageCreator(self.__lg19, angleStart, angleStop, i, self) | |||
self.__lock.acquire() | |||
self.__creators.append(c) | |||
self.__lock.release() | |||
c.start() | |||
t = threading.Thread(target=c.run) | |||
threads.append(t) | |||
t.start() | |||
for t in threads: | |||
t.join() | |||
self.__lock.acquire() | |||
self.__creators = [] | |||
self.__allFrames = [] | |||
for frame in self.__data: | |||
self.__allFrames += frame | |||
self.__lock.release() | |||
def store(self, slot, frames): | |||
self.__lock.acquire() | |||
print "committing {0}".format(slot) | |||
self.__data[slot] = frames | |||
self.__lock.release() | |||
class XplanetRenderer(Runnable): | |||
'''Renderer which renderes current data from DataStore.''' | |||
def __init__(self, lg19, dataStore): | |||
Runnable.__init__(self) | |||
self.__dataStore = dataStore | |||
self.__fps = 25 | |||
self.__lastTime = time.clock() | |||
self.__lg19 = lg19 | |||
def execute(self): | |||
frames = reversed(self.__dataStore.get_data()) | |||
if not frames: | |||
time.sleep(1) | |||
counter = 0 | |||
for frame in frames: | |||
counter += 1 | |||
if counter > self.__fps: | |||
counter = 0 | |||
if self.is_about_to_stop(): | |||
break | |||
now = time.clock() | |||
diff = self.__lastTime - now + (1.0 / self.__fps) | |||
if diff > 0: | |||
time.sleep(diff) | |||
self.__lastTime = time.clock() | |||
self.__lg19.send_frame(frame) | |||
class XplanetInputProcessor(InputProcessor): | |||
def __init__(self, xplanet): | |||
self.__xplanet = xplanet | |||
def process_input(self, inputEvent): | |||
processed = False | |||
if Key.PLAY in inputEvent.keysDown: | |||
self.__xplanet.start() | |||
processed = True | |||
if Key.STOP in inputEvent.keysDown: | |||
self.__xplanet.stop() | |||
processed = True | |||
return processed | |||
class Xplanet(object): | |||
def __init__(self, lg19): | |||
self.__dataStore = DataStore(lg19) | |||
self.__lg19 = lg19 | |||
self.__renderer = XplanetRenderer(lg19, self.__dataStore) | |||
self.__inputProcessor = XplanetInputProcessor(self) | |||
def get_input_processor(self): | |||
return self.__inputProcessor | |||
def start(self): | |||
t = threading.Thread(target=self.__dataStore.update) | |||
t.start() | |||
t = threading.Thread(target=self.__renderer.run) | |||
self.__renderer.start() | |||
t.start() | |||
def stop(self): | |||
self.__renderer.stop() | |||
self.__dataStore.abort_update() | |||
if __name__ == '__main__': | |||
lg19 = G19() | |||
xplanet = Xplanet(lg19) | |||
xplanet.start() | |||
try: | |||
while True: | |||
time.sleep(10) | |||
finally: | |||
xplanet.stop() |
@ -0,0 +1,484 @@ | |||
# 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() |
@ -0,0 +1,178 @@ | |||
class Key(object): | |||
'''Static container containing all keys.''' | |||
# G/M keys | |||
# light switch | |||
LIGHT, \ | |||
M1, \ | |||
M2, \ | |||
M3, \ | |||
MR, \ | |||
G01, \ | |||
G02, \ | |||
G03, \ | |||
G04, \ | |||
G05, \ | |||
G06, \ | |||
G07, \ | |||
G08, \ | |||
G09, \ | |||
G10, \ | |||
G11, \ | |||
G12 = range(17) | |||
# special keys at display | |||
BACK, \ | |||
DOWN, \ | |||
LEFT, \ | |||
MENU, \ | |||
OK, \ | |||
RIGHT, \ | |||
SETTINGS, \ | |||
UP = range(G12 + 1, G12 + 9) | |||
# multimedia keys | |||
WINKEY_SWITCH, \ | |||
NEXT, \ | |||
PREV, \ | |||
STOP, \ | |||
PLAY, \ | |||
MUTE, \ | |||
SCROLL_UP, \ | |||
SCROLL_DOWN = range(UP + 1, UP + 9) | |||
mmKeys = set([ | |||
WINKEY_SWITCH, | |||
NEXT, | |||
PREV, | |||
STOP, | |||
PLAY, | |||
MUTE, | |||
SCROLL_UP, | |||
SCROLL_DOWN]) | |||
gmKeys = set([ | |||
G01, | |||
G02, | |||
G03, | |||
G04, | |||
G05, | |||
G06, | |||
G07, | |||
G08, | |||
G09, | |||
G10, | |||
G11, | |||
G12, | |||
LIGHT, | |||
M1, | |||
M2, | |||
M3, | |||
MR]) | |||
class Data(object): | |||
'''Static container with all data values for all keys.''' | |||
## | |||
## display keys | |||
## | |||
# special keys at display | |||
# The current state of pressed keys is an OR-combination of the following | |||
# codes. | |||
# Incoming data always has 0x80 appended, e.g. pressing and releasing the menu | |||
# key results in two INTERRUPT transmissions: [0x04, 0x80] and [0x00, 0x80] | |||
# Pressing (and holding) UP and OK at the same time results in [0x88, 0x80]. | |||
displayKeys = {} | |||
displayKeys[0x01] = Key.SETTINGS | |||
displayKeys[0x02] = Key.BACK | |||
displayKeys[0x04] = Key.MENU | |||
displayKeys[0x08] = Key.OK | |||
displayKeys[0x10] = Key.RIGHT | |||
displayKeys[0x20] = Key.LEFT | |||
displayKeys[0x40] = Key.DOWN | |||
displayKeys[0x80] = Key.UP | |||
## | |||
## G- and M-Keys | |||
## | |||
# these are the bit fields for setting the currently illuminated keys | |||
# (see set_enabled_m_keys()) | |||
LIGHT_KEY_M1 = 0x80 | |||
LIGHT_KEY_M2 = 0x40 | |||
LIGHT_KEY_M3 = 0x20 | |||
LIGHT_KEY_MR = 0x10 | |||
# specific codes sent by M- and G-keys | |||
# received as [0x02, keyL, keyH, 0x40] | |||
# example: G3: [0x02, 0x04, 0x00, 0x40] | |||
# G1 + G2 + G11: [0x02, 0x03, 0x04, 0x40] | |||
KEY_G01 = 0x000001 | |||
KEY_G02 = 0x000002 | |||
KEY_G03 = 0x000004 | |||
KEY_G04 = 0x000008 | |||
KEY_G05 = 0x000010 | |||
KEY_G06 = 0x000020 | |||
KEY_G07 = 0x000040 | |||
KEY_G08 = 0x000080 | |||
KEY_G09 = 0x000100 | |||
KEY_G10 = 0x000200 | |||
KEY_G11 = 0x000400 | |||
KEY_G12 = 0x000800 | |||
KEY_M1 = 0x001000 | |||
KEY_M2 = 0x002000 | |||
KEY_M3 = 0x004000 | |||
KEY_MR = 0x008000 | |||
# light switch | |||
# this on is similar to G-keys: | |||
# down: [0x02, 0x00, 0x00, 0x48] | |||
# up: [0x02, 0x00, 0x00, 0x40] | |||
KEY_LIGHT = 0x080000 | |||
gmKeys = {} | |||
gmKeys[KEY_G01] = Key.G01 | |||
gmKeys[KEY_G02] = Key.G02 | |||
gmKeys[KEY_G03] = Key.G03 | |||
gmKeys[KEY_G04] = Key.G04 | |||
gmKeys[KEY_G05] = Key.G05 | |||
gmKeys[KEY_G06] = Key.G06 | |||
gmKeys[KEY_G07] = Key.G07 | |||
gmKeys[KEY_G08] = Key.G08 | |||
gmKeys[KEY_G09] = Key.G09 | |||
gmKeys[KEY_G10] = Key.G10 | |||
gmKeys[KEY_G11] = Key.G11 | |||
gmKeys[KEY_G12] = Key.G12 | |||
gmKeys[KEY_G12] = Key.G12 | |||
gmKeys[KEY_M1] = Key.M1 | |||
gmKeys[KEY_M2] = Key.M2 | |||
gmKeys[KEY_M3] = Key.M3 | |||
gmKeys[KEY_MR] = Key.MR | |||
gmKeys[KEY_LIGHT] = Key.LIGHT | |||
## | |||
## MM-keys | |||
## | |||
# multimedia keys | |||
# received as [0x01, key] | |||
# example: NEXT+SCROLL_UP: [0x01, 0x21] | |||
# after scroll stopped: [0x01, 0x01] | |||
# after release: [0x01, 0x00] | |||
mmKeys = {} | |||
mmKeys[0x01] = Key.NEXT | |||
mmKeys[0x02] = Key.PREV | |||
mmKeys[0x04] = Key.STOP | |||
mmKeys[0x08] = Key.PLAY | |||
mmKeys[0x10] = Key.MUTE | |||
mmKeys[0x20] = Key.SCROLL_UP | |||
mmKeys[0x40] = Key.SCROLL_DOWN | |||
# winkey switch to winkey off: [0x03, 0x01] | |||
# winkey switch to winkey on: [0x03, 0x00] | |||
KEY_WIN_SWITCH = 0x0103 | |||
@ -0,0 +1,449 @@ | |||
# 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. | |||
''' | |||
return (math.trunc(r * 31 / 255) << 11) | (math.trunc(g * 63 / 255) << 5) | math.trunc(b * 31 / 255) | |||
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() |
@ -0,0 +1,86 @@ | |||
from PyQt4 import QtCore | |||
from PyQt4 import QtGui | |||
def convert_image(img): | |||
data = [0] * (320 * 240 * 2) | |||
for x in range(320): | |||
for y in range(240): | |||
val = img.pixel(x, y) | |||
data[2*(x * 240 + y)] = val >> 8 | |||
data[2*(x * 240 + y) + 1] = val & 0xff | |||
return data | |||
QtGui.QApplication.setGraphicsSystem("raster"); | |||
app = QtGui.QApplication(["-graphicssystem", "raster"]) | |||
img = QtGui.QImage(320, 240, QtGui.QImage.Format_RGB16) | |||
class Fuck(QtGui.QWidget): | |||
def __init__(self): | |||
QtGui.QWidget.__init__(self) | |||
self.setAttribute(QtCore.Qt.WA_PaintOnScreen) | |||
def paintEngine(self): | |||
return img.paintEngine() | |||
#def paintEvent(self, evt): | |||
# evt.accept() | |||
# self.render(img) | |||
def logit(func): | |||
def wrapped(*lols): | |||
print "called" | |||
return fund(*lols) | |||
return wrapped | |||
#QtGui.QWidget.paintEngine = lambda a: img.paintEngine() | |||
w = QtGui.QWidget() | |||
#w = Fuck() | |||
w.resize(320,240) | |||
l = QtGui.QVBoxLayout(w) | |||
w.setLayout(l) | |||
text1 = QtGui.QLabel("text1", w) | |||
l.addWidget(text1) | |||
text2 = QtGui.QLabel("text2", w) | |||
l.addWidget(text2) | |||
button1 = QtGui.QPushButton("Push me now", w) | |||
l.addWidget(button1) | |||
button2 = QtGui.QPushButton("Cancel this shit", w) | |||
l.addWidget(button2) | |||
w.render(img) | |||
data = convert_image(img) | |||
from logitech.g19 import G19 | |||
lg19 = G19() | |||
lg19.reset() | |||
lg19 = G19() | |||
lg19.send_frame(data) | |||
evtDownPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier) | |||
evtDownRelease = QtGui.QKeyEvent(QtCore.QEvent.KeyRelease, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier) | |||
QtCore.QCoreApplication.postEvent(w, evtDownPress) | |||
QtCore.QCoreApplication.postEvent(w, evtDownRelease) | |||
w.render(img) | |||
data = convert_image(img) | |||
evtDownPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Tab, QtCore.Qt.NoModifier) | |||
evtDownRelease = QtGui.QKeyEvent(QtCore.QEvent.KeyRelease, QtCore.Qt.Key_Tab, QtCore.Qt.NoModifier) | |||
QtCore.QCoreApplication.postEvent(w, evtDownPress) | |||
QtCore.QCoreApplication.postEvent(w, evtDownRelease) | |||
# Xvfb :1 -screen 0 320x240x16 -fbdir /tmp/lala | |||
# xwud -in /tmp/lala/Xvfb_screen0 | |||
# xwdtopnm |
@ -0,0 +1,238 @@ | |||
from .g19_keys import (Data, Key) | |||
from .runnable import Runnable | |||
import threading | |||
import time | |||
class InputProcessor(object): | |||
'''Object to process key presses.''' | |||
def process_input(self, inputEvent): | |||
'''Processes given event. | |||
Should return as fast as possible. Any time-consuming processing | |||
should be done in another thread. | |||
@param inputEvent Event to process. | |||
@return True if event was consumed, or False if ignored. | |||
''' | |||
return False | |||
class InputEvent(object): | |||
'''Event created by a key press or release.''' | |||
def __init__(self, oldState, newState, keysDown, keysUp): | |||
'''Creates an InputEvent. | |||
@param oldState State before event happened. | |||
@param newState State after event happened. | |||
@param keysDown Keys newly pressed. | |||
@param keysUp Kys released by this event. | |||
''' | |||
self.oldState = oldState | |||
self.newState = newState | |||
self.keysDown = keysDown | |||
self.keysUp = keysUp | |||
class State(object): | |||
'''Current state of keyboard.''' | |||
def __init__(self): | |||
self.__keysDown = set() | |||