Skip to content

Commit 733aa66

Browse files
Refactor Python for 3.12 integration (#1807)
* Correct JS requestConfig call * Update requestWriteConfig to new API format * Add hyperion-light and bare-minimum preset scenarios * Refactor Python * Windows add bcrypt until mbedtls is fixed (Mbed-TLS/mbedtls#9554) * Corrections * Use ScreenCaptureKit under macOS 15 and above * ReSigning macOS package * Python 3.11.10 test * Revert "Python 3.11.10 test" This reverts commit ee921e4. * Handle defined exits from python scripts * Update change.log * CodeQL findings --------- Co-authored-by: Paulchen-Panther <[email protected]>
1 parent 6e3357e commit 733aa66

File tree

18 files changed

+838
-518
lines changed

18 files changed

+838
-518
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Added
1616

1717
- Support for ftdi chip based LED-devices with ws2812, sk6812 apa102 LED types (Many thanks to @nurikk) (#1746)
18-
- Support for Skydimo devices (being an Adalight variant)
18+
- Support for Skydimo devices
1919
- Support gaps on Matrix Layout (#1696)
2020
- Windows: Added a new grabber that uses the DXGI DDA (Desktop Duplication API). This has much better performance than the DX grabber as it does more of its work on the GPU.
2121

@@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
- Fixed: Philip Hue APIv2 support without Entertainment group defined (#1742)
4242
- Refactored: Database access layer
4343
- Refactored: Hyperion's configuration database is validated before start-up (and migrated, if required)
44+
- Refactored: Python to enable parallel effect processing under Python 3.12
45+
- Fixed: Python 3.12 crashes (#1747)
46+
- osX Grabber: Use ScreenCaptureKit under macOS 15 and above
4447

4548
**JSON-API**
4649
- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.

CMakePresets.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"name": "hyperion-bare-minimum",
5252
"hidden": true,
5353
"cacheVariables": {
54-
// Disable Grabbers
5554
"ENABLE_AMLOGIC": "OFF",
5655
"ENABLE_DDA": "OFF",
5756
"ENABLE_DISPMANX": "OFF",
@@ -64,32 +63,23 @@
6463
"ENABLE_X11": "OFF",
6564
"ENABLE_XCB": "OFF",
6665
"ENABLE_AUDIO": "OFF",
67-
68-
// LED-Devices
6966
"ENABLE_DEV_FTDI": "OFF",
7067
"ENABLE_DEV_NETWORK": "OFF",
7168
"ENABLE_DEV_SERIAL": "ON",
7269
"ENABLE_DEV_SPI": "OFF",
7370
"ENABLE_DEV_TINKERFORGE": "OFF",
7471
"ENABLE_DEV_USB_HID": "OFF",
7572
"ENABLE_DEV_WS281XPWM": "OFF",
76-
77-
// Disable Input Servers
7873
"ENABLE_BOBLIGHT_SERVER": "OFF",
7974
"ENABLE_CEC": "OFF",
8075
"ENABLE_FLATBUF_SERVER": "OFF",
8176
"ENABLE_PROTOBUF_SERVER": "OFF",
82-
83-
// Disable Output Connectors
8477
"ENABLE_FORWARDER": "OFF",
8578
"ENABLE_FLATBUF_CONNECT": "OFF",
86-
87-
// Disable Services
8879
"ENABLE_EXPERIMENTAL": "OFF",
8980
"ENABLE_MDNS": "OFF",
9081
"ENABLE_REMOTE_CTL": "OFF",
9182
"ENABLE_EFFECTENGINE": "OFF",
92-
9383
"ENABLE_JSONCHECKS": "ON",
9484
"ENABLE_DEPLOY_DEPENDENCIES": "ON"
9585
}

cmake/Dependencies.cmake

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ macro(DeployMacOS TARGET)
99
OUTPUT_STRIP_TRAILING_WHITESPACE
1010
)
1111

12-
install(CODE "set(TARGET_FILE \"${TARGET_FILE}\")" COMPONENT "Hyperion")
13-
install(CODE "set(TARGET_BUNDLE_NAME \"${TARGET}.app\")" COMPONENT "Hyperion")
14-
install(CODE "set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\")" COMPONENT "Hyperion")
15-
install(CODE "set(BUILD_DIR \"${CMAKE_BINARY_DIR}\")" COMPONENT "Hyperion")
12+
install(CODE "set(TARGET_FILE \"${TARGET_FILE}\")" COMPONENT "Hyperion")
13+
install(CODE "set(TARGET_BUNDLE_NAME \"${TARGET}.app\")" COMPONENT "Hyperion")
14+
install(CODE "set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\")" COMPONENT "Hyperion")
1615
install(CODE "set(ENABLE_EFFECTENGINE \"${ENABLE_EFFECTENGINE}\")" COMPONENT "Hyperion")
1716

1817
install(CODE [[
1918

19+
set(BUNDLE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}")
20+
2021
file(GET_RUNTIME_DEPENDENCIES
2122
EXECUTABLES ${TARGET_FILE}
2223
RESOLVED_DEPENDENCIES_VAR resolved_deps
@@ -28,13 +29,13 @@ macro(DeployMacOS TARGET)
2829
if (${_index} GREATER -1)
2930
file(INSTALL
3031
FILES "${dependency}"
31-
DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/Frameworks"
32+
DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/Frameworks"
3233
TYPE SHARED_LIBRARY
3334
)
3435
else()
3536
file(INSTALL
3637
FILES "${dependency}"
37-
DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib"
38+
DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/lib"
3839
TYPE SHARED_LIBRARY
3940
FOLLOW_SYMLINK_CHAIN
4041
)
@@ -58,18 +59,18 @@ macro(DeployMacOS TARGET)
5859

5960
foreach(DEPENDENCY ${PLUGINS})
6061
file(INSTALL
61-
DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib"
62+
DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/lib"
6263
TYPE SHARED_LIBRARY
6364
FILES ${DEPENDENCY}
6465
FOLLOW_SYMLINK_CHAIN
6566
)
6667
endforeach()
6768

6869
get_filename_component(singleQtLib ${file} NAME)
69-
list(APPEND QT_PLUGINS "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/plugins/${PLUGIN}/${singleQtLib}")
70+
list(APPEND QT_PLUGINS "${BUNDLE_INSTALL_DIR}/Contents/plugins/${PLUGIN}/${singleQtLib}")
7071
file(INSTALL
7172
FILES ${file}
72-
DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/plugins/${PLUGIN}"
73+
DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/plugins/${PLUGIN}"
7374
TYPE SHARED_LIBRARY
7475
)
7576

@@ -78,10 +79,10 @@ macro(DeployMacOS TARGET)
7879
endforeach()
7980

8081
include(BundleUtilities)
81-
fixup_bundle("${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}" "${QT_PLUGINS}" "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib" IGNORE_ITEM "python;python3;Python;Python3;.Python;.Python3")
82+
fixup_bundle("${BUNDLE_INSTALL_DIR}" "${QT_PLUGINS}" "${BUNDLE_INSTALL_DIR}/Contents/lib" IGNORE_ITEM "python;python3;Python;Python3;.Python;.Python3")
83+
file(REMOVE_RECURSE "${BUNDLE_INSTALL_DIR}/Contents/lib")
8284

8385
if(ENABLE_EFFECTENGINE)
84-
8586
# Detect the Python version and modules directory
8687
if(NOT CMAKE_VERSION VERSION_LESS "3.12")
8788
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
@@ -98,24 +99,37 @@ macro(DeployMacOS TARGET)
9899

99100
# Copy Python modules to '/../Frameworks/Python.framework/Versions/Current/lib/PythonMAJOR.MINOR' and ignore the unnecessary stuff listed below
100101
if (PYTHON_MODULES_DIR)
102+
set(PYTHON_FRAMEWORK "${BUNDLE_INSTALL_DIR}/Contents/Frameworks/Python.framework")
101103
file(
102104
COPY ${PYTHON_MODULES_DIR}/
103-
DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/Frameworks/Python.framework/Versions/Current/lib/python${PYTHON_VERSION_MAJOR_MINOR}"
104-
PATTERN "*.pyc" EXCLUDE # compiled bytecodes
105-
PATTERN "__pycache__" EXCLUDE # any cache
106-
PATTERN "config-${PYTHON_VERSION_MAJOR_MINOR}*" EXCLUDE # static libs
107-
PATTERN "lib2to3" EXCLUDE # automated Python 2 to 3 code translation
108-
PATTERN "tkinter" EXCLUDE # Tk interface
109-
PATTERN "turtledemo" EXCLUDE # Tk demo folder
110-
PATTERN "turtle.py" EXCLUDE # Tk demo file
111-
PATTERN "test" EXCLUDE # unittest module
112-
PATTERN "sitecustomize.py" EXCLUDE # site-specific configs
105+
DESTINATION "${PYTHON_FRAMEWORK}/Versions/Current/lib/python${PYTHON_VERSION_MAJOR_MINOR}"
106+
PATTERN "*.pyc" EXCLUDE # compiled bytecodes
107+
PATTERN "__pycache__" EXCLUDE # any cache
108+
PATTERN "config-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}*" EXCLUDE # static libs
109+
PATTERN "lib2to3" EXCLUDE # automated Python 2 to 3 code translation
110+
PATTERN "tkinter" EXCLUDE # Tk interface
111+
PATTERN "lib-dynload/_tkinter.*" EXCLUDE
112+
PATTERN "idlelib" EXCLUDE
113+
PATTERN "turtle.py" EXCLUDE # Tk demo
114+
PATTERN "test" EXCLUDE # unittest module
115+
PATTERN "sitecustomize.py" EXCLUDE # site-specific configs
113116
)
114117
endif(PYTHON_MODULES_DIR)
115118
endif(ENABLE_EFFECTENGINE)
116119

117-
file(REMOVE_RECURSE "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib")
118-
file(REMOVE_RECURSE "${CMAKE_INSTALL_PREFIX}/share")
120+
file(GLOB_RECURSE LIBS FOLLOW_SYMLINKS "${BUNDLE_INSTALL_DIR}/*.dylib")
121+
file(GLOB FRAMEWORKS FOLLOW_SYMLINKS LIST_DIRECTORIES ON "${BUNDLE_INSTALL_DIR}/Contents/Frameworks/*")
122+
foreach(item ${LIBS} ${FRAMEWORKS} ${PYTHON_FRAMEWORK} ${BUNDLE_INSTALL_DIR})
123+
set(cmd codesign --deep --force --sign - "${item}")
124+
execute_process(
125+
COMMAND ${cmd}
126+
RESULT_VARIABLE codesign_result
127+
)
128+
129+
if(NOT codesign_result EQUAL 0)
130+
message(WARNING "macOS signing failed; ${cmd} returned ${codesign_result}")
131+
endif()
132+
endforeach()
119133

120134
]] COMPONENT "Hyperion")
121135

cmake/osxbundle/AppleScript.scpt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ on run argv
4949
delay 1
5050
close
5151

52-
-- one last open and close so you can see everything looks correct
53-
open
54-
delay 5
55-
close
56-
5752
end tell
5853

5954
delay 1

cmake/osxbundle/Info.plist.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<string>APPL</string>
2727
<key>LSUIElement</key>
2828
<string>1</string>
29+
<key>NSCameraUsageDescription</key>
30+
<string>Hyperion uses this access to record screencasts</string>
2931
<key>NSHumanReadableCopyright</key>
3032
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
3133
<key>Source Code</key>

effects/collision.py

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,80 @@
1-
# Two projectiles are sent from random positions and collide with each other
2-
# Template from https://github.com/nickpesce/lit/blob/master/lit/effects/collision.py
31
import hyperion, time, colorsys, random
42

53
# Get parameters
64
sleepTime = max(0.02, float(hyperion.args.get('speed', 100))/1000.0)
75
trailLength = max(3, int(hyperion.args.get('trailLength', 5)))
86
explodeRadius = int(hyperion.args.get('explodeRadius', 8))
97

8+
# Ensure that the range for pixel indices stays within bounds
9+
maxPixelIndex = hyperion.ledCount - 1
10+
if trailLength > maxPixelIndex or explodeRadius > maxPixelIndex:
11+
exit(f"Error: Color length ({trailLength}) and detonation range ({explodeRadius}) must be less than number of LEDs configured ({hyperion.ledCount})")
12+
1013
# Create additional variables
1114
increment = None
1215
projectiles = []
1316

1417
# Initialize the led data
1518
ledData = bytearray()
1619
for i in range(hyperion.ledCount):
17-
ledData += bytearray((0,0,0))
20+
ledData += bytearray((0,0,0))
1821

1922
# Start the write data loop
2023
while not hyperion.abort():
21-
if (len(projectiles) != 2):
22-
projectiles = [ [0, 1, random.uniform(0.0, 1.0)], [hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)] ]
23-
increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1)
24-
25-
ledDataBuf = ledData[:]
26-
for i, v in enumerate(projectiles):
27-
projectiles[i][0] = projectiles[i][0]+projectiles[i][1]
28-
for t in range(0, trailLength):
29-
pixel = v[0] - v[1]*t
30-
if pixel + 2 < 0:
31-
pixel += hyperion.ledCount
32-
if pixel + 2 > hyperion.ledCount-1:
33-
pixel -= hyperion.ledCount-1
34-
rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0*t)/trailLength)
35-
ledDataBuf[3*pixel ] = int(255*rgb[0])
36-
ledDataBuf[3*pixel + 1] = int(255*rgb[1])
37-
ledDataBuf[3*pixel + 2] = int(255*rgb[2])
38-
39-
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
40-
41-
for i1, p1 in enumerate(projectiles):
42-
for i2, p2 in enumerate(projectiles):
43-
if (p1 is not p2):
44-
prev1 = p1[0] - p1[1]
45-
prev2 = p2[0] - p2[1]
46-
if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
47-
for d in range(0, explodeRadius):
48-
for pixel in range(p1[0] - d, p1[0] + d):
49-
rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
50-
ledDataBuf[3*pixel ] = int(255*rgb[0])
51-
ledDataBuf[3*pixel + 1] = int(255*rgb[1])
52-
ledDataBuf[3*pixel + 2] = int(255*rgb[2])
53-
54-
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
55-
time.sleep(sleepTime)
56-
57-
projectiles.remove(p1)
58-
projectiles.remove(p2)
59-
60-
time.sleep(sleepTime)
24+
if len(projectiles) != 2:
25+
projectiles = [
26+
[0, 1, random.uniform(0.0, 1.0)], # Start positions of projectiles
27+
[hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)]
28+
]
29+
increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1)
30+
31+
# Backup the LED data
32+
ledDataBuf = ledData[:]
33+
for i, v in enumerate(projectiles):
34+
# Update projectile positions
35+
projectiles[i][0] = projectiles[i][0] + projectiles[i][1]
36+
37+
for t in range(0, trailLength):
38+
# Calculate pixel index for the trail
39+
pixel = v[0] - v[1] * t
40+
if pixel < 0:
41+
pixel += hyperion.ledCount
42+
if pixel >= hyperion.ledCount:
43+
pixel -= hyperion.ledCount
44+
45+
# Make sure pixel is within bounds
46+
if pixel < 0 or pixel >= hyperion.ledCount:
47+
continue
48+
49+
rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0 * t) / trailLength)
50+
ledDataBuf[3*pixel] = int(255 * rgb[0])
51+
ledDataBuf[3*pixel + 1] = int(255 * rgb[1])
52+
ledDataBuf[3*pixel + 2] = int(255 * rgb[2])
53+
54+
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
55+
56+
# Check for collision and handle explosion
57+
for i1, p1 in enumerate(projectiles):
58+
for i2, p2 in enumerate(projectiles):
59+
if p1 is not p2:
60+
prev1 = p1[0] - p1[1]
61+
prev2 = p2[0] - p2[1]
62+
if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
63+
for d in range(0, explodeRadius):
64+
for pixel in range(p1[0] - d, p1[0] + d):
65+
# Check if pixel is out of bounds
66+
if pixel < 0 or pixel >= hyperion.ledCount:
67+
continue
68+
69+
rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
70+
ledDataBuf[3 * pixel] = int(255 * rgb[0])
71+
ledDataBuf[3 * pixel + 1] = int(255 * rgb[1])
72+
ledDataBuf[3 * pixel + 2] = int(255 * rgb[2])
73+
74+
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
75+
time.sleep(sleepTime)
76+
77+
projectiles.remove(p1)
78+
projectiles.remove(p2)
79+
80+
time.sleep(sleepTime)

include/effectengine/Effect.h

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ class Effect : public QThread
2424

2525
friend class EffectModule;
2626

27-
Effect(Hyperion *hyperion
28-
, int priority
29-
, int timeout
30-
, const QString &script
31-
, const QString &name
32-
, const QJsonObject &args = QJsonObject()
33-
, const QString &imageData = ""
27+
Effect(Hyperion* hyperion
28+
, int priority
29+
, int timeout
30+
, const QString& script
31+
, const QString& name
32+
, const QJsonObject& args = QJsonObject()
33+
, const QString& imageData = ""
3434
);
3535
~Effect() override;
3636

@@ -64,20 +64,20 @@ class Effect : public QThread
6464
QString getScript() const { return _script; }
6565
QString getName() const { return _name; }
6666

67-
int getTimeout() const {return _timeout; }
67+
int getTimeout() const { return _timeout; }
6868
bool isEndless() const { return _isEndless; }
6969

7070
QJsonObject getArgs() const { return _args; }
7171

7272
signals:
73-
void setInput(int priority, const std::vector<ColorRgb> &ledColors, int timeout_ms, bool clearEffect);
74-
void setInputImage(int priority, const Image<ColorRgb> &image, int timeout_ms, bool clearEffect);
73+
void setInput(int priority, const std::vector<ColorRgb>& ledColors, int timeout_ms, bool clearEffect);
74+
void setInputImage(int priority, const Image<ColorRgb>& image, int timeout_ms, bool clearEffect);
7575

7676
private:
77-
void setModuleParameters();
77+
bool setModuleParameters();
7878
void addImage();
7979

80-
Hyperion *_hyperion;
80+
Hyperion* _hyperion;
8181

8282
const int _priority;
8383

@@ -95,12 +95,12 @@ class Effect : public QThread
9595
/// Buffer for colorData
9696
QVector<ColorRgb> _colors;
9797

98-
Logger *_log;
98+
Logger* _log;
9999
// Reflects whenever this effects should interrupt (timeout or external request)
100-
std::atomic<bool> _interupt {};
100+
std::atomic<bool> _interupt{};
101101

102102
QSize _imageSize;
103103
QImage _image;
104-
QPainter *_painter;
104+
QPainter* _painter;
105105
QVector<QImage> _imageStack;
106106
};

0 commit comments

Comments
 (0)