diff options
author | Joshua Liu <joshua.liu@sourceobby.com> | 2025-04-30 16:29:48 -0400 |
---|---|---|
committer | Joshua Liu <joshua.liu@sourceobby.com> | 2025-04-30 16:29:48 -0400 |
commit | dd58ddd94dfecbe378f3a0fc190a5f219321acc1 (patch) | |
tree | 81e6907aa0d24ce92fccc6a9ee9c180d72d9d260 |
feat: inital commit, mostly moving files from old repository. streamdeck.c now contains basic HIDAPI implementations, util library is still mostly empty
-rw-r--r-- | annotated_example.py | 140 | ||||
-rw-r--r-- | streamdeck.c | 64 | ||||
-rw-r--r-- | streamdeck.h | 18 | ||||
-rw-r--r-- | util.c | 0 | ||||
-rw-r--r-- | util.h | 2 |
5 files changed, 224 insertions, 0 deletions
diff --git a/annotated_example.py b/annotated_example.py new file mode 100644 index 0000000..a602a12 --- /dev/null +++ b/annotated_example.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +# Python Stream Deck Library +# Released under the MIT license +# +# dean [at] fourwalledcubicle [dot] com +# www.fourwalledcubicle.com +# + +# Example script showing basic library usage - updating key images with new +# tiles generated at runtime, and responding to button state change events. + +import os +import threading + +from PIL import Image, ImageDraw, ImageFont +from StreamDeck.DeviceManager import DeviceManager +from StreamDeck.ImageHelpers import PILHelper + +# Folder location of image assets used by this example. +ASSETS_PATH = os.path.join(os.path.dirname(__file__), "Assets") + + +# Generates a custom tile with run-time generated text and custom image via the +# PIL module. +def render_key_image(deck, icon_filename, font_filename, label_text): + # Resize the source image asset to best-fit the dimensions of a single key, + # leaving a margin at the bottom so that we can draw the key title + # afterwards. + icon = Image.open(icon_filename) + image = PILHelper.create_scaled_key_image(deck, icon, margins=[0, 0, 20, 0]) + + # Load a custom TrueType font and use it to overlay the key index, draw key + # label onto the image a few pixels from the bottom of the key. + draw = ImageDraw.Draw(image) + font = ImageFont.truetype(font_filename, 14) + draw.text((image.width / 2, image.height - 5), text=label_text, font=font, anchor="ms", fill="white") + + return PILHelper.to_native_key_format(deck, image) + + +# Returns styling information for a key based on its position and state. +def get_key_style(deck, key, state): + # Last button in the example application is the exit button. + exit_key_index = deck.key_count() - 1 + + if key == exit_key_index: + name = "exit" + icon = "{}.png".format("Exit") + font = "Roboto-Regular.ttf" + label = "Bye" if state else "Exit" + else: + name = "emoji" + icon = "{}.png".format("Pressed" if state else "Released") + font = "Roboto-Regular.ttf" + label = "Pressed!" if state else "Key {}".format(key) + + return { + "name": name, + "icon": os.path.join(ASSETS_PATH, icon), + "font": os.path.join(ASSETS_PATH, font), + "label": label + } + + +# Creates a new key image based on the key index, style and current key state +# and updates the image on the StreamDeck. +def update_key_image(deck, key, state): + # Determine what icon and label to use on the generated key. + key_style = get_key_style(deck, key, state) + + # Generate the custom key with the requested image and label. + image = render_key_image(deck, key_style["icon"], key_style["font"], key_style["label"]) + + # Use a scoped-with on the deck to ensure we're the only thread using it + # right now. + with deck: + # Update requested key with the generated image. + deck.set_key_image(key, image) + + +# Prints key state change information, updates rhe key image and performs any +# associated actions when a key is pressed. +def key_change_callback(deck, key, state): + # Print new key state + print("Deck {} Key {} = {}".format(deck.id(), key, state), flush=True) + + # Update the key image based on the new key state. + update_key_image(deck, key, state) + + # Check if the key is changing to the pressed state. + if state: + key_style = get_key_style(deck, key, state) + + # When an exit button is pressed, close the application. + if key_style["name"] == "exit": + # Use a scoped-with on the deck to ensure we're the only thread + # using it right now. + with deck: + # Reset deck, clearing all button images. + deck.reset() + + # Close deck handle, terminating internal worker threads. + deck.close() + + +if __name__ == "__main__": + streamdecks = DeviceManager().enumerate() + + print("Found {} Stream Deck(s).\n".format(len(streamdecks))) + + for index, deck in enumerate(streamdecks): + # This example only works with devices that have screens. + if not deck.is_visual(): + continue + + deck.open() + deck.reset() + + print("Opened '{}' device (serial number: '{}', fw: '{}')".format( + deck.deck_type(), deck.get_serial_number(), deck.get_firmware_version() + )) + + # Set initial screen brightness to 30%. + deck.set_brightness(30) + + # Set initial key images. + for key in range(deck.key_count()): + update_key_image(deck, key, False) + + # Register callback function for when a key state changes. + deck.set_key_callback(key_change_callback) + + # Wait until all application threads have terminated (for this example, + # this is when all deck handles are closed). + for t in threading.enumerate(): + try: + t.join() + except RuntimeError: + pass diff --git a/streamdeck.c b/streamdeck.c new file mode 100644 index 0000000..e11d79d --- /dev/null +++ b/streamdeck.c @@ -0,0 +1,64 @@ +#include "streamdeck.h" +#include <hidapi/hidapi.h> + + +struct Image { + void* data; + size_t size; +}; + +struct Key { + bool isFolder; + void* command; + image* key; +}; + +struct Screen { + key* keys; +}; + +struct Handler { + hid_device* handler; +}; + +handler* create_hid_handler() { + handler* res = NULL; + res = malloc(sizeof(handler)); + if (!res) { + return NULL; + } + + /* + * Init the HIDAPI library + */ + if (hid_init() < 0) { + (void) fprintf(stderr, "Could not init the HID library\n"); + free(res); + return NULL; + } + + /* + * Create the device handler + */ + res->handler = hid_open(0x0fd9, 0x0063, NULL); + if (!res->handler) { + (void) fprintf(stderr, "Could not open Streamdeck!\n"); + free(res); + hid_exit(); + return NULL; + } + + /* + * Set handler to be non-blocking + */ + + if (hid_set_nonblocking(res->handler, 1) < 0) { + (void) fprintf(stderr, "Could not set HIDAPI handler to be non-blocking\n"); + free(res); + hid_exit(); + return NULL; + } + + return res; +} + diff --git a/streamdeck.h b/streamdeck.h new file mode 100644 index 0000000..150623b --- /dev/null +++ b/streamdeck.h @@ -0,0 +1,18 @@ +#include <assert.h> +#include <string.h> +#include <stdbool.h> +#include <libusb-1.0/libusb.h> +#include <stdlib.h> +#include <stdio.h> +#include <hidapi/hidapi.h> +#define IMAGE_REPORT_LENGTH 8191 + +typedef struct Image image; +typedef struct Key key; +typedef struct Screen screen; +typedef struct Streamdeck streamdeck; +typedef struct Handler handler; + +int connect(); +int close(); +handler* create_hid_handler(); @@ -0,0 +1,2 @@ + +int die(); |