Skip to content

Commit 31dcfba

Browse files
Move mpos.apps functionality to PackageManager
1 parent ffdabf1 commit 31dcfba

24 files changed

+211
-185
lines changed

internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
# All icons took: 1250ms
1010
# Most of this time is actually spent reading and parsing manifests.
1111
import lvgl as lv
12-
import mpos.apps
1312
from mpos import AppearanceManager, PackageManager, Activity, DisplayMetrics
1413
import time
1514
import uhashlib
@@ -129,7 +128,7 @@ def onResume(self, screen):
129128

130129
# ----- events --------------------------------------------------
131130
app_cont.add_event_cb(
132-
lambda e, fullname=app.fullname: mpos.apps.start_app(fullname),
131+
lambda e, fullname=app.fullname: PackageManager.start_app(fullname),
133132
lv.EVENT.CLICKED, None)
134133
app_cont.add_event_cb(
135134
lambda e, cont=app_cont: self.focus_app_cont(cont),

internal_filesystem/lib/mpos/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
from .ui import focus_direction
4747

4848
# Utility modules
49-
from . import apps
5049
from . import bootloader
5150
from . import ui
5251
from . import config
@@ -91,7 +90,7 @@
9190
"click_button", "click_label", "click_keyboard_button", "find_button_with_text",
9291
"get_all_widgets_with_text",
9392
# Submodules
94-
"apps", "ui", "config", "net", "content", "time", "sensor_manager",
93+
"ui", "config", "net", "content", "time", "sensor_manager",
9594
"camera_manager", "sdcard", "battery_voltage", "audio", "hardware", "bootloader",
9695
# Timezone utilities
9796
"TimeZone"

internal_filesystem/lib/mpos/apps.py

Lines changed: 0 additions & 115 deletions
This file was deleted.

internal_filesystem/lib/mpos/content/package_manager.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import traceback
23

34
try:
45
import zipfile
@@ -232,3 +233,115 @@ def is_installed_by_name(app_fullname):
232233
print(f"Checking if app {app_fullname} is installed...")
233234
return PackageManager.is_installed_by_path(f"apps/{app_fullname}") or PackageManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
234235

236+
@staticmethod
237+
def execute_script(script_source, is_file, classname, cwd=None):
238+
"""Run the script in the current thread. Returns True if successful."""
239+
import utime # for timing read and compile
240+
import lvgl as lv
241+
import mpos.ui
242+
import _thread
243+
thread_id = _thread.get_ident()
244+
compile_name = 'script' if not is_file else script_source
245+
print(f"Thread {thread_id}: executing script with cwd: {cwd}")
246+
try:
247+
if is_file:
248+
print(f"Thread {thread_id}: reading script from file {script_source}")
249+
with open(script_source, 'r') as f: # No need to check if it exists as exceptions are caught
250+
start_time = utime.ticks_ms()
251+
script_source = f.read()
252+
read_time = utime.ticks_diff(utime.ticks_ms(), start_time)
253+
print(f"execute_script: reading script_source took {read_time}ms")
254+
script_globals = {
255+
'lv': lv,
256+
'__name__': "__main__"
257+
}
258+
print(f"Thread {thread_id}: starting script")
259+
import sys
260+
path_before = sys.path[:] # Make a copy, not a reference
261+
if cwd:
262+
sys.path.append(cwd)
263+
try:
264+
start_time = utime.ticks_ms()
265+
compiled_script = compile(script_source, compile_name, 'exec')
266+
compile_time = utime.ticks_diff(utime.ticks_ms(), start_time)
267+
print(f"execute_script: compiling script_source took {compile_time}ms")
268+
start_time = utime.ticks_ms()
269+
exec(compiled_script, script_globals)
270+
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
271+
print(f"apps.py execute_script: exec took {end_time}ms")
272+
# Introspect globals
273+
classes = {k: v for k, v in script_globals.items() if isinstance(v, type)}
274+
functions = {k: v for k, v in script_globals.items() if callable(v) and not isinstance(v, type)}
275+
variables = {k: v for k, v in script_globals.items() if not callable(v)}
276+
print("Classes:", classes.keys()) # This lists a whole bunch of classes, including lib/mpos/ stuff
277+
print("Functions:", functions.keys())
278+
print("Variables:", variables.keys())
279+
main_activity = script_globals.get(classname)
280+
if main_activity:
281+
from ..app.activity import Activity
282+
from .intent import Intent
283+
start_time = utime.ticks_ms()
284+
Activity.startActivity(None, Intent(activity_class=main_activity))
285+
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
286+
print(f"execute_script: Activity.startActivity took {end_time}ms")
287+
else:
288+
print(f"Warning: could not find app's main_activity {classname}")
289+
return False
290+
except Exception as e:
291+
print(f"Thread {thread_id}: exception during execution:")
292+
# Print stack trace with exception type, value, and traceback
293+
tb = getattr(e, '__traceback__', None)
294+
traceback.print_exception(type(e), e, tb)
295+
return False
296+
finally:
297+
# Always restore sys.path, even if we return early or raise an exception
298+
print(f"Thread {thread_id}: script {compile_name} finished, restoring sys.path from {sys.path} to {path_before}")
299+
sys.path = path_before
300+
return True
301+
except Exception as e:
302+
print(f"Thread {thread_id}: error:")
303+
tb = getattr(e, '__traceback__', None)
304+
traceback.print_exception(type(e), e, tb)
305+
return False
306+
307+
@staticmethod
308+
def start_app(fullname):
309+
"""Start an app by fullname. Returns True if successful."""
310+
import mpos.ui
311+
mpos.ui.set_foreground_app(fullname)
312+
import utime
313+
start_time = utime.ticks_ms()
314+
app = PackageManager.get(fullname)
315+
if not app:
316+
print(f"Warning: start_app can't find app {fullname}")
317+
return
318+
if not app.installed_path:
319+
print(f"Warning: start_app can't start {fullname} because no it doesn't have an installed_path")
320+
return
321+
entrypoint = "assets/main.py"
322+
classname = "Main"
323+
if not app.main_launcher_activity:
324+
print(f"WARNING: app {fullname} doesn't have a main_launcher_activity, defaulting to class {classname} in {entrypoint}")
325+
else:
326+
entrypoint = app.main_launcher_activity.get('entrypoint')
327+
classname = app.main_launcher_activity.get("classname")
328+
result = PackageManager.execute_script(app.installed_path + "/" + entrypoint, True, classname, app.installed_path + "/assets/")
329+
# Launchers have the bar, other apps don't have it
330+
if app.is_valid_launcher():
331+
mpos.ui.topmenu.open_bar()
332+
else:
333+
mpos.ui.topmenu.close_bar()
334+
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
335+
print(f"start_app() took {end_time}ms")
336+
return result
337+
338+
@staticmethod
339+
def restart_launcher():
340+
"""Restart the launcher by stopping all activities and starting the launcher app."""
341+
import mpos.ui
342+
print("restart_launcher")
343+
# Stop all apps
344+
mpos.ui.remove_and_stop_all_activities()
345+
# No need to stop the other launcher first, because it exits after building the screen
346+
return PackageManager.start_app(PackageManager.get_launcher().fullname)
347+

internal_filesystem/lib/mpos/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import _thread
33
import lvgl as lv
44

5-
import mpos.apps
65
import mpos.ui
76
import mpos.ui.topmenu
87

@@ -126,11 +125,11 @@ def custom_exception_handler(e):
126125

127126
# Start launcher so it's always at bottom of stack
128127
launcher_app = PackageManager.get_launcher()
129-
started_launcher = mpos.apps.start_app(launcher_app.fullname)
128+
started_launcher = PackageManager.start_app(launcher_app.fullname)
130129
# Then start auto_start_app if configured
131130
auto_start_app = prefs.get_string("auto_start_app", None)
132131
if auto_start_app and launcher_app.fullname != auto_start_app:
133-
result = mpos.apps.start_app(auto_start_app)
132+
result = PackageManager.start_app(auto_start_app)
134133
if result is not True:
135134
print(f"WARNING: could not run {auto_start_app} app")
136135

internal_filesystem/lib/mpos/task_manager.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import asyncio # this is the only place where asyncio is allowed to be imported - apps should not use it directly but use this TaskManager
22
import _thread
3-
import mpos.apps
43

54
class TaskManager:
65

internal_filesystem/lib/mpos/testing/mocks.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,13 +815,49 @@ def get_started_threads(cls):
815815

816816
class MockApps:
817817
"""
818-
Mock mpos.apps module for testing.
818+
Mock mpos.apps module for testing (deprecated, use MockPackageManager instead).
819+
820+
This is kept for backward compatibility with existing tests.
819821
820822
Usage:
821823
sys.modules['mpos.apps'] = MockApps
822824
"""
823825

824826
@staticmethod
825-
def good_stack_size():
826-
"""Return a reasonable stack size for testing."""
827-
return 8192
827+
def start_app(fullname):
828+
"""Mock start_app function."""
829+
return True
830+
831+
@staticmethod
832+
def restart_launcher():
833+
"""Mock restart_launcher function."""
834+
return True
835+
836+
@staticmethod
837+
def execute_script(script_source, is_file, classname, cwd=None):
838+
"""Mock execute_script function."""
839+
return True
840+
841+
842+
class MockPackageManager:
843+
"""
844+
Mock mpos.content.package_manager module for testing.
845+
846+
Usage:
847+
sys.modules['mpos.content.package_manager'] = MockPackageManager
848+
"""
849+
850+
@staticmethod
851+
def start_app(fullname):
852+
"""Mock start_app function."""
853+
return True
854+
855+
@staticmethod
856+
def restart_launcher():
857+
"""Mock restart_launcher function."""
858+
return True
859+
860+
@staticmethod
861+
def execute_script(script_source, is_file, classname, cwd=None):
862+
"""Mock execute_script function."""
863+
return True

internal_filesystem/lib/mpos/ui/testing.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
1414
Usage in tests:
1515
from mpos.ui.testing import wait_for_render, capture_screenshot
16+
from mpos import PackageManager
1617
1718
# Start your app
18-
mpos.apps.start_app("com.example.myapp")
19+
PackageManager.start_app("com.example.myapp")
1920
2021
# Wait for UI to render
2122
wait_for_render()
@@ -62,7 +63,8 @@ def wait_for_render(iterations=10):
6263
iterations: Number of task handler iterations to run (default: 10)
6364
6465
Example:
65-
mpos.apps.start_app("com.example.myapp")
66+
from mpos import PackageManager
67+
PackageManager.start_app("com.example.myapp")
6668
wait_for_render() # Ensure UI is ready
6769
assert verify_text_present(lv.screen_active(), "Welcome")
6870
"""

0 commit comments

Comments
 (0)