Add auto_start_app setting

This commit is contained in:
Thomas Farstrike
2025-11-12 11:56:16 +01:00
parent f8788ccc61
commit bae7fb057a
7 changed files with 86 additions and 22 deletions
@@ -36,10 +36,13 @@ class SettingsActivity(Activity):
("Turquoise", "40e0d0")
]
self.settings = [
# Novice settings, alphabetically:
{"title": "Light/Dark Theme", "key": "theme_light_dark", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Light", "light"), ("Dark", "dark")]},
{"title": "Theme Color", "key": "theme_primary_color", "value_label": None, "cont": None, "placeholder": "HTML hex color, like: EC048C", "ui": "dropdown", "ui_options": theme_colors},
{"title": "Restart to Bootloader", "key": "boot_mode", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Normal", "normal"), ("Bootloader", "bootloader")]}, # special that doesn't get saved
{"title": "Timezone", "key": "timezone", "value_label": None, "cont": None, "ui": "dropdown", "ui_options": self.get_timezone_tuples(), "changed_callback": lambda : mpos.time.refresh_timezone_preference()},
# Advanced settings, alphabetically:
{"title": "Auto Start App", "key": "auto_start_app", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Launcher", "com.micropythonos.launcher"), ("LightningPiggy", "com.lightningpiggy.displaywallet")]},
{"title": "Restart to Bootloader", "key": "boot_mode", "value_label": None, "cont": None, "ui": "radiobuttons", "ui_options": [("Normal", "normal"), ("Bootloader", "bootloader")]}, # special that doesn't get saved
# This is currently only in the drawer but would make sense to have it here for completeness:
#{"title": "Display Brightness", "key": "display_brightness", "value_label": None, "cont": None, "placeholder": "A value from 0 to 100."},
# Maybe also add font size (but ideally then all fonts should scale up/down)
+22 -16
View File
@@ -17,6 +17,7 @@ def good_stack_size():
return stacksize
# Run the script in the current thread:
# Returns True if successful
def execute_script(script_source, is_file, cwd=None, classname=None):
import utime # for timing read and compile
thread_id = _thread.get_ident()
@@ -55,26 +56,32 @@ def execute_script(script_source, is_file, cwd=None, classname=None):
#print("Classes:", classes.keys())
#print("Functions:", functions.keys())
#print("Variables:", variables.keys())
if classname:
main_activity = script_globals.get(classname)
if main_activity:
start_time = utime.ticks_ms()
Activity.startActivity(None, Intent(activity_class=main_activity))
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
print(f"execute_script: Activity.startActivity took {end_time}ms")
else:
print("Warning: could not find main_activity")
if not classname:
print("Running without a classname isn't supported right now.")
return False
main_activity = script_globals.get(classname)
if main_activity:
start_time = utime.ticks_ms()
Activity.startActivity(None, Intent(activity_class=main_activity))
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
print(f"execute_script: Activity.startActivity took {end_time}ms")
else:
print(f"Warning: could not find app's main_activity {main_activity}")
return False
except Exception as e:
print(f"Thread {thread_id}: exception during execution:")
# Print stack trace with exception type, value, and traceback
tb = getattr(e, '__traceback__', None)
traceback.print_exception(type(e), e, tb)
print(f"Thread {thread_id}: script {compile_name} finished")
return False
print(f"Thread {thread_id}: script {compile_name} finished, restoring sys.path to {sys.path}")
sys.path = path_before
return True
except Exception as e:
print(f"Thread {thread_id}: error:")
tb = getattr(e, '__traceback__', None)
traceback.print_exception(type(e), e, tb)
return False
""" Unused:
# Run the script in a new thread:
@@ -104,6 +111,7 @@ def execute_script_new_thread(scriptname, is_file):
print("main.py: execute_script_new_thread(): error starting new thread thread: ", e)
"""
# Returns True if successful
def start_app(fullname):
mpos.ui.set_foreground_app(fullname)
import utime
@@ -119,7 +127,7 @@ def start_app(fullname):
print(f"WARNING: start_app can't start {fullname} because it doesn't have a main_launcher_activity")
return
start_script_fullpath = f"{app.installed_path}/{app.main_launcher_activity.get('entrypoint')}"
execute_script(start_script_fullpath, True, app.installed_path + "/assets/", app.main_launcher_activity.get("classname"))
result = execute_script(start_script_fullpath, True, app.installed_path + "/assets/", app.main_launcher_activity.get("classname"))
# Launchers have the bar, other apps don't have it
if app.is_valid_launcher():
mpos.ui.topmenu.open_bar()
@@ -127,6 +135,8 @@ def start_app(fullname):
mpos.ui.topmenu.close_bar()
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
print(f"start_app() took {end_time}ms")
return result
# Starts the first launcher that's found
def restart_launcher():
@@ -134,9 +144,5 @@ def restart_launcher():
# Stop all apps
mpos.ui.remove_and_stop_all_activities()
# No need to stop the other launcher first, because it exits after building the screen
for app in PackageManager.get_app_list():
if app.is_valid_launcher():
print(f"Found launcher, starting {app.fullname}")
start_app(app.fullname)
break
return start_app(PackageManager.get_launcher().fullname)
@@ -73,8 +73,17 @@ class PackageManager:
@classmethod
def get(cls, fullname):
if not cls._app_list:
cls.refresh_apps()
return cls._by_fullname.get(fullname)
@classmethod
def get_launcher(cls):
for app in cls.get_app_list():
if app.is_valid_launcher():
print(f"Found launcher {app.fullname}")
return app
@classmethod
def clear(cls):
"""Empty the internal caches. Call ``get_app_list()`` afterwards to repopulate."""
@@ -223,4 +232,3 @@ class PackageManager:
print(f"Checking if app {app_fullname} is installed...")
return PackageManager.is_installed_by_path(f"apps/{app_fullname}") or PackageManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
+2 -2
View File
@@ -271,10 +271,10 @@ def create_drawer(display=None):
launcher_btn.set_size(lv.pct(drawer_button_pct),lv.pct(20))
launcher_btn.align(lv.ALIGN.CENTER,0,0)
launcher_label=lv.label(launcher_btn)
launcher_label.set_text(lv.SYMBOL.HOME+" Home")
launcher_label.set_text(lv.SYMBOL.HOME+" Launch")
launcher_label.center()
def launcher_event(e):
print("Home button pressed!")
print("Launch button pressed!")
close_drawer(True)
mpos.apps.restart_launcher()
launcher_btn.add_event_cb(launcher_event,lv.EVENT.CLICKED,None)
+8 -1
View File
@@ -12,6 +12,7 @@ import mpos.config
import mpos.ui
import mpos.ui.topmenu
from mpos.ui.display import init_rootscreen
from mpos.content.package_manager import PackageManager
prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
@@ -71,7 +72,13 @@ try:
except Exception as e:
print(f"Couldn't start mpos.wifi.WifiService.auto_connect thread because: {e}")
mpos.apps.restart_launcher()
# Start launcher so it's always at bottom of stack
launcher_app = PackageManager.get_launcher()
mpos.apps.start_app(launcher_app.fullname)
# Then start another app if configured
auto_start_app = prefs.get_string("auto_start_app", None)
if auto_start_app and launcher_app.fullname != auto_start_app:
mpos.apps.start_app(auto_start_app)
# If we got this far without crashing, then no need to rollback the update:
try:
+4 -1
View File
@@ -14,7 +14,10 @@ mkdir -p "$output"
#rm "$output"/*.png
rm "$outputjson"
blacklist="com.micropythonos.filemanager com.example.bla"
# These apps are for testing, or aren't ready yet:
# com.quasikili.quasidoodle doesn't work on touch screen devices
# com.micropythonos.filemanager doesn't do anything other than let you browse the filesystem, so it's confusing
blacklist="com.micropythonos.filemanager com.quasikili.quasidoodle"
echo "[" | tee -a "$outputjson"
+37
View File
@@ -0,0 +1,37 @@
import unittest
import sdl_display
import lcd_bus
import lvgl as lv
import mpos.ui
import task_handler
import mpos.apps
import mpos.ui.topmenu
import mpos.config
from mpos.ui.display import init_rootscreen
class TestStartApp(unittest.TestCase):
def __init__(self):
TFT_HOR_RES=320
TFT_VER_RES=240
bus = lcd_bus.SDLBus(flags=0)
buf1 = bus.allocate_framebuffer(320 * 240 * 2, 0)
display = sdl_display.SDLDisplay(data_bus=bus,display_width=TFT_HOR_RES,display_height=TFT_VER_RES,frame_buffer1=buf1,color_space=lv.COLOR_FORMAT.RGB565)
display.init()
init_rootscreen()
mpos.ui.topmenu.create_notification_bar()
mpos.ui.topmenu.create_drawer(display)
mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
def test_normal(self):
self.assertTrue(mpos.apps.start_app("com.micropythonos.launcher"), "com.micropythonos.launcher should start")
def test_nonexistent(self):
self.assertFalse(mpos.apps.start_app("com.micropythonos.nonexistent"), "com.micropythonos.nonexistent should not start")
def test_restart_launcher(self):
self.assertTrue(mpos.apps.restart_launcher(), "restart_launcher() should succeed")