diff --git a/internal_filesystem/apps/com.example.camtest/META-INF/MANIFEST.JSON b/internal_filesystem/apps/com.example.camtest/META-INF/MANIFEST.JSON deleted file mode 100644 index 903e81c2..00000000 --- a/internal_filesystem/apps/com.example.camtest/META-INF/MANIFEST.JSON +++ /dev/null @@ -1,13 +0,0 @@ -{ -"name": "Camera", -"publisher": "ACME Inc", -"short_description": "Simple test of the camera", -"long_description": "A simple test of the camera makes it possible to validate the hardware.", -"icon_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk_icon_64x64.png", -"download_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk", -"fullname": "com.example.camtest", -"version": "0.0.2", -"entrypoint": "assets/camtest.py", -"category": "camera" -} - diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/META-INF/MANIFEST.JSON b/internal_filesystem/apps/com.lightningpiggy.displaywallet/META-INF/MANIFEST.JSON index 552723aa..74c44512 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/META-INF/MANIFEST.JSON +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/META-INF/MANIFEST.JSON @@ -3,10 +3,21 @@ "publisher": "LightningPiggy Foundation", "short_description": "Display wallet that shows balance, transactions, receive QR code etc.", "long_description": "See https://www.LightningPiggy.com", -"icon_url": "http://demo.lnpiggy.com:2121/apps/com.lightningpiggy.displaywallet_0.0.1.mpk_icon_64x64.png", -"download_url": "http://demo.lnpiggy.com:2121/apps/com.lightningpiggy.displaywallet_0.0.1.mpk", +"icon_url": "https://apps.micropythonos.com/icons/com.lightningpiggy.displaywallet_0.0.1.mpk_icon_64x64.png", +"download_url": "https://apps.micropythonos.com/mpks/com.lightningpiggy.displaywallet_0.0.1.mpk", "fullname": "com.lightningpiggy.displaywallet", "version": "0.0.1", -"entrypoint": "assets/displaywallet.py", -"category": "finance" +"category": "finance", +"activities": [ + { + "entrypoint": "assets/displaywallet.py", + "classname": "MainActivity", + "intent_filters": [ + { + "action": "main", + "category": "launcher" + } + ] + } + ] } diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py index f3e673a3..b06bd138 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py @@ -21,9 +21,10 @@ status_label_text = "No camera found." status_label_text_searching = "Searching QR codes...\n\nHold still and make them big!\n10cm for simple QR codes,\n20cm for complex." status_label_text_found = "Decoding QR..." -class CameraActivity(Activity): +class Camera(Activity): def __init__(self): + super().__init__() self.cam = None self.current_cam_buffer = None # Variable to hold the current memoryview to prevent garbage collection self.image_dsc = None diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py index 8981614c..d8c6cd4f 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py @@ -1,9 +1,8 @@ from mpos.apps import Activity, Intent import mpos.config -import mpos.ui from wallet import LNBitsWallet, NWCWallet -from captureqr import CameraActivity +from captureqr import Camera class MainActivity(Activity): @@ -328,7 +327,7 @@ class SettingActivity(Activity): def cambutton_cb(self, event): print("cambutton clicked!") - self.startActivity(Intent(activity_class=CameraActivity).putExtra("scanqr_callback", self.gotqr_callback)) + self.startActivity(Intent(activity_class=Camera).putExtra("scanqr_callback", self.gotqr_callback)) def save_setting(self, setting): if setting["key"] == "wallet_type" and self.radio_container: @@ -344,9 +343,7 @@ class SettingActivity(Activity): setting["value_label"].set_text(new_value if new_value else "Not set") self.finish() - class FullscreenQR(Activity): - def onCreate(self): receive_qr_data = self.getIntent().extras.get("receive_qr_data") qr_screen = lv.obj() diff --git a/internal_filesystem/apps/com.micropythonos.camera/META-INF/MANIFEST.JSON b/internal_filesystem/apps/com.micropythonos.camera/META-INF/MANIFEST.JSON new file mode 100644 index 00000000..0965e814 --- /dev/null +++ b/internal_filesystem/apps/com.micropythonos.camera/META-INF/MANIFEST.JSON @@ -0,0 +1,28 @@ +{ +"name": "Camera", +"publisher": "MicroPythonOS", +"short_description": "Camera with QR decoding", +"long_description": "Camera for both internal camera's and webcams, that includes QR decoding.", +"icon_url": "https://apps.micropythonos.com/com.micropython.camera_0.0.3.mpk_icon_64x64.png", +"download_url": "https://apps.micropythonos.com/com.micropython.camera_0.0.3.mpk", +"fullname": "com.micropython.camera", +"version": "0.0.3", +"category": "camera", +"activities": [ + { + "entrypoint": "assets/camera.py", + "classname": "Camera", + "intent_filters": [ + { + "action": "main", + "category": "launcher" + }, + { + "action": "scan_qr_code", + "category": "default" + } + ] + } + ] +} + diff --git a/internal_filesystem/apps/com.example.camtest/assets/camtest.py b/internal_filesystem/apps/com.micropythonos.camera/assets/camera.py similarity index 100% rename from internal_filesystem/apps/com.example.camtest/assets/camtest.py rename to internal_filesystem/apps/com.micropythonos.camera/assets/camera.py diff --git a/internal_filesystem/apps/com.example.camtest/res/mipmap-mdpi/icon_64x64.png b/internal_filesystem/apps/com.micropythonos.camera/res/mipmap-mdpi/icon_64x64.png similarity index 100% rename from internal_filesystem/apps/com.example.camtest/res/mipmap-mdpi/icon_64x64.png rename to internal_filesystem/apps/com.micropythonos.camera/res/mipmap-mdpi/icon_64x64.png diff --git a/internal_filesystem/builtin/apps/com.example.launcher/META-INF/MANIFEST.JSON b/internal_filesystem/builtin/apps/com.example.launcher/META-INF/MANIFEST.JSON deleted file mode 100644 index 2d5cb414..00000000 --- a/internal_filesystem/builtin/apps/com.example.launcher/META-INF/MANIFEST.JSON +++ /dev/null @@ -1,13 +0,0 @@ -{ -"name": "Launcher", -"publisher": "ACME Inc", -"short_description": "Simple launcher to start apps.", -"long_description": "", -"icon_url": "http://demo.lnpiggy.com:2121/apps/com.example.launcher_0.0.2.mpk_icon_64x64.png", -"download_url": "http://demo.lnpiggy.com:2121/apps/com.example.launcher_0.0.2.mpk", -"fullname": "com.example.launcher", -"version": "0.0.2", -"entrypoint": "assets/launcher.py", -"category": "launcher" -} - diff --git a/internal_filesystem/builtin/apps/com.micropythonos.launcher/META-INF/MANIFEST.JSON b/internal_filesystem/builtin/apps/com.micropythonos.launcher/META-INF/MANIFEST.JSON new file mode 100644 index 00000000..0bc46e98 --- /dev/null +++ b/internal_filesystem/builtin/apps/com.micropythonos.launcher/META-INF/MANIFEST.JSON @@ -0,0 +1,24 @@ +{ +"name": "Launcher", +"publisher": "MicroPythonOS", +"short_description": "Simple launcher to start apps.", +"long_description": "", +"icon_url": "https://apps.micropythonos.com/icons/com.micropythonos.launcher_0.0.3.mpk_icon_64x64.png", +"download_url": "https://apps.micropythonos.com/mpks/com.micropythonos.launcher_0.0.3.mpk", +"fullname": "com.micropythonos.launcher", +"version": "0.0.3", +"category": "launcher", +"activities": [ + { + "entrypoint": "assets/launcher.py", + "classname": "MainActivity", + "intent_filters": [ + { + "action": "main", + "category": "launcher" + } + ] + } + ] +} + diff --git a/internal_filesystem/builtin/apps/com.example.launcher/assets/launcher.py b/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py similarity index 91% rename from internal_filesystem/builtin/apps/com.example.launcher/assets/launcher.py rename to internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py index 7a03ae07..d1eb5e6d 100644 --- a/internal_filesystem/builtin/apps/com.example.launcher/assets/launcher.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py @@ -18,6 +18,7 @@ import mpos.ui class MainActivity(mpos.apps.Activity): def onCreate(self): + print("launcher.py onCreate()") main_screen = lv.obj() main_screen.set_style_border_width(0, 0) main_screen.set_style_radius(0, 0) @@ -27,7 +28,6 @@ class MainActivity(mpos.apps.Activity): main_screen.set_flex_flow(lv.FLEX_FLOW.ROW_WRAP) self.setContentView(main_screen) - def onResume(self, main_screen): # Grid parameters icon_size = 64 # Adjust based on your display label_height = 24 @@ -63,9 +63,12 @@ class MainActivity(mpos.apps.Activity): base_name = d if base_name not in seen_base_names: # Avoid duplicates seen_base_names.add(base_name) + #print(f"seen_base_names: {seen_base_names}") app = mpos.apps.parse_manifest(f"{full_path}/META-INF/MANIFEST.JSON") if app.category != "launcher": # Skip launchers - app_list.append((app.name, full_path)) + main_launcher = mpos.apps.find_main_launcher_activity(app) + if main_launcher: + app_list.append((app.name, full_path)) except OSError: pass @@ -74,7 +77,7 @@ class MainActivity(mpos.apps.Activity): # Create UI for each app for app_name, app_dir_fullpath in app_list: - #print(f"Adding app {app_name} from {app_dir_fullpath}") + print(f"Adding app {app_name} from {app_dir_fullpath}") # Create container for each app (icon + label) app_cont = lv.obj(main_screen) app_cont.set_size(iconcont_width, iconcont_height) diff --git a/internal_filesystem/builtin/apps/com.example.launcher/res/mipmap-mdpi/icon_64x64.png b/internal_filesystem/builtin/apps/com.micropythonos.launcher/res/mipmap-mdpi/icon_64x64.png similarity index 100% rename from internal_filesystem/builtin/apps/com.example.launcher/res/mipmap-mdpi/icon_64x64.png rename to internal_filesystem/builtin/apps/com.micropythonos.launcher/res/mipmap-mdpi/icon_64x64.png diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index 232793d1..79b0d89d 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -18,7 +18,7 @@ def good_stack_size(): return stacksize # Run the script in the current thread: -def execute_script(script_source, is_file, cwd=None): +def execute_script(script_source, is_file, cwd=None, classname=None): thread_id = _thread.get_ident() compile_name = 'script' if not is_file else script_source print(f"Thread {thread_id}: executing script with cwd: {cwd}") @@ -46,17 +46,12 @@ def execute_script(script_source, is_file, cwd=None): #print("Classes:", classes.keys()) #print("Functions:", functions.keys()) #print("Variables:", variables.keys()) - main_activity = script_globals.get("MainActivity") - if not main_activity: # Fallback to taking the first non-generic Activity class, but that's slower - for k, v in script_globals.items(): - if k != "Activity" and isinstance(v, type): - main_activity = v # first one is the 'Activity' from which it inherits so take the second one - break - print(f"Got main_activity: {main_activity}") - if main_activity: - Activity.startActivity(None, Intent(activity_class=main_activity)) - else: - print("Warning: could not find main_activity") + if classname: + main_activity = script_globals.get(classname) + if main_activity: + Activity.startActivity(None, Intent(activity_class=main_activity)) + else: + print("Warning: could not find main_activity") except Exception as e: print(f"Thread {thread_id}: exception during execution:") # Print stack trace with exception type, value, and traceback @@ -110,9 +105,13 @@ def start_app(app_dir, is_launcher=False): mpos.ui.set_foreground_app(app_dir) # would be better to store only the app name... manifest_path = f"{app_dir}/META-INF/MANIFEST.JSON" app = mpos.apps.parse_manifest(manifest_path) - start_script_fullpath = f"{app_dir}/{app.entrypoint}" - #execute_script_new_thread(start_script_fullpath, True, is_launcher, True) # Starting (GUI?) apps in a new thread can cause hangs (GIL lock?) - execute_script(start_script_fullpath, True, app_dir + "/assets/") + print(f"start_app parsed manifest and got: {str(app)}") + main_launcher_activity = find_main_launcher_activity(app) + if not main_launcher_activity: + print(f"WARNING: can't start {app_dir} because no main_launcher_activity was found.") + return + start_script_fullpath = f"{app_dir}/{main_launcher_activity.get('entrypoint')}" + execute_script(start_script_fullpath, True, app_dir + "/assets/", main_launcher_activity.get("classname")) # Launchers have the bar, other apps don't have it if is_launcher: mpos.ui.open_bar() @@ -122,8 +121,22 @@ def start_app(app_dir, is_launcher=False): def restart_launcher(): mpos.ui.empty_screen_stack() # No need to stop the other launcher first, because it exits after building the screen - start_app_by_name("com.example.launcher", True) + start_app_by_name("com.micropythonos.launcher", True) # Would be better to query the PackageManager for Activities that are launchers +def find_main_launcher_activity(app): + result = None + for activity in app.activities: + if not activity.get("entrypoint") or not activity.get("classname"): + print(f"Warning: activity {activity} has no entrypoint and classname, skipping...") + continue + print("checking activity's intent_filters...") + for intent_filter in activity.get("intent_filters"): + print("checking intent_filter...") + if intent_filter.get("action") == "main" and intent_filter.get("category") == "launcher": + print("found main_launcher!") + result = activity + break + return result def is_launcher(app_name): print(f"checking is_launcher for {app_name}") @@ -132,7 +145,7 @@ def is_launcher(app_name): class App: - def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, entrypoint, category): + def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities): self.name = name self.publisher = publisher self.short_description = short_description @@ -141,10 +154,18 @@ class App: self.download_url = download_url self.fullname = fullname self.version = version - self.entrypoint = entrypoint self.category = category self.image = None self.image_dsc = None + self.activities = activities + + def __str__(self): + return (f"App(name='{self.name}', " + f"publisher='{self.publisher}', " + f"short_description='{self.short_description}', " + f"version='{self.version}', " + f"category='{self.category}', " + f"activities={self.activities})") def parse_manifest(manifest_path): # Default values for App object @@ -157,12 +178,13 @@ def parse_manifest(manifest_path): download_url="", fullname="Unknown", version="0.0.0", - entrypoint="assets/start.py", - category="" + category="", + activities=[] ) try: with open(manifest_path, 'r') as f: app_info = ujson.load(f) + #print(f"parsed app: {app_info}") # Create App object with values from manifest, falling back to defaults return App( name=app_info.get("name", default_app.name), @@ -173,12 +195,14 @@ def parse_manifest(manifest_path): download_url=app_info.get("download_url", default_app.download_url), fullname=app_info.get("fullname", default_app.fullname), version=app_info.get("version", default_app.version), - entrypoint=app_info.get("entrypoint", default_app.entrypoint), - category=app_info.get("category", default_app.category) + category=app_info.get("category", default_app.category), + activities=app_info.get("activities", default_app.activities) ) except OSError: print(f"parse_manifest: error loading manifest_path: {manifest_path}") return default_app + + def auto_connect(): # A generic "start at boot" mechanism hasn't been implemented yet, so do it like this: @@ -201,6 +225,8 @@ class Activity: def __init__(self): self.intent = None # Store the intent that launched this activity + self.result = None + self._result_callback = None def getIntent(self): return self.intent @@ -221,8 +247,15 @@ class Activity: def setContentView(self, screen): mpos.ui.setContentView(self, screen) + def setResult(self, result_code, data=None): + """Set the result to be returned when the activity finishes.""" + self.result = {"result_code": result_code, "data": data or {}} + def finish(self): mpos.ui.back_screen() + if self._result_callback and self.result: + self._result_callback(self.result) + self._result_callback = None # Clean up def startActivity(self, intent): ActivityNavigator.startActivity(intent) diff --git a/internal_filesystem/lib/mpos/ui.py b/internal_filesystem/lib/mpos/ui.py index 507b28c1..f9ed8351 100644 --- a/internal_filesystem/lib/mpos/ui.py +++ b/internal_filesystem/lib/mpos/ui.py @@ -464,8 +464,8 @@ def empty_screen_stack(): screen_stack.clear() -def load_screen(screen): - setContentView(None, screen) # for compatibility with old apps +#def load_screen(screen): +# setContentView(None, screen) # for compatibility with old apps # new_activity might be None for compatibility, can be removed if compatibility is no longer needed def setContentView(new_activity, new_screen): @@ -477,7 +477,7 @@ def setContentView(new_activity, new_screen): current_activity, current_screen = screen_stack[-1] if current_activity and current_screen: - # Notify current activity it's being backgrounded: + # Notify current activity that it's being backgrounded: current_activity.onPause(current_screen) current_activity.onStop(current_screen)