Skip to content
Open
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
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.sh text eol=lf
*.mk text eol=lf
SConstruct text eol=lf
*/SConstruct text eol=lf
bin/M5CardputerZero-AppStore text eol=lf
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,74 @@ M5APPSTORE_STATE_DIR=/root/.local/share/cardputerzero-appstore
M5APPSTORE_APP_ROOT=/usr/share/APPLaunch
```

## Display runtimes

The default CardputerZero deployment is unchanged: it builds and installs the
single `M5CardputerZero-AppStore` framebuffer/evdev binary through the existing
APPLaunch path. Wayland support is an optional dual-runtime deployment; it does
not replace the default framebuffer runtime.

For an explicit dual-runtime deployment, the stable APPLaunch entry becomes a
wrapper that selects the display implementation at runtime:

```text
/usr/share/APPLaunch/bin/M5CardputerZero-AppStore
wrapper at the existing launcher path
/usr/share/APPLaunch/bin/M5CardputerZero-AppStore-wayland
SDL/LVGL runtime for labwc/Wayland and X11 sessions
/usr/share/APPLaunch/bin/M5CardputerZero-AppStore-fbdev
direct framebuffer runtime for the legacy launcher
```

When the optional wrapper is installed, `M5APPSTORE_DISPLAY_BACKEND` selects a
runtime explicitly. If it is unset, the wrapper uses the Wayland/SDL runtime if
`WAYLAND_DISPLAY` or `DISPLAY` is present; otherwise it uses the framebuffer
runtime. This preserves the APPLaunch entry path:

```bash
M5APPSTORE_DISPLAY_BACKEND=wayland M5CardputerZero-AppStore
M5APPSTORE_DISPLAY_BACKEND=fbdev M5CardputerZero-AppStore
```

The wrapper preserves a caller-provided `SDL_VIDEODRIVER`. In a Wayland session
where it is not already set, it selects SDL's Wayland driver and disables
libdecor so labwc owns the window decorations. The SDL runtime identifies its
Wayland window as `cardputerzero-appstore` and creates a fixed 320×170 AppStore
window.

The normal framebuffer-only device build remains the existing command:

```bash
CardputerZero=y scons -j8
```

To make a dual-runtime release, build the two named runtime binaries separately
(use separate build directories or clean between configurations), then assemble
the wrapper and both binaries into one release directory:

```bash
# CardputerZero cross build: primary framebuffer runtime
CardputerZero=y APPSTORE_DISPLAY_BACKEND=fbdev scons -j8

# Native aarch64 Linux build on the Wayland-capable runtime image: optional runtime
APPSTORE_DISPLAY_BACKEND=wayland scons -j8
```

Install the dual-runtime release only when all three files are present:

```bash
install -m 755 M5CardputerZero-AppStore /usr/share/APPLaunch/bin/M5CardputerZero-AppStore
install -m 755 M5CardputerZero-AppStore-fbdev /usr/share/APPLaunch/bin/M5CardputerZero-AppStore-fbdev
install -m 755 M5CardputerZero-AppStore-wayland /usr/share/APPLaunch/bin/M5CardputerZero-AppStore-wayland
```

The framebuffer build deliberately remains the primary cross-compilation
profile. The Wayland build is native because it links against the runtime
image's SDL2/Wayland libraries; requesting it with `CardputerZero=y` fails
early instead of producing an incompatible binary. The stock `setup.ini`
continues to deploy the framebuffer-only artifact; dual-runtime release
assembly and installation are an explicit opt-in.

Default registry:

```text
Expand Down
46 changes: 46 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,54 @@ version_file = local_path.parent.parent / "ext_components" / "cp0_lvgl" / "sdk_v
version = version_file.read_text(encoding="utf-8")
static_lib_path = sdk_path / "github_source" / f"static_lib_{version}"

def requested_display_backend():
value = (
os.environ.get("APPSTORE_DISPLAY_BACKEND") or
os.environ.get("M5APPSTORE_DISPLAY_BACKEND") or
"auto"
).strip().lower()
aliases = {
"": "auto",
"auto": "auto",
"fb": "fbdev",
"fbdev": "fbdev",
"framebuffer": "fbdev",
"direct-fb": "fbdev",
"device": "fbdev",
"sdl": "wayland",
"wayland": "wayland",
"labwc": "wayland",
"x11": "wayland",
"window": "wayland",
}
if value not in aliases:
raise RuntimeError(
"Unsupported APPSTORE_DISPLAY_BACKEND={!r}. "
"Use auto, fbdev, sdl, wayland, labwc, or x11.".format(value)
)
return aliases[value]


display_backend = requested_display_backend()
if os.environ.get("CardputerZero", '') == 'y':
if display_backend == "wayland":
raise RuntimeError(
"APPSTORE_DISPLAY_BACKEND=wayland is a native Linux build. "
"The CardputerZero cross build remains the fbdev runtime."
)
os.environ["CONFIG_DEFAULT_FILE"] = "linux_x86_cross_cp0_config_defaults.mk"
if display_backend == "fbdev":
os.environ["APPSTORE_RUNTIME_BINARY"] = "M5CardputerZero-AppStore-fbdev"
elif display_backend == "wayland":
os.environ["CONFIG_DEFAULT_FILE"] = "linux_x86_sdl2_config_defaults.mk"
os.environ["APPSTORE_RUNTIME_BINARY"] = "M5CardputerZero-AppStore-wayland"
elif display_backend == "fbdev":
if os.environ.get("CONFIG_DEFAULT_FILE") is None:
raise RuntimeError(
"APPSTORE_DISPLAY_BACKEND=fbdev requires CardputerZero=y for the "
"cross profile, or an explicit framebuffer CONFIG_DEFAULT_FILE."
)
os.environ["APPSTORE_RUNTIME_BINARY"] = "M5CardputerZero-AppStore-fbdev"

if os.environ.get("CONFIG_DEFAULT_FILE") == None:
if platform.machine() == 'x86_64':
Expand Down
58 changes: 58 additions & 0 deletions bin/M5CardputerZero-AppStore
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/sh
# SPDX-License-Identifier: MIT

set -eu

case "$0" in
*/*) app_dir=${0%/*} ;;
*) app_dir=/usr/share/APPLaunch/bin ;;
esac

backend=${M5APPSTORE_DISPLAY_BACKEND:-${APPSTORE_DISPLAY_BACKEND:-auto}}

case "$backend" in
auto|"")
if [ -n "${WAYLAND_DISPLAY:-}" ] || [ -n "${DISPLAY:-}" ]; then
backend=wayland
else
backend=fbdev
fi
;;
wayland|labwc|sdl|x11|window)
backend=wayland
;;
fb|fbdev|framebuffer|direct-fb|device)
backend=fbdev
;;
*)
echo "M5CardputerZero-AppStore: unsupported M5APPSTORE_DISPLAY_BACKEND=$backend" >&2
exit 2
;;
esac

if [ "$backend" = "wayland" ]; then
binary=$app_dir/M5CardputerZero-AppStore-wayland
if [ ! -x "$binary" ]; then
echo "M5CardputerZero-AppStore: Wayland/SDL runtime is not installed: $binary" >&2
exit 127
fi
if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -z "${SDL_VIDEODRIVER:-}" ]; then
export SDL_VIDEODRIVER=wayland
fi
export SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR=0
export SDL_VIDEO_WAYLAND_PREFER_LIBDECOR=0
exec "$binary" "$@"
fi

binary=$app_dir/M5CardputerZero-AppStore-fbdev
if [ ! -x "$binary" ]; then
legacy=$app_dir/M5CardputerZero-AppStore.bin
if [ -x "$legacy" ]; then
binary=$legacy
else
echo "M5CardputerZero-AppStore: framebuffer runtime is not installed: $binary" >&2
exit 127
fi
fi

exec "$binary" "$@"
9 changes: 8 additions & 1 deletion main/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ APPSTORE_STATIC_ROOT = os.path.join(PROJECT_ROOT, "APPLaunch")
APPLAUNCH_STATIC_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, "..", "APPLaunch", "APPLaunch"))
STAGED_STATIC_ROOT = os.path.abspath(os.path.join("build", "static"))
STAGED_APPLAUNCH = os.path.join(STAGED_STATIC_ROOT, "APPLaunch")
RUNTIME_BINARY = os.environ.get("APPSTORE_RUNTIME_BINARY", "M5CardputerZero-AppStore")
RUNTIME_WRAPPER = os.path.join(PROJECT_ROOT, "bin", "M5CardputerZero-AppStore")


def stage_appstore_static_files():
Expand Down Expand Up @@ -65,6 +67,8 @@ def stage_appstore_static_files():

stage_appstore_static_files()
STATIC_FILES += [(STAGED_APPLAUNCH, "APPLaunch")]
if RUNTIME_BINARY != "M5CardputerZero-AppStore":
STATIC_FILES += [(RUNTIME_WRAPPER, "M5CardputerZero-AppStore")]

def pkg_config_flags(kind, package):
try:
Expand Down Expand Up @@ -108,6 +112,8 @@ if FREETYPE_INCLUDE:
if "CONFIG_V9_5_LV_USE_FREETYPE" in os.environ:
REQUIREMENTS += ["freetype"]
if "CONFIG_V9_5_LV_USE_SDL" in os.environ:
REQUIREMENTS += ["SDL2"]
INCLUDE += ["/usr/include/SDL2"]
lvgl_component["REQUIREMENTS"] += ["SDL2"]
lvgl_component["INCLUDE"] += ["/usr/include/SDL2"]
for sdl_include in [
Expand All @@ -117,6 +123,7 @@ if "CONFIG_V9_5_LV_USE_SDL" in os.environ:
"/usr/local/include/SDL2",
]:
if os.path.exists(sdl_include):
INCLUDE += [sdl_include]
lvgl_component["INCLUDE"] += [sdl_include]
for sdl_lib in ["/opt/homebrew/lib", "/usr/local/lib"]:
if os.path.exists(sdl_lib):
Expand All @@ -130,7 +137,7 @@ if "CONFIG_V9_5_LV_USE_SDL" in os.environ:

env["COMPONENTS"].append(
{
"target": "M5CardputerZero-AppStore",
"target": RUNTIME_BINARY,
"SRCS": SRCS,
"INCLUDE": INCLUDE,
"PRIVATE_INCLUDE": PRIVATE_INCLUDE,
Expand Down
13 changes: 13 additions & 0 deletions main/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "hal/hal_paths.h"
#endif

#include <cstdlib>
#include <string>

namespace {
Expand All @@ -20,6 +21,17 @@ std::string executable_dir(const char *path)
return slash == 0 ? "/" : value.substr(0, slash);
}

#ifdef CONFIG_V9_5_LV_USE_SDL
void configure_sdl_runtime()
{
// These must be configured before cp0_lvgl_run_app() initializes SDL.
setenv("SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR", "0", 0);
setenv("SDL_VIDEO_WAYLAND_PREFER_LIBDECOR", "0", 0);
setenv("SDL_VIDEO_WAYLAND_WMCLASS", "cardputerzero-appstore", 0);
setenv("SDL_VIDEO_X11_WMCLASS", "cardputerzero-appstore", 0);
}
#endif

} // namespace

int main(int argc, char **argv)
Expand All @@ -29,6 +41,7 @@ int main(int argc, char **argv)
if (appstore::run_backend_process_mode(argc, argv, &backend_exit_code))
return backend_exit_code;
#ifdef CONFIG_V9_5_LV_USE_SDL
configure_sdl_runtime();
const std::string directory = executable_dir(argv && argv[0] ? argv[0] : nullptr);
hal_paths_init(directory.c_str());
#endif
Expand Down
24 changes: 24 additions & 0 deletions main/ui/appstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
#include "cp0_lvgl_app_runner.hpp"
#include "hal_lvgl_bsp.h"

#ifdef CONFIG_V9_5_LV_USE_SDL
#include <SDL.h>
#include "lvgl/src/drivers/sdl/lv_sdl_window.h"
#endif

#include <cstdarg>
#include <csignal>
#include <cstdio>
Expand Down Expand Up @@ -111,6 +116,22 @@ void handle_signal(int)
request_quit();
}

#ifdef CONFIG_V9_5_LV_USE_SDL
void configure_sdl_window()
{
lv_display_t *display = lv_display_get_default();
SDL_Window *window = display ? lv_sdl_window_get_window(display) : nullptr;
if (!window) return;
SDL_HideWindow(window);
SDL_SetWindowTitle(window, "AppStore");
SDL_SetWindowBordered(window, SDL_FALSE);
SDL_SetWindowResizable(window, SDL_FALSE);
SDL_SetWindowMinimumSize(window, kScreenWidth, kScreenHeight);
SDL_SetWindowSize(window, kScreenWidth, kScreenHeight);
SDL_ShowWindow(window);
}
#endif

class AppStoreSyncTopBarComponent final : public AppTopBarComponent
{
public:
Expand Down Expand Up @@ -588,6 +609,9 @@ void ui_init(int argc, char **argv)
std::signal(SIGINT, handle_signal);
std::signal(SIGTERM, handle_signal);
cp0_zmq_log_init();
#ifdef CONFIG_V9_5_LV_USE_SDL
configure_sdl_window();
#endif
if (auto *app = appstore_ui::AppStoreApp::current())
app->lifecycle().initialize(argc, argv);
}
Expand Down
2 changes: 2 additions & 0 deletions tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ set -eu
build_dir="${TMPDIR:-/tmp}/appstore-tests"
mkdir -p "$build_dir"
"$(dirname "$0")/test_backend_boundaries.sh"
"$(dirname "$0")/test_runtime_deployment.sh"
"$(dirname "$0")/test_runtime_launcher.sh"
${CXX:-g++} -std=c++17 -Wall -Wextra -Werror \
-I"$(dirname "$0")/../main/include" -I"$(dirname "$0")/../main/interface" -I"$(dirname "$0")/../main/ui" \
"$(dirname "$0")/test_appstore_paths.cpp" \
Expand Down
19 changes: 19 additions & 0 deletions tests/test_runtime_deployment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
set -eu

project_dir=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
setup_ini="$project_dir/setup.ini"
after_cmd=$(sed -n 's/^after_cmd = //p' "$setup_ini")

case "$after_cmd" in
*M5CardputerZero-AppStore-wayland*|*M5CardputerZero-AppStore-fbdev*)
echo "default setup.ini deployment must remain framebuffer-only" >&2
exit 1
;;
esac

printf '%s\n' "$after_cmd" |
grep -F 'install -m 755 /home/pi/dist/M5CardputerZero-AppStore /usr/share/APPLaunch/bin/M5CardputerZero-AppStore' \
>/dev/null

echo "default framebuffer deployment test passed"
Loading