Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/appkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ hiding the other. When `apps/` has no `__init__.py`, `scan()` makes one;
where it can't (a read-only filesystem) it stops with an error saying to
add an empty one.

When `apps/` holds an app named `home`, `appkit.run()` starts that as the
home screen instead of its own list of buttons.

`appkit.run("hello")` runs one app on its own, with the whole screen and no
home screen. An app set that wants its own home screen, chrome or launcher
setup builds a `Launcher` itself. Its `Shell` subclass gives apps a smaller
Expand Down
14 changes: 12 additions & 2 deletions lib/appkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
def run(name=None, folder="apps", *, data_root=None):
"""Run the apps in *folder*: a home screen listing them, or app *name* alone.

The home screen is the folder's own app named ``home`` when it has one,
and otherwise appkit's list of buttons, ``appkit.home``.

It ends in the main-thread loop, `serve`, and returns the
``Launcher`` when the program quits. In a browser, where the page has
the main thread, it returns the launcher at once. *data_root* moves
Expand All @@ -33,10 +36,17 @@ def run(name=None, folder="apps", *, data_root=None):
launcher = Launcher(app)
launcher.scan(folder)
if name is None:
launcher.add("home", "appkit.home")
launcher.home = "home"
_use_home(launcher)
launcher.shell = Shell(launcher)
launcher.boot(name)
if host() != "browser":
serve(app)
return launcher


def _use_home(launcher):
"""Make ``home`` the launcher's home screen: the folder's own app named
home when the scan found one, else appkit's list of buttons."""
if "home" not in launcher.names:
launcher.add("home", "appkit.home")
launcher.home = "home"
15 changes: 15 additions & 0 deletions tests/appkit/launcher_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,21 @@ def restarts_in_a_row_are_bounded():
la.stop()


@check
def run_keeps_a_scanned_home():
import appkit

la = L.Launcher(_app())
la.scan(_dir + _sep + "sample_home" + _sep + "apps")
appkit._use_home(la)
expect(la.home == "home", "home is %r" % (la.home,))
expect(la._apps["home"] == "apps.home", "the scanned home was replaced by %r" % (la._apps["home"],))
la = L.Launcher(_app())
la.scan(_dir + _sep + "sample_set" + _sep + "apps")
appkit._use_home(la)
expect(la._apps["home"] == "appkit.home", "a folder without home got %r" % (la._apps["home"],))


@check
def pending_app_round_trip():
# On a board this is the real RTC memory (or /next_app), without the reset.
Expand Down
5 changes: 5 additions & 0 deletions tests/appkit/sample_home/apps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# The apps folder is a package: appkit imports each app in it as apps.<app>.
8 changes: 8 additions & 0 deletions tests/appkit/sample_home/apps/home/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT
"""A folder's own home screen, which appkit.run() starts instead of its list."""


def main(scope):
pass
1 change: 1 addition & 0 deletions tests/appkit/sample_home/apps/home/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name": "home", "title": "Home", "tile": false}
Loading