diff --git a/src/app/script/engine.cpp b/src/app/script/engine.cpp index 08717f818..bc88f63b8 100644 --- a/src/app/script/engine.cpp +++ b/src/app/script/engine.cpp @@ -38,6 +38,7 @@ #include "ui/manager.h" #include "ui/message_loop.h" #include "ui/mouse_button.h" +#include "ui/system.h" #include #include @@ -259,7 +260,13 @@ RunScriptTask::RunScriptTask(lua_State* L, int nelems, const std::string& descri RunScriptTask* task = (RunScriptTask*)lua_topointer(L, -1); lua_pop(L, 2); if (task->wantsToStop()) { - lua_pushliteral(L, "Script stopped"); + luaL_where(L, 0); + const char* where = lua_tostring(L, -1); + luaL_traceback(L, L, "Script stopped", 0); + const char* traceback = lua_tostring(L, -1); + std::string msg(fmt::format("{}{}", where, traceback)); + lua_pop(L, 2); + lua_pushstring(L, msg.c_str()); lua_error(L); } } @@ -739,8 +746,10 @@ void Engine::callInTask(lua_State* parentL, int nargs, const std::string& descri executeTask(parentL, nargs + 1, description, [this, nargs](lua_State* L) { try { if (lua_pcall(L, nargs, 0, 0)) { - if (const char* s = lua_tostring(L, -1)) - consolePrint(s); + if (const char* s = lua_tostring(L, -1)) { + std::string error = std::string(s); + ui::execute_from_ui_thread([this, error]() { consolePrint(error.c_str()); }); + } } } catch (const std::exception& ex) { @@ -764,12 +773,14 @@ void Engine::executeTask(lua_State* parentL, std::lock_guard lock(m_mutex); m_tasks.push_back(std::move(task)); } + TaskStart(taskPtr); taskPtr->execute(m_threadPool); } void Engine::onTaskDone(const RunScriptTask* task) { std::lock_guard lock(m_mutex); + TaskDone(task); for (auto it = m_tasks.begin(); it != m_tasks.end(); ++it) { if ((*it).get() == task) { m_tasks.erase(it); @@ -810,8 +821,10 @@ bool Engine::evalUserFileInTask(const std::string& filename, const Params& param if (luaL_loadbuffer(L, code.c_str(), code.size(), atFilename.c_str()) || lua_pcall(L, 0, 1, 0)) { const char* s = lua_tostring(L, -1); - if (s) - onConsoleError(s); + if (s) { + std::string error = std::string(s); + ui::execute_from_ui_thread([this, error]() { onConsoleError(error.c_str()); }); + } ok = false; returnCode = -1; } @@ -873,10 +886,14 @@ void Engine::stopDebugger() lua_sethook(L, nullptr, 0, 0); } -void Engine::stopScript() +void Engine::stopTask(const RunScriptTask* task) { - lua_pushliteral(L, "Script stopped"); - lua_error(L); + std::lock_guard lock(m_mutex); + for (const auto& t : m_tasks) { + if (t.get() == task) { + t->stop(); + } + } } void Engine::onConsoleError(const char* text) diff --git a/src/app/script/engine.h b/src/app/script/engine.h index 60d9199b8..c928bb7cb 100644 --- a/src/app/script/engine.h +++ b/src/app/script/engine.h @@ -100,6 +100,8 @@ public: int ref() const { return m_LRef; } const std::string& description() const { return m_description; } + bool isRunning() const { return m_task.running(); } + bool isEnqueued() const { return m_task.enqueued(); } private: // Lua's thread state. @@ -136,7 +138,8 @@ public: std::vector tasks() const { std::lock_guard lock(m_mutex); - std::vector tasks(m_tasks.size()); + std::vector tasks; + tasks.reserve(m_tasks.size()); for (const auto& task : m_tasks) tasks.push_back(task.get()); return tasks; @@ -153,8 +156,11 @@ public: void startDebugger(DebuggerDelegate* debuggerDelegate); void stopDebugger(); - // Stops the currently running lua chunk - void stopScript(); + // Stops the specified task + void stopTask(const RunScriptTask* task); + + obs::signal TaskStart; + obs::signal TaskDone; private: void onConsoleError(const char* text); diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp index 5c52392c9..86157eb32 100644 --- a/src/app/ui/status_bar.cpp +++ b/src/app/ui/status_bar.cpp @@ -22,6 +22,7 @@ #include "app/modules/gui.h" #include "app/modules/palettes.h" #include "app/pref/preferences.h" +#include "app/script/engine.h" #include "app/tools/active_tool.h" #include "app/tools/tool.h" #include "app/ui/button_set.h" @@ -44,8 +45,16 @@ #include "fmt/format.h" #include "gfx/size.h" #include "os/surface.h" +#include "os/system.h" #include "text/font.h" +#include "ui/box.h" +#include "ui/button.h" +#include "ui/label.h" +#include "ui/listbox.h" +#include "ui/listitem.h" +#include "ui/popup_window.h" #include "ui/ui.h" +#include "ui/view.h" #include "ver/info.h" #include @@ -607,6 +616,70 @@ private: ui::Button m_button; }; +class StatusBar::RunningScriptsWindow : public ui::Window { +public: + // TODO: Replace the title by a string + RunningScriptsWindow() : ui::Window(Type::WithTitleBar, "Running scripts") + { + setMinSize({ 500, 200 }); + m_runningScripts.setExpansive(true); + m_view.setExpansive(true); + m_view.attachToView(&m_runningScripts); + addChild(&m_view); + + initTheme(); + + refreshList(); + + App::instance()->scriptEngine()->TaskStart.connect([this](const script::RunScriptTask*) { + ui::execute_from_ui_thread([this] { refreshList(); }); + }); + App::instance()->scriptEngine()->TaskDone.connect([this](const script::RunScriptTask*) { + ui::execute_from_ui_thread([this] { refreshList(); }); + }); + } + + void refreshList() + { + m_runningScripts.removeAllChildren(); + for (const auto* task : App::instance()->scriptEngine()->tasks()) { + auto* item = new TaskItem(task); + m_runningScripts.addChild(item); + } + m_runningScripts.layout(); + flushRedraw(); + } + +private: + class TaskItem : public ListItem { + public: + TaskItem(const script::RunScriptTask* task) : m_label(""), m_stop("Stop") + { + m_task = task; + m_label.setText( + fmt::format("{} ({})", task->description(), (task->isEnqueued() ? "enqueued" : "running"))); + + m_label.setExpansive(true); + m_row.setExpansive(true); + m_row.addChild(&m_label); + if (!task->isEnqueued()) + m_row.addChild(&m_stop); + addChild(&m_row); + + m_stop.Click.connect([this]() { App::instance()->scriptEngine()->stopTask(m_task); }); + } + + private: + const script::RunScriptTask* m_task; + ui::HBox m_row; + ui::Label m_label; + ui::Button m_stop; + }; + + ui::View m_view; + ui::ListBox m_runningScripts; +}; + // This widget is used to show the current frame. class GotoFrameEntry : public Entry { public: @@ -886,6 +959,25 @@ void StatusBar::showSnapToGridWarning(bool state) } } +void StatusBar::showRunningScriptsWindow(bool state) +{ + if (state) { + if (!m_runningScriptsWindow) + m_runningScriptsWindow = new RunningScriptsWindow; + + m_runningScriptsWindow->setDisplay(display(), false); + + if (!m_runningScriptsWindow->isVisible()) { + m_runningScriptsWindow->openWindow(); + m_runningScriptsWindow->remapWindow(); + // updateRunningScriptsWindowPosition(); + } + } + else if (m_runningScriptsWindow) { + m_runningScriptsWindow->closeWindow(nullptr); + } +} + ////////////////////////////////////////////////////////////////////// // StatusBar message handler diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h index 90075fe72..38a652fac 100644 --- a/src/app/ui/status_bar.h +++ b/src/app/ui/status_bar.h @@ -66,6 +66,7 @@ public: void showTile(int msecs, doc::tile_t tile, const std::string& text = std::string()); void showTool(int msecs, tools::Tool* tool); void showSnapToGridWarning(bool state); + void showRunningScriptsWindow(bool state); // Used by AppEditor to update the zoom level in the status bar. void updateFromEditor(Editor* editor); @@ -119,6 +120,9 @@ private: // Snap to grid window class SnapToGridWindow; SnapToGridWindow* m_snapToGridWindow; + + class RunningScriptsWindow; + RunningScriptsWindow* m_runningScriptsWindow; }; } // namespace app