Browse Source

initial

master
Gustavo Adolfo Mesa Roldán 4 years ago
commit
c50b5646e9
24 changed files with 1951 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. BIN
      img/1.jpg
  3. BIN
      img/2.gif
  4. BIN
      img/2.jpg
  5. BIN
      img/3.jpg
  6. BIN
      img/4.jpg
  7. BIN
      img/5.jpg
  8. BIN
      img/Captura de pantalla de 2019-07-04 06-46-11.png
  9. +20
    -0
      img/pyboy.svg
  10. +0
    -0
      logitech/__init__.py
  11. +0
    -0
      logitech/applets/__init__.py
  12. +0
    -0
      logitech/applets/simple_bg_light/__init__.py
  13. +84
    -0
      logitech/applets/simple_bg_light/simple_bg_light.py
  14. +0
    -0
      logitech/applets/simple_display_brightness/__init__.py
  15. +42
    -0
      logitech/applets/simple_display_brightness/simple_display_brightness.py
  16. +0
    -0
      logitech/applets/xplanet/__init__.py
  17. +205
    -0
      logitech/applets/xplanet/xplanet.py
  18. +484
    -0
      logitech/g19.py
  19. +178
    -0
      logitech/g19_keys.py
  20. +449
    -0
      logitech/g19_old.py
  21. +86
    -0
      logitech/g19_qt.py
  22. +238
    -0
      logitech/g19_receivers.py
  23. +69
    -0
      logitech/runnable.py
  24. +93
    -0
      main.py

+ 3
- 0
.gitignore View File

@ -0,0 +1,3 @@
*.pyc
*.swp
test.*

BIN
img/1.jpg View File

Before After
Width: 900  |  Height: 1600  |  Size: 135 KiB

BIN
img/2.gif View File

Before After
Width: 360  |  Height: 360  |  Size: 13 MiB

BIN
img/2.jpg View File

Before After
Width: 1920  |  Height: 1080  |  Size: 158 KiB

BIN
img/3.jpg View File

Before After
Width: 717  |  Height: 717  |  Size: 50 KiB

BIN
img/4.jpg View File

Before After
Width: 320  |  Height: 240  |  Size: 17 KiB

BIN
img/5.jpg View File

Before After
Width: 320  |  Height: 240  |  Size: 9.2 KiB

BIN
img/Captura de pantalla de 2019-07-04 06-46-11.png View File

Before After
Width: 1920  |  Height: 1080  |  Size: 743 KiB

+ 20
- 0
img/pyboy.svg View File

@ -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
logitech/__init__.py View File


+ 0
- 0
logitech/applets/__init__.py View File


+ 0
- 0
logitech/applets/simple_bg_light/__init__.py View File


+ 84
- 0
logitech/applets/simple_bg_light/simple_bg_light.py View File

@ -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
logitech/applets/simple_display_brightness/__init__.py View File


+ 42
- 0
logitech/applets/simple_display_brightness/simple_display_brightness.py View File

@ -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
logitech/applets/xplanet/__init__.py View File


+ 205
- 0
logitech/applets/xplanet/xplanet.py View File

@ -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()

+ 484
- 0
logitech/g19.py View File

@ -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()

+ 178
- 0
logitech/g19_keys.py View File

@ -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

+ 449
- 0
logitech/g19_old.py View File

@ -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()

+ 86
- 0
logitech/g19_qt.py View File

@ -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

+ 238
- 0
logitech/g19_receivers.py View File

@ -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()
def _data_to_keys_g_and_m(self, data):
'''Converts a G/M keys data package to a set of keys defined as
pressed by it.
'''
if len(data) != 4 or data[0] != 2:
raise ValueError("not a multimedia key packet: " + str(data))
empty = 0x400000
curVal = data[3] << 16 | data[2] << 8 | data[1]
keys = []
while curVal != empty:
foundAKey = False
for val in Data.gmKeys.keys():
if val & curVal == val:
curVal ^= val
keys.append(Data.gmKeys[val])
foundAKey = True
if not foundAKey:
raise ValueError("incorrect g/m key packet: " +
str(data))
return set(keys)
def _data_to_keys_mm(self, data):
'''Converts a multimedia keys data package to a set of keys defined as
pressed by it.
'''
if len(data) != 2 or data[0] not in [1, 3]:
raise ValueError("not a multimedia key packet: " + str(data))
if data[0] == 1:
curVal = data[1]
keys = []
while curVal:
foundAKey = False
for val in Data.mmKeys.keys():
if val & curVal == val:
curVal ^= val
keys.append(Data.mmKeys[val])
foundAKey = True
if not foundAKey:
raise ValueError("incorrect multimedia key packet: " +
str(data))
elif data == [3, 1]:
keys = [Key.WINKEY_SWITCH]
elif data == [3, 0]:
keys = []
else:
raise ValueError("incorrect multimedia key packet: " + str(data))
return set(keys)
def _update_keys_down(self, possibleKeys, keys):
'''Updates internal keysDown set.
Updates the current state of all keys in 'possibleKeys' with state
given in 'keys'.
Example:
Currently set as pressed in self.__keysDown: [A, B]
possibleKeys: [B, C, D]
keys: [C]
This would set self.__keysDown to [A, C] and return ([C], [B])
@param possibleKeys Keys whose state could be given as 'pressed' at the
same time by 'keys'.
@param keys Current state of all keys in possibleKeys.
@return A pair of sets listing newly pressed and newly released keys.
'''
keysDown = set()
keysUp = set()
for key in possibleKeys:
if key in keys:
if key not in self.__keysDown:
self.__keysDown.add(key)
keysDown.add(key)
else:
if key in self.__keysDown:
self.__keysDown.remove(key)
keysUp.add(key)
return (keysDown, keysUp)
def clone(self):
'''Returns an exact copy of this state.'''
state = State()
state.__keysDown = set(self.__keysDown)
return state
def packet_received_g_and_m(self, data):
'''Mutates the state by given data packet from G- and M- keys.
@param data Data packet received.
@return InputEvent for data packet, or None if data packet was ignored.
'''
oldState = self.clone()
evt = None
if len(data) == 4:
keys = self._data_to_keys_g_and_m(data)
keysDown, keysUp = self._update_keys_down(Key.gmKeys, keys)
newState = self.clone()
evt = InputEvent(oldState, newState, keysDown, keysUp)
return evt
def packet_received_mm(self, data):
'''Mutates the state by given data packet from multimedia keys.
@param data Data packet received.
@return InputEvent for data packet.
'''
oldState = self.clone()
if len(data) != 2:
raise ValueError("incorrect multimedia key packet: " + str(data))
keys = self._data_to_keys_mm(data)
winKeySet = set([Key.WINKEY_SWITCH])
if data[0] == 1:
# update state of all mm keys
possibleKeys = Key.mmKeys.difference(winKeySet)
keysDown, keysUp = self._update_keys_down(possibleKeys, keys)
else:
# update winkey state
keysDown, keysUp = self._update_keys_down(winKeySet, keys)
newState = self.clone()
return InputEvent(oldState, newState, keysDown, keysUp)
class G19Receiver(Runnable):
'''This receiver consumes all data sent by special keys.'''
def __init__(self, g19):
Runnable.__init__(self)
self.__g19 = g19
self.__ips = []
self.__mutex = threading.Lock()
self.__state = State()
def add_input_processor(self, processor):
'''Adds an input processor.'''
self.__mutex.acquire()
self.__ips.append(processor)
self.__mutex.release()
pass
def execute(self):
gotData = False
processors = self.list_all_input_processors()
data = self.__g19.read_multimedia_keys()
if data:
evt = self.__state.packet_received_mm(data)
if evt:
for proc in processors:
if proc.process_input(evt):
break
else:
print("mm ignored: ", data)
gotData = True
data = self.__g19.read_g_and_m_keys()
if data:
evt = self.__state.packet_received_g_and_m(data)
if evt:
for proc in processors:
if proc.process_input(evt):
break
else:
print("m/g ignored: ", data)
gotData = True
data = self.__g19.read_display_menu_keys()
if data:
print("dis: ", data)
gotData = True
if not gotData:
time.sleep(0.03)
def list_all_input_processors(self):
'''Returns a list of all input processors currently registered to this
receiver.
@return All registered processors. This list is a copy of the internal
one.
'''
self.__mutex.acquire()
allProcessors = list(self.__ips)
self.__mutex.release()
return allProcessors

+ 69
- 0
logitech/runnable.py View File

@ -0,0 +1,69 @@
import threading
class Runnable(object):
'''Helper object to create thread content objects doing periodic tasks, or
tasks supporting premature termination.
Override execute() in inherited class. This will be called until the
thread is stopped. A Runnable can be started multiple times opposed to
threading.Thread.
To write a non-periodic task that should support premature termination,
simply override run() and call is_about_to_stop() at possible termination
points.
'''
def __init__(self):
self.__keepRunning = True
self.__mutex = threading.Lock()
def execute(self):
'''This method must be implemented and will be executed in an infinite
loop as long as stop() was not called.
An implementation is free to check is_about_to_stop() at any time to
allow a clean termination of current processing before reaching the end
of execute().
'''
pass
def is_about_to_stop(self):
'''Returns whether this thread will terminate after completing the
current execution cycle.
@return True if thread will terminate after current execution cycle.
'''
self.__mutex.acquire()
val = self.__keepRunning
self.__mutex.release()
return not val
def run(self):
'''Implements the infinite loop. Do not override, but override
execute() instead.
'''
while not self.is_about_to_stop():
self.execute()
def start(self):
'''Starts the thread. If stop() was called, but start() was not, run()
will do nothing.
'''
self.__mutex.acquire()
self.__keepRunning = True
self.__mutex.release()
def stop(self):
'''Flags this thread to be terminated after next completed execution
cycle. Calling this method will NOT stop the thread instantaniously,
but will complete the current operation and terminate in a clean way.
'''
self.__mutex.acquire()
self.__keepRunning = False
self.__mutex.release()

+ 93
- 0
main.py View File

@ -0,0 +1,93 @@
from logitech.g19 import G19
import time
import array
print("Start")
print("-----------------------")
if __name__ == '__main__':
lg19 = G19(True)
lg19.start_event_handling()
try:
data = []
r = 255
g = 0
b = 255
# b1 = lg19.rgb_to_uint16(r,g,b) >> 8
# b2 = lg19.rgb_to_uint16(r,g,b) & 0xff
# print(lg19.rgb_to_uint16(r,g,b))
# print("-----------------------")
# print(b1)
# print(b2)
# for x in range(320):
# for y in range(240):
# data.append(0)
# data.append(b2)
#print(str(data))
#print(array.array('B', str(data)))
#frame = array.array('B', str(data))
#lg19.fill_display_with_color(0, 0, 255)
#lg19.fill_display_with_color(255, 0, 0)
#lg19.set_display_colorful
c = 1
while True:
lg19.load_image("img/" + str(c) + ".jpg")
if c == 5:
c = 1
else:
c = c + 1
time.sleep(1)
# lg19.load_image("img/3.jpeg")
# data = []
# for x in range(320):
# for y in range(240):
# data.append(0)
# data.append(248)
# print(c)
# lg19.send_frame(data)
print("-----------------------")
finally:
print("Stop!!!!")
lg19.stop_event_handling()
# # if you get an error: lg19 = G19(True)
# lg19 = G19()
# lg19.reset()
# print(lg19)
# # # setting backlight to red
# # fill your display with green
# lg19.fill_display_with_color(0, 255, 0)
# # test your screen
# lg19.set_display_colorful()
# # set backlight to blue after reset
# # this will be your backlight color after a bus reset (or switching the keyboard
# # off and no)
# lg19.save_default_bg_color(0, 0, 255)
# # send an image to display
# # data = [...] # format described in g19.py
# # lg19.send_frame(data)
# # load an arbitrary image from disk to display (will be resized non-uniform)
# lg19.load_image("img/pyboy.jpg")
# # reset the keyboard via USB
# # now you have to rebuild the connection:
# lg19 = G19()

Loading…
Cancel
Save