From 03fbf0e8ca409fe4c26e246286a975724ad0994b Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 17 Dec 2022 20:19:32 -0800 Subject: GUI: Add ability to start & stop transcription engine --- GUI/GUI/GUI/Frame.cpp | 83 +++++++++++++++++++++++++++++++++++++++++-- GUI/GUI/GUI/Frame.h | 8 +++++ GUI/GUI/GUI/PythonWrapper.cpp | 38 ++++++++++++++++++++ GUI/GUI/GUI/PythonWrapper.h | 11 ++++++ 4 files changed, 137 insertions(+), 3 deletions(-) (limited to 'GUI') diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp index 74683ca..db2bdd7 100644 --- a/GUI/GUI/GUI/Frame.cpp +++ b/GUI/GUI/GUI/Frame.cpp @@ -10,6 +10,8 @@ namespace { ID_PY_PANEL, ID_PY_VERSION_BUTTON, ID_PY_SETUP_BUTTON, + ID_PY_APP_START_BUTTON, + ID_PY_APP_STOP_BUTTON, ID_PY_OUT, }; }; @@ -20,11 +22,16 @@ Frame::Frame() py_panel_sizer_(wxVERTICAL), py_version_button_(&py_panel_, ID_PY_VERSION_BUTTON, "Check embedded Python version"), py_setup_button_(&py_panel_, ID_PY_SETUP_BUTTON, "Set up Python virtual environment"), + py_app_start_button_(&py_panel_, ID_PY_APP_START_BUTTON, "Begin transcribing"), + py_app_stop_button_(&py_panel_, ID_PY_APP_STOP_BUTTON, "Stop transcribing"), py_out_(&py_panel_, ID_PY_OUT, wxEmptyString, wxDefaultPosition, - wxSize(/*x_px=*/480, /*y_px=*/160), wxTE_MULTILINE) + wxSize(/*x_px=*/480, /*y_px=*/160), wxTE_MULTILINE), + py_app_(nullptr) { Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT); Bind(wxEVT_BUTTON, &Frame::OnGetPythonVersion, this, ID_PY_VERSION_BUTTON); + Bind(wxEVT_BUTTON, &Frame::OnAppStart, this, ID_PY_APP_START_BUTTON); + Bind(wxEVT_BUTTON, &Frame::OnAppStop, this, ID_PY_APP_STOP_BUTTON); Bind(wxEVT_BUTTON, &Frame::OnSetupPython, this, ID_PY_SETUP_BUTTON); // wx needs this to be able to load PNGs. @@ -38,6 +45,8 @@ Frame::Frame() py_panel_.SetSizer(&py_panel_sizer_); py_panel_sizer_.Add(&py_version_button_); py_panel_sizer_.Add(&py_setup_button_); + py_panel_sizer_.Add(&py_app_start_button_); + py_panel_sizer_.Add(&py_app_stop_button_); py_panel_sizer_.Add(&py_out_); } @@ -60,8 +69,8 @@ void Frame::OnSetupPython(wxCommandEvent& event) py_out_.AppendText("Setting up Python virtual environment\n"); py_out_.AppendText("This could take several minutes, please be patient!\n"); py_out_.AppendText("This will download ~5GB of dependencies.\n"); - py_out_.AppendText("Dependencies are installed in the executable folder, " - "so deleting the folder is all that's needed to undo this."); + py_out_.AppendText("Dependencies are installed in the GUI's folder, " + "so deleting the folder is all that's needed to uninstall.\n"); { std::string py_out; @@ -107,6 +116,74 @@ void Frame::OnSetupPython(wxCommandEvent& event) py_out_.AppendText("Python virtual environment successfully set up!\n"); } +void Frame::OnAppStart(wxCommandEvent& event) { + if (py_app_) { + if (wxProcess::Exists(py_app_->GetPid())) { + py_out_.AppendText("Transcription engine already running\n"); + return; + } + delete py_app_; + py_app_ = nullptr; + } + + py_out_.AppendText("Launching transcription engine\n"); + + PythonWrapper py; + auto cb = [&](wxProcess* proc, int ret) -> void { + std::ostringstream py_out_oss; + py_out_oss << "Transcription engine exited with code " << ret << std::endl; + + py_out_.AppendText(py_out_oss.str()); + return; + }; + + wxProcess* p = py.StartApp(std::move(cb)); + if (!p) { + py_out_.AppendText("Failed to launch transcription engine\n"); + return; + } + + py_app_ = p; +} + +void Frame::OnAppStop(wxCommandEvent& event) { + if (py_app_) { + const long pid = py_app_->GetPid(); + + // Try to kill it politely. + wxProcess::Kill(pid); + for (int i = 0; i < 10; i++) { + if (!wxProcess::Exists(pid)) { + break; + } + wxMilliSleep(10); + } + + // If it doesn't accept its fate, murder it with an axe. + bool first = true; + int loop_cnt = 0; + while (wxProcess::Exists(pid)) { + if (first) { + first = false; + py_out_.AppendText("Timed out trying to stop transcription engine " + "cleanly, sending SIGKILL\n"); + } + else if (++loop_cnt % 100 == 0) { + py_out_.AppendText("Waiting for transcription engine to exit"); + } + wxProcess::Kill(pid, wxSIGKILL); + wxMilliSleep(10); + } + + // Since we don't process the termination event, py_app_ deletes itself! + py_app_ = nullptr; + py_out_.AppendText("Stopped transcription engine\n"); + } + else { + py_out_.AppendText("Transcription engine already stopped\n"); + } +} + void Frame::LoadAndSetIcon(const std::string& icon_path) { if (!std::filesystem::exists(icon_path)) { wxLogFatalError("Logo is missing from %s", icon_path.c_str()); diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h index 62e9169..414d2b3 100644 --- a/GUI/GUI/GUI/Frame.h +++ b/GUI/GUI/GUI/Frame.h @@ -6,6 +6,8 @@ #include #endif +#include + class Frame : public wxFrame { public: @@ -17,11 +19,17 @@ private: wxBoxSizer py_panel_sizer_; wxButton py_version_button_; wxButton py_setup_button_; + wxButton py_app_start_button_; + wxButton py_app_stop_button_; wxTextCtrl py_out_; + wxProcess* py_app_; + void OnExit(wxCommandEvent& event); void OnGetPythonVersion(wxCommandEvent& event); void OnSetupPython(wxCommandEvent& event); + void OnAppStart(wxCommandEvent& event); + void OnAppStop(wxCommandEvent& event); void LoadAndSetIcon(const std::string& icon_path); }; diff --git a/GUI/GUI/GUI/PythonWrapper.cpp b/GUI/GUI/GUI/PythonWrapper.cpp index 27d12fd..7270ab5 100644 --- a/GUI/GUI/GUI/PythonWrapper.cpp +++ b/GUI/GUI/GUI/PythonWrapper.cpp @@ -4,6 +4,38 @@ #include +class PythonProcess : public wxProcess { +public: + PythonProcess(std::function&& exit_callback) : exit_cb_(exit_callback) {} + + virtual void OnTerminate(int pid, int status) wxOVERRIDE { + exit_cb_(this, status); + } + +private: + const std::function exit_cb_; +}; + +wxProcess* PythonWrapper::InvokeAsyncWithArgs(std::vector&& args, + std::function&& exit_callback) { + std::ostringstream cmd_oss; + cmd_oss << "Resources/Python/python.exe"; + for (const auto& arg : args) { + cmd_oss << " " << arg; + } + + auto *p = new PythonProcess(std::move(exit_callback)); + // TODO(yum) we should hide the console & stream output to a friendlier interface + int pid = wxExecute(cmd_oss.str(), wxEXEC_ASYNC, p); + + if (!pid) { + delete p; + p = nullptr; + } + + return p; +} + bool PythonWrapper::InvokeWithArgs(std::vector&& args, std::string* out) { std::ostringstream cmd_oss; cmd_oss << "Resources/Python/python.exe"; @@ -52,3 +84,9 @@ bool PythonWrapper::InstallPip(std::string* out) { std::string pip_path = "Resources/Python/get-pip.py"; return InvokeWithArgs({ pip_path }, out); } + +wxProcess* PythonWrapper::StartApp(std::function&& exit_callback) { + return InvokeAsyncWithArgs({ "Resources/Scripts/transcribe.py" }, + std::move(exit_callback)); +} + diff --git a/GUI/GUI/GUI/PythonWrapper.h b/GUI/GUI/GUI/PythonWrapper.h index 607507d..4407b5e 100644 --- a/GUI/GUI/GUI/PythonWrapper.h +++ b/GUI/GUI/GUI/PythonWrapper.h @@ -6,6 +6,8 @@ #include #endif +#include + #include #include @@ -15,6 +17,13 @@ class PythonWrapper { public: + + // Invoke the interpreter asynchronously with the given arguments. + // When the process exits, `exit_callback` runs. + // The caller is responsible for deleting wxProcess. + wxProcess* InvokeAsyncWithArgs(std::vector&& args, + std::function&& exit_callback); + // Invoke the interpreter with arguments. // On error, sets `out` to an error message and returns false. bool InvokeWithArgs(std::vector&& args, std::string* out); @@ -24,5 +33,7 @@ public: // Execute get-pip.py. bool InstallPip(std::string* out); + + wxProcess* StartApp(std::function&& exit_callback); }; -- cgit v1.2.3