diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index b4c41e6b0..2b038e6e5 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -152,14 +152,25 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot()); - /* Just after a software update, the battery timer does not have time to - * fire before the calculator enters DFU mode. As the DFU mode blocks the - * event loop, we update the battery state "manually" here. */ - updateBatteryState(); - switchTo(usbConnectedAppSnapshot()); - Ion::USB::DFU(); - switchTo(activeSnapshot); - didProcessEvent = true; + if (activeApp() == nullptr || activeApp()->prepareForExit()) { + /* Just after a software update, the battery timer does not have time to + * fire before the calculator enters DFU mode. As the DFU mode blocks the + * event loop, we update the battery state "manually" here. */ + updateBatteryState(); + switchTo(usbConnectedAppSnapshot()); + Ion::USB::DFU(); + switchTo(activeSnapshot); + didProcessEvent = true; + } else { + /* activeApp()->prepareForExit() returned false, which means that the + * app needs another event loop to prepare for being switched off. + * Discard the current enumeration interruption. + * The USB host tries a few times in a row to enumerate the device, so + * hopefully the device will get another enumeration event soon and this + * time the device will be ready to go in DFU mode. Otherwise, the user + * needs to re-plug the device to go into DFU mode. */ + Ion::USB::clearEnumerationInterrupt(); + } } else { /* Sometimes, the device gets an ENUMDNE interrupts when being unplugged * from a non-USB communicating host (e.g. a USB charger). The interrupt @@ -212,6 +223,7 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } void AppsContainer::switchTo(App::Snapshot * snapshot) { + assert(activeApp() == nullptr || activeApp()->prepareForExit()); if (activeApp() && snapshot != activeApp()->snapshot()) { resetShiftAlphaStatus(); } diff --git a/apps/code/app.cpp b/apps/code/app.cpp index afc1e7840..bedd3d0f1 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -88,6 +88,7 @@ App::App(Container * container, Snapshot * snapshot) : } App::~App() { + assert(!m_consoleController.inputRunLoopActive()); deinitPython(); } diff --git a/apps/code/app.h b/apps/code/app.h index 58d007e3c..db1c0bfc6 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -37,6 +37,13 @@ public: ScriptStore m_scriptStore; }; ~App(); + bool prepareForExit() override { + if (m_consoleController.inputRunLoopActive()) { + m_consoleController.terminateInputLoop(); + return false; + } + return true; + } StackViewController * stackViewController() { return &m_codeStackViewController; } ConsoleController * consoleController() { return &m_consoleController; } diff --git a/escher/include/escher/app.h b/escher/include/escher/app.h index ed164c7f4..15790005a 100644 --- a/escher/include/escher/app.h +++ b/escher/include/escher/app.h @@ -50,6 +50,9 @@ public: void setFirstResponder(Responder * responder); Responder * firstResponder(); virtual bool processEvent(Ion::Events::Event event); + /* prepareForExit returns true if the app can be switched off in the current + * runloop step, else it prepares for a switch off and returns false. */ + virtual bool prepareForExit() { return true; } void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment, KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0); void dismissModalViewController();