Skip to content
This repository was archived by the owner on Sep 26, 2022. It is now read-only.

Commit c6a8506

Browse files
committed
Initial commit
0 parents  commit c6a8506

4 files changed

Lines changed: 164 additions & 0 deletions

File tree

config.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class AppConfig:
2+
"""Configuration information for the application"""
3+
4+
TITLE = 'DesktopVisualiser'
5+
DESCRIPTION = 'An application for visualising the user desktop'
6+
AUTHOR_NAME = 'Amrish Parmar'
7+
AUTHOR_CONTACT = 'aparm_dev@outlook.com'
8+
DEVICE_SUPPORTED = ['keyboard']
9+
CATEGORY = 'application'
10+
11+
def to_dict(self):
12+
"""Return a dictionary representation of the JSON required by the Razer SDK"""
13+
return {
14+
'title': self.TITLE,
15+
'description': self.DESCRIPTION,
16+
'author': {
17+
'name': self.AUTHOR_NAME,
18+
'contact': self.AUTHOR_CONTACT,
19+
},
20+
'device_supported': self.DEVICE_SUPPORTED,
21+
'category': 'application',
22+
}

image_process.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from PIL import ImageGrab
2+
from ctypes import windll
3+
4+
5+
# enable DPI-aware behaviour for Pillow functions
6+
user32 = windll.user32
7+
user32.SetProcessDPIAware()
8+
9+
10+
class ImageProcessor:
11+
"""A collection of functions for turning images into Razer SDK keyboard-friendly representations"""
12+
13+
@staticmethod
14+
def _get_resized_screen_pixels(columns=22, rows=6):
15+
"""Grab a screenshot and resize to keyboard-friendly dimensions
16+
17+
:param columns: An integer, the number of columns on the keyboard
18+
:param rows: An integer, the number of rows on the keyboard
19+
:return: An Image object, the resized image
20+
"""
21+
img = ImageGrab.grab()
22+
img = img.resize((columns, rows))
23+
return img
24+
25+
@staticmethod
26+
def get_keyboard_pixels():
27+
"""Take a screenshot of the display, resize it and convert to a 6 x 22 list of BGR integers
28+
29+
:return: A 6 x 22 list of lists of BGR integers
30+
"""
31+
img = ImageProcessor._get_resized_screen_pixels()
32+
33+
pixel_list = []
34+
35+
for r in range(img.height):
36+
row = []
37+
for c in range(img.width):
38+
p = ImageProcessor._rgb_to_bgr_int(img.getpixel((c, r)))
39+
row.append(p)
40+
pixel_list.append(row)
41+
42+
return pixel_list
43+
44+
@staticmethod
45+
def _rgb_to_bgr_int(rgb):
46+
"""Convert an rgb tuple into a corresponding BGR integer
47+
48+
:param rgb: A tuple in the format (R, G, B) where R, G, B are 0 - 255
49+
:return: An integer, a BGR integer representation
50+
"""
51+
return rgb[0] * pow(2, 0) + rgb[1] * pow(2, 8) + rgb[2] * pow(2, 16)

sdk_connection.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import logging
2+
from threading import Thread
3+
from time import sleep
4+
import requests
5+
6+
7+
class RazerApp:
8+
"""Handle connections to the SDK server"""
9+
10+
def __init__(self, payload):
11+
r = requests.post("http://localhost:54235/razer/chromasdk", json=payload)
12+
self.uri = r.json()['uri'] # assign the uri from the SDK server response
13+
self.alive = True
14+
self._start_heartbeat()
15+
16+
def __enter__(self):
17+
return self
18+
19+
def __exit__(self, exc_type, exc_val, exc_tb):
20+
self.disconnect()
21+
22+
def disconnect(self):
23+
"""End connection to the SDK server"""
24+
logging.getLogger('Terminating connection to SDK server')
25+
self.alive = False
26+
self._hb_thread.join()
27+
requests.delete(self.uri)
28+
29+
def _heartbeat(self):
30+
"""Send a heartbeat to the SDK every second"""
31+
while self.alive:
32+
requests.put(self.uri + '/heartbeat')
33+
sleep(1)
34+
35+
def _start_heartbeat(self):
36+
"""Start the heartbeat thread to keep the SDK server connection alive"""
37+
self._hb_thread = Thread(target=self._heartbeat)
38+
self._hb_thread.start()
39+
40+
def set_colour(self, values):
41+
"""Update the colours on the keyboard with new values
42+
43+
:param values: A 6 x 22 list of lists of BGR integers
44+
"""
45+
payload = {
46+
'effect': 'CHROMA_CUSTOM',
47+
'param': values,
48+
}
49+
50+
requests.put(self.uri + '/keyboard', json=payload)

visualiser.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
import signal
3+
import sys
4+
from config import AppConfig
5+
from sdk_connection import RazerApp
6+
from image_process import ImageProcessor
7+
8+
9+
# set up the logger
10+
logger = logging.getLogger(__name__)
11+
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO)
12+
13+
14+
def quit_program(sig, frame):
15+
"""Callback for when program is quit"""
16+
logger.warning('Quitting program')
17+
sys.exit()
18+
19+
20+
def main():
21+
"""Main function"""
22+
signal.signal(signal.SIGINT, quit_program)
23+
24+
logging.info('Initialising connection to SDK server')
25+
config = AppConfig()
26+
with RazerApp(config.to_dict()) as app:
27+
logging.info('Connected to the the SDK server')
28+
logging.info('Beginning screen capture (Press Ctrl-C to quit)')
29+
30+
while True:
31+
try:
32+
pixels = ImageProcessor.get_keyboard_pixels()
33+
except OSError as ose:
34+
logger.error(f'Error grabbing screenshot: {ose.strerror}')
35+
continue
36+
37+
app.set_colour(pixels)
38+
39+
40+
if __name__ == '__main__':
41+
main()

0 commit comments

Comments
 (0)