summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Liu <joshua.liu@sourceobby.com>2025-04-30 16:29:48 -0400
committerJoshua Liu <joshua.liu@sourceobby.com>2025-04-30 16:29:48 -0400
commitdd58ddd94dfecbe378f3a0fc190a5f219321acc1 (patch)
tree81e6907aa0d24ce92fccc6a9ee9c180d72d9d260
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.py140
-rw-r--r--streamdeck.c64
-rw-r--r--streamdeck.h18
-rw-r--r--util.c0
-rw-r--r--util.h2
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();
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/util.c
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..921a5de
--- /dev/null
+++ b/util.h
@@ -0,0 +1,2 @@
+
+int die();