mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into 1804
This commit is contained in:
		@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Support for ftdi chip based LED-devices with ws2812, sk6812 apa102 LED types (Many thanks to @nurikk) (#1746)
 | 
			
		||||
- Support for Skydimo devices (being an Adalight variant)
 | 
			
		||||
- Support for Skydimo devices
 | 
			
		||||
- Support gaps on Matrix Layout (#1696)
 | 
			
		||||
- 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.
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
- Fixed: Philip Hue APIv2 support without Entertainment group defined (#1742)
 | 
			
		||||
- Refactored: Database access layer
 | 
			
		||||
- Refactored: Hyperion's configuration database is validated before start-up (and migrated, if required)
 | 
			
		||||
- Refactored: Python to enable parallel effect processing under Python 3.12
 | 
			
		||||
- Fixed: Python 3.12 crashes (#1747)
 | 
			
		||||
- osX Grabber: Use ScreenCaptureKit under macOS 15 and above
 | 
			
		||||
 | 
			
		||||
**JSON-API**
 | 
			
		||||
- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,6 @@
 | 
			
		||||
      "name": "hyperion-bare-minimum",
 | 
			
		||||
      "hidden": true,
 | 
			
		||||
      "cacheVariables": {
 | 
			
		||||
        // Disable Grabbers
 | 
			
		||||
        "ENABLE_AMLOGIC": "OFF",
 | 
			
		||||
        "ENABLE_DDA": "OFF",
 | 
			
		||||
        "ENABLE_DISPMANX": "OFF",
 | 
			
		||||
@@ -64,8 +63,6 @@
 | 
			
		||||
        "ENABLE_X11": "OFF",
 | 
			
		||||
        "ENABLE_XCB": "OFF",
 | 
			
		||||
        "ENABLE_AUDIO": "OFF",
 | 
			
		||||
 | 
			
		||||
        // LED-Devices
 | 
			
		||||
        "ENABLE_DEV_FTDI": "OFF",
 | 
			
		||||
        "ENABLE_DEV_NETWORK": "OFF",
 | 
			
		||||
        "ENABLE_DEV_SERIAL": "ON",
 | 
			
		||||
@@ -73,23 +70,16 @@
 | 
			
		||||
        "ENABLE_DEV_TINKERFORGE": "OFF",
 | 
			
		||||
        "ENABLE_DEV_USB_HID": "OFF",
 | 
			
		||||
        "ENABLE_DEV_WS281XPWM": "OFF",
 | 
			
		||||
 | 
			
		||||
        // Disable Input Servers
 | 
			
		||||
        "ENABLE_BOBLIGHT_SERVER": "OFF",
 | 
			
		||||
        "ENABLE_CEC": "OFF",
 | 
			
		||||
        "ENABLE_FLATBUF_SERVER": "OFF",
 | 
			
		||||
        "ENABLE_PROTOBUF_SERVER": "OFF",
 | 
			
		||||
 | 
			
		||||
        // Disable Output Connectors
 | 
			
		||||
        "ENABLE_FORWARDER": "OFF",
 | 
			
		||||
        "ENABLE_FLATBUF_CONNECT": "OFF",
 | 
			
		||||
 | 
			
		||||
        // Disable Services
 | 
			
		||||
        "ENABLE_EXPERIMENTAL": "OFF",
 | 
			
		||||
        "ENABLE_MDNS": "OFF",
 | 
			
		||||
        "ENABLE_REMOTE_CTL": "OFF",
 | 
			
		||||
        "ENABLE_EFFECTENGINE": "OFF",
 | 
			
		||||
 | 
			
		||||
        "ENABLE_JSONCHECKS": "ON",
 | 
			
		||||
        "ENABLE_DEPLOY_DEPENDENCIES": "ON"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,15 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
			OUTPUT_STRIP_TRAILING_WHITESPACE
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		install(CODE "set(TARGET_FILE \"${TARGET_FILE}\")" 		 			COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(TARGET_BUNDLE_NAME \"${TARGET}.app\")" 			COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\")" 	 			COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(BUILD_DIR \"${CMAKE_BINARY_DIR}\")"	 			COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(TARGET_FILE \"${TARGET_FILE}\")"					COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(TARGET_BUNDLE_NAME \"${TARGET}.app\")"			COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\")"					COMPONENT "Hyperion")
 | 
			
		||||
		install(CODE "set(ENABLE_EFFECTENGINE \"${ENABLE_EFFECTENGINE}\")"	COMPONENT "Hyperion")
 | 
			
		||||
 | 
			
		||||
		install(CODE [[
 | 
			
		||||
 | 
			
		||||
				set(BUNDLE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}")
 | 
			
		||||
 | 
			
		||||
				file(GET_RUNTIME_DEPENDENCIES
 | 
			
		||||
					EXECUTABLES ${TARGET_FILE}
 | 
			
		||||
					RESOLVED_DEPENDENCIES_VAR resolved_deps
 | 
			
		||||
@@ -28,13 +29,13 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
					if (${_index} GREATER -1)
 | 
			
		||||
						file(INSTALL
 | 
			
		||||
							FILES "${dependency}"
 | 
			
		||||
							DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/Frameworks"
 | 
			
		||||
							DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/Frameworks"
 | 
			
		||||
							TYPE SHARED_LIBRARY
 | 
			
		||||
						)
 | 
			
		||||
					else()
 | 
			
		||||
						file(INSTALL
 | 
			
		||||
							FILES "${dependency}"
 | 
			
		||||
							DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib"
 | 
			
		||||
							DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/lib"
 | 
			
		||||
							TYPE SHARED_LIBRARY
 | 
			
		||||
							FOLLOW_SYMLINK_CHAIN
 | 
			
		||||
						)
 | 
			
		||||
@@ -58,7 +59,7 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
 | 
			
		||||
								foreach(DEPENDENCY ${PLUGINS})
 | 
			
		||||
										file(INSTALL
 | 
			
		||||
											DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib"
 | 
			
		||||
											DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/lib"
 | 
			
		||||
											TYPE SHARED_LIBRARY
 | 
			
		||||
											FILES ${DEPENDENCY}
 | 
			
		||||
											FOLLOW_SYMLINK_CHAIN
 | 
			
		||||
@@ -66,10 +67,10 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
								endforeach()
 | 
			
		||||
 | 
			
		||||
								get_filename_component(singleQtLib ${file} NAME)
 | 
			
		||||
								list(APPEND QT_PLUGINS "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/plugins/${PLUGIN}/${singleQtLib}")
 | 
			
		||||
								list(APPEND QT_PLUGINS "${BUNDLE_INSTALL_DIR}/Contents/plugins/${PLUGIN}/${singleQtLib}")
 | 
			
		||||
								file(INSTALL
 | 
			
		||||
									FILES ${file}
 | 
			
		||||
									DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/plugins/${PLUGIN}"
 | 
			
		||||
									DESTINATION "${BUNDLE_INSTALL_DIR}/Contents/plugins/${PLUGIN}"
 | 
			
		||||
									TYPE SHARED_LIBRARY
 | 
			
		||||
								)
 | 
			
		||||
 | 
			
		||||
@@ -78,10 +79,10 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
				endforeach()
 | 
			
		||||
 | 
			
		||||
				include(BundleUtilities)
 | 
			
		||||
				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")
 | 
			
		||||
				fixup_bundle("${BUNDLE_INSTALL_DIR}" "${QT_PLUGINS}" "${BUNDLE_INSTALL_DIR}/Contents/lib" IGNORE_ITEM "python;python3;Python;Python3;.Python;.Python3")
 | 
			
		||||
				file(REMOVE_RECURSE "${BUNDLE_INSTALL_DIR}/Contents/lib")
 | 
			
		||||
 | 
			
		||||
				if(ENABLE_EFFECTENGINE)
 | 
			
		||||
 | 
			
		||||
					# Detect the Python version and modules directory
 | 
			
		||||
					if(NOT CMAKE_VERSION VERSION_LESS "3.12")
 | 
			
		||||
						find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
 | 
			
		||||
@@ -98,24 +99,37 @@ macro(DeployMacOS TARGET)
 | 
			
		||||
 | 
			
		||||
					# Copy Python modules to '/../Frameworks/Python.framework/Versions/Current/lib/PythonMAJOR.MINOR' and ignore the unnecessary stuff listed below
 | 
			
		||||
					if (PYTHON_MODULES_DIR)
 | 
			
		||||
						set(PYTHON_FRAMEWORK "${BUNDLE_INSTALL_DIR}/Contents/Frameworks/Python.framework")
 | 
			
		||||
						file(
 | 
			
		||||
							COPY ${PYTHON_MODULES_DIR}/
 | 
			
		||||
							DESTINATION "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/Frameworks/Python.framework/Versions/Current/lib/python${PYTHON_VERSION_MAJOR_MINOR}"
 | 
			
		||||
							PATTERN "*.pyc"                                 EXCLUDE # compiled bytecodes
 | 
			
		||||
							PATTERN "__pycache__"                           EXCLUDE # any cache
 | 
			
		||||
							PATTERN "config-${PYTHON_VERSION_MAJOR_MINOR}*" EXCLUDE # static libs
 | 
			
		||||
							PATTERN "lib2to3"                               EXCLUDE # automated Python 2 to 3 code translation
 | 
			
		||||
							PATTERN "tkinter"                               EXCLUDE # Tk interface
 | 
			
		||||
							PATTERN "turtledemo"                            EXCLUDE # Tk demo folder
 | 
			
		||||
							PATTERN "turtle.py"                             EXCLUDE # Tk demo file
 | 
			
		||||
							PATTERN "test"                                  EXCLUDE # unittest module
 | 
			
		||||
							PATTERN "sitecustomize.py"                      EXCLUDE # site-specific configs
 | 
			
		||||
							DESTINATION "${PYTHON_FRAMEWORK}/Versions/Current/lib/python${PYTHON_VERSION_MAJOR_MINOR}"
 | 
			
		||||
							PATTERN "*.pyc"														EXCLUDE # compiled bytecodes
 | 
			
		||||
							PATTERN "__pycache__"												EXCLUDE # any cache
 | 
			
		||||
							PATTERN "config-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}*"	EXCLUDE # static libs
 | 
			
		||||
							PATTERN "lib2to3"													EXCLUDE # automated Python 2 to 3 code translation
 | 
			
		||||
							PATTERN "tkinter"													EXCLUDE # Tk interface
 | 
			
		||||
							PATTERN "lib-dynload/_tkinter.*"									EXCLUDE
 | 
			
		||||
							PATTERN "idlelib"													EXCLUDE
 | 
			
		||||
							PATTERN "turtle.py"													EXCLUDE # Tk demo
 | 
			
		||||
							PATTERN "test"														EXCLUDE # unittest module
 | 
			
		||||
							PATTERN "sitecustomize.py"											EXCLUDE # site-specific configs
 | 
			
		||||
						)
 | 
			
		||||
					endif(PYTHON_MODULES_DIR)
 | 
			
		||||
				endif(ENABLE_EFFECTENGINE)
 | 
			
		||||
 | 
			
		||||
				file(REMOVE_RECURSE "${CMAKE_INSTALL_PREFIX}/${TARGET_BUNDLE_NAME}/Contents/lib")
 | 
			
		||||
				file(REMOVE_RECURSE "${CMAKE_INSTALL_PREFIX}/share")
 | 
			
		||||
				file(GLOB_RECURSE LIBS FOLLOW_SYMLINKS "${BUNDLE_INSTALL_DIR}/*.dylib")
 | 
			
		||||
				file(GLOB FRAMEWORKS FOLLOW_SYMLINKS LIST_DIRECTORIES ON "${BUNDLE_INSTALL_DIR}/Contents/Frameworks/*")
 | 
			
		||||
				foreach(item ${LIBS} ${FRAMEWORKS} ${PYTHON_FRAMEWORK} ${BUNDLE_INSTALL_DIR})
 | 
			
		||||
					set(cmd codesign --deep --force --sign - "${item}")
 | 
			
		||||
					execute_process(
 | 
			
		||||
						COMMAND ${cmd}
 | 
			
		||||
						RESULT_VARIABLE codesign_result
 | 
			
		||||
					)
 | 
			
		||||
 | 
			
		||||
					if(NOT codesign_result EQUAL 0)
 | 
			
		||||
						message(WARNING "macOS signing failed; ${cmd} returned ${codesign_result}")
 | 
			
		||||
					endif()
 | 
			
		||||
				endforeach()
 | 
			
		||||
 | 
			
		||||
		]] COMPONENT "Hyperion")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,11 +49,6 @@ on run argv
 | 
			
		||||
                delay 1
 | 
			
		||||
            close
 | 
			
		||||
 | 
			
		||||
            -- one last open and close so you can see everything looks correct
 | 
			
		||||
            open
 | 
			
		||||
                delay 5
 | 
			
		||||
            close
 | 
			
		||||
 | 
			
		||||
        end tell
 | 
			
		||||
 | 
			
		||||
        delay 1
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,8 @@
 | 
			
		||||
		<string>APPL</string>
 | 
			
		||||
		<key>LSUIElement</key>
 | 
			
		||||
		<string>1</string>
 | 
			
		||||
		<key>NSCameraUsageDescription</key>
 | 
			
		||||
		<string>Hyperion uses this access to record screencasts</string>
 | 
			
		||||
		<key>NSHumanReadableCopyright</key>
 | 
			
		||||
		<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
 | 
			
		||||
		<key>Source Code</key>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
# Two projectiles are sent from random positions and collide with each other
 | 
			
		||||
# Template from https://github.com/nickpesce/lit/blob/master/lit/effects/collision.py
 | 
			
		||||
import hyperion, time, colorsys, random
 | 
			
		||||
 | 
			
		||||
# Get parameters
 | 
			
		||||
@@ -7,6 +5,11 @@ sleepTime     = max(0.02, float(hyperion.args.get('speed', 100))/1000.0)
 | 
			
		||||
trailLength   = max(3, int(hyperion.args.get('trailLength', 5)))
 | 
			
		||||
explodeRadius = int(hyperion.args.get('explodeRadius', 8))
 | 
			
		||||
 | 
			
		||||
# Ensure that the range for pixel indices stays within bounds
 | 
			
		||||
maxPixelIndex = hyperion.ledCount - 1
 | 
			
		||||
if trailLength > maxPixelIndex or explodeRadius > maxPixelIndex:
 | 
			
		||||
    exit(f"Error: Color length ({trailLength}) and detonation range ({explodeRadius}) must be less than number of LEDs configured ({hyperion.ledCount})")
 | 
			
		||||
 | 
			
		||||
# Create additional variables
 | 
			
		||||
increment = None
 | 
			
		||||
projectiles = []
 | 
			
		||||
@@ -14,47 +17,64 @@ projectiles = []
 | 
			
		||||
# Initialize the led data
 | 
			
		||||
ledData = bytearray()
 | 
			
		||||
for i in range(hyperion.ledCount):
 | 
			
		||||
	ledData += bytearray((0,0,0))
 | 
			
		||||
    ledData += bytearray((0,0,0))
 | 
			
		||||
 | 
			
		||||
# Start the write data loop
 | 
			
		||||
while not hyperion.abort():
 | 
			
		||||
	if (len(projectiles) != 2):
 | 
			
		||||
		projectiles = [ [0, 1, random.uniform(0.0, 1.0)], [hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)] ]
 | 
			
		||||
		increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1)
 | 
			
		||||
    if len(projectiles) != 2:
 | 
			
		||||
        projectiles = [
 | 
			
		||||
            [0, 1, random.uniform(0.0, 1.0)],  # Start positions of projectiles
 | 
			
		||||
            [hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)]
 | 
			
		||||
        ]
 | 
			
		||||
        increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1)
 | 
			
		||||
 | 
			
		||||
	ledDataBuf = ledData[:]
 | 
			
		||||
	for i, v in enumerate(projectiles):
 | 
			
		||||
		projectiles[i][0] = projectiles[i][0]+projectiles[i][1]
 | 
			
		||||
		for t in range(0, trailLength):
 | 
			
		||||
			pixel = v[0] - v[1]*t
 | 
			
		||||
			if pixel + 2 < 0:
 | 
			
		||||
				pixel += hyperion.ledCount
 | 
			
		||||
			if pixel + 2 > hyperion.ledCount-1:
 | 
			
		||||
				pixel -= hyperion.ledCount-1
 | 
			
		||||
			rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0*t)/trailLength)
 | 
			
		||||
			ledDataBuf[3*pixel    ] = int(255*rgb[0])
 | 
			
		||||
			ledDataBuf[3*pixel + 1] = int(255*rgb[1])
 | 
			
		||||
			ledDataBuf[3*pixel + 2] = int(255*rgb[2])
 | 
			
		||||
    # Backup the LED data
 | 
			
		||||
    ledDataBuf = ledData[:]
 | 
			
		||||
    for i, v in enumerate(projectiles):
 | 
			
		||||
        # Update projectile positions
 | 
			
		||||
        projectiles[i][0] = projectiles[i][0] + projectiles[i][1]
 | 
			
		||||
        
 | 
			
		||||
        for t in range(0, trailLength):
 | 
			
		||||
            # Calculate pixel index for the trail
 | 
			
		||||
            pixel = v[0] - v[1] * t
 | 
			
		||||
            if pixel < 0:
 | 
			
		||||
                pixel += hyperion.ledCount
 | 
			
		||||
            if pixel >= hyperion.ledCount:
 | 
			
		||||
                pixel -= hyperion.ledCount
 | 
			
		||||
 | 
			
		||||
	hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
 | 
			
		||||
            # Make sure pixel is within bounds
 | 
			
		||||
            if pixel < 0 or pixel >= hyperion.ledCount:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
	for i1, p1 in enumerate(projectiles):
 | 
			
		||||
		for i2, p2 in enumerate(projectiles):
 | 
			
		||||
			if (p1 is not p2):
 | 
			
		||||
				prev1 = p1[0] - p1[1]
 | 
			
		||||
				prev2 = p2[0] - p2[1]
 | 
			
		||||
				if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
 | 
			
		||||
					for d in range(0, explodeRadius):
 | 
			
		||||
						for pixel in range(p1[0] - d, p1[0] + d):
 | 
			
		||||
							rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
 | 
			
		||||
							ledDataBuf[3*pixel    ] = int(255*rgb[0])
 | 
			
		||||
							ledDataBuf[3*pixel + 1] = int(255*rgb[1])
 | 
			
		||||
							ledDataBuf[3*pixel + 2] = int(255*rgb[2])
 | 
			
		||||
            rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0 * t) / trailLength)
 | 
			
		||||
            ledDataBuf[3*pixel] = int(255 * rgb[0])
 | 
			
		||||
            ledDataBuf[3*pixel + 1] = int(255 * rgb[1])
 | 
			
		||||
            ledDataBuf[3*pixel + 2] = int(255 * rgb[2])
 | 
			
		||||
 | 
			
		||||
						hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
 | 
			
		||||
						time.sleep(sleepTime)
 | 
			
		||||
    hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
 | 
			
		||||
 | 
			
		||||
					projectiles.remove(p1)
 | 
			
		||||
					projectiles.remove(p2)
 | 
			
		||||
    # Check for collision and handle explosion
 | 
			
		||||
    for i1, p1 in enumerate(projectiles):
 | 
			
		||||
        for i2, p2 in enumerate(projectiles):
 | 
			
		||||
            if p1 is not p2:
 | 
			
		||||
                prev1 = p1[0] - p1[1]
 | 
			
		||||
                prev2 = p2[0] - p2[1]
 | 
			
		||||
                if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
 | 
			
		||||
                    for d in range(0, explodeRadius):
 | 
			
		||||
                        for pixel in range(p1[0] - d, p1[0] + d):
 | 
			
		||||
                            # Check if pixel is out of bounds
 | 
			
		||||
                            if pixel < 0 or pixel >= hyperion.ledCount:
 | 
			
		||||
                                continue
 | 
			
		||||
 | 
			
		||||
	time.sleep(sleepTime)
 | 
			
		||||
                            rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
 | 
			
		||||
                            ledDataBuf[3 * pixel] = int(255 * rgb[0])
 | 
			
		||||
                            ledDataBuf[3 * pixel + 1] = int(255 * rgb[1])
 | 
			
		||||
                            ledDataBuf[3 * pixel + 2] = int(255 * rgb[2])
 | 
			
		||||
 | 
			
		||||
                        hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
 | 
			
		||||
                        time.sleep(sleepTime)
 | 
			
		||||
 | 
			
		||||
                    projectiles.remove(p1)
 | 
			
		||||
                    projectiles.remove(p2)
 | 
			
		||||
 | 
			
		||||
    time.sleep(sleepTime)
 | 
			
		||||
 
 | 
			
		||||
@@ -24,13 +24,13 @@ public:
 | 
			
		||||
 | 
			
		||||
	friend class EffectModule;
 | 
			
		||||
 | 
			
		||||
	Effect(Hyperion *hyperion
 | 
			
		||||
				, int priority
 | 
			
		||||
				, int timeout
 | 
			
		||||
				, const QString &script
 | 
			
		||||
				, const QString &name
 | 
			
		||||
				, const QJsonObject &args = QJsonObject()
 | 
			
		||||
				, const QString &imageData = ""
 | 
			
		||||
	Effect(Hyperion* hyperion
 | 
			
		||||
		, int priority
 | 
			
		||||
		, int timeout
 | 
			
		||||
		, const QString& script
 | 
			
		||||
		, const QString& name
 | 
			
		||||
		, const QJsonObject& args = QJsonObject()
 | 
			
		||||
		, const QString& imageData = ""
 | 
			
		||||
	);
 | 
			
		||||
	~Effect() override;
 | 
			
		||||
 | 
			
		||||
@@ -64,20 +64,20 @@ public:
 | 
			
		||||
	QString getScript() const { return _script; }
 | 
			
		||||
	QString getName() const { return _name; }
 | 
			
		||||
 | 
			
		||||
	int getTimeout() const {return _timeout; }
 | 
			
		||||
	int getTimeout() const { return _timeout; }
 | 
			
		||||
	bool isEndless() const { return _isEndless; }
 | 
			
		||||
 | 
			
		||||
	QJsonObject getArgs() const { return _args; }
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void setInput(int priority, const std::vector<ColorRgb> &ledColors, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInputImage(int priority, const Image<ColorRgb> &image, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInput(int priority, const std::vector<ColorRgb>& ledColors, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInputImage(int priority, const Image<ColorRgb>& image, int timeout_ms, bool clearEffect);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void setModuleParameters();
 | 
			
		||||
	bool setModuleParameters();
 | 
			
		||||
	void addImage();
 | 
			
		||||
 | 
			
		||||
	Hyperion *_hyperion;
 | 
			
		||||
	Hyperion* _hyperion;
 | 
			
		||||
 | 
			
		||||
	const int _priority;
 | 
			
		||||
 | 
			
		||||
@@ -95,13 +95,13 @@ private:
 | 
			
		||||
	/// Buffer for colorData
 | 
			
		||||
	QVector<ColorRgb> _colors;
 | 
			
		||||
 | 
			
		||||
	Logger *_log;
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	// Reflects whenever this effects should interrupt (timeout or external request)
 | 
			
		||||
	std::atomic<bool> _interupt {};
 | 
			
		||||
	std::atomic<bool> _interupt{};
 | 
			
		||||
 | 
			
		||||
	QSize           _imageSize;
 | 
			
		||||
	QImage          _image;
 | 
			
		||||
	QPainter       *_painter;
 | 
			
		||||
	QPainter*       _painter;
 | 
			
		||||
	QVector<QImage> _imageStack;
 | 
			
		||||
 | 
			
		||||
	double	_lowestUpdateIntervalInSeconds;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,22 +8,16 @@
 | 
			
		||||
 | 
			
		||||
class Effect;
 | 
			
		||||
 | 
			
		||||
class EffectModule: public QObject
 | 
			
		||||
class EffectModule : public QObject
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	// Python 3 module def
 | 
			
		||||
	static struct PyModuleDef moduleDef;
 | 
			
		||||
 | 
			
		||||
	// Init module
 | 
			
		||||
	static PyObject* PyInit_hyperion();
 | 
			
		||||
 | 
			
		||||
	// Register module once
 | 
			
		||||
	static void registerHyperionExtensionModule();
 | 
			
		||||
 | 
			
		||||
	// json 2 python
 | 
			
		||||
	static PyObject * json2python(const QJsonValue & jsonData);
 | 
			
		||||
	static PyObject* json2python(const QJsonValue& jsonData);
 | 
			
		||||
 | 
			
		||||
	// Wrapper methods for Python interpreter extra buildin methods
 | 
			
		||||
	static PyMethodDef effectMethods[];
 | 
			
		||||
 
 | 
			
		||||
@@ -71,5 +71,7 @@ private:
 | 
			
		||||
	quint16 _port;
 | 
			
		||||
	const QJsonDocument _config;
 | 
			
		||||
 | 
			
		||||
	int _pixelDecimation;
 | 
			
		||||
 | 
			
		||||
	QVector<FlatBufferClient*> _openConnections;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
// OSX includes
 | 
			
		||||
// CoreGraphics
 | 
			
		||||
#include <CoreGraphics/CoreGraphics.h>
 | 
			
		||||
 | 
			
		||||
// Utils includes
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#undef slots
 | 
			
		||||
#include <Python.h>
 | 
			
		||||
#define slots Q_SLOTS
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// @brief Handle the PythonInit, module registers and DeInit
 | 
			
		||||
///
 | 
			
		||||
@@ -10,4 +14,8 @@ private:
 | 
			
		||||
 | 
			
		||||
	PythonInit();
 | 
			
		||||
	~PythonInit();
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
	void handlePythonError(PyStatus status, PyConfig& config);
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@
 | 
			
		||||
#include "Python.h"
 | 
			
		||||
#define slots
 | 
			
		||||
 | 
			
		||||
#include <python/PythonUtils.h>
 | 
			
		||||
 | 
			
		||||
class Logger;
 | 
			
		||||
 | 
			
		||||
class PythonProgram
 | 
			
		||||
@@ -17,9 +19,15 @@ public:
 | 
			
		||||
	PythonProgram(const QString & name, Logger * log);
 | 
			
		||||
	~PythonProgram();
 | 
			
		||||
 | 
			
		||||
	operator PyThreadState* ()
 | 
			
		||||
	{
 | 
			
		||||
		return _tstate;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void execute(const QByteArray &python_code);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	QString _name;
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	PyThreadState* _tstate;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ public:
 | 
			
		||||
 | 
			
		||||
	void setHorizontalPixelDecimation(int decimator) { _horizontalDecimation = decimator; }
 | 
			
		||||
	void setVerticalPixelDecimation(int decimator) { _verticalDecimation = decimator; }
 | 
			
		||||
	void setPixelDecimation(int decimator) { _horizontalDecimation = decimator; _verticalDecimation = decimator;}
 | 
			
		||||
	void setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom);
 | 
			
		||||
	void setVideoMode(VideoMode mode) { _videoMode = mode; }
 | 
			
		||||
	void setFlipMode(FlipMode mode) { _flipMode = mode; }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@
 | 
			
		||||
// python utils
 | 
			
		||||
#include <python/PythonProgram.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
	int DEFAULT_MAX_UPDATE_RATE_HZ { 200 };
 | 
			
		||||
@@ -67,41 +68,81 @@ int Effect::getRemaining() const
 | 
			
		||||
 | 
			
		||||
	if (timeout >= 0)
 | 
			
		||||
	{
 | 
			
		||||
		timeout = static_cast<int>( _endTime - QDateTime::currentMSecsSinceEpoch());
 | 
			
		||||
		timeout = static_cast<int>(_endTime - QDateTime::currentMSecsSinceEpoch());
 | 
			
		||||
	}
 | 
			
		||||
	return timeout;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Effect::setModuleParameters()
 | 
			
		||||
bool Effect::setModuleParameters()
 | 
			
		||||
{
 | 
			
		||||
	// import the buildtin Hyperion module
 | 
			
		||||
	PyObject * module = PyImport_ImportModule("hyperion");
 | 
			
		||||
	PyObject* module = PyImport_ImportModule("hyperion");
 | 
			
		||||
 | 
			
		||||
	// add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function
 | 
			
		||||
	PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr));
 | 
			
		||||
	if (module == nullptr) {
 | 
			
		||||
		PyErr_Print();  // Print error if module import fails
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add ledCount variable to the interpreter
 | 
			
		||||
	// Add a capsule containing 'this' to the module for callback access
 | 
			
		||||
	PyObject* capsule = PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr);
 | 
			
		||||
	if (capsule == nullptr || PyModule_AddObject(module, "__effectObj", capsule) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if adding capsule fails
 | 
			
		||||
		Py_XDECREF(module);  // Clean up if capsule addition fails
 | 
			
		||||
		Py_XDECREF(capsule);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add ledCount variable to the interpreter
 | 
			
		||||
	int ledCount = 0;
 | 
			
		||||
	QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, ledCount));
 | 
			
		||||
	PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", ledCount));
 | 
			
		||||
	PyObject* ledCountObj = Py_BuildValue("i", ledCount);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "ledCount", ledCountObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(ledCountObj);
 | 
			
		||||
 | 
			
		||||
	// add minimumWriteTime variable to the interpreter
 | 
			
		||||
	// Add minimumWriteTime variable to the interpreter
 | 
			
		||||
	int latchTime = 0;
 | 
			
		||||
	QMetaObject::invokeMethod(_hyperion, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, latchTime));
 | 
			
		||||
	PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", latchTime));
 | 
			
		||||
	PyObject* latchTimeObj = Py_BuildValue("i", latchTime);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "latchTime", latchTimeObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(latchTimeObj);
 | 
			
		||||
 | 
			
		||||
	// add a args variable to the interpreter
 | 
			
		||||
	PyObject_SetAttrString(module, "args", EffectModule::json2python(_args));
 | 
			
		||||
	// Add args variable to the interpreter
 | 
			
		||||
	PyObject* argsObj = EffectModule::json2python(_args);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "args", argsObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(argsObj);
 | 
			
		||||
 | 
			
		||||
	// decref the module
 | 
			
		||||
	// Decrement module reference
 | 
			
		||||
	Py_XDECREF(module);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Effect::run()
 | 
			
		||||
{
 | 
			
		||||
	PythonProgram program(_name, _log);
 | 
			
		||||
 | 
			
		||||
	setModuleParameters();
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(program);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	if (!setModuleParameters())
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "Failed to set Module parameters. Effect will not be executed.");
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	// Set the end time if applicable
 | 
			
		||||
	if (_timeout > 0)
 | 
			
		||||
@@ -110,7 +151,7 @@ void Effect::run()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run the effect script
 | 
			
		||||
	QFile file (_script);
 | 
			
		||||
	QFile file(_script);
 | 
			
		||||
	if (file.open(QIODevice::ReadOnly))
 | 
			
		||||
	{
 | 
			
		||||
		program.execute(file.readAll());
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,4 +1,5 @@
 | 
			
		||||
#include "FlatBufferClient.h"
 | 
			
		||||
#include <utils/PixelFormat.h>
 | 
			
		||||
 | 
			
		||||
// qt
 | 
			
		||||
#include <QTcpSocket>
 | 
			
		||||
@@ -15,6 +16,8 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, int timeout, QObject *par
 | 
			
		||||
	, _timeout(timeout * 1000)
 | 
			
		||||
	, _priority()
 | 
			
		||||
{
 | 
			
		||||
	_imageResampler.setPixelDecimation(1);
 | 
			
		||||
	
 | 
			
		||||
	// timer setup
 | 
			
		||||
	_timeoutTimer->setSingleShot(true);
 | 
			
		||||
	_timeoutTimer->setInterval(_timeout);
 | 
			
		||||
@@ -25,6 +28,11 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, int timeout, QObject *par
 | 
			
		||||
	connect(_socket, &QTcpSocket::disconnected, this, &FlatBufferClient::disconnected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FlatBufferClient::setPixelDecimation(int decimator)
 | 
			
		||||
{
 | 
			
		||||
	_imageResampler.setPixelDecimation(decimator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FlatBufferClient::readyRead()
 | 
			
		||||
{
 | 
			
		||||
	_timeoutTimer->start();
 | 
			
		||||
@@ -141,55 +149,71 @@ void FlatBufferClient::handleRegisterCommand(const hyperionnet::Register *regReq
 | 
			
		||||
 | 
			
		||||
void FlatBufferClient::handleImageCommand(const hyperionnet::Image *image)
 | 
			
		||||
{
 | 
			
		||||
	Image<ColorRgb> imageRGB;
 | 
			
		||||
 | 
			
		||||
	// extract parameters
 | 
			
		||||
	int duration = image->duration();
 | 
			
		||||
 | 
			
		||||
	const void* reqPtr;
 | 
			
		||||
	if ((reqPtr = image->data_as_RawImage()) != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		const auto *img = static_cast<const hyperionnet::RawImage*>(reqPtr);
 | 
			
		||||
		const auto & imageData = img->data();
 | 
			
		||||
		const int width = img->width();
 | 
			
		||||
		const int height = img->height();
 | 
			
		||||
		const auto* img = static_cast<const hyperionnet::RawImage*>(reqPtr);
 | 
			
		||||
 | 
			
		||||
		if (width <= 0 || height <= 0)
 | 
			
		||||
		hyperionnet::RawImageT rawImageNative;
 | 
			
		||||
		img->UnPackTo(&rawImageNative);
 | 
			
		||||
 | 
			
		||||
		const int width = rawImageNative.width;
 | 
			
		||||
		const int height = rawImageNative.height;
 | 
			
		||||
 | 
			
		||||
		if (width <= 0 || height <= 0 || rawImageNative.data.empty())
 | 
			
		||||
		{
 | 
			
		||||
			sendErrorReply("Size of image data does not match with the width and height");
 | 
			
		||||
			sendErrorReply("Invalid width and/or height or no raw image data provided");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check consistency of the size of the received data
 | 
			
		||||
		int channelCount = (int)imageData->size()/(width*height);
 | 
			
		||||
		if (channelCount != 3 && channelCount != 4)
 | 
			
		||||
		int bytesPerPixel = rawImageNative.data.size() / (width * height);
 | 
			
		||||
		if (bytesPerPixel != 3 && bytesPerPixel != 4)
 | 
			
		||||
		{
 | 
			
		||||
			sendErrorReply("Size of image data does not match with the width and height");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// create ImageRgb
 | 
			
		||||
		Image<ColorRgb> imageRGB(width, height);
 | 
			
		||||
		if (channelCount == 3)
 | 
			
		||||
		{
 | 
			
		||||
			memmove(imageRGB.memptr(), imageData->data(), imageData->size());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (channelCount == 4)
 | 
			
		||||
		{
 | 
			
		||||
			for (int source=0, destination=0; source < width * height * static_cast<int>(sizeof(ColorRgb)); source+=sizeof(ColorRgb), destination+=sizeof(ColorRgba))
 | 
			
		||||
			{
 | 
			
		||||
				memmove((uint8_t*)imageRGB.memptr() + source, imageData->data() + destination, sizeof(ColorRgb));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		emit setGlobalInputImage(_priority, imageRGB, duration);
 | 
			
		||||
		emit setBufferImage("FlatBuffer", imageRGB);
 | 
			
		||||
		imageRGB.resize(width, height);
 | 
			
		||||
		processRawImage(rawImageNative, bytesPerPixel, _imageResampler, imageRGB);
 | 
			
		||||
	}
 | 
			
		||||
	else if ((reqPtr = image->data_as_NV12Image()) != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		const auto* img = static_cast<const hyperionnet::NV12Image*>(reqPtr);
 | 
			
		||||
 | 
			
		||||
		hyperionnet::NV12ImageT nv12ImageNative;
 | 
			
		||||
		img->UnPackTo(&nv12ImageNative);
 | 
			
		||||
 | 
			
		||||
		const int width = nv12ImageNative.width;
 | 
			
		||||
		const int height = nv12ImageNative.height;
 | 
			
		||||
 | 
			
		||||
		if (width <= 0 || height <= 0 || nv12ImageNative.data_y.empty() || nv12ImageNative.data_uv.empty())
 | 
			
		||||
		{
 | 
			
		||||
			sendErrorReply("Invalid width and/or height or no complete NV12 image data provided");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		imageRGB.resize(width, height);
 | 
			
		||||
		processNV12Image(nv12ImageNative, _imageResampler, imageRGB);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		sendErrorReply("No or unknown image data provided");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit setGlobalInputImage(_priority, imageRGB, duration);
 | 
			
		||||
	emit setBufferImage("FlatBuffer", imageRGB);
 | 
			
		||||
 | 
			
		||||
	// send reply
 | 
			
		||||
	sendSuccessReply();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void FlatBufferClient::handleClearCommand(const hyperionnet::Clear *clear)
 | 
			
		||||
{
 | 
			
		||||
	// extract parameters
 | 
			
		||||
@@ -242,3 +266,50 @@ void FlatBufferClient::sendErrorReply(const std::string &error)
 | 
			
		||||
 | 
			
		||||
	_builder.Clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void FlatBufferClient::processRawImage(const hyperionnet::RawImageT& raw_image, int bytesPerPixel, ImageResampler& resampler, Image<ColorRgb>& outputImage) {
 | 
			
		||||
	
 | 
			
		||||
	int width = raw_image.width;
 | 
			
		||||
	int height = raw_image.height;
 | 
			
		||||
 | 
			
		||||
	int lineLength = width * bytesPerPixel;
 | 
			
		||||
	PixelFormat pixelFormat = (bytesPerPixel == 4) ? PixelFormat::RGB32 : PixelFormat::RGB24;
 | 
			
		||||
 | 
			
		||||
	// Process the image
 | 
			
		||||
	resampler.processImage(
 | 
			
		||||
		raw_image.data.data(),	  // Raw RGB/RGBA buffer
 | 
			
		||||
		width,                    // Image width
 | 
			
		||||
		height,                   // Image height
 | 
			
		||||
		lineLength,               // Line length
 | 
			
		||||
		pixelFormat,              // Pixel format (RGB24/RGB32)
 | 
			
		||||
		outputImage               // Output image
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void FlatBufferClient::processNV12Image(const hyperionnet::NV12ImageT& nv12_image, ImageResampler& resampler, Image<ColorRgb>& outputImage) {
 | 
			
		||||
	// Combine data_y and data_uv into a single buffer
 | 
			
		||||
	int width = nv12_image.width;
 | 
			
		||||
	int height = nv12_image.height;
 | 
			
		||||
 | 
			
		||||
	size_t y_size = nv12_image.data_y.size();
 | 
			
		||||
	size_t uv_size = nv12_image.data_uv.size();
 | 
			
		||||
	std::vector<uint8_t> combined_buffer(y_size + uv_size);
 | 
			
		||||
 | 
			
		||||
	std::memcpy(combined_buffer.data(), nv12_image.data_y.data(), y_size);
 | 
			
		||||
	std::memcpy(combined_buffer.data() + y_size, nv12_image.data_uv.data(), uv_size);
 | 
			
		||||
 | 
			
		||||
	// Determine line length (stride_y)
 | 
			
		||||
	int lineLength = nv12_image.stride_y > 0 ? nv12_image.stride_y : width;
 | 
			
		||||
 | 
			
		||||
	PixelFormat pixelFormat = PixelFormat::NV12;
 | 
			
		||||
 | 
			
		||||
	// Process the image
 | 
			
		||||
	resampler.processImage(
 | 
			
		||||
		combined_buffer.data(),   // Combined NV12 buffer
 | 
			
		||||
		width,                    // Image width
 | 
			
		||||
		height,                   // Image height
 | 
			
		||||
		lineLength,               // Line length for Y plane
 | 
			
		||||
		pixelFormat,              // Pixel format (NV12)
 | 
			
		||||
		outputImage               // Output image
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
#include <utils/ColorRgb.h>
 | 
			
		||||
#include <utils/ColorRgba.h>
 | 
			
		||||
#include <utils/Components.h>
 | 
			
		||||
#include "utils/ImageResampler.h"
 | 
			
		||||
 | 
			
		||||
// flatbuffer FBS
 | 
			
		||||
#include "hyperion_reply_generated.h"
 | 
			
		||||
@@ -33,6 +34,8 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	explicit FlatBufferClient(QTcpSocket* socket, int timeout, QObject *parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	void setPixelDecimation(int decimator);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief forward register data to HyperionDaemon
 | 
			
		||||
@@ -138,6 +141,9 @@ private:
 | 
			
		||||
	///
 | 
			
		||||
	void sendErrorReply(const std::string & error);
 | 
			
		||||
 | 
			
		||||
	void processRawImage(const hyperionnet::RawImageT& raw_image, int bytesPerPixel, ImageResampler& resampler, Image<ColorRgb>& outputImage);
 | 
			
		||||
	void processNV12Image(const hyperionnet::NV12ImageT& nv12_image, ImageResampler& resampler, Image<ColorRgb>& outputImage);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Logger *_log;
 | 
			
		||||
	QTcpSocket *_socket;
 | 
			
		||||
@@ -148,6 +154,8 @@ private:
 | 
			
		||||
 | 
			
		||||
	QByteArray _receiveBuffer;
 | 
			
		||||
 | 
			
		||||
	ImageResampler _imageResampler;
 | 
			
		||||
 | 
			
		||||
	// Flatbuffers builder
 | 
			
		||||
	flatbuffers::FlatBufferBuilder _builder;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,12 @@ void FlatBufferServer::handleSettingsUpdate(settings::type type, const QJsonDocu
 | 
			
		||||
		_timeout = obj["timeout"].toInt(5000);
 | 
			
		||||
		// enable check
 | 
			
		||||
		obj["enable"].toBool(true) ? startServer() : stopServer();
 | 
			
		||||
 | 
			
		||||
		_pixelDecimation = obj["pixelDecimation"].toInt(1);
 | 
			
		||||
		for (const auto& client : _openConnections)
 | 
			
		||||
		{
 | 
			
		||||
			client->setPixelDecimation(_pixelDecimation);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +81,9 @@ void FlatBufferServer::newConnection()
 | 
			
		||||
			{
 | 
			
		||||
				Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
 | 
			
		||||
				FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
 | 
			
		||||
 | 
			
		||||
				client->setPixelDecimation(_pixelDecimation);
 | 
			
		||||
 | 
			
		||||
				// internal
 | 
			
		||||
				connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
 | 
			
		||||
				connect(client, &FlatBufferClient::registerGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,17 @@ table RawImage {
 | 
			
		||||
  height:int = -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
union ImageType {RawImage}
 | 
			
		||||
table NV12Image {
 | 
			
		||||
  data_y:[ubyte];
 | 
			
		||||
  data_uv:[ubyte];
 | 
			
		||||
  width:int;
 | 
			
		||||
  height:int;
 | 
			
		||||
  stride_y:int = 0;
 | 
			
		||||
  stride_uv:int = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
union ImageType {RawImage, NV12Image}
 | 
			
		||||
 | 
			
		||||
// Either RGB or RGBA data can be transferred
 | 
			
		||||
table Image {
 | 
			
		||||
  data:ImageType (required);
 | 
			
		||||
  duration:int = -1;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,28 @@
 | 
			
		||||
add_library(osx-grabber
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/include/grabber/osx/OsxFrameGrabber.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/include/grabber/osx/OsxWrapper.h
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxFrameGrabber.cpp
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxFrameGrabber.mm
 | 
			
		||||
	${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxWrapper.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(osx-grabber
 | 
			
		||||
	hyperion
 | 
			
		||||
	"$<LINK_LIBRARY:FRAMEWORK,CoreGraphics.framework>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
file(WRITE ${CMAKE_BINARY_DIR}/tmp/SDK15Available.c
 | 
			
		||||
	"#include <AvailabilityMacros.h>
 | 
			
		||||
	#if __MAC_OS_X_VERSION_MAX_ALLOWED < 150000
 | 
			
		||||
	#error __MAC_OS_X_VERSION_MAX_ALLOWED < 150000
 | 
			
		||||
	#endif
 | 
			
		||||
	int main(int argc, char** argv)
 | 
			
		||||
	{
 | 
			
		||||
		return 0;
 | 
			
		||||
	}"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
try_compile(SDK_15_AVAILABLE ${CMAKE_BINARY_DIR} SOURCES ${CMAKE_BINARY_DIR}/tmp/SDK15Available.c)
 | 
			
		||||
if(SDK_15_AVAILABLE)
 | 
			
		||||
	target_compile_definitions(osx-grabber PRIVATE SDK_15_AVAILABLE)
 | 
			
		||||
	target_link_libraries(osx-grabber "$<LINK_LIBRARY:WEAK_FRAMEWORK,ScreenCaptureKit.framework>")
 | 
			
		||||
endif()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,16 @@
 | 
			
		||||
// STL includes
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
 | 
			
		||||
// Local includes
 | 
			
		||||
// Header
 | 
			
		||||
#include <grabber/osx/OsxFrameGrabber.h>
 | 
			
		||||
 | 
			
		||||
// ScreenCaptureKit
 | 
			
		||||
#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
//Qt
 | 
			
		||||
#include <QJsonObject>
 | 
			
		||||
#include <QJsonArray>
 | 
			
		||||
@@ -15,9 +21,70 @@ namespace {
 | 
			
		||||
const bool verbose = false;
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
	static CGImageRef capture15(CGDirectDisplayID id, CGRect diIntersectDisplayLocal)
 | 
			
		||||
	{
 | 
			
		||||
		dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
 | 
			
		||||
		__block CGImageRef image1 = nil;
 | 
			
		||||
		[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent* content, NSError* error)
 | 
			
		||||
		{
 | 
			
		||||
			@autoreleasepool
 | 
			
		||||
			{
 | 
			
		||||
				if (error || !content)
 | 
			
		||||
				{
 | 
			
		||||
					dispatch_semaphore_signal(semaphore);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				SCDisplay* target = nil;
 | 
			
		||||
				for (SCDisplay *display in content.displays)
 | 
			
		||||
				{
 | 
			
		||||
					if (display.displayID == id)
 | 
			
		||||
					{
 | 
			
		||||
						target = display;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (!target)
 | 
			
		||||
				{
 | 
			
		||||
					dispatch_semaphore_signal(semaphore);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:target excludingWindows:@[]];
 | 
			
		||||
				SCStreamConfiguration* config = [[SCStreamConfiguration alloc] init];
 | 
			
		||||
				config.queueDepth = 5;
 | 
			
		||||
				config.sourceRect = diIntersectDisplayLocal;
 | 
			
		||||
				config.scalesToFit = false;
 | 
			
		||||
				config.captureResolution = SCCaptureResolutionBest;
 | 
			
		||||
 | 
			
		||||
				CGDisplayModeRef modeRef = CGDisplayCopyDisplayMode(id);
 | 
			
		||||
				double sysScale = CGDisplayModeGetPixelWidth(modeRef) / CGDisplayModeGetWidth(modeRef);
 | 
			
		||||
				config.width = diIntersectDisplayLocal.size.width * sysScale;
 | 
			
		||||
				config.height = diIntersectDisplayLocal.size.height * sysScale;
 | 
			
		||||
 | 
			
		||||
				[SCScreenshotManager captureImageWithFilter:filter
 | 
			
		||||
					configuration:config
 | 
			
		||||
					completionHandler:^(CGImageRef img, NSError* error)
 | 
			
		||||
					{
 | 
			
		||||
						if (!error)
 | 
			
		||||
						{
 | 
			
		||||
							image1 = CGImageCreateCopyWithColorSpace(img, CGColorSpaceCreateDeviceRGB());
 | 
			
		||||
						}
 | 
			
		||||
						dispatch_semaphore_signal(semaphore);
 | 
			
		||||
				}];
 | 
			
		||||
			}
 | 
			
		||||
		}];
 | 
			
		||||
 | 
			
		||||
		dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
 | 
			
		||||
		dispatch_release(semaphore);
 | 
			
		||||
		return image1;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
OsxFrameGrabber::OsxFrameGrabber(int display)
 | 
			
		||||
	: Grabber("GRABBER-OSX")
 | 
			
		||||
	  , _screenIndex(display)
 | 
			
		||||
	, _screenIndex(display)
 | 
			
		||||
{
 | 
			
		||||
	_isEnabled = false;
 | 
			
		||||
	_useImageResampler = true;
 | 
			
		||||
@@ -31,6 +98,15 @@ bool OsxFrameGrabber::setupDisplay()
 | 
			
		||||
{
 | 
			
		||||
	bool rc (false);
 | 
			
		||||
 | 
			
		||||
#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
	if (!CGPreflightScreenCaptureAccess())
 | 
			
		||||
	{
 | 
			
		||||
		if(!CGRequestScreenCaptureAccess())
 | 
			
		||||
			Error(_log, "Screen capture permission required to start the grabber");
 | 
			
		||||
			return false;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	rc = setDisplayIndex(_screenIndex);
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
@@ -41,39 +117,38 @@ int OsxFrameGrabber::grabFrame(Image<ColorRgb> & image)
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
	if (_isEnabled && !_isDeviceInError)
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
		CGImageRef dispImage;
 | 
			
		||||
		CFDataRef imgData;
 | 
			
		||||
		unsigned char * pImgData;
 | 
			
		||||
		unsigned dspWidth;
 | 
			
		||||
		unsigned dspHeight;
 | 
			
		||||
 | 
			
		||||
		dispImage = CGDisplayCreateImage(_display);
 | 
			
		||||
		#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
			dispImage = capture15(_display, CGDisplayBounds(_display));
 | 
			
		||||
		#else
 | 
			
		||||
			dispImage = CGDisplayCreateImageForRect(_display, CGDisplayBounds(_display));
 | 
			
		||||
		#endif
 | 
			
		||||
 | 
			
		||||
		// display lost, use main
 | 
			
		||||
		if (dispImage == nullptr && _display != 0)
 | 
			
		||||
		{
 | 
			
		||||
			dispImage = CGDisplayCreateImage(kCGDirectMainDisplay);
 | 
			
		||||
			// no displays connected, return
 | 
			
		||||
			if (dispImage == nullptr)
 | 
			
		||||
			{
 | 
			
		||||
				Error(_log, "No display connected...");
 | 
			
		||||
				return -1;
 | 
			
		||||
			}
 | 
			
		||||
			#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
				dispImage = capture15(kCGDirectMainDisplay, CGDisplayBounds(kCGDirectMainDisplay));
 | 
			
		||||
			#else
 | 
			
		||||
				dispImage = CGDisplayCreateImageForRect(kCGDirectMainDisplay, CGDisplayBounds(kCGDirectMainDisplay));
 | 
			
		||||
			#endif
 | 
			
		||||
		}
 | 
			
		||||
		imgData   = CGDataProviderCopyData(CGImageGetDataProvider(dispImage));
 | 
			
		||||
		pImgData  = (unsigned char*) CFDataGetBytePtr(imgData);
 | 
			
		||||
		dspWidth  = CGImageGetWidth(dispImage);
 | 
			
		||||
		dspHeight = CGImageGetHeight(dispImage);
 | 
			
		||||
 | 
			
		||||
		_imageResampler.processImage( pImgData,
 | 
			
		||||
									  static_cast<int>(dspWidth),
 | 
			
		||||
									  static_cast<int>(dspHeight),
 | 
			
		||||
									  static_cast<int>(CGImageGetBytesPerRow(dispImage)),
 | 
			
		||||
									  PixelFormat::BGR32,
 | 
			
		||||
									  image);
 | 
			
		||||
		// no displays connected, return
 | 
			
		||||
		if (dispImage == nullptr)
 | 
			
		||||
		{
 | 
			
		||||
			Error(_log, "No display connected...");
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		CFDataRef imgData = CGDataProviderCopyData(CGImageGetDataProvider(dispImage));
 | 
			
		||||
		if (imgData != nullptr)
 | 
			
		||||
		{
 | 
			
		||||
			_imageResampler.processImage((uint8_t *)CFDataGetBytePtr(imgData), static_cast<int>(CGImageGetWidth(dispImage)), static_cast<int>(CGImageGetHeight(dispImage)), static_cast<int>(CGImageGetBytesPerRow(dispImage)), PixelFormat::BGR32, image);
 | 
			
		||||
			CFRelease(imgData);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		CFRelease(imgData);
 | 
			
		||||
		CGImageRelease(dispImage);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
@@ -108,7 +183,12 @@ bool OsxFrameGrabber::setDisplayIndex(int index)
 | 
			
		||||
				{
 | 
			
		||||
					_display = activeDspys[_screenIndex];
 | 
			
		||||
 | 
			
		||||
					image = CGDisplayCreateImage(_display);
 | 
			
		||||
					#if defined(SDK_15_AVAILABLE)
 | 
			
		||||
						image = capture15(_display, CGDisplayBounds(_display));
 | 
			
		||||
					#else
 | 
			
		||||
						image = CGDisplayCreateImageForRect(_display, CGDisplayBounds(_display));
 | 
			
		||||
					#endif
 | 
			
		||||
 | 
			
		||||
					if(image == nullptr)
 | 
			
		||||
					{
 | 
			
		||||
						setEnabled(false);
 | 
			
		||||
@@ -1,35 +1,41 @@
 | 
			
		||||
{
 | 
			
		||||
	"type" : "object",
 | 
			
		||||
	"title" : "edt_conf_fbs_heading_title",
 | 
			
		||||
	"properties" :
 | 
			
		||||
	{
 | 
			
		||||
		"enable" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "boolean",
 | 
			
		||||
			"required" : true,
 | 
			
		||||
			"title" : "edt_conf_general_enable_title",
 | 
			
		||||
			"default" : true,
 | 
			
		||||
			"propertyOrder" : 1
 | 
			
		||||
	"properties": {
 | 
			
		||||
		"enable": {
 | 
			
		||||
			"type": "boolean",
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"title": "edt_conf_general_enable_title",
 | 
			
		||||
			"default": true,
 | 
			
		||||
			"propertyOrder": 1
 | 
			
		||||
		},
 | 
			
		||||
		"port" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "integer",
 | 
			
		||||
			"required" : true,
 | 
			
		||||
			"title" : "edt_conf_general_port_title",
 | 
			
		||||
			"minimum" : 1024,
 | 
			
		||||
			"maximum" : 65535,
 | 
			
		||||
			"default" : 19400,
 | 
			
		||||
			"propertyOrder" : 2
 | 
			
		||||
		"port": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"title": "edt_conf_general_port_title",
 | 
			
		||||
			"minimum": 1024,
 | 
			
		||||
			"maximum": 65535,
 | 
			
		||||
			"default": 19400,
 | 
			
		||||
			"propertyOrder": 2
 | 
			
		||||
		},
 | 
			
		||||
		"timeout" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "integer",
 | 
			
		||||
			"required" : true,
 | 
			
		||||
			"title" : "edt_conf_fbs_timeout_title",
 | 
			
		||||
			"append" : "edt_append_s",
 | 
			
		||||
			"minimum" : 1,
 | 
			
		||||
			"default" : 5,
 | 
			
		||||
			"propertyOrder" : 3
 | 
			
		||||
		"timeout": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"title": "edt_conf_fbs_timeout_title",
 | 
			
		||||
			"append": "edt_append_s",
 | 
			
		||||
			"minimum": 1,
 | 
			
		||||
			"default": 5,
 | 
			
		||||
			"propertyOrder": 3
 | 
			
		||||
		},
 | 
			
		||||
		"pixelDecimation": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"title": "edt_conf_fg_pixelDecimation_title",
 | 
			
		||||
			"minimum": 1,
 | 
			
		||||
			"maximum": 30,
 | 
			
		||||
			"default": 1,
 | 
			
		||||
			"required": false,
 | 
			
		||||
			"access": "advanced",
 | 
			
		||||
			"propertyOrder": 4
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties" : false
 | 
			
		||||
 
 | 
			
		||||
@@ -115,7 +115,11 @@ if(ENABLE_DEV_NETWORK)
 | 
			
		||||
	if(NOT DEFAULT_USE_SYSTEM_MBEDTLS_LIBS)
 | 
			
		||||
		if(MBEDTLS_LIBRARIES)
 | 
			
		||||
			include_directories(${MBEDTLS_INCLUDE_DIR})
 | 
			
		||||
			target_link_libraries(leddevice ${MBEDTLS_LIBRARIES})
 | 
			
		||||
			target_link_libraries(
 | 
			
		||||
				leddevice
 | 
			
		||||
				${MBEDTLS_LIBRARIES}
 | 
			
		||||
				$<$<BOOL:${WIN32}>:bcrypt.lib>
 | 
			
		||||
			)
 | 
			
		||||
			target_include_directories(leddevice PRIVATE ${MBEDTLS_INCLUDE_DIR})
 | 
			
		||||
		endif (MBEDTLS_LIBRARIES)
 | 
			
		||||
	endif()
 | 
			
		||||
 
 | 
			
		||||
@@ -64,5 +64,9 @@ int LedDeviceSkydimo::write(const std::vector<ColorRgb> & ledValues)
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert(HEADER_SIZE + ledValues.size() * sizeof(ColorRgb) <= _ledBuffer.size());
 | 
			
		||||
 | 
			
		||||
	memcpy(HEADER_SIZE + _ledBuffer.data(), ledValues.data(), ledValues.size() * sizeof(ColorRgb));
 | 
			
		||||
 | 
			
		||||
	return writeBytes(_bufferLength, _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
#include <HyperionConfig.h>
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
	#include <stdexcept>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define STRINGIFY2(x) #x
 | 
			
		||||
@@ -44,14 +44,14 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
	status = PyConfig_SetString(&config, &config.program_name, programName);
 | 
			
		||||
	if (PyStatus_Exception(status)) {
 | 
			
		||||
		goto exception;
 | 
			
		||||
		handlePythonError(status, config);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
#else
 | 
			
		||||
	Py_SetProgramName(programName);
 | 
			
		||||
#endif
 | 
			
		||||
	{
 | 
			
		||||
		// set Python module path when exists
 | 
			
		||||
		// set Python module path when it exists
 | 
			
		||||
		QString py_path = QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + "." + STRINGIFY(PYTHON_VERSION_MINOR));
 | 
			
		||||
		QString py_file = QDir::cleanPath(qApp->applicationDirPath() + "/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + STRINGIFY(PYTHON_VERSION_MINOR) + ".zip");
 | 
			
		||||
		QString py_framework = QDir::cleanPath(qApp->applicationDirPath() + "/../Frameworks/Python.framework/Versions/Current/lib/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + "." + STRINGIFY(PYTHON_VERSION_MINOR));
 | 
			
		||||
@@ -59,21 +59,23 @@ PythonInit::PythonInit()
 | 
			
		||||
		if (QFile(py_file).exists() || QDir(py_path).exists() || QDir(py_framework).exists())
 | 
			
		||||
		{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x030C0000)
 | 
			
		||||
            config.site_import = 0;
 | 
			
		||||
			config.site_import = 0;
 | 
			
		||||
#else
 | 
			
		||||
            Py_NoSiteFlag++;
 | 
			
		||||
			Py_NoSiteFlag++;
 | 
			
		||||
#endif
 | 
			
		||||
			if (QFile(py_file).exists()) // Windows
 | 
			
		||||
			{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(py_file));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(py_file.toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				Py_SetPythonHome(Py_DecodeLocale(py_file.toLatin1().data(), nullptr));
 | 
			
		||||
@@ -85,18 +87,21 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(QDir::cleanPath(qApp->applicationDirPath() + "/../")));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_path).absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_path + "/lib-dynload").absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				QStringList python_paths;
 | 
			
		||||
@@ -114,18 +119,21 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(QDir::cleanPath(qApp->applicationDirPath() + "/../Frameworks/Python.framework/Versions/Current")));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_framework).absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_framework + "/lib-dynload").absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				QStringList python_paths;
 | 
			
		||||
@@ -146,7 +154,8 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
	status = Py_InitializeFromConfig(&config);
 | 
			
		||||
	if (PyStatus_Exception(status)) {
 | 
			
		||||
		goto exception;
 | 
			
		||||
		handlePythonError(status, config);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	PyConfig_Clear(&config);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -154,7 +163,8 @@ PythonInit::PythonInit()
 | 
			
		||||
	// init Python
 | 
			
		||||
	Debug(Logger::getInstance("DAEMON"), "Initializing Python interpreter");
 | 
			
		||||
	Py_InitializeEx(0);
 | 
			
		||||
	if ( !Py_IsInitialized() )
 | 
			
		||||
 | 
			
		||||
	if (!Py_IsInitialized())
 | 
			
		||||
	{
 | 
			
		||||
		throw std::runtime_error("Initializing Python failed!");
 | 
			
		||||
	}
 | 
			
		||||
@@ -165,20 +175,28 @@ PythonInit::PythonInit()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	mainThreadState = PyEval_SaveThread();
 | 
			
		||||
	return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error handling function to replace goto exception
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
exception:
 | 
			
		||||
void PythonInit::handlePythonError(PyStatus status, PyConfig& config)
 | 
			
		||||
{
 | 
			
		||||
	Error(Logger::getInstance("DAEMON"), "Initializing Python config failed with error [%s]", status.err_msg);
 | 
			
		||||
	PyConfig_Clear(&config);
 | 
			
		||||
 | 
			
		||||
	throw std::runtime_error("Initializing Python failed!");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
PythonInit::~PythonInit()
 | 
			
		||||
{
 | 
			
		||||
	Debug(Logger::getInstance("DAEMON"), "Cleaning up Python interpreter");
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyEval_RestoreThread(mainThreadState);
 | 
			
		||||
	Py_Finalize();
 | 
			
		||||
#else
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	int rc = Py_FinalizeEx();
 | 
			
		||||
	Debug(Logger::getInstance("DAEMON"), "Cleaning up Python interpreter %s", rc == 0 ? "succeeded" : "failed");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,173 +1,238 @@
 | 
			
		||||
#include <python/PythonProgram.h>
 | 
			
		||||
#include <python/PythonUtils.h>
 | 
			
		||||
 | 
			
		||||
#include <utils/Logger.h>
 | 
			
		||||
 | 
			
		||||
#include <QThread>
 | 
			
		||||
 | 
			
		||||
PyThreadState* mainThreadState;
 | 
			
		||||
 | 
			
		||||
PythonProgram::PythonProgram(const QString & name, Logger * log) :
 | 
			
		||||
	_name(name), _log(log), _tstate(nullptr)
 | 
			
		||||
PythonProgram::PythonProgram(const QString& name, Logger* log) :
 | 
			
		||||
	_name(name)
 | 
			
		||||
	, _log(log)
 | 
			
		||||
	, _tstate(nullptr)
 | 
			
		||||
{
 | 
			
		||||
	// we probably need to wait until mainThreadState is available
 | 
			
		||||
	while(mainThreadState == nullptr){};
 | 
			
		||||
	QThread::msleep(10);
 | 
			
		||||
	while (mainThreadState == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		QThread::msleep(10);  // Wait with delay to avoid busy waiting
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a new subinterpreter for this thread
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	// get global lock
 | 
			
		||||
	PyEval_RestoreThread(mainThreadState);
 | 
			
		||||
 | 
			
		||||
	// Initialize a new thread state
 | 
			
		||||
	_tstate = Py_NewInterpreter();
 | 
			
		||||
	if(_tstate == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03020000)
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
		PyEval_SaveThread();
 | 
			
		||||
#else
 | 
			
		||||
		PyEval_ReleaseLock();
 | 
			
		||||
	PyThreadState* prev = PyThreadState_Swap(NULL);
 | 
			
		||||
 | 
			
		||||
	// Create a new interpreter configuration object
 | 
			
		||||
	PyInterpreterConfig config{};
 | 
			
		||||
 | 
			
		||||
	// Set configuration options
 | 
			
		||||
	config.use_main_obmalloc = 0;
 | 
			
		||||
	config.allow_fork = 0;
 | 
			
		||||
	config.allow_exec = 0;
 | 
			
		||||
	config.allow_threads = 1;
 | 
			
		||||
	config.allow_daemon_threads = 0;
 | 
			
		||||
	config.check_multi_interp_extensions = 1;
 | 
			
		||||
	config.gil = PyInterpreterConfig_OWN_GIL;
 | 
			
		||||
	Py_NewInterpreterFromConfig(&_tstate, &config);
 | 
			
		||||
#endif
 | 
			
		||||
		Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name));
 | 
			
		||||
 | 
			
		||||
	if (_tstate == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyEval_SaveThread();
 | 
			
		||||
#endif
 | 
			
		||||
		Error(_log, "Failed to get thread state for %s", QSTRING_CSTR(_name));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(_tstate);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PythonProgram::~PythonProgram()
 | 
			
		||||
{
 | 
			
		||||
	if (!_tstate)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	// stop sub threads if needed
 | 
			
		||||
	for (PyThreadState* s = PyInterpreterState_ThreadHead(_tstate->interp), *old = nullptr; s;)
 | 
			
		||||
	{
 | 
			
		||||
		if (s == _tstate)
 | 
			
		||||
		{
 | 
			
		||||
			s = s->next;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (old != s)
 | 
			
		||||
		{
 | 
			
		||||
			Debug(_log,"ID %s: Waiting on thread %u", QSTRING_CSTR(_name), s->thread_id);
 | 
			
		||||
			old = s;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
		QThread::msleep(100);
 | 
			
		||||
		Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
		s = PyInterpreterState_ThreadHead(_tstate->interp);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(_tstate);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	// Clean up the thread state
 | 
			
		||||
	Py_EndInterpreter(_tstate);
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03020000)
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
	PyEval_SaveThread();
 | 
			
		||||
#else
 | 
			
		||||
	PyEval_ReleaseLock();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PythonProgram::execute(const QByteArray & python_code)
 | 
			
		||||
void PythonProgram::execute(const QByteArray& python_code)
 | 
			
		||||
{
 | 
			
		||||
	if (!_tstate)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(_tstate);
 | 
			
		||||
#else
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(_tstate);
 | 
			
		||||
#endif
 | 
			
		||||
	PyObject* main_module = PyImport_ImportModule("__main__");
 | 
			
		||||
	if (!main_module)
 | 
			
		||||
	{
 | 
			
		||||
		// Restore the previous thread state
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#else
 | 
			
		||||
		PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PyObject *main_module = PyImport_ImportModule("__main__"); // New Reference
 | 
			
		||||
	PyObject *main_dict = PyModule_GetDict(main_module); // Borrowed reference
 | 
			
		||||
	Py_INCREF(main_dict); // Incref "main_dict" to use it in PyRun_String(), because PyModule_GetDict() has decref "main_dict"
 | 
			
		||||
	Py_DECREF(main_module); // // release "main_module" when done
 | 
			
		||||
	PyObject *result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict); // New Reference
 | 
			
		||||
 | 
			
		||||
	PyObject* main_dict = PyModule_GetDict(main_module);  // Borrowed reference to globals
 | 
			
		||||
	PyObject* result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict);
 | 
			
		||||
	if (!result)
 | 
			
		||||
	{
 | 
			
		||||
		if (PyErr_Occurred()) // Nothing needs to be done for a borrowed reference
 | 
			
		||||
		if (PyErr_Occurred())
 | 
			
		||||
		{
 | 
			
		||||
			Error(_log,"###### PYTHON EXCEPTION ######");
 | 
			
		||||
			Error(_log,"## In effect '%s'", QSTRING_CSTR(_name));
 | 
			
		||||
			/* Objects all initialized to NULL for Py_XDECREF */
 | 
			
		||||
			PyObject *errorType = NULL, *errorValue = NULL, *errorTraceback = NULL;
 | 
			
		||||
			PyObject* errorType = NULL, * errorValue = NULL, * errorTraceback = NULL;
 | 
			
		||||
 | 
			
		||||
			PyErr_Fetch(&errorType, &errorValue, &errorTraceback); // New Reference or NULL
 | 
			
		||||
			PyErr_Fetch(&errorType, &errorValue, &errorTraceback);
 | 
			
		||||
			PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback);
 | 
			
		||||
 | 
			
		||||
			// Extract exception message from "errorValue"
 | 
			
		||||
			if(errorValue)
 | 
			
		||||
			{
 | 
			
		||||
				QString message;
 | 
			
		||||
				if(PyObject_HasAttrString(errorValue, "__class__"))
 | 
			
		||||
				{
 | 
			
		||||
					PyObject *classPtr = PyObject_GetAttrString(errorValue, "__class__"); // New Reference
 | 
			
		||||
					PyObject *class_name = NULL; /* Object "class_name" initialized to NULL for Py_XDECREF */
 | 
			
		||||
					class_name = PyObject_GetAttrString(classPtr, "__name__"); // New Reference or NULL
 | 
			
		||||
			// Check if the exception is a SystemExit
 | 
			
		||||
			PyObject* systemExitType = PyExc_SystemExit;
 | 
			
		||||
			bool isSystemExit = PyObject_IsInstance(errorValue, systemExitType);
 | 
			
		||||
 | 
			
		||||
					if(class_name && PyUnicode_Check(class_name))
 | 
			
		||||
			if (isSystemExit)
 | 
			
		||||
			{
 | 
			
		||||
				// Extract the exit argument
 | 
			
		||||
				PyObject* exitArg = PyObject_GetAttrString(errorValue, "code");
 | 
			
		||||
 | 
			
		||||
				if (exitArg)
 | 
			
		||||
				{
 | 
			
		||||
					QString logErrorText;
 | 
			
		||||
					if (PyTuple_Check(exitArg)) {
 | 
			
		||||
						PyObject* errorMessage = PyTuple_GetItem(exitArg, 0); // Borrowed reference
 | 
			
		||||
						PyObject* exitCode = PyTuple_GetItem(exitArg, 1);     // Borrowed reference
 | 
			
		||||
 | 
			
		||||
						if (exitCode && PyLong_Check(exitCode))
 | 
			
		||||
						{
 | 
			
		||||
							logErrorText = QString("[%1]: ").arg(PyLong_AsLong(exitCode));
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if (errorMessage && PyUnicode_Check(errorMessage)) {
 | 
			
		||||
							logErrorText.append(PyUnicode_AsUTF8(errorMessage));
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					else if (PyUnicode_Check(exitArg)) {
 | 
			
		||||
						// If the code is just a string, treat it as an error message
 | 
			
		||||
						logErrorText.append(PyUnicode_AsUTF8(exitArg));
 | 
			
		||||
					}
 | 
			
		||||
					else if (PyLong_Check(exitArg)) {
 | 
			
		||||
						// If the code is just an integer, treat it as an exit code
 | 
			
		||||
						logErrorText = QString("[%1]").arg(PyLong_AsLong(exitArg));
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					Error(_log, "Effect '%s' failed with error %s", QSTRING_CSTR(_name), QSTRING_CSTR(logErrorText));
 | 
			
		||||
 | 
			
		||||
					Py_DECREF(exitArg); // Release the reference
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					Debug(_log, "No 'code' attribute found on SystemExit exception.");
 | 
			
		||||
				}
 | 
			
		||||
				// Clear the error so it won't propagate
 | 
			
		||||
				PyErr_Clear();
 | 
			
		||||
 | 
			
		||||
				Py_DECREF(systemExitType);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			Py_DECREF(systemExitType);
 | 
			
		||||
 | 
			
		||||
			if (errorValue)
 | 
			
		||||
			{
 | 
			
		||||
				Error(_log, "###### PYTHON EXCEPTION ######");
 | 
			
		||||
				Error(_log, "## In effect '%s'", QSTRING_CSTR(_name));
 | 
			
		||||
 | 
			
		||||
				QString message;
 | 
			
		||||
				if (PyObject_HasAttrString(errorValue, "__class__"))
 | 
			
		||||
				{
 | 
			
		||||
					PyObject* classPtr = PyObject_GetAttrString(errorValue, "__class__");
 | 
			
		||||
					PyObject* class_name = classPtr ? PyObject_GetAttrString(classPtr, "__name__") : NULL;
 | 
			
		||||
 | 
			
		||||
					if (class_name && PyUnicode_Check(class_name))
 | 
			
		||||
						message.append(PyUnicode_AsUTF8(class_name));
 | 
			
		||||
 | 
			
		||||
					Py_DECREF(classPtr); // release "classPtr" when done
 | 
			
		||||
					Py_XDECREF(class_name); // Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
					Py_XDECREF(class_name);
 | 
			
		||||
					Py_DECREF(classPtr);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Object "class_name" initialized to NULL for Py_XDECREF
 | 
			
		||||
				PyObject *valueString = NULL;
 | 
			
		||||
				valueString = PyObject_Str(errorValue); // New Reference or NULL
 | 
			
		||||
				PyObject* valueString = PyObject_Str(errorValue);
 | 
			
		||||
 | 
			
		||||
				if(valueString && PyUnicode_Check(valueString))
 | 
			
		||||
				if (valueString && PyUnicode_Check(valueString))
 | 
			
		||||
				{
 | 
			
		||||
					if(!message.isEmpty())
 | 
			
		||||
					if (!message.isEmpty())
 | 
			
		||||
						message.append(": ");
 | 
			
		||||
 | 
			
		||||
					message.append(PyUnicode_AsUTF8(valueString));
 | 
			
		||||
				}
 | 
			
		||||
				Py_XDECREF(valueString); // Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
				Py_XDECREF(valueString);
 | 
			
		||||
 | 
			
		||||
				Error(_log, "## %s", QSTRING_CSTR(message));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Extract exception message from "errorTraceback"
 | 
			
		||||
			if(errorTraceback)
 | 
			
		||||
			if (errorTraceback)
 | 
			
		||||
			{
 | 
			
		||||
				// Object "tracebackList" initialized to NULL for Py_XDECREF
 | 
			
		||||
				PyObject *tracebackModule = NULL, *methodName = NULL, *tracebackList = NULL;
 | 
			
		||||
				QString tracebackMsg;
 | 
			
		||||
				PyObject* tracebackModule = PyImport_ImportModule("traceback");
 | 
			
		||||
				PyObject* methodName = PyUnicode_FromString("format_exception");
 | 
			
		||||
				PyObject* tracebackList = tracebackModule && methodName
 | 
			
		||||
					? PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL)
 | 
			
		||||
					: NULL;
 | 
			
		||||
 | 
			
		||||
				tracebackModule = PyImport_ImportModule("traceback"); // New Reference or NULL
 | 
			
		||||
				methodName = PyUnicode_FromString("format_exception"); // New Reference or NULL
 | 
			
		||||
				tracebackList = PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL); // New Reference or NULL
 | 
			
		||||
 | 
			
		||||
				if(tracebackList)
 | 
			
		||||
				if (tracebackList)
 | 
			
		||||
				{
 | 
			
		||||
					PyObject* iterator = PyObject_GetIter(tracebackList); // New Reference
 | 
			
		||||
 | 
			
		||||
					PyObject* iterator = PyObject_GetIter(tracebackList);
 | 
			
		||||
					PyObject* item;
 | 
			
		||||
					while( (item = PyIter_Next(iterator)) ) // New Reference
 | 
			
		||||
					while ((item = PyIter_Next(iterator)))
 | 
			
		||||
					{
 | 
			
		||||
						Error(_log, "## %s",QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed()));
 | 
			
		||||
						Py_DECREF(item); // release "item" when done
 | 
			
		||||
						Error(_log, "## %s", QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed()));
 | 
			
		||||
						Py_DECREF(item);
 | 
			
		||||
					}
 | 
			
		||||
					Py_DECREF(iterator);  // release "iterator" when done
 | 
			
		||||
					Py_DECREF(iterator);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
				Py_XDECREF(tracebackModule);
 | 
			
		||||
				Py_XDECREF(methodName);
 | 
			
		||||
				Py_XDECREF(tracebackList);
 | 
			
		||||
 | 
			
		||||
				// Give the exception back to python and print it to stderr in case anyone else wants it.
 | 
			
		||||
				Py_XINCREF(errorType);
 | 
			
		||||
				Py_XINCREF(errorValue);
 | 
			
		||||
				Py_XINCREF(errorTraceback);
 | 
			
		||||
 | 
			
		||||
				PyErr_Restore(errorType, errorValue, errorTraceback);
 | 
			
		||||
				//PyErr_PrintEx(0); // Remove this line to switch off stderr output
 | 
			
		||||
			}
 | 
			
		||||
			Error(_log,"###### EXCEPTION END ######");
 | 
			
		||||
			Error(_log, "###### EXCEPTION END ######");
 | 
			
		||||
		}
 | 
			
		||||
		// Clear the error so it won't propagate
 | 
			
		||||
		PyErr_Clear();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Py_DECREF(result);  // release "result" when done
 | 
			
		||||
		Py_DECREF(result);  // Release result when done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Py_DECREF(main_dict);  // release "main_dict" when done
 | 
			
		||||
	Py_DECREF(main_module);
 | 
			
		||||
 | 
			
		||||
	// Restore the previous thread state
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#else
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user