ScriptableObjectAdapter.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #include "ScriptableObjectAdapter.h"
  2. #include <cstring>
  3. #include <utility>
  4. #include "WallpaperEngine/Data/Model/DynamicValue.h"
  5. #include "WallpaperEngine/Data/Model/Object.h"
  6. #include "WallpaperEngine/Data/Utils/ScopeGuard.h"
  7. #include "WallpaperEngine/Logging/Log.h"
  8. #include "WallpaperEngine/Scripting/ScriptEngine.h"
  9. #include "WallpaperEngine/Scripting/ScriptableObject.h"
  10. using namespace WallpaperEngine::Data::Model;
  11. using namespace WallpaperEngine::Data::Utils;
  12. using namespace WallpaperEngine::Scripting::Adapters;
  13. #define SCRIPTABLE_OPAQUE_MAGIC 0xdeadbeef
  14. struct OpaqueScriptableObjectAdapter {
  15. unsigned int magic;
  16. ScriptableObjectAdapter& adapter;
  17. WallpaperEngine::Scripting::ScriptableObject& object;
  18. };
  19. JSValue scriptableobject_property_get (JSContext* ctx, JSValueConst obj_val, JSAtom atom, JSValueConst receiver) {
  20. JSClassID classId = 0;
  21. auto* container = static_cast<OpaqueScriptableObjectAdapter*> (JS_GetAnyOpaque (obj_val, &classId));
  22. if (!container || container->magic != SCRIPTABLE_OPAQUE_MAGIC) {
  23. return JS_EXCEPTION;
  24. }
  25. const char* name = JS_AtomToCString (ctx, atom);
  26. if (name == nullptr) {
  27. return JS_EXCEPTION;
  28. }
  29. ScopeGuard guard ([=] { JS_FreeCString (ctx, name); });
  30. if (auto* property = container->object.tryGetProperty (name); property != nullptr) {
  31. return container->adapter.getEngine ().dynamicToJs (*property);
  32. }
  33. // "size" isn't a DynamicValue-backed property, but thisLayer.size is a commonly used part
  34. // of the WE scripting API, so it's special-cased here. Checked against the data model
  35. // (Image/Text) rather than the render object (CImage/CText) because scripted properties can
  36. // run their first update() from inside the base ScriptableObject constructor, before the
  37. // derived render object has finished constructing - a dynamic_cast to it at that point would
  38. // incorrectly report "not yet that type".
  39. if (std::strcmp (name, "size") == 0) {
  40. const auto& modelObject = container->object.getObject ();
  41. const glm::vec2* size = nullptr;
  42. if (modelObject.is<Image> ()) {
  43. size = &modelObject.as<Image> ()->size;
  44. } else if (modelObject.is<Text> ()) {
  45. size = &modelObject.as<Text> ()->size;
  46. }
  47. if (size != nullptr) {
  48. const DynamicValue sizeValue (*size);
  49. return container->adapter.getEngine ().getAdapters ().vec2->instantiate (
  50. const_cast<DynamicValue&> (sizeValue), true
  51. );
  52. }
  53. }
  54. return JS_UNDEFINED;
  55. }
  56. int scriptableobject_property_set (
  57. JSContext* ctx, JSValueConst obj_val, JSAtom atom, JSValueConst val, JSValueConst receiver, int flags
  58. ) {
  59. JSClassID classId = 0;
  60. auto* container = static_cast<OpaqueScriptableObjectAdapter*> (JS_GetAnyOpaque (obj_val, &classId));
  61. if (!container || container->magic != SCRIPTABLE_OPAQUE_MAGIC) {
  62. return -1;
  63. }
  64. const char* name = JS_AtomToCString (ctx, atom);
  65. if (name == nullptr) {
  66. return -1;
  67. }
  68. ScopeGuard guard ([=] { JS_FreeCString (ctx, name); });
  69. // Write through to the real property so `thisLayer.visible = ...` etc. actually takes
  70. // effect. Properties not backed by a DynamicValue (e.g. "horizontalalign", plain model
  71. // strings) fall through and return 0 rather than -1: since scripts run as strict-mode ES
  72. // modules, returning -1 here would throw and abort the whole script over an unsupported
  73. // property, so silently accepting the write is the safer default.
  74. if (auto* property = container->object.tryGetProperty (name); property != nullptr) {
  75. container->adapter.getEngine ().assignJsValue (val, *property);
  76. }
  77. return 0;
  78. }
  79. ScriptableObjectAdapter::ScriptableObjectAdapter (ScriptEngine& engine, std::string name) :
  80. ObjectAdapter (engine),
  81. m_exoticMethods ({ .get_property = scriptableobject_property_get, .set_property = scriptableobject_property_set }),
  82. m_name (std::move (name)) {
  83. this->registerType (
  84. {
  85. .class_name = m_name.c_str (),
  86. .exotic = &m_exoticMethods,
  87. }
  88. );
  89. }
  90. JSValue ScriptableObjectAdapter::instantiate (ScriptableObject& object) {
  91. JSValue result = this->ObjectAdapter::instantiate (object);
  92. JS_SetOpaque (
  93. result,
  94. new OpaqueScriptableObjectAdapter { .magic = SCRIPTABLE_OPAQUE_MAGIC, .adapter = *this, .object = object }
  95. );
  96. return result;
  97. }
  98. JSValue ScriptableObjectAdapter::instantiate (DynamicValue& value) {
  99. throw std::runtime_error ("Cannot create a ScriptableObject instance from a DynamicValue");
  100. }