184 lines
6.4 KiB
Python
184 lines
6.4 KiB
Python
import asyncio
|
|
import struct
|
|
|
|
from typing_extensions import Self
|
|
|
|
from greendeck.lib.elgato.streamdeck import StreamDeck
|
|
from greendeck.lib.hidapi.library import HIDAPIError
|
|
from greendeck.lib.util import task_done_callback
|
|
|
|
|
|
class StreamDeckGen2Base(StreamDeck):
|
|
MAX_PACKET_SIZE: int = 1024
|
|
|
|
async def _read(self: Self) -> None:
|
|
try:
|
|
while self.run_read_task:
|
|
data = await self.device.read(64)
|
|
if data is None:
|
|
continue
|
|
|
|
match data[0]:
|
|
case 0x01:
|
|
key_count = data[2]
|
|
states = [bool(s) for s in data[4 : 4 + key_count]]
|
|
t = asyncio.create_task(self.handleKeyReport(states))
|
|
t.add_done_callback(task_done_callback)
|
|
|
|
case 0x02:
|
|
# lcd input
|
|
# position1 = struct.unpack("<HH", data[5:9])
|
|
match data[3]:
|
|
case 0x01:
|
|
# short press
|
|
pass
|
|
|
|
case 0x02:
|
|
# long press
|
|
pass
|
|
|
|
case 0x03:
|
|
# swipe
|
|
# position2 = struct.unpack("<HH", data[9:12])
|
|
pass
|
|
|
|
case x:
|
|
print(f"unknown lcd input type {x}")
|
|
|
|
case 0x03:
|
|
# encoder
|
|
match data[3]:
|
|
case 0x00:
|
|
# encoder press/release
|
|
pass
|
|
|
|
case 0x01:
|
|
# rotate
|
|
pass
|
|
|
|
case x:
|
|
print(f"unknown input report type {x}")
|
|
|
|
except HIDAPIError:
|
|
print("HIDAPI Error")
|
|
self.run_read_thread = False
|
|
return
|
|
|
|
except asyncio.CancelledError:
|
|
print("cancelled")
|
|
self.run_read_thread = False
|
|
return
|
|
|
|
async def _reset_key_stream(self) -> None:
|
|
async with self.mutex:
|
|
payload = struct.pack(f"<B{self.MAX_PACKET_SIZE-1}x", 0x02)
|
|
|
|
await self.device.write(payload)
|
|
|
|
async def reset(self) -> None:
|
|
async with self.mutex:
|
|
payload = struct.pack("<BB30x", 0x03, 0x02)
|
|
# payload = b"\x03\x02" + (b"\x00" * 30)
|
|
await self.device.write_feature(payload)
|
|
|
|
async def set_brightness(self, percent: int | float):
|
|
"""
|
|
Sets the global screen brightness of the StreamDeck, across all the
|
|
physical buttons.
|
|
|
|
:param int/float percent: brightness percent, from [0-100] as an `int`,
|
|
or normalized to [0.0-1.0] as a `float`.
|
|
"""
|
|
|
|
async with self.mutex:
|
|
if isinstance(percent, float):
|
|
percent = int(round(100.0 * percent, 0))
|
|
|
|
percent = min(max(percent, 0), 100)
|
|
|
|
payload = struct.pack("<BBB29x", 0x03, 0x08, percent)
|
|
# payload = b"\x03\x08" + percent + (b"\x00" * 29)
|
|
|
|
await self.device.write_feature(payload)
|
|
|
|
async def get_serial_number(self) -> str:
|
|
"""
|
|
Gets the serial number of the attached StreamDeck.
|
|
|
|
:rtype: str
|
|
:return: String containing the serial number of the attached device.
|
|
"""
|
|
|
|
if self._serial_number is not None:
|
|
return self._serial_number
|
|
|
|
async with self.mutex:
|
|
if self._serial_number is None:
|
|
data = await self.device.read_feature(0x06, 32)
|
|
self._serial_number = self._extract_string(data[2:])
|
|
return self._serial_number
|
|
|
|
async def get_firmware_version(self) -> str:
|
|
"""
|
|
Gets the firmware version of the attached StreamDeck.
|
|
|
|
:rtype: str
|
|
:return: String containing the firmware version of the attached device.
|
|
"""
|
|
|
|
if self._firmware_version is not None:
|
|
return self._firmware_version
|
|
|
|
async with self.mutex:
|
|
if self._firmware_version is None:
|
|
data = await self.device.read_feature(0x05, 32)
|
|
self._firmware_version = self._extract_string(data[6:])
|
|
return self._firmware_version
|
|
|
|
async def set_key_image(self, key: int, image: bytes | None) -> None:
|
|
"""
|
|
Sets the image of a button on the StreamDeck to the given image. The
|
|
image being set should be in the correct format for the device, as an
|
|
enumerable collection of bytes.
|
|
|
|
.. seealso:: See :func:`~StreamDeck.get_key_image_format` method for
|
|
information on the image format accepted by the device.
|
|
|
|
:param int key: Index of the button whose image is to be updated.
|
|
:param enumerable image: Raw data of the image to set on the button.
|
|
If `None`, the key will be cleared to a black
|
|
color.
|
|
"""
|
|
|
|
IMAGE_REPORT_HEADER_LENGTH = struct.calcsize("<BBBBHH")
|
|
IMAGE_REPORT_PAYLOAD_LENGTH = self.MAX_PACKET_SIZE - IMAGE_REPORT_HEADER_LENGTH
|
|
|
|
async with self.mutex:
|
|
if min(max(key, 0), self.KEY_COUNT) != key:
|
|
raise IndexError("Invalid key index {}.".format(key))
|
|
|
|
image = bytes(image or self.BLANK_KEY_IMAGE)
|
|
|
|
page_number = 0
|
|
bytes_remaining = len(image)
|
|
while bytes_remaining > 0:
|
|
page_length = min(bytes_remaining, IMAGE_REPORT_PAYLOAD_LENGTH)
|
|
bytes_sent = page_number * IMAGE_REPORT_PAYLOAD_LENGTH
|
|
last_page = 1 if page_length == bytes_remaining else 0
|
|
header = struct.pack(
|
|
"<BBBBHH",
|
|
0x02,
|
|
0x07,
|
|
key,
|
|
last_page,
|
|
page_length,
|
|
page_number,
|
|
)
|
|
|
|
payload = header + image[bytes_sent : bytes_sent + page_length]
|
|
padding = b"\x00" * (self.MAX_PACKET_SIZE - len(payload))
|
|
await self.device.write(payload + padding)
|
|
|
|
bytes_remaining = bytes_remaining - page_length
|
|
page_number = page_number + 1
|