You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
18KB

  1. #include <rack.hpp>
  2. #include <osdialog.h>
  3. #include <iostream>
  4. #include <fstream>
  5. #include <sstream>
  6. #include <thread>
  7. #include <mutex>
  8. #include "ScriptEngine.hpp"
  9. #include <efsw/efsw.h>
  10. using namespace rack;
  11. Plugin* pluginInstance;
  12. // Don't bother deleting this with a destructor.
  13. __attribute((init_priority(999)))
  14. std::map<std::string, ScriptEngineFactory*> scriptEngineFactories;
  15. ScriptEngine* createScriptEngine(std::string extension) {
  16. auto it = scriptEngineFactories.find(extension);
  17. if (it == scriptEngineFactories.end())
  18. return NULL;
  19. return it->second->createScriptEngine();
  20. }
  21. // json_t *settingsToJson() {
  22. // json_t *rootJ = json_object();
  23. // json_object_set_new(rootJ, "securityAccepted", json_boolean(securityAccepted));
  24. // return rootJ;
  25. // }
  26. // void settingsFromJson(json_t *rootJ) {
  27. // json_t *securityAcceptedJ = json_object_get(rootJ, "securityAccepted");
  28. // if (securityAcceptedJ)
  29. // securityAccepted = json_boolean_value(securityAcceptedJ);
  30. // }
  31. // void settingsLoad() {
  32. // // Load plugin settings
  33. // std::string filename = asset::user("VCV-Prototype.json");
  34. // FILE *file = fopen(filename.c_str(), "r");
  35. // if (!file) {
  36. // return;
  37. // }
  38. // DEFER({
  39. // fclose(file);
  40. // });
  41. // json_error_t error;
  42. // json_t *rootJ = json_loadf(file, 0, &error);
  43. // if (rootJ) {
  44. // settingsFromJson(rootJ);
  45. // json_decref(rootJ);
  46. // }
  47. // }
  48. // void settingsSave() {
  49. // json_t *rootJ = settingsToJson();
  50. // std::string filename = asset::user("VCV-Prototype.json");
  51. // FILE *file = fopen(filename.c_str(), "w");
  52. // if (file) {
  53. // json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  54. // fclose(file);
  55. // }
  56. // json_decref(rootJ);
  57. // }
  58. struct Prototype : Module {
  59. enum ParamIds {
  60. ENUMS(KNOB_PARAMS, NUM_ROWS),
  61. ENUMS(SWITCH_PARAMS, NUM_ROWS),
  62. NUM_PARAMS
  63. };
  64. enum InputIds {
  65. ENUMS(IN_INPUTS, NUM_ROWS),
  66. NUM_INPUTS
  67. };
  68. enum OutputIds {
  69. ENUMS(OUT_OUTPUTS, NUM_ROWS),
  70. NUM_OUTPUTS
  71. };
  72. enum LightIds {
  73. ENUMS(LIGHT_LIGHTS, NUM_ROWS * 3),
  74. ENUMS(SWITCH_LIGHTS, NUM_ROWS * 3),
  75. NUM_LIGHTS
  76. };
  77. std::string message;
  78. std::string path;
  79. std::string script;
  80. std::string engineName;
  81. std::mutex scriptMutex;
  82. ScriptEngine* scriptEngine = NULL;
  83. int frame = 0;
  84. int frameDivider;
  85. // This is dynamically allocated to have some protection against script bugs.
  86. ProcessBlock* block;
  87. int bufferIndex = 0;
  88. efsw_watcher efsw = NULL;
  89. /** Script that has not yet been approved to load */
  90. std::string securityScript;
  91. bool securityRequested = false;
  92. bool securityAccepted = false;
  93. Prototype() {
  94. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  95. for (int i = 0; i < NUM_ROWS; i++)
  96. configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1));
  97. for (int i = 0; i < NUM_ROWS; i++)
  98. configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1));
  99. block = new ProcessBlock;
  100. setPath("");
  101. }
  102. ~Prototype() {
  103. setPath("");
  104. delete block;
  105. }
  106. void onReset() override {
  107. setScript(script);
  108. }
  109. void process(const ProcessArgs& args) override {
  110. // Load security-sandboxed script if the security warning message is accepted.
  111. if (securityScript != "" && securityAccepted) {
  112. setScript(securityScript);
  113. securityScript = "";
  114. }
  115. // Frame divider for reducing sample rate
  116. if (++frame < frameDivider)
  117. return;
  118. frame = 0;
  119. // Clear outputs if no script is running
  120. if (!scriptEngine) {
  121. for (int i = 0; i < NUM_ROWS; i++)
  122. for (int c = 0; c < 3; c++)
  123. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(0.f);
  124. for (int i = 0; i < NUM_ROWS; i++)
  125. for (int c = 0; c < 3; c++)
  126. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f);
  127. for (int i = 0; i < NUM_ROWS; i++)
  128. outputs[OUT_OUTPUTS + i].setVoltage(0.f);
  129. return;
  130. }
  131. // Inputs
  132. for (int i = 0; i < NUM_ROWS; i++)
  133. block->inputs[i][bufferIndex] = inputs[IN_INPUTS + i].getVoltage();
  134. // Process block
  135. if (++bufferIndex >= block->bufferSize) {
  136. std::lock_guard<std::mutex> lock(scriptMutex);
  137. bufferIndex = 0;
  138. // Block settings
  139. block->sampleRate = args.sampleRate;
  140. block->sampleTime = args.sampleTime;
  141. // Params
  142. for (int i = 0; i < NUM_ROWS; i++)
  143. block->knobs[i] = params[KNOB_PARAMS + i].getValue();
  144. for (int i = 0; i < NUM_ROWS; i++)
  145. block->switches[i] = params[SWITCH_PARAMS + i].getValue() > 0.f;
  146. float oldKnobs[NUM_ROWS];
  147. std::memcpy(oldKnobs, block->knobs, sizeof(oldKnobs));
  148. // Run ScriptEngine's process function
  149. {
  150. // Process buffer
  151. if (scriptEngine) {
  152. if (scriptEngine->process()) {
  153. WARN("Script %s process() failed. Stopped script.", path.c_str());
  154. delete scriptEngine;
  155. scriptEngine = NULL;
  156. return;
  157. }
  158. }
  159. }
  160. // Params
  161. // Only set params if values were changed by the script. This avoids issues when the user is manipulating them from the UI thread.
  162. for (int i = 0; i < NUM_ROWS; i++) {
  163. if (block->knobs[i] != oldKnobs[i])
  164. params[KNOB_PARAMS + i].setValue(block->knobs[i]);
  165. }
  166. // Lights
  167. for (int i = 0; i < NUM_ROWS; i++)
  168. for (int c = 0; c < 3; c++)
  169. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(block->lights[i][c]);
  170. for (int i = 0; i < NUM_ROWS; i++)
  171. for (int c = 0; c < 3; c++)
  172. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(block->switchLights[i][c]);
  173. }
  174. // Outputs
  175. for (int i = 0; i < NUM_ROWS; i++)
  176. outputs[OUT_OUTPUTS + i].setVoltage(block->outputs[i][bufferIndex]);
  177. }
  178. void setPath(std::string path) {
  179. // Cleanup
  180. if (efsw) {
  181. efsw_release(efsw);
  182. efsw = NULL;
  183. }
  184. this->path = "";
  185. setScript("");
  186. if (path == "")
  187. return;
  188. this->path = path;
  189. loadPath();
  190. if (this->script == "")
  191. return;
  192. // Watch file
  193. std::string dir = string::directory(path);
  194. efsw = efsw_create(false);
  195. efsw_addwatch(efsw, dir.c_str(), watchCallback, false, this);
  196. efsw_watch(efsw);
  197. }
  198. void loadPath() {
  199. // Read file
  200. std::ifstream file;
  201. file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  202. try {
  203. file.open(path);
  204. std::stringstream buffer;
  205. buffer << file.rdbuf();
  206. std::string script = buffer.str();
  207. setScript(script);
  208. }
  209. catch (const std::runtime_error& err) {
  210. // Fail silently
  211. }
  212. }
  213. void setScript(std::string script) {
  214. std::lock_guard<std::mutex> lock(scriptMutex);
  215. // Reset script state
  216. if (scriptEngine) {
  217. delete scriptEngine;
  218. scriptEngine = NULL;
  219. }
  220. this->script = "";
  221. this->engineName = "";
  222. this->message = "";
  223. // Reset process state
  224. frameDivider = 32;
  225. frame = 0;
  226. bufferIndex = 0;
  227. // Reset block
  228. *block = ProcessBlock();
  229. if (script == "")
  230. return;
  231. this->script = script;
  232. // Create script engine from path extension
  233. std::string extension = string::filenameExtension(string::filename(path));
  234. scriptEngine = createScriptEngine(extension);
  235. if (!scriptEngine) {
  236. message = string::f("No engine for .%s extension", extension.c_str());
  237. return;
  238. }
  239. scriptEngine->module = this;
  240. // Run script
  241. if (scriptEngine->run(path, script)) {
  242. // Error message should have been set by ScriptEngine
  243. delete scriptEngine;
  244. scriptEngine = NULL;
  245. return;
  246. }
  247. this->engineName = scriptEngine->getEngineName();
  248. }
  249. static void watchCallback(efsw_watcher watcher, efsw_watchid watchid, const char* dir, const char* filename, enum efsw_action action, const char* old_filename, void* param) {
  250. Prototype* that = (Prototype*) param;
  251. if (action == EFSW_ADD || action == EFSW_DELETE || action == EFSW_MODIFIED || action == EFSW_MOVED) {
  252. // Check filename
  253. std::string pathFilename = string::filename(that->path);
  254. if (pathFilename == filename) {
  255. that->loadPath();
  256. }
  257. }
  258. }
  259. json_t* dataToJson() override {
  260. json_t* rootJ = json_object();
  261. json_object_set_new(rootJ, "path", json_string(path.c_str()));
  262. std::string script = this->script;
  263. // If we haven't accepted the security of this script, serialize the security-sandboxed script anyway.
  264. if (script == "")
  265. script = securityScript;
  266. json_object_set_new(rootJ, "script", json_stringn(script.data(), script.size()));
  267. return rootJ;
  268. }
  269. void dataFromJson(json_t* rootJ) override {
  270. json_t* pathJ = json_object_get(rootJ, "path");
  271. if (pathJ) {
  272. std::string path = json_string_value(pathJ);
  273. setPath(path);
  274. }
  275. // Only get the script string if the script file wasn't found.
  276. if (this->path != "" && this->script == "") {
  277. WARN("Script file %s not found, using script in patch", this->path.c_str());
  278. json_t* scriptJ = json_object_get(rootJ, "script");
  279. if (scriptJ) {
  280. std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ));
  281. if (script != "") {
  282. // Request security warning message
  283. securityAccepted = false;
  284. securityRequested = true;
  285. securityScript = script;
  286. }
  287. }
  288. }
  289. }
  290. void loadScriptDialog() {
  291. std::string dir = asset::plugin(pluginInstance, "examples");
  292. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL);
  293. if (!pathC) {
  294. return;
  295. }
  296. std::string path = pathC;
  297. std::free(pathC);
  298. setPath(path);
  299. }
  300. void reloadScript() {
  301. loadPath();
  302. }
  303. void saveScriptDialog() {
  304. if (script == "")
  305. return;
  306. std::string ext = string::filenameExtension(string::filename(path));
  307. std::string dir = asset::plugin(pluginInstance, "examples");
  308. std::string filename = "Untitled." + ext;
  309. char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL);
  310. if (!newPathC) {
  311. return;
  312. }
  313. std::string newPath = newPathC;
  314. std::free(newPathC);
  315. // Add extension if user didn't specify one
  316. std::string newExt = string::filenameExtension(string::filename(newPath));
  317. if (newExt == "")
  318. newPath += "." + ext;
  319. // Write and close file
  320. {
  321. std::ofstream f(newPath);
  322. f << script;
  323. }
  324. // Load path so that it reloads and is watched.
  325. setPath(newPath);
  326. }
  327. };
  328. void ScriptEngine::display(const std::string& message) {
  329. module->message = message;
  330. }
  331. void ScriptEngine::setFrameDivider(int frameDivider) {
  332. module->frameDivider = std::max(frameDivider, 1);
  333. }
  334. void ScriptEngine::setBufferSize(int bufferSize) {
  335. module->block->bufferSize = clamp(bufferSize, 1, MAX_BUFFER_SIZE);
  336. }
  337. ProcessBlock* ScriptEngine::getProcessBlock() {
  338. return module->block;
  339. }
  340. struct FileChoice : LedDisplayChoice {
  341. Prototype* module;
  342. void step() override {
  343. if (module && module->engineName != "")
  344. text = module->engineName;
  345. else
  346. text = "Script";
  347. text += ": ";
  348. if (module && module->path != "")
  349. text += string::filename(module->path);
  350. else
  351. text += "(click to load)";
  352. }
  353. void onAction(const event::Action& e) override {
  354. module->loadScriptDialog();
  355. }
  356. };
  357. struct MessageChoice : LedDisplayChoice {
  358. Prototype* module;
  359. void step() override {
  360. text = module ? module->message : "";
  361. }
  362. void draw(const DrawArgs& args) override {
  363. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  364. if (font->handle >= 0) {
  365. nvgFillColor(args.vg, color);
  366. nvgFontFaceId(args.vg, font->handle);
  367. nvgTextLetterSpacing(args.vg, 0.0);
  368. nvgTextLineHeight(args.vg, 1.08);
  369. nvgFontSize(args.vg, 12);
  370. nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x - textOffset.x, text.c_str(), NULL);
  371. }
  372. nvgResetScissor(args.vg);
  373. }
  374. };
  375. struct PrototypeDisplay : LedDisplay {
  376. PrototypeDisplay() {
  377. box.size = mm2px(Vec(69.879, 27.335));
  378. }
  379. void setModule(Prototype* module) {
  380. FileChoice* fileChoice = new FileChoice;
  381. fileChoice->box.size.x = box.size.x;
  382. fileChoice->module = module;
  383. addChild(fileChoice);
  384. LedDisplaySeparator* fileSeparator = new LedDisplaySeparator;
  385. fileSeparator->box.size.x = box.size.x;
  386. fileSeparator->box.pos = fileChoice->box.getBottomLeft();
  387. addChild(fileSeparator);
  388. MessageChoice* messageChoice = new MessageChoice;
  389. messageChoice->box.pos = fileChoice->box.getBottomLeft();
  390. messageChoice->box.size.x = box.size.x;
  391. messageChoice->box.size.y = box.size.y - messageChoice->box.pos.y;
  392. messageChoice->module = module;
  393. addChild(messageChoice);
  394. }
  395. };
  396. struct LoadScriptItem : MenuItem {
  397. Prototype* module;
  398. void onAction(const event::Action& e) override {
  399. module->loadScriptDialog();
  400. }
  401. };
  402. struct ReloadScriptItem : MenuItem {
  403. Prototype* module;
  404. void onAction(const event::Action& e) override {
  405. module->reloadScript();
  406. }
  407. };
  408. struct SaveScriptItem : MenuItem {
  409. Prototype* module;
  410. void onAction(const event::Action& e) override {
  411. module->saveScriptDialog();
  412. }
  413. };
  414. struct PrototypeWidget : ModuleWidget {
  415. PrototypeWidget(Prototype* module) {
  416. setModule(module);
  417. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Prototype.svg")));
  418. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  419. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  420. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  421. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  422. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(8.099, 64.401)), module, Prototype::KNOB_PARAMS + 0));
  423. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(20.099, 64.401)), module, Prototype::KNOB_PARAMS + 1));
  424. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(32.099, 64.401)), module, Prototype::KNOB_PARAMS + 2));
  425. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(44.099, 64.401)), module, Prototype::KNOB_PARAMS + 3));
  426. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(56.099, 64.401)), module, Prototype::KNOB_PARAMS + 4));
  427. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(68.099, 64.401)), module, Prototype::KNOB_PARAMS + 5));
  428. addParam(createParamCentered<PB61303>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_PARAMS + 0));
  429. addParam(createParamCentered<PB61303>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_PARAMS + 1));
  430. addParam(createParamCentered<PB61303>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_PARAMS + 2));
  431. addParam(createParamCentered<PB61303>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_PARAMS + 3));
  432. addParam(createParamCentered<PB61303>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_PARAMS + 4));
  433. addParam(createParamCentered<PB61303>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_PARAMS + 5));
  434. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.099, 96.025)), module, Prototype::IN_INPUTS + 0));
  435. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.099, 96.025)), module, Prototype::IN_INPUTS + 1));
  436. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(32.099, 96.025)), module, Prototype::IN_INPUTS + 2));
  437. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(44.099, 96.025)), module, Prototype::IN_INPUTS + 3));
  438. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(56.099, 96.025)), module, Prototype::IN_INPUTS + 4));
  439. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(68.099, 96.025)), module, Prototype::IN_INPUTS + 5));
  440. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(8.099, 112.25)), module, Prototype::OUT_OUTPUTS + 0));
  441. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(20.099, 112.25)), module, Prototype::OUT_OUTPUTS + 1));
  442. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(32.099, 112.25)), module, Prototype::OUT_OUTPUTS + 2));
  443. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(44.099, 112.25)), module, Prototype::OUT_OUTPUTS + 3));
  444. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(56.099, 112.25)), module, Prototype::OUT_OUTPUTS + 4));
  445. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(68.099, 112.25)), module, Prototype::OUT_OUTPUTS + 5));
  446. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(8.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 0));
  447. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(20.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 1));
  448. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(32.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 2));
  449. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(44.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 3));
  450. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(56.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 4));
  451. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(68.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 5));
  452. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 0));
  453. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 1));
  454. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 2));
  455. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 3));
  456. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 4));
  457. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 5));
  458. PrototypeDisplay* display = createWidget<PrototypeDisplay>(mm2px(Vec(3.16, 14.837)));
  459. display->setModule(module);
  460. addChild(display);
  461. }
  462. void appendContextMenu(Menu* menu) override {
  463. Prototype* module = dynamic_cast<Prototype*>(this->module);
  464. menu->addChild(new MenuEntry);
  465. LoadScriptItem* loadScriptItem = createMenuItem<LoadScriptItem>("Load script");
  466. loadScriptItem->module = module;
  467. menu->addChild(loadScriptItem);
  468. ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload script");
  469. reloadScriptItem->module = module;
  470. menu->addChild(reloadScriptItem);
  471. SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("Save script as");
  472. saveScriptItem->module = module;
  473. menu->addChild(saveScriptItem);
  474. }
  475. void onPathDrop(const event::PathDrop& e) override {
  476. Prototype* module = dynamic_cast<Prototype*>(this->module);
  477. if (module && e.paths.size() >= 1) {
  478. module->setPath(e.paths[0]);
  479. }
  480. }
  481. void step() override {
  482. Prototype* module = dynamic_cast<Prototype*>(this->module);
  483. if (module && module->securityRequested) {
  484. if (osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "VCV Prototype is requesting to run a script from a patch or module preset. Running Prototype scripts from untrusted sources may compromise your computer and personal information. Proceed and run script?")) {
  485. module->securityAccepted = true;
  486. }
  487. module->securityRequested = false;
  488. }
  489. ModuleWidget::step();
  490. }
  491. };
  492. void init(Plugin* p) {
  493. pluginInstance = p;
  494. p->addModel(createModel<Prototype, PrototypeWidget>("Prototype"));
  495. }