ScriptableObjectAdapter.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. #include "ScriptableObjectAdapter.h"
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <ranges>
  5. #include <utility>
  6. #include "WallpaperEngine/Data/Model/DynamicValue.h"
  7. #include "WallpaperEngine/Data/Model/Object.h"
  8. #include "WallpaperEngine/Data/Utils/ScopeGuard.h"
  9. #include "WallpaperEngine/Logging/Log.h"
  10. #include "WallpaperEngine/Render/Objects/CImage.h"
  11. #include "WallpaperEngine/Scripting/ScriptEngine.h"
  12. #include "WallpaperEngine/Scripting/ScriptableObject.h"
  13. #include "WallpaperEngine/VideoPlayback/MPV/GLPlayer.h"
  14. using namespace WallpaperEngine::Data::Model;
  15. using namespace WallpaperEngine::Data::Utils;
  16. using namespace WallpaperEngine::Scripting::Adapters;
  17. #define SCRIPTABLE_OPAQUE_MAGIC 0xdeadbeef
  18. struct OpaqueScriptableObjectAdapter {
  19. unsigned int magic;
  20. ScriptableObjectAdapter& adapter;
  21. WallpaperEngine::Scripting::ScriptableObject& object;
  22. };
  23. // the object's address rides along as function data, safe because every ScriptableObject outlives the script context
  24. JSValue scriptableobject_playback_call (
  25. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  26. ) {
  27. int64_t address = 0;
  28. JS_ToInt64 (ctx, &address, func_data[0]);
  29. auto* object = reinterpret_cast<WallpaperEngine::Scripting::ScriptableObject*> (static_cast<intptr_t> (address));
  30. if (magic == 2) {
  31. return JS_NewBool (ctx, object->isPlaying ());
  32. }
  33. using Playback = WallpaperEngine::Scripting::ScriptableObject::Playback;
  34. object->setPlayback (magic == 0 ? Playback::Playing : magic == 1 ? Playback::Paused : Playback::Stopped);
  35. return JS_UNDEFINED;
  36. }
  37. // only scriptable layers can be handed to scripts, a plain group parent comes back as null
  38. JSValue scriptableobject_hierarchy_call (
  39. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  40. ) {
  41. int64_t objectAddress = 0;
  42. int64_t engineAddress = 0;
  43. JS_ToInt64 (ctx, &objectAddress, func_data[0]);
  44. JS_ToInt64 (ctx, &engineAddress, func_data[1]);
  45. auto* object
  46. = reinterpret_cast<WallpaperEngine::Scripting::ScriptableObject*> (static_cast<intptr_t> (objectAddress));
  47. auto* engine = reinterpret_cast<WallpaperEngine::Scripting::ScriptEngine*> (static_cast<intptr_t> (engineAddress));
  48. const auto& scene = engine->getScene ();
  49. if (magic == 0) {
  50. const auto& parentId = object->getObject ().parent;
  51. if (!parentId.has_value ()) {
  52. return JS_NULL;
  53. }
  54. const auto* parent = scene.getObject (*parentId);
  55. if (parent == nullptr || !parent->is<WallpaperEngine::Scripting::ScriptableObject> ()) {
  56. return JS_NULL;
  57. }
  58. return engine->getAdapters ().object->instantiate (
  59. const_cast<WallpaperEngine::Scripting::ScriptableObject&> (
  60. *parent->as<WallpaperEngine::Scripting::ScriptableObject> ()
  61. )
  62. );
  63. }
  64. JSValue children = JS_NewArray (ctx);
  65. uint32_t index = 0;
  66. for (const auto* candidate : scene.getObjectsByRenderOrder ()) {
  67. const auto& candidateParent = candidate->getObject ().parent;
  68. if (!candidateParent.has_value () || *candidateParent != object->getObject ().id
  69. || !candidate->is<WallpaperEngine::Scripting::ScriptableObject> ()) {
  70. continue;
  71. }
  72. JS_SetPropertyUint32 (
  73. ctx, children, index++,
  74. engine->getAdapters ().object->instantiate (
  75. const_cast<WallpaperEngine::Scripting::ScriptableObject&> (
  76. *candidate->as<WallpaperEngine::Scripting::ScriptableObject> ()
  77. )
  78. )
  79. );
  80. }
  81. return children;
  82. }
  83. // effect handles keep the effect's address, safe for the same reason as the object address above
  84. JSValue scriptableeffect_visible_call (
  85. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  86. ) {
  87. int64_t effectAddress = 0;
  88. int64_t engineAddress = 0;
  89. JS_ToInt64 (ctx, &effectAddress, func_data[0]);
  90. JS_ToInt64 (ctx, &engineAddress, func_data[1]);
  91. auto* effect = reinterpret_cast<ImageEffect*> (static_cast<intptr_t> (effectAddress));
  92. auto* engine = reinterpret_cast<WallpaperEngine::Scripting::ScriptEngine*> (static_cast<intptr_t> (engineAddress));
  93. if (magic == 0) {
  94. return JS_NewBool (ctx, effect->visible->value->getBool ());
  95. }
  96. if (argc > 0) {
  97. engine->assignJsValue (argv[0], *effect->visible->value);
  98. }
  99. return JS_UNDEFINED;
  100. }
  101. const std::vector<ImageEffectUniquePtr>* effectsOf (const Object& object) {
  102. if (object.is<Image> ()) {
  103. return &object.as<Image> ()->effects;
  104. }
  105. if (object.is<Text> ()) {
  106. return &object.as<Text> ()->effects;
  107. }
  108. return nullptr;
  109. }
  110. JSValue
  111. scriptableeffect_instantiate (JSContext* ctx, ImageEffect& effect, WallpaperEngine::Scripting::ScriptEngine& engine) {
  112. JSValue handle = JS_NewObject (ctx);
  113. JSValue data[] = {
  114. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&effect))),
  115. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&engine))),
  116. };
  117. const JSAtom visible = JS_NewAtom (ctx, "visible");
  118. JS_DefinePropertyGetSet (
  119. ctx, handle, visible, JS_NewCFunctionData (ctx, scriptableeffect_visible_call, 0, 0, 2, data),
  120. JS_NewCFunctionData (ctx, scriptableeffect_visible_call, 1, 1, 2, data), JS_PROP_ENUMERABLE
  121. );
  122. JS_FreeAtom (ctx, visible);
  123. JS_SetPropertyStr (ctx, handle, "name", JS_NewString (ctx, effect.name.c_str ()));
  124. return handle;
  125. }
  126. JSValue scriptableobject_effect_call (
  127. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  128. ) {
  129. int64_t objectAddress = 0;
  130. int64_t engineAddress = 0;
  131. JS_ToInt64 (ctx, &objectAddress, func_data[0]);
  132. JS_ToInt64 (ctx, &engineAddress, func_data[1]);
  133. auto* object
  134. = reinterpret_cast<WallpaperEngine::Scripting::ScriptableObject*> (static_cast<intptr_t> (objectAddress));
  135. auto* engine = reinterpret_cast<WallpaperEngine::Scripting::ScriptEngine*> (static_cast<intptr_t> (engineAddress));
  136. const auto* effects = effectsOf (object->getObject ());
  137. if (magic == 0) {
  138. return JS_NewInt32 (ctx, effects == nullptr ? 0 : static_cast<int> (effects->size ()));
  139. }
  140. if (effects == nullptr || argc < 1) {
  141. return JS_UNDEFINED;
  142. }
  143. if (JS_IsNumber (argv[0])) {
  144. int index = 0;
  145. JS_ToInt32 (ctx, &index, argv[0]);
  146. if (index < 0 || static_cast<size_t> (index) >= effects->size ()) {
  147. return JS_UNDEFINED;
  148. }
  149. return scriptableeffect_instantiate (ctx, *(*effects)[index], *engine);
  150. }
  151. const char* name = JS_ToCString (ctx, argv[0]);
  152. if (name == nullptr) {
  153. return JS_UNDEFINED;
  154. }
  155. ScopeGuard guard ([=] { JS_FreeCString (ctx, name); });
  156. for (const auto& effect : *effects) {
  157. if (effect->name == name) {
  158. return scriptableeffect_instantiate (ctx, *effect, *engine);
  159. }
  160. }
  161. return JS_UNDEFINED;
  162. }
  163. // video textures are only ever reached through the player's address, kept alive by the layer's texture for as long as
  164. // the scene exists
  165. JSValue videotexture_call (
  166. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  167. ) {
  168. int64_t playerAddress = 0;
  169. int64_t engineAddress = 0;
  170. JS_ToInt64 (ctx, &playerAddress, func_data[0]);
  171. JS_ToInt64 (ctx, &engineAddress, func_data[1]);
  172. auto* player
  173. = reinterpret_cast<WallpaperEngine::VideoPlayback::MPV::GLPlayer*> (static_cast<intptr_t> (playerAddress));
  174. auto* engine = reinterpret_cast<WallpaperEngine::Scripting::ScriptEngine*> (static_cast<intptr_t> (engineAddress));
  175. switch (magic) {
  176. case 0:
  177. if (player->hasEnded ()) {
  178. player->seek (0.0);
  179. }
  180. player->clearPaused ();
  181. return JS_UNDEFINED;
  182. case 1:
  183. player->setPaused ();
  184. return JS_UNDEFINED;
  185. case 2:
  186. player->setPaused ();
  187. player->seek (0.0);
  188. return JS_UNDEFINED;
  189. case 3:
  190. return JS_NewBool (ctx, !player->isPaused ());
  191. case 4:
  192. return JS_NewBool (ctx, player->isPaused ());
  193. case 5:
  194. return JS_NewBool (ctx, player->isPaused () && player->getPlaybackPosition () < 0.001);
  195. case 6:
  196. return JS_NewFloat64 (ctx, player->getPlaybackPosition ());
  197. case 7:
  198. {
  199. double seconds = 0.0;
  200. if (argc > 0 && JS_ToFloat64 (ctx, &seconds, argv[0]) == 0) {
  201. player->seek (std::max (seconds, 0.0));
  202. }
  203. return JS_UNDEFINED;
  204. }
  205. case 8:
  206. if (argc > 0) {
  207. engine->addVideoEndedCallback (player, argv[0]);
  208. }
  209. return JS_UNDEFINED;
  210. case 9:
  211. return JS_NewBool (ctx, player->isLooping ());
  212. case 10:
  213. if (argc > 0) {
  214. player->setLoop (JS_ToBool (ctx, argv[0]) > 0);
  215. }
  216. return JS_UNDEFINED;
  217. case 11:
  218. return JS_NewFloat64 (ctx, player->getDuration ());
  219. case 12:
  220. return JS_NewFloat64 (ctx, player->getSpeed ());
  221. case 13:
  222. {
  223. double rate = 0.0;
  224. if (argc > 0 && JS_ToFloat64 (ctx, &rate, argv[0]) == 0 && rate > 0.0) {
  225. player->setSpeed (rate);
  226. }
  227. return JS_UNDEFINED;
  228. }
  229. default:
  230. return JS_UNDEFINED;
  231. }
  232. }
  233. JSValue videotexture_instantiate (
  234. JSContext* ctx, WallpaperEngine::VideoPlayback::MPV::GLPlayer& player,
  235. WallpaperEngine::Scripting::ScriptEngine& engine
  236. ) {
  237. JSValue handle = JS_NewObject (ctx);
  238. JSValue data[] = {
  239. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&player))),
  240. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&engine))),
  241. };
  242. static constexpr struct {
  243. const char* name;
  244. int magic;
  245. int length;
  246. } calls[] = { { "play", 0, 0 }, { "pause", 1, 0 }, { "stop", 2, 0 },
  247. { "isPlaying", 3, 0 }, { "isPaused", 4, 0 }, { "isStopped", 5, 0 },
  248. { "getCurrentTime", 6, 0 }, { "setCurrentTime", 7, 1 }, { "addEndedCallback", 8, 1 } };
  249. for (const auto& call : calls) {
  250. JS_SetPropertyStr (
  251. ctx, handle, call.name, JS_NewCFunctionData (ctx, videotexture_call, call.length, call.magic, 2, data)
  252. );
  253. }
  254. static constexpr struct {
  255. const char* name;
  256. int getter;
  257. int setter;
  258. } properties[] = { { "loop", 9, 10 }, { "duration", 11, 14 }, { "rate", 12, 13 } };
  259. // "duration" gets an ignoring setter (14) since strict mode scripts throw on a property with none
  260. for (const auto& property : properties) {
  261. const JSAtom atom = JS_NewAtom (ctx, property.name);
  262. JS_DefinePropertyGetSet (
  263. ctx, handle, atom, JS_NewCFunctionData (ctx, videotexture_call, 0, property.getter, 2, data),
  264. JS_NewCFunctionData (ctx, videotexture_call, 1, property.setter, 2, data), JS_PROP_ENUMERABLE
  265. );
  266. JS_FreeAtom (ctx, atom);
  267. }
  268. return handle;
  269. }
  270. // scripts call this on image layers whose material is a video (mp4 texture), everything else gets null
  271. JSValue scriptableobject_video_texture_call (
  272. JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
  273. ) {
  274. int64_t objectAddress = 0;
  275. int64_t engineAddress = 0;
  276. JS_ToInt64 (ctx, &objectAddress, func_data[0]);
  277. JS_ToInt64 (ctx, &engineAddress, func_data[1]);
  278. auto* object
  279. = reinterpret_cast<WallpaperEngine::Scripting::ScriptableObject*> (static_cast<intptr_t> (objectAddress));
  280. auto* engine = reinterpret_cast<WallpaperEngine::Scripting::ScriptEngine*> (static_cast<intptr_t> (engineAddress));
  281. if (!object->is<WallpaperEngine::Render::Objects::CImage> ()) {
  282. return JS_NULL;
  283. }
  284. const auto texture = object->as<WallpaperEngine::Render::Objects::CImage> ()->getTexture ();
  285. if (texture == nullptr || texture->getPlayer () == nullptr) {
  286. return JS_NULL;
  287. }
  288. return videotexture_instantiate (ctx, *texture->getPlayer (), *engine);
  289. }
  290. JSValue scriptableobject_property_get (JSContext* ctx, JSValueConst obj_val, JSAtom atom, JSValueConst receiver) {
  291. JSClassID classId = 0;
  292. auto* container = static_cast<OpaqueScriptableObjectAdapter*> (JS_GetAnyOpaque (obj_val, &classId));
  293. if (!container || container->magic != SCRIPTABLE_OPAQUE_MAGIC) {
  294. return JS_ThrowTypeError (ctx, "scriptableobject_property_get: not a layer");
  295. }
  296. const char* name = JS_AtomToCString (ctx, atom);
  297. if (name == nullptr) {
  298. return JS_ThrowTypeError (ctx, "scriptableobject_property_get: invalid property name");
  299. }
  300. ScopeGuard guard ([=] { JS_FreeCString (ctx, name); });
  301. if (auto* property = container->object.tryGetProperty (name); property != nullptr) {
  302. return container->adapter.getEngine ().propertyToJs (*property, name);
  303. }
  304. static constexpr struct {
  305. const char* name;
  306. int magic;
  307. } playbackCalls[] = { { "play", 0 }, { "pause", 1 }, { "stop", 3 }, { "isPlaying", 2 } };
  308. for (const auto& call : playbackCalls) {
  309. if (std::strcmp (name, call.name) == 0) {
  310. JSValue address[]
  311. = { JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->object))) };
  312. return JS_NewCFunctionData (ctx, scriptableobject_playback_call, 0, call.magic, 1, address);
  313. }
  314. }
  315. static constexpr struct {
  316. const char* name;
  317. int magic;
  318. } hierarchyCalls[] = { { "getParent", 0 }, { "getChildren", 1 } };
  319. for (const auto& call : hierarchyCalls) {
  320. if (std::strcmp (name, call.name) == 0) {
  321. JSValue data[] = {
  322. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->object))),
  323. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->adapter.getEngine ()))),
  324. };
  325. return JS_NewCFunctionData (ctx, scriptableobject_hierarchy_call, 0, call.magic, 2, data);
  326. }
  327. }
  328. static constexpr struct {
  329. const char* name;
  330. int magic;
  331. } effectCalls[] = { { "getEffectCount", 0 }, { "getEffect", 1 } };
  332. for (const auto& call : effectCalls) {
  333. if (std::strcmp (name, call.name) == 0) {
  334. JSValue data[] = {
  335. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->object))),
  336. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->adapter.getEngine ()))),
  337. };
  338. return JS_NewCFunctionData (ctx, scriptableobject_effect_call, 1, call.magic, 2, data);
  339. }
  340. }
  341. if (std::strcmp (name, "getVideoTexture") == 0) {
  342. JSValue data[] = {
  343. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->object))),
  344. JS_NewInt64 (ctx, static_cast<int64_t> (reinterpret_cast<intptr_t> (&container->adapter.getEngine ()))),
  345. };
  346. return JS_NewCFunctionData (ctx, scriptableobject_video_texture_call, 0, 0, 2, data);
  347. }
  348. if (std::strcmp (name, "name") == 0) {
  349. return JS_NewString (ctx, container->object.getObject ().name.c_str ());
  350. }
  351. if (std::strcmp (name, "id") == 0) {
  352. return JS_NewInt32 (ctx, container->object.getObject ().id);
  353. }
  354. // "size" isn't a DynamicValue-backed property, but thisLayer.size is a commonly used part
  355. // of the WE scripting API, so it's special-cased here. Checked against the data model
  356. // (Image/Text) rather than the render object (CImage/CText) because scripted properties can
  357. // run their first update() from inside the base ScriptableObject constructor, before the
  358. // derived render object has finished constructing - a dynamic_cast to it at that point would
  359. // incorrectly report "not yet that type".
  360. if (std::strcmp (name, "size") == 0) {
  361. const auto& modelObject = container->object.getObject ();
  362. const glm::vec2* size = nullptr;
  363. if (modelObject.is<Image> ()) {
  364. size = &modelObject.as<Image> ()->size;
  365. } else if (modelObject.is<Text> ()) {
  366. size = &modelObject.as<Text> ()->size;
  367. }
  368. if (size != nullptr) {
  369. const DynamicValue sizeValue (*size);
  370. return container->adapter.getEngine ().getAdapters ().vec2->instantiate (
  371. const_cast<DynamicValue&> (sizeValue), true
  372. );
  373. }
  374. }
  375. return JS_UNDEFINED;
  376. }
  377. int scriptableobject_property_set (
  378. JSContext* ctx, JSValueConst obj_val, JSAtom atom, JSValueConst val, JSValueConst receiver, int flags
  379. ) {
  380. JSClassID classId = 0;
  381. auto* container = static_cast<OpaqueScriptableObjectAdapter*> (JS_GetAnyOpaque (obj_val, &classId));
  382. if (!container || container->magic != SCRIPTABLE_OPAQUE_MAGIC) {
  383. return -1;
  384. }
  385. const char* name = JS_AtomToCString (ctx, atom);
  386. if (name == nullptr) {
  387. return -1;
  388. }
  389. ScopeGuard guard ([=] { JS_FreeCString (ctx, name); });
  390. // Write through to the real property so `thisLayer.visible = ...` etc. actually takes
  391. // effect. Properties not backed by a DynamicValue (e.g. "horizontalalign", plain model
  392. // strings) fall through and return 0 rather than -1: since scripts run as strict-mode ES
  393. // modules, returning -1 here would throw and abort the whole script over an unsupported
  394. // property, so silently accepting the write is the safer default.
  395. if (auto* property = container->object.tryGetProperty (name); property != nullptr) {
  396. container->adapter.getEngine ().assignPropertyJsValue (val, *property, name);
  397. return 0;
  398. }
  399. if (std::strcmp (name, "name") == 0 || std::strcmp (name, "id") == 0 || std::strcmp (name, "size") == 0) {
  400. return 0;
  401. }
  402. // WE's layer objects are ordinary V8 objects, scripts hang their own state off them
  403. // (3378399626 keeps each widget's base origin on the layer). Own properties are found
  404. // before the exotic handlers, so later reads and writes never come back here.
  405. return JS_DefinePropertyValue (ctx, receiver, atom, JS_DupValue (ctx, val), JS_PROP_C_W_E);
  406. }
  407. ScriptableObjectAdapter::ScriptableObjectAdapter (ScriptEngine& engine, std::string name) :
  408. ObjectAdapter (engine),
  409. m_exoticMethods ({ .get_property = scriptableobject_property_get, .set_property = scriptableobject_property_set }),
  410. m_name (std::move (name)) {
  411. this->registerType (
  412. {
  413. .class_name = m_name.c_str (),
  414. .exotic = &m_exoticMethods,
  415. }
  416. );
  417. }
  418. JSValue ScriptableObjectAdapter::instantiate (ScriptableObject& object) {
  419. JSContext* ctx = this->getEngine ().getContext ();
  420. if (const auto it = this->m_instances.find (&object); it != this->m_instances.end ()) {
  421. return JS_DupValue (ctx, it->second);
  422. }
  423. JSValue result = this->ObjectAdapter::instantiate (object);
  424. JS_SetOpaque (
  425. result,
  426. new OpaqueScriptableObjectAdapter { .magic = SCRIPTABLE_OPAQUE_MAGIC, .adapter = *this, .object = object }
  427. );
  428. this->m_instances.emplace (&object, JS_DupValue (ctx, result));
  429. return result;
  430. }
  431. void ScriptableObjectAdapter::forget (const ScriptableObject& object) {
  432. if (const auto it = this->m_instances.find (&object); it != this->m_instances.end ()) {
  433. JS_FreeValue (this->getEngine ().getContext (), it->second);
  434. this->m_instances.erase (it);
  435. }
  436. }
  437. void ScriptableObjectAdapter::clear () {
  438. for (const auto& value : this->m_instances | std::views::values) {
  439. JS_FreeValue (this->getEngine ().getContext (), value);
  440. }
  441. this->m_instances.clear ();
  442. }
  443. JSValue ScriptableObjectAdapter::instantiate (DynamicValue& value) {
  444. throw std::runtime_error ("Cannot create a ScriptableObject instance from a DynamicValue");
  445. }
  446. WallpaperEngine::Scripting::ScriptableObject* ScriptableObjectAdapter::getObject (JSValueConst value) {
  447. JSClassID classId = 0;
  448. auto* container = static_cast<OpaqueScriptableObjectAdapter*> (JS_GetAnyOpaque (value, &classId));
  449. if (container == nullptr || container->magic != SCRIPTABLE_OPAQUE_MAGIC) {
  450. return nullptr;
  451. }
  452. return &container->object;
  453. }