Browse Source

Fix vector crash and improve user setting parsing (#545) (#555)

* Fix vector crash and improve user setting parsing (#545)

* Unify scalar parsing logic for non-vector user settings

* Set RPATH to $ORIGIN and migrate animation layers to UserSetting

Convert ImageAnimationLayer fields `rate`, `blend`, and `animation` from
raw
types to UserSettingUniquePtr, parsed with `it.user()` instead of
`it.require()`, so they respect per-project user settings.

Add try-catch guards around JSON parsing in `ShaderUnit` combo and
parameter
configuration to prevent crashes from malformed shader metadata.

Replace the manual `install(DIRECTORY)` and `install(FILES)` with
`install(TARGETS)` so CMake automatically applies the new `$ORIGIN`
RPATH to
the installed binary and library, while preserving CEF resource
installation
via `install(DIRECTORY)` with appropriate exclusions.

---------

Co-authored-by: Alexis Maiquez <wpengine@alma.mu>
jjsy212 4 months ago
parent
commit
be6454d659

+ 3 - 1
.gitignore

@@ -3,9 +3,11 @@ cmake-build-*
 .cache/
 .cache/
 build/
 build/
 .vscode/
 .vscode/
+.zed/
 third_party/
 third_party/
 packaging/archlinux/*
 packaging/archlinux/*
 !packaging/archlinux/PKGBUILD
 !packaging/archlinux/PKGBUILD
 *-protocol.o
 *-protocol.o
 *-protocol.c
 *-protocol.c
-*-protocol.h
+*-protocol.h
+AGENTS.md

+ 22 - 3
CMakeLists.txt

@@ -132,6 +132,13 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output)
 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
 set(TARGET_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output)
 set(TARGET_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output)
 
 
+# RPATH configuration: use build directories during development, but $ORIGIN after installation
+# NOTE: \$ORIGIN must be escaped because CMake treats $VAR as variable expansion.
+# IMPORTANT: This must be set BEFORE any add_executable()/add_library() calls in this directory.
+set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+set(CMAKE_INSTALL_RPATH "\$ORIGIN;\$ORIGIN/lib")
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
+
 add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper)
 add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper)
 add_subdirectory(src/External/glslang-WallpaperEngine glslang)
 add_subdirectory(src/External/glslang-WallpaperEngine glslang)
 add_subdirectory(src/External/SPIRV-Cross-WallpaperEngine spirv-cross)
 add_subdirectory(src/External/SPIRV-Cross-WallpaperEngine spirv-cross)
@@ -615,6 +622,18 @@ add_executable(linux-wallpaperengine
 
 
 target_link_libraries (linux-wallpaperengine PUBLIC linux-wallpaperengine-lib)
 target_link_libraries (linux-wallpaperengine PUBLIC linux-wallpaperengine-lib)
 
 
-# set some install parameters if not in debug mode
-install(DIRECTORY ${TARGET_OUTPUT_DIRECTORY}/ DESTINATION .)
-install(FILES ${TARGET_OUTPUT_DIRECTORY}/${PROJECT_NAME} PERMISSIONS OWNER_READ OWNER_WRITE WORLD_EXECUTE WORLD_READ GROUP_READ DESTINATION .)
+# Install the main executable and shared library via install(TARGETS)
+# so that CMake automatically strips/replaces the build-time RPATH.
+install(TARGETS linux-wallpaperengine
+    RUNTIME DESTINATION .
+)
+install(TARGETS linux-wallpaperengine-lib
+    LIBRARY DESTINATION .
+)
+
+# Install everything else from the output directory (CEF resources, helper binaries, etc.)
+# but exclude the files already handled by install(TARGETS) above.
+install(DIRECTORY ${TARGET_OUTPUT_DIRECTORY}/ DESTINATION .
+    PATTERN "linux-wallpaperengine" EXCLUDE
+    PATTERN "liblinux-wallpaperengine-lib.so" EXCLUDE
+)

+ 1 - 1
src/WallpaperEngine/Data/Builders/VectorBuilder.h

@@ -38,7 +38,7 @@ public:
 	const char* third = second ? strchr (second + 1, ' ') : nullptr;
 	const char* third = second ? strchr (second + 1, ' ') : nullptr;
 
 
 	if (first == nullptr) {
 	if (first == nullptr) {
-	    return 1;
+	    return 0;
 	}
 	}
 
 
 	if (second == nullptr) {
 	if (second == nullptr) {

+ 6 - 6
src/WallpaperEngine/Data/Model/Object.h

@@ -83,10 +83,10 @@ struct ImageEffect {
  */
  */
 struct ImageAnimationLayer {
 struct ImageAnimationLayer {
     int id;
     int id;
-    float rate;
+    UserSettingUniquePtr rate;
     UserSettingUniquePtr visible;
     UserSettingUniquePtr visible;
-    float blend;
-    int animation;
+    UserSettingUniquePtr blend;
+    UserSettingUniquePtr animation;
 };
 };
 
 
 struct ImageData {
 struct ImageData {
@@ -108,9 +108,9 @@ struct ImageData {
     /** Parallax depth used for parallax scrolling */
     /** Parallax depth used for parallax scrolling */
     UserSettingUniquePtr parallaxDepth;
     UserSettingUniquePtr parallaxDepth;
     /** The color blending mode for this image */
     /** The color blending mode for this image */
-    int colorBlendMode;
+    UserSettingUniquePtr colorBlendMode;
     /** The brightness of the image */
     /** The brightness of the image */
-    float brightness;
+    UserSettingUniquePtr brightness;
     /** The material in use for this image */
     /** The material in use for this image */
     ModelUniquePtr model;
     ModelUniquePtr model;
     /** The effects applied to this image after the material is rendered */
     /** The effects applied to this image after the material is rendered */
@@ -618,4 +618,4 @@ public:
 	Object (std::move (data)), TextData (std::move (textData)) { };
 	Object (std::move (data)), TextData (std::move (textData)) { };
     ~Text () override = default;
     ~Text () override = default;
 };
 };
-} // namespace WallpaperEngine::Data::Model
+} // namespace WallpaperEngine::Data::Model

+ 14 - 8
src/WallpaperEngine/Data/Parsers/ObjectParser.cpp

@@ -93,6 +93,8 @@ ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
     const auto particleIt = it.find ("particle");
     const auto particleIt = it.find ("particle");
     const auto textIt = it.find ("text");
     const auto textIt = it.find ("text");
     const auto lightIt = it.find ("light");
     const auto lightIt = it.find ("light");
+    // use shape to refer to VolumeLight
+    const auto shapeIt = it.find ("shape");
 
 
     // Parse base object data
     // Parse base object data
     // Some particle objects have numeric 'name' fields, so handle type mismatches gracefully
     // Some particle objects have numeric 'name' fields, so handle type mismatches gracefully
@@ -131,6 +133,8 @@ ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
 	return parseText (it, project, std::move (basedata));
 	return parseText (it, project, std::move (basedata));
     } else if (lightIt != it.end ()) {
     } else if (lightIt != it.end ()) {
 	sLog.error ("Light objects are not supported yet");
 	sLog.error ("Light objects are not supported yet");
+    } else if (shapeIt != it.end ()) {
+	sLog.error ("VolumeLight objects are not supported yet");
     } else {
     } else {
 	// dump the object for now, might want to change later
 	// dump the object for now, might want to change later
 	// TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
 	// TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
@@ -238,8 +242,8 @@ ObjectParser::parseImage (const JSON& it, const Project& project, ObjectData bas
 	    .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
 	    .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
 	    .size = it.optional ("size", glm::vec2 (0.0f)),
 	    .size = it.optional ("size", glm::vec2 (0.0f)),
 	    .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
 	    .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
-	    .colorBlendMode = it.optional ("colorBlendMode", 0),
-	    .brightness = it.optional ("brightness", 1.0f),
+	    .colorBlendMode = it.user ("colorBlendMode", properties, 0),
+	    .brightness = it.user ("brightness", properties, 1.0f),
 	    .model = ModelParser::load (project, image),
 	    .model = ModelParser::load (project, image),
 	    .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
 	    .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
 	    .animationLayers = animationLayers.has_value () ? parseAnimationLayers (*animationLayers, project)
 	    .animationLayers = animationLayers.has_value () ? parseAnimationLayers (*animationLayers, project)
@@ -365,12 +369,14 @@ std::vector<ImageAnimationLayerUniquePtr> ObjectParser::parseAnimationLayers (co
 }
 }
 
 
 ImageAnimationLayerUniquePtr ObjectParser::parseAnimationLayer (const JSON& it, const Project& project) {
 ImageAnimationLayerUniquePtr ObjectParser::parseAnimationLayer (const JSON& it, const Project& project) {
+    const auto& properties = project.properties;
+
     return std::make_unique<ImageAnimationLayer> (ImageAnimationLayer {
     return std::make_unique<ImageAnimationLayer> (ImageAnimationLayer {
-	.id = it.require ("id", "Animation layer must have an id"),
-	.rate = it.require ("rate", "Animation layer must have a rate"),
-	.visible = it.user ("visible", project.properties, false),
-	.blend = it.require ("blend", "Animation layer must include blend"),
-	.animation = it.require ("animation", "Animation layer must include an animation"),
+	.id = it.require<int> ("id", "Animation layer must have an id"),
+	.rate = it.user ("rate", properties, 1.0f),
+	.visible = it.user ("visible", properties, false),
+	.blend = it.user ("blend", properties, 1.0f),
+	.animation = it.user ("animation", properties, 0),
     });
     });
 }
 }
 
 
@@ -1013,4 +1019,4 @@ ParticleInstanceOverride ObjectParser::parseParticleInstanceOverride (const JSON
 	.color = it.user ("color", properties, glm::vec3 (1.0f)),
 	.color = it.user ("color", properties, glm::vec3 (1.0f)),
 	.colorn = it.user ("colorn", properties, glm::vec3 (1.0f)),
 	.colorn = it.user ("colorn", properties, glm::vec3 (1.0f)),
     };
     };
-}
+}

+ 15 - 1
src/WallpaperEngine/Data/Parsers/UserSettingParser.cpp

@@ -65,8 +65,22 @@ UserSettingUniquePtr UserSettingParser::parse (const json& data, const Propertie
 	    value->update (static_cast<glm::vec2> (valueIt));
 	    value->update (static_cast<glm::vec2> (valueIt));
 	} else if (size == 3) {
 	} else if (size == 3) {
 	    value->update (static_cast<glm::vec3> (valueIt));
 	    value->update (static_cast<glm::vec3> (valueIt));
-	} else {
+	} else if (size == 4) {
 	    value->update (static_cast<glm::vec4> (valueIt));
 	    value->update (static_cast<glm::vec4> (valueIt));
+	} else {
+	    // preparseSize returned 0: no spaces found — try parsing as a scalar float,
+	    // fall back to a plain string for non-numeric values (e.g. "bottom", "center").
+	    std::size_t parsed = 0;
+	    try {
+		float f = std::stof (str, &parsed);
+		if (parsed == str.size ()) {
+		    value->update (f);
+		} else {
+		    value->update (str);
+		}
+	    } catch (const std::exception&) {
+		value->update (str);
+	    }
 	}
 	}
     } else if (valueIt.is_number_integer ()) {
     } else if (valueIt.is_number_integer ()) {
 	value->update (valueIt.get<int> ());
 	value->update (valueIt.get<int> ());

+ 3 - 3
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -365,13 +365,13 @@ void CImage::setup () {
     }
     }
 
 
     // extra render pass if there's any blending to be done
     // extra render pass if there's any blending to be done
-    if (this->m_image.colorBlendMode > 0) {
+    if (this->m_image.colorBlendMode->value->getInt() > 0) {
 	this->m_materials.colorBlending.material
 	this->m_materials.colorBlending.material
 	    = MaterialParser::load (this->getScene ().getScene ().project, "materials/util/effectpassthrough.json");
 	    = MaterialParser::load (this->getScene ().getScene ().project, "materials/util/effectpassthrough.json");
 	this->m_materials.colorBlending.override = std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
 	this->m_materials.colorBlending.override = std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
             .id = -1,
             .id = -1,
             .combos = {
             .combos = {
-                {"BLENDMODE", this->m_image.colorBlendMode},
+                {"BLENDMODE", this->m_image.colorBlendMode->value->getInt()},
             },
             },
             .constants = {},
             .constants = {},
             .textures = {},
             .textures = {},
@@ -521,7 +521,7 @@ void CImage::render () {
 #endif /* DEBUG */
 #endif /* DEBUG */
 }
 }
 
 
-const float& CImage::getBrightness () const { return this->m_image.brightness; }
+const float& CImage::getBrightness () const { return this->m_image.brightness->value->getFloat(); }
 
 
 const float& CImage::getUserAlpha () const { return this->m_image.alpha->value->getFloat (); }
 const float& CImage::getUserAlpha () const { return this->m_image.alpha->value->getFloat (); }
 
 

+ 16 - 2
src/WallpaperEngine/Render/Shaders/ShaderUnit.cpp

@@ -373,7 +373,14 @@ std::string ShaderUnit::generateLightingV1 () const {
 
 
 void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
 void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
     // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
     // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
-    const auto data = JSON::parse (content);
+    JSON data;
+    try {
+	data = JSON::parse (content);
+    } catch (const std::exception& e) {
+	sLog.error ("Failed to parse combo configuration in shader ", this->m_file, ": ", e.what ());
+	sLog.error ("Content: ", content);
+	return;
+    }
     const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
     const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
     // ignore type as it seems to be used only on the editor
     // ignore type as it seems to be used only on the editor
     // const auto type = data.find ("type");
     // const auto type = data.find ("type");
@@ -408,7 +415,14 @@ void ShaderUnit::parseComboConfiguration (const std::string& content, const int
 void ShaderUnit::parseParameterConfiguration (
 void ShaderUnit::parseParameterConfiguration (
     const std::string& type, const std::string& name, const std::string& content
     const std::string& type, const std::string& name, const std::string& content
 ) {
 ) {
-    const auto data = JSON::parse (content);
+    JSON data;
+    try {
+	data = JSON::parse (content);
+    } catch (const std::exception& e) {
+	sLog.error ("Failed to parse parameter configuration in shader ", this->m_file, ": ", e.what ());
+	sLog.error ("Parameter: ", type, " ", name, ", content: ", content);
+	return;
+    }
     const auto material = data.optional ("material");
     const auto material = data.optional ("material");
     const auto defvalue = data.optional ("default");
     const auto defvalue = data.optional ("default");
     // auto range = data.find ("range");
     // auto range = data.find ("range");