diff --git a/apps/external/Makefile b/apps/external/Makefile index 0929e8d218c..51188a138ba 100644 --- a/apps/external/Makefile +++ b/apps/external/Makefile @@ -3,6 +3,7 @@ ifdef HOME_DISPLAY_EXTERNALS app_external_src = $(addprefix apps/external/,\ extapp_api.cpp \ archive.cpp \ + execution_environment.cpp \ ) $(eval $(call depends_on_image,apps/home/controller.cpp,apps/external/external_icon.png)) @@ -18,6 +19,7 @@ app_external_src = $(addprefix apps/external/,\ archive.cpp \ main_controller.cpp \ pointer_text_table_cell.cpp \ + execution_environment.cpp \ ) $(eval $(call depends_on_image,apps/external/app.cpp,apps/external/external_icon.png)) diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp index 285e262a586..e03f78a1bf7 100644 --- a/apps/external/archive.cpp +++ b/apps/external/archive.cpp @@ -1,6 +1,8 @@ #include "archive.h" #include "extapp_api.h" #include "../global_preferences.h" +#include "execution_environment.h" +#include #include #include @@ -101,6 +103,12 @@ extern "C" void (* const apiPointers[])(void); typedef uint32_t (*entrypoint)(const uint32_t, const void *, void *, const uint32_t); uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + uint32_t pythonResult = executePython(name, heap, heapSize); + // If the python file was executed, we're done. + if (pythonResult != 1) { + return pythonResult; + } + // Else, execute the native external app. File entry; if(fileAtIndex(indexFromName(name), entry)) { if(!entry.isExecutable) { @@ -133,6 +141,12 @@ bool fileAtIndex(size_t index, File &entry) { extern "C" void extapp_main(void); uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + uint32_t pythonResult = executePython(name, heap, heapSize); + // If the python file was executed, we're done. + if (pythonResult != 1) { + return pythonResult; + } + // Else, execute the native external app. extapp_main(); return 0; } @@ -196,5 +210,47 @@ size_t numberOfExecutables() { return final_count; } +// Private function to check if a file is a Python script and if so, execute it. +uint32_t executePython(const char *name, void * heap, const uint32_t heapSize) { + // Check if the file exists + File entry; + if (!fileAtIndex(indexFromName(name), entry)) { + return 1; + } + + // Get the extension without using strrchr + const char *ext = entry.name + strlen(entry.name) - 3; + if (ext == NULL) { + return 1; + } + + // Check if it is a Python script + if (strcmp(ext, ".py") != 0) { + return 1; + } + + // Clear the screen, exclude the top bar + // Prevent global-buffer-overflow when using Metric::TitleBarHeight + // KDRect rect(0, Metric::TitleBarHeight, 320, 240); + KDRect rect(0, 0, 320, 240); + Ion::Display::pushRectUniform(rect, KDColor::RGB16(0xFFFF)); + // On simulator, we need to refresh the screen to see the changes + #ifndef DEVICE + Ion::Keyboard::scan(); + #endif + + + uint32_t heapSizeReal = 16536; + // TODO: Use a namespace + // Clear the heap to avoid memory errors + memset(heap, 0, heapSizeReal); + // Initialize the Python interpreter + ExternalExecutionEnvironment env = init_environnement(heap, heapSizeReal); + bool result = execute_input(env, (const char *)entry.name); + deinit_environment(); + + return result ? 0 : 3; +} + } } diff --git a/apps/external/archive.h b/apps/external/archive.h index de96e6d204f..cd795317b64 100644 --- a/apps/external/archive.h +++ b/apps/external/archive.h @@ -23,6 +23,7 @@ size_t numberOfFiles(); size_t numberOfExecutables(); bool executableAtIndex(size_t index, File &entry); uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize); +uint32_t executePython(const char *name, void * heap, const uint32_t heapSize); } } diff --git a/apps/external/execution_environment.cpp b/apps/external/execution_environment.cpp new file mode 100644 index 00000000000..5d3feea10a2 --- /dev/null +++ b/apps/external/execution_environment.cpp @@ -0,0 +1,36 @@ +#include "execution_environment.h" +#include +#include + +void ExternalExecutionEnvironment::printText(const char * text, size_t length) { + // TODO: Store the text somewhere +} + +// TODO: Use a namespace + +bool execute_input(ExternalExecutionEnvironment env, const char * filename) { + // Use the following syntax to execute a Python script: + // exec(open("filename").read()) + const char * pythonCodeStart = "exec(open(\""; + const char * pythonCodeEnd = "\").read())"; + // Initialize the buffer to store the python code (size = 50) + char pythonCode[50]; + // Add the start of the python code in the buffer (until the file name) + strlcpy(pythonCode, pythonCodeStart, sizeof(pythonCode)); + // Add the file name in the buffer + strlcpy(pythonCode + strlen(pythonCode), filename, sizeof(pythonCode) - strlen(pythonCode)); + // Add the end of the python code in the buffer + strlcpy(pythonCode + strlen(pythonCode), pythonCodeEnd, sizeof(pythonCode) - strlen(pythonCode)); + // Execute the python code + bool executionResult = env.runCode(pythonCode); + return executionResult; +} + +ExternalExecutionEnvironment init_environnement(void * heap, const uint32_t heapSize) { + MicroPython::init((uint32_t *)heap, (uint32_t *)heap + heapSize); + return ExternalExecutionEnvironment(); +} + +void deinit_environment() { + MicroPython::deinit(); +} diff --git a/apps/external/execution_environment.h b/apps/external/execution_environment.h new file mode 100644 index 00000000000..8799536627c --- /dev/null +++ b/apps/external/execution_environment.h @@ -0,0 +1,17 @@ +#include +#include + +class ExternalExecutionEnvironment : public MicroPython::ExecutionEnvironment { +public: + ExternalExecutionEnvironment() : m_printTextIndex(0) {} + void printText(const char * text, size_t length) override; + const char * lastPrintedText() const { return m_printTextBuffer; } +private: + static constexpr size_t k_maxPrintedTextSize = 256; + char m_printTextBuffer[k_maxPrintedTextSize]; + size_t m_printTextIndex; +}; + +ExternalExecutionEnvironment init_environnement(void * heap, const uint32_t heapSize); +void deinit_environment(); +bool execute_input(ExternalExecutionEnvironment env, const char * filename); diff --git a/python/port/mod/ion/file.cpp b/python/port/mod/ion/file.cpp index c9eb9bc6636..c3305fa141e 100644 --- a/python/port/mod/ion/file.cpp +++ b/python/port/mod/ion/file.cpp @@ -14,6 +14,7 @@ extern "C" { #include #include #include +#include STATIC void file_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); @@ -199,10 +200,16 @@ typedef struct _file_obj_t { file_bin_t binary_mode; Ion::Storage::Record record; - + size_t position; bool closed; + + /// Index, for external files. + int index; + + /// True for the ram fs, False for the flash fs (external) + bool filesystem; } file_obj_t; @@ -417,13 +424,25 @@ STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n Ion::Storage::Record::ErrorStatus status; + bool isExternalRecord = External::Archive::indexFromName(file_name) != -1; + switch(file->open_mode) { case READ: file->record = Ion::Storage::sharedStorage()->recordNamed(file_name); file->position = 0; + // File not found in RAM file system if (file->record == Ion::Storage::Record()) { - mp_raise_OSError(2); + // If the file is not found in the external archive, raise an error + if (!isExternalRecord) { + // File not found in external archive + mp_raise_OSError(ENOENT); + break; + } + // File found in external archive + file->index = External::Archive::indexFromName(file_name); + break; } + file->filesystem = true; break; case CREATE: file->position = 0; @@ -499,6 +518,11 @@ STATIC mp_obj_t file_make_new(const mp_obj_type_t *type, size_t n_args, size_t n } if (file->edit_mode) { + // Error read only file on external archive + if (!file->filesystem) { + mp_raise_OSError(30); + // TODO: Return ? + } file_mode[1] = '+'; offset = 1; } @@ -529,7 +553,7 @@ void check_closed(file_obj_t* file) { const char* file_name = mp_obj_str_get_data(file->name, &l); file->record = Ion::Storage::sharedStorage()->recordNamed(file_name); - if (file->record == Ion::Storage::Record()) { + if (file->record == Ion::Storage::Record() && file->filesystem) { mp_raise_OSError(2); } } @@ -765,45 +789,92 @@ STATIC mp_obj_t file_writelines(mp_obj_t o_in, mp_obj_t o_lines) { * Simpler read function used by read and readline. */ STATIC mp_obj_t __file_read_backend(file_obj_t* file, mp_int_t size, bool with_line_sep) { - size_t file_size = file->record.value().size; - size_t start = file->position; - - // Handle seek pos > file size - // And size = 0 - if (start >= file_size || size == 0) { + // If the file system is the RAM filesystem, we can directly read the data. + if (file->filesystem) { + size_t file_size = file->record.value().size; + size_t start = file->position; + + // Handle seek pos > file size + // And size = 0 + if (start >= file_size || size == 0) { + return mp_const_none; + } + + size_t end = 0; + + // size == 0 handled earlier. + if (size < 0) { + end = file_size; + } else { + end = std::min(file_size, file->position + size); + } + + // Handle line separator case. + // Always use \n, because simpler. + if (with_line_sep) { + for(size_t i = start; i < end; i++) { + if (*((uint8_t*)(file->record.value().buffer) + i) == '\n') { + end = i + 1; + break; + } + } + } + + file->position = end; + + + // Return different type based on mode. + if (file->binary_mode == TEXT) + return mp_obj_new_str((const char*)file->record.value().buffer + start, end - start); + if (file->binary_mode == BINARY) + return mp_obj_new_bytes((const byte*)file->record.value().buffer + start, end - start); return mp_const_none; } - - size_t end = 0; - - // size == 0 handled earlier. - if (size < 0) { - end = file_size; - } else { - end = std::min(file_size, file->position + size); - } - - // Handle line separator case. - // Always use \n, because simpler. - if (with_line_sep) { - for(size_t i = start; i < end; i++) { - if (*((uint8_t*)(file->record.value().buffer) + i) == '\n') { - end = i + 1; - break; + else { + // Read the data from the external archive + // Get the file using fileAtIndex + External::Archive::File file_archive; + External::Archive::fileAtIndex(file->index, file_archive); + size_t file_size = file_archive.dataLength; + size_t start = file->position; + + // Handle seek pos > file size + // And size = 0 + if (start >= file_size || size == 0) { + return mp_const_none; + } + + size_t end = 0; + + // size == 0 handled earlier. + if (size < 0) { + end = file_size; + } else { + end = std::min(file_size, file->position + size); + } + + // Handle line separator case. + // Always use \n, because simpler. + if (with_line_sep) { + for(size_t i = start; i < end; i++) { + if (*((uint8_t*)(file_archive.data) + i) == '\n') { + end = i + 1; + break; + } } } + + file->position = end; + + + // Return different type based on mode. + if (file->binary_mode == TEXT) + return mp_obj_new_str((const char*)file_archive.data + start, end - start); + if (file->binary_mode == BINARY) + return mp_obj_new_bytes((const byte*)file_archive.data + start, end - start); + return mp_const_none; + } - - file->position = end; - - - // Return different type based on mode. - if (file->binary_mode == TEXT) - return mp_obj_new_str((const char*)file->record.value().buffer + start, end - start); - if (file->binary_mode == BINARY) - return mp_obj_new_bytes((const byte*)file->record.value().buffer + start, end - start); - - return mp_const_none; } STATIC mp_obj_t file_read(size_t n_args, const mp_obj_t* args) { diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 63e48320933..d2169fa2a08 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -63,8 +63,13 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : Palette::PrimaryText; KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : Palette::HomeBackground; const KDFont * font = (n_args >= 6) ? ((mp_obj_is_true(args[5])) ? KDFont::SmallFont : KDFont::LargeFont) : KDFont::LargeFont; - MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); - KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor); + // Get tbe context and draw the string. + KDIonContext * ctx = KDIonContext::sharedContext(); + ctx->setClippingRect(KDRect(0, 0, 320, 240)); + ctx->setOrigin(KDPoint(0, 0)); + ctx->drawString(text, point, font, KDColor::RGB16(textColor), KDColor::RGB16(backgroundColor)); + // MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + // KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor); return mp_const_none; } @@ -111,8 +116,15 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { } KDRect rect(x, y, width, height); KDColor color = MicroPython::Color::Parse(args[4]); - MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); - KDIonContext::sharedContext()->fillRect(rect, color); + // TODO: Get if the actual application is Python or not. + // If it isn't, we need to call directly the Ion fillRect function. + // If it is, we call the displaySandbox function, which will hide the console + // and switch to another mode. + // KDIonContext::sharedContext()->fillRect(rect, color); + Ion::Display::pushRectUniform(rect, color); + // KDColor color = KDColorBlue; + // MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + // KDIonContext::sharedContext()->fillRect(rect, color); return mp_const_none; }