2026-10-07 20:35:26 +02:00

149 lines
4.6 KiB
C++

#include "tactility/memory.h"
#include <Tactility/app/terminal/Terminal.h>
#include <Tactility/app/terminal/TerminalRendererGeneric.h>
#include <Tactility/app/terminal/TerminalRendererLvgl.h>
#include <Tactility/app/terminal/TerminalRendererPpa.h>
#include <tactility/log.h>
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <tactility/device.h>
#include <tactility/drivers/display.h>
#include <tactility/module.h>
#include <lvgl/devices/indev.h>
#include <lvgl/module.h>
#include <lvgl/widgets/toolbar.h>
#include <lvgl_window_manager/window_manager.h>
#include <cstring>
constexpr auto* TAG = "terminal";
namespace tt::app::terminal {
namespace {
struct WindowContext {
TerminalRendererLvgl renderer;
AppInstanceId appInstanceId;
};
// The toolbar's default action closes the topmost app instance, which is the shell app rather than this one.
void onClosePressed(lv_event_t* event) {
auto* ctx = static_cast<WindowContext*>(lv_event_get_user_data(event));
app_event_emit_close(ctx->appInstanceId);
}
void onCanvasClicked(lv_event_t* event) {
auto* toolbar = static_cast<lv_obj_t*>(lv_event_get_user_data(event));
lv_obj_set_flag(toolbar, LV_OBJ_FLAG_HIDDEN, !lv_obj_has_flag(toolbar, LV_OBJ_FLAG_HIDDEN));
}
void createWidgets(lv_obj_t* root, void* userData) {
auto* ctx = static_cast<WindowContext*>(userData);
lv_obj_set_flex_flow(root, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(root, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_row(root, 0, LV_STATE_DEFAULT);
auto* canvas = lv_canvas_create(root);
lv_obj_set_width(canvas, LV_PCT(100));
lv_obj_set_flex_grow(canvas, 1);
// The toolbar only offers a touch close button. It's hidden to give the terminal all the room, and touching the terminal shows it on top.
if (lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
auto* toolbar = lvgl_toolbar_create(root, "Terminal");
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onClosePressed, ctx);
lv_obj_add_flag(toolbar, LV_OBJ_FLAG_FLOATING);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_add_flag(toolbar, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(canvas, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(canvas, onCanvasClicked, LV_EVENT_SHORT_CLICKED, toolbar);
}
ctx->renderer.attachCanvas(canvas);
}
void destroyWidgets(void* userData) {
static_cast<WindowContext*>(userData)->renderer.attachCanvas(nullptr);
}
bool hasArgument(int argc, char* argv[], const char* argument) {
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], argument) == 0) {
return true;
}
}
return false;
}
void runInWindow(Device* display) {
WindowContext ctx { .appInstanceId = app_scheduler_current_app_id() };
WindowId window = window_manager_create_ext(ctx.appInstanceId, createWidgets, destroyWidgets, &ctx);
runTerminal(display, ctx.renderer, false);
window_manager_remove(window);
}
void runFullscreen(Device* display) {
// Stop LVGL, because terminal has custom rendering
auto lvgl_active = module_is_started(&lvgl_module);
if (lvgl_active) {
module_stop(&lvgl_module);
}
TerminalRendererPpa ppaRenderer;
TerminalRendererGeneric genericRenderer;
TerminalRenderer& renderer = TerminalRendererPpa::isSupported()
? static_cast<TerminalRenderer&>(ppaRenderer)
: static_cast<TerminalRenderer&>(genericRenderer);
runTerminal(display, renderer, true);
// If needed, restart LVGL
if (lvgl_active && !module_is_started(&lvgl_module)) {
LOG_I(TAG, "Restarting LVGL");
module_start(&lvgl_module);
}
}
} // namespace
int main(int argc, char* argv[]) {
Device* display_device = nullptr;
if (device_get_first_active_by_type(&DISPLAY_TYPE, &display_device) != ERROR_NONE) {
LOG_E(TAG, "No display device found");
return 0;
}
// The canvas holds a full frame, which needs external RAM on larger displays.
const bool useLvgl = module_is_started(&lvgl_module) &&
memory_external_total() > 0 &&
!hasArgument(argc, argv, "--no-lvgl");
if (useLvgl) {
runInWindow(display_device);
} else {
runFullscreen(display_device);
}
device_put(display_device);
return 0;
}
extern const ::AppManifest manifest = {
.id = "tactility.terminal",
.name = "Terminal",
.category = APP_CATEGORY_SYSTEM,
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(main) },
.flags = 0,
.stack = { .depth = 5120, .desired_memory_capability = 0 },
};
}