Smart tab5keyboard (#533)
This commit is contained in:
@@ -16,9 +16,12 @@
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h"
|
||||
#include <Tactility/kernel/PanicHandler.h>
|
||||
#include <esp_system.h>
|
||||
#include <sdkconfig.h>
|
||||
#else
|
||||
#define CONFIG_TT_SPLASH_DURATION 0
|
||||
@@ -36,6 +39,11 @@ static std::shared_ptr<hal::display::DisplayDevice> getHalDisplay() {
|
||||
|
||||
class BootApp : public App {
|
||||
|
||||
// Snapshot of hal::usb::isUsbBootMode(), taken before the boot thread starts and
|
||||
// potentially clears the underlying flag via setupUsbBootMode()/resetUsbBootMode().
|
||||
// onShow() reads this instead of the live flag to avoid a race between the two.
|
||||
static std::atomic<bool> isUsbBootSplash;
|
||||
|
||||
Thread thread = Thread(
|
||||
"boot",
|
||||
5120,
|
||||
@@ -76,12 +84,12 @@ class BootApp : public App {
|
||||
auto mode = hal::usb::getUsbBootMode(); // Get mode before reset
|
||||
hal::usb::resetUsbBootMode();
|
||||
if (mode == hal::usb::BootMode::Flash) {
|
||||
if (!hal::usb::startMassStorageWithFlash()) {
|
||||
if (!hal::usb::startMassStorageWithFlash(true)) {
|
||||
LOGGER.error("Unable to start flash mass storage");
|
||||
return false;
|
||||
}
|
||||
} else if (mode == hal::usb::BootMode::Sdmmc) {
|
||||
if (!hal::usb::startMassStorageWithSdmmc()) {
|
||||
if (!hal::usb::startMassStorageWithSdmmc(true)) {
|
||||
LOGGER.error("Unable to start SD mass storage");
|
||||
return false;
|
||||
}
|
||||
@@ -174,6 +182,9 @@ class BootApp : public App {
|
||||
public:
|
||||
|
||||
void onCreate(AppContext& app) override {
|
||||
// Snapshot before the boot thread potentially clears the flag via setupUsbBootMode()
|
||||
isUsbBootSplash = hal::usb::isUsbBootMode();
|
||||
|
||||
// Just in case this app is somehow resumed
|
||||
if (thread.getState() == Thread::State::Stopped) {
|
||||
thread.start();
|
||||
@@ -197,16 +208,31 @@ public:
|
||||
const char* logo;
|
||||
// TODO: Replace with automatic asset buckets like on Android
|
||||
if (getSmallestDimension() < 150) { // e.g. Cardputer
|
||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo_small.png";
|
||||
logo = isUsbBootSplash ? "logo_usb.png" : "logo_small.png";
|
||||
} else {
|
||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo.png";
|
||||
logo = isUsbBootSplash ? "logo_usb.png" : "logo.png";
|
||||
}
|
||||
const auto logo_path = lvgl::PATH_PREFIX + paths->getAssetsPath(logo);
|
||||
LOGGER.info("{}", logo_path);
|
||||
lv_image_set_src(image, logo_path.c_str());
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (isUsbBootSplash) {
|
||||
auto* button = lv_button_create(parent);
|
||||
lv_obj_align(button, LV_ALIGN_BOTTOM_MID, 0, -16);
|
||||
auto* label = lv_label_create(button);
|
||||
lv_label_set_text(label, "Return to OS");
|
||||
lv_obj_add_event_cb(button, [](lv_event_t*) {
|
||||
hal::usb::stop();
|
||||
esp_restart();
|
||||
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<bool> BootApp::isUsbBootSplash = false;
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.appId = "Boot",
|
||||
.appName = "Boot",
|
||||
|
||||
@@ -232,7 +232,7 @@ void I2cScannerApp::onScanTimer() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t address = 0; address < 128; ++address) {
|
||||
for (uint8_t address = 1; address < 128; ++address) {
|
||||
if (i2c_controller_has_device_at_address(safe_port, address, 10 / portTICK_PERIOD_MS) == ERROR_NONE) {
|
||||
logger.info("Found device at address 0x{:02X}", address);
|
||||
if (!shouldStopScanTimer()) {
|
||||
|
||||
@@ -26,6 +26,11 @@ static uint32_t getButtonPadding(UiDensity density, uint32_t buttonSize) {
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) {
|
||||
const int32_t usable = std::max<int32_t>(0, available_span - (3 * total_button_size));
|
||||
return std::min<int32_t>(usable / 16, total_button_size / 2);
|
||||
}
|
||||
|
||||
class LauncherApp final : public App {
|
||||
|
||||
static lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
|
||||
@@ -84,6 +89,50 @@ class LauncherApp final : public App {
|
||||
}
|
||||
}
|
||||
|
||||
// The screen object outlives the launcher's views (it's recreated by GuiService::redraw()
|
||||
// via lv_obj_clean() on every app switch), so the LV_EVENT_SIZE_CHANGED callback registered
|
||||
// on it must be removed once buttons_wrapper is destroyed, to avoid a dangling user-data
|
||||
// pointer on the next rotation while a different app is visible.
|
||||
static void onButtonsWrapperDeleted(lv_event_t* e) {
|
||||
auto* buttons_wrapper = lv_event_get_target_obj(e);
|
||||
auto* screen = lv_obj_get_screen(buttons_wrapper);
|
||||
lv_obj_remove_event_cb_with_user_data(screen, onButtonsWrapperResized, buttons_wrapper);
|
||||
}
|
||||
|
||||
// Re-applies the flex direction and per-button margins when the display orientation
|
||||
// changes while the launcher is the visible app (these are decided once at onShow()
|
||||
// based on the resolution at that time, so a later rotation needs this to catch up).
|
||||
static void onButtonsWrapperResized(lv_event_t* e) {
|
||||
auto* buttons_wrapper = static_cast<lv_obj_t*>(lv_event_get_user_data(e));
|
||||
const auto* display = lv_obj_get_display(buttons_wrapper);
|
||||
|
||||
const auto button_size = lvgl_get_launcher_icon_font_height();
|
||||
const auto button_padding = getButtonPadding(lvgl_get_ui_density(), button_size);
|
||||
const auto total_button_size = button_size + (button_padding * 2);
|
||||
|
||||
const auto horizontal_px = lv_display_get_horizontal_resolution(display);
|
||||
const auto vertical_px = lv_display_get_vertical_resolution(display);
|
||||
const bool is_landscape_display = horizontal_px >= vertical_px;
|
||||
const auto current_flow = lv_obj_get_style_flex_flow(buttons_wrapper, LV_PART_MAIN);
|
||||
const bool was_landscape = current_flow == LV_FLEX_FLOW_ROW;
|
||||
if (is_landscape_display == was_landscape) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_set_flex_flow(buttons_wrapper, is_landscape_display ? LV_FLEX_FLOW_ROW : LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
const int32_t margin = is_landscape_display
|
||||
? computeButtonMargin(horizontal_px, total_button_size)
|
||||
: computeButtonMargin(vertical_px, total_button_size);
|
||||
|
||||
const uint32_t child_count = lv_obj_get_child_count(buttons_wrapper);
|
||||
for (uint32_t i = 0; i < child_count; i++) {
|
||||
auto* button = lv_obj_get_child(buttons_wrapper, i);
|
||||
lv_obj_set_style_margin_hor(button, is_landscape_display ? margin : 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_margin_ver(button, is_landscape_display ? 0 : margin, LV_STATE_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void onCreate(AppContext& app) override {
|
||||
@@ -133,19 +182,20 @@ public:
|
||||
lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
}
|
||||
|
||||
int32_t margin;
|
||||
if (is_landscape_display) {
|
||||
const int32_t available_width = std::max<int32_t>(0, lv_display_get_horizontal_resolution(display) - (3 * total_button_size));
|
||||
margin = std::min<int32_t>(available_width / 16, total_button_size / 2);
|
||||
} else {
|
||||
const int32_t available_height = std::max<int32_t>(0, lv_display_get_vertical_resolution(display) - (3 * total_button_size));
|
||||
margin = std::min<int32_t>(available_height / 16, total_button_size / 2);
|
||||
}
|
||||
const int32_t margin = is_landscape_display
|
||||
? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size)
|
||||
: computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size);
|
||||
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "AppList", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "Files", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
||||
|
||||
// The launcher's container is several levels below the screen, and LVGL only sends
|
||||
// LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the
|
||||
// handler is attached there, with buttons_wrapper passed through as user data.
|
||||
lv_obj_add_event_cb(lv_obj_get_screen(parent), onButtonsWrapperResized, LV_EVENT_SIZE_CHANGED, buttons_wrapper);
|
||||
lv_obj_add_event_cb(buttons_wrapper, onButtonsWrapperDeleted, LV_EVENT_DELETE, nullptr);
|
||||
|
||||
if (shouldShowPowerButton()) {
|
||||
auto* power_button = lv_button_create(parent);
|
||||
lv_obj_set_style_pad_all(power_button, 8, 0);
|
||||
|
||||
@@ -69,13 +69,13 @@ bool isSupported() {
|
||||
return tusbIsSupported();
|
||||
}
|
||||
|
||||
bool startMassStorageWithSdmmc() {
|
||||
bool startMassStorageWithSdmmc(bool fromBootMode) {
|
||||
if (!canStartNewMode()) {
|
||||
LOGGER.error("Can't start");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tusbStartMassStorageWithSdmmc()) {
|
||||
if (tusbStartMassStorageWithSdmmc(fromBootMode)) {
|
||||
currentMode = Mode::MassStorageSdmmc;
|
||||
return true;
|
||||
} else {
|
||||
@@ -110,13 +110,13 @@ void rebootIntoMassStorageSdmmc() {
|
||||
}
|
||||
|
||||
// NEW: Flash mass storage functions
|
||||
bool startMassStorageWithFlash() {
|
||||
bool startMassStorageWithFlash(bool fromBootMode) {
|
||||
if (!canStartNewMode()) {
|
||||
LOGGER.error("Can't start flash mass storage");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tusbStartMassStorageWithFlash()) {
|
||||
if (tusbStartMassStorageWithFlash(fromBootMode)) {
|
||||
currentMode = Mode::MassStorageFlash;
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace tt::hal::usb {
|
||||
|
||||
bool startMassStorageWithSdmmc() { return false; }
|
||||
bool startMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
|
||||
void stop() {}
|
||||
Mode getMode() { return Mode::Default; }
|
||||
BootMode getUsbBootMode() { return BootMode::None; }
|
||||
@@ -12,7 +12,7 @@ bool isSupported() { return false; }
|
||||
|
||||
bool canRebootIntoMassStorageSdmmc() { return false; }
|
||||
void rebootIntoMassStorageSdmmc() {}
|
||||
bool startMassStorageWithFlash() { return false; }
|
||||
bool startMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
|
||||
bool canRebootIntoMassStorageFlash() { return false; }
|
||||
void rebootIntoMassStorageFlash() {}
|
||||
bool isUsbBootMode() { return false; }
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#if CONFIG_TINYUSB_MSC_ENABLED == 1
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
#include <esp_system.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <tinyusb.h>
|
||||
#include <tusb_msc_storage.h>
|
||||
#include <wear_levelling.h>
|
||||
@@ -26,6 +29,10 @@ namespace tt::hal::usb {
|
||||
extern sdmmc_card_t* getCard();
|
||||
}
|
||||
|
||||
// Set when mass storage was started as part of the dedicated reboot-into-MSC boot flow.
|
||||
// Used to decide whether ejecting the volume should automatically reboot back to normal OS.
|
||||
static bool startedFromBootMode = false;
|
||||
|
||||
enum {
|
||||
ITF_NUM_MSC = 0,
|
||||
ITF_NUM_TOTAL
|
||||
@@ -99,6 +106,15 @@ static uint8_t const msc_hs_configuration_desc[] = {
|
||||
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
||||
if (event->mount_changed_data.is_mounted) {
|
||||
LOGGER.info("MSC Mounted");
|
||||
// Storage is only (re)mounted into our own filesystem after the host sends a SCSI
|
||||
// START STOP UNIT eject (see tud_msc_start_stop_cb() in tusb_msc_storage.c). Windows
|
||||
// is known not to send this reliably, so this is a best-effort path for hosts that do
|
||||
// (e.g. Linux/macOS) - the "Return to OS" button on the boot screen is the primary one.
|
||||
// If we got here while booted into MSC mode, it's safe to reboot back into normal OS now.
|
||||
if (startedFromBootMode) {
|
||||
LOGGER.info("MSC ejected by host, rebooting into normal OS");
|
||||
esp_restart();
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("MSC Unmounted");
|
||||
}
|
||||
@@ -147,8 +163,11 @@ static bool ensureDriverInstalled() {
|
||||
|
||||
bool tusbIsSupported() { return true; }
|
||||
|
||||
bool tusbStartMassStorageWithSdmmc() {
|
||||
ensureDriverInstalled();
|
||||
bool tusbStartMassStorageWithSdmmc(bool fromBootMode) {
|
||||
if (!ensureDriverInstalled()) {
|
||||
return false;
|
||||
}
|
||||
startedFromBootMode = fromBootMode;
|
||||
|
||||
auto* card = tt::hal::usb::getCard();
|
||||
if (card == nullptr) {
|
||||
@@ -179,9 +198,12 @@ bool tusbStartMassStorageWithSdmmc() {
|
||||
return result == ESP_OK;
|
||||
}
|
||||
|
||||
bool tusbStartMassStorageWithFlash() {
|
||||
bool tusbStartMassStorageWithFlash(bool fromBootMode) {
|
||||
LOGGER.info("Starting flash MSC");
|
||||
ensureDriverInstalled();
|
||||
if (!ensureDriverInstalled()) {
|
||||
return false;
|
||||
}
|
||||
startedFromBootMode = fromBootMode;
|
||||
|
||||
wl_handle_t handle = tt::getDataPartitionWlHandle();
|
||||
if (handle == WL_INVALID_HANDLE) {
|
||||
@@ -212,6 +234,12 @@ bool tusbStartMassStorageWithFlash() {
|
||||
}
|
||||
|
||||
void tusbStop() {
|
||||
// Actively signal a disconnect to the host before tearing down the peripheral, otherwise
|
||||
// a subsequent esp_restart() resets the chip too fast for the host to notice the device
|
||||
// went away, leaving it stuck showing the old MSC device until the cable is replugged.
|
||||
tud_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
|
||||
tinyusb_msc_storage_deinit();
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
||||
@@ -225,8 +253,8 @@ bool tusbCanStartMassStorageWithFlash() {
|
||||
#else
|
||||
|
||||
bool tusbIsSupported() { return false; }
|
||||
bool tusbStartMassStorageWithSdmmc() { return false; }
|
||||
bool tusbStartMassStorageWithFlash() { return false; }
|
||||
bool tusbStartMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; }
|
||||
bool tusbStartMassStorageWithFlash(bool /*fromBootMode*/) { return false; }
|
||||
void tusbStop() {}
|
||||
bool tusbCanStartMassStorageWithFlash() { return false; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user