Project restructuring (fixes macOS builds) (#198)

- Create `Include/` folder for all main projects
- Fix some issues here and there (found while moving things)
- All includes are now in `Tactility/` subfolder and must be included with that prefix. This fixes issues with clashing POSIX headers (e.g. `<semaphore.h>` versus Tactility's `Semaphore.h`)
This commit is contained in:
Ken Van Hoeylandt
2025-02-01 18:13:20 +01:00
committed by GitHub
parent 7856827ecf
commit c87200a80d
350 changed files with 967 additions and 870 deletions
@@ -0,0 +1,92 @@
#pragma once
#include "Tactility/app/AppContext.h"
#include "Tactility/app/AppManifest.h"
#include "Tactility/app/ElfApp.h"
#include <Tactility/Bundle.h>
#include <Tactility/Mutex.h>
#include <memory>
#include <utility>
namespace tt::app {
enum class State {
Initial, // App is being activated in loader
Started, // App is in memory
Showing, // App view is created
Hiding, // App view is destroyed
Stopped // App is not in memory
};
/**
* Thread-safe app instance.
*/
class AppInstance : public AppContext {
private:
Mutex mutex = Mutex(Mutex::Type::Normal);
const std::shared_ptr<AppManifest> manifest;
State state = State::Initial;
Flags flags = { .showStatusbar = true };
/** @brief Optional parameters to start the app with
* When these are stored in the app struct, the struct takes ownership.
* Do not mutate after app creation.
*/
std::shared_ptr<const tt::Bundle> _Nullable parameters;
std::shared_ptr<App> app;
static std::shared_ptr<app::App> createApp(
const std::shared_ptr<app::AppManifest>& manifest
) {
if (manifest->location.isInternal()) {
assert(manifest->createApp != nullptr);
return manifest->createApp();
} else if (manifest->location.isExternal()) {
if (manifest->createApp != nullptr) {
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
}
#ifdef ESP_PLATFORM
return app::createElfApp(manifest);
#else
tt_crash("not supported");
#endif
} else {
tt_crash("not implemented");
}
}
public:
explicit AppInstance(const std::shared_ptr<AppManifest>& manifest) :
manifest(manifest),
app(createApp(manifest))
{}
AppInstance(const std::shared_ptr<AppManifest>& manifest, std::shared_ptr<const Bundle> parameters) :
manifest(manifest),
parameters(std::move(parameters)),
app(createApp(manifest)) {}
~AppInstance() override = default;
void setState(State state);
State getState() const;
const AppManifest& getManifest() const override;
Flags getFlags() const;
void setFlags(Flags flags);
Flags& mutableFlags() { return flags; } // TODO: locking mechanism
std::shared_ptr<const Bundle> getParameters() const override;
std::unique_ptr<Paths> getPaths() const override;
std::shared_ptr<App> getApp() const override { return app; }
};
} // namespace
@@ -0,0 +1,28 @@
#pragma once
#include "Tactility/app/AppInstance.h"
namespace tt::app {
class AppInstancePaths final : public Paths {
private:
const AppManifest& manifest;
public:
explicit AppInstancePaths(const AppManifest& manifest) : manifest(manifest) {}
~AppInstancePaths() final = default;
std::string getDataDirectory() const final;
std::string getDataDirectoryLvgl() const final;
std::string getDataPath(const std::string& childPath) const final;
std::string getDataPathLvgl(const std::string& childPath) const final;
std::string getSystemDirectory() const final;
std::string getSystemDirectoryLvgl() const final;
std::string getSystemPath(const std::string& childPath) const final;
std::string getSystemPathLvgl(const std::string& childPath) const final;
};
}
@@ -0,0 +1,11 @@
#pragma once
#ifdef ESP_PLATFORM
namespace tt::app::crashdiagnostics {
void start();
}
#endif
@@ -0,0 +1,5 @@
#pragma once
#include <cstdio>
bool getQrVersionForBinaryDataLength(size_t inLength, int& outVersion);
@@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getUrlFromCrashData();
@@ -0,0 +1,64 @@
#pragma once
#include <dirent.h>
#include <string>
#include <vector>
namespace tt::app::files {
/** File types for `dirent`'s `d_type`. */
enum {
TT_DT_UNKNOWN = 0,
#define TT_DT_UNKNOWN TT_DT_UNKNOWN // Unknown type
TT_DT_FIFO = 1,
#define TT_DT_FIFO TT_DT_FIFO // Named pipe or FIFO
TT_DT_CHR = 2,
#define TT_DT_CHR TT_DT_CHR // Character device
TT_DT_DIR = 4,
#define TT_DT_DIR TT_DT_DIR // Directory
TT_DT_BLK = 6,
#define TT_DT_BLK TT_DT_BLK // Block device
TT_DT_REG = 8,
#define TT_DT_REG TT_DT_REG // Regular file
TT_DT_LNK = 10,
#define TT_DT_LNK TT_DT_LNK // Symbolic link
TT_DT_SOCK = 12,
#define TT_DT_SOCK TT_DT_SOCK // Local-domain socket
TT_DT_WHT = 14
#define TT_DT_WHT TT_DT_WHT // Whiteout inodes
};
std::string getChildPath(const std::string& basePath, const std::string& childPath);
typedef int (*ScandirFilter)(const struct dirent*);
typedef bool (*ScandirSort)(const struct dirent&, const struct dirent&);
bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right);
int dirent_filter_dot_entries(const struct dirent* entry);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.
* It returns an allocated output array with allocated dirent instances.
* The caller is responsible for free-ing the memory of these.
*
* @param[in] path path the scan for files and directories
* @param[out] outList a pointer to vector of dirent
* @param[in] filter an optional filter to filter out specific items
* @param[in] sort an optional sorting function
* @return the amount of items that were stored in "output" or -1 when an error occurred
*/
int scandir(
const std::string& path,
std::vector<dirent>& outList,
ScandirFilter _Nullable filter,
ScandirSort _Nullable sort
);
bool isSupportedExecutableFile(const std::string& filename);
bool isSupportedImageFile(const std::string& filename);
bool isSupportedTextFile(const std::string& filename);
} // namespace
@@ -0,0 +1,73 @@
#pragma once
#include <Tactility/Mutex.h>
#include <string>
#include <vector>
#include <dirent.h>
namespace tt::app::files {
class State {
public:
enum PendingAction {
ActionNone,
ActionDelete,
ActionRename
};
private:
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::vector<dirent> dir_entries;
std::string current_path;
std::string selected_child_entry;
PendingAction action = ActionNone;
public:
State();
void freeEntries() {
dir_entries.clear();
}
~State() {
freeEntries();
}
bool setEntriesForChildPath(const std::string& child_path);
bool setEntriesForPath(const std::string& path);
template <std::invocable<const std::vector<dirent> &> Func>
void withEntries(Func&& onEntries) const {
mutex.withLock([&]() {
std::invoke(std::forward<Func>(onEntries), dir_entries);
});
}
bool getDirent(uint32_t index, dirent& dirent);
void setSelectedChildEntry(const std::string& newFile) {
selected_child_entry = newFile;
action = ActionNone;
}
std::string getSelectedChildEntry() const { return selected_child_entry; }
std::string getCurrentPath() const { return current_path; }
std::string getSelectedChildPath() const;
PendingAction getPendingAction() const {
return action;
}
void setPendingAction(PendingAction newAction) {
action = newAction;
}
};
}
@@ -0,0 +1,42 @@
#pragma once
#include "./State.h"
#include "Tactility/app/AppManifest.h"
#include <lvgl.h>
#include <memory>
namespace tt::app::files {
class View {
std::shared_ptr<State> state;
lv_obj_t* dir_entry_list = nullptr;
lv_obj_t* action_list = nullptr;
lv_obj_t* navigate_up_button = nullptr;
void showActionsForDirectory();
void showActionsForFile();
void viewFile(const std::string&path, const std::string&filename);
void createDirEntryWidget(lv_obj_t* parent, struct dirent& dir_entry);
void onNavigate();
public:
explicit View(const std::shared_ptr<State>& state) : state(state) {}
void init(lv_obj_t* parent);
void update();
void onNavigateUpPressed();
void onDirEntryPressed(uint32_t index);
void onDirEntryLongPressed(int32_t index);
void onRenamePressed();
void onDeletePressed();
void onDirEntryListScrollBegin();
void onResult(Result result, std::unique_ptr<Bundle> bundle);
};
}
@@ -0,0 +1,13 @@
#pragma once
#ifdef ESP_PLATFORM
#include "driver/gpio.h"
#define GPIO_NUM_MIN GPIO_NUM_0
#else
#define GPIO_NUM_MIN 0
#define GPIO_NUM_MAX 50
#endif
#ifndef ESP_PLATFORM
int gpio_get_level(int gpio_num);
#endif
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <cstdint>
namespace tt::app::i2cscanner {
std::string getAddressText(uint8_t address);
std::string getPortNamesForDropdown();
}
@@ -0,0 +1,15 @@
#pragma once
#include <TactilityCore.h>
#include <Mutex.h>
#include <Thread.h>
#include "lvgl.h"
#include <Tactility/hal/i2c/I2c.h>
#include "Timer.h"
#include <memory>
namespace tt::app::i2cscanner {
void start();
}
@@ -0,0 +1,28 @@
#pragma once
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/Mutex.h>
#include <Tactility/TactilityCore.h>
#include <Tactility/Thread.h>
#include <Tactility/Timer.h>
#include <lvgl.h>
#include <memory>
namespace tt::app::i2cscanner {
#define TAG "i2cscanner"
enum ScanState {
ScanStateInitial,
ScanStateScanning,
ScanStateStopped
};
struct Data {
};
void onScanTimerFinished(std::shared_ptr<Data> data);
}
@@ -0,0 +1,12 @@
#pragma once
#include "./I2cScannerPrivate.h"
#include <memory>
namespace tt::app::i2cscanner {
bool hasScanTimer(std::shared_ptr<Data> data);
void startScanning(std::shared_ptr<Data> data);
void stopScanning(std::shared_ptr<Data> data);
}
@@ -0,0 +1,7 @@
#pragma once
namespace tt::app::launcher {
void start();
}
@@ -0,0 +1,7 @@
#pragma once
namespace tt::app::timedatesettings {
void start();
}
@@ -0,0 +1,12 @@
#pragma once
#include <Tactility/Bundle.h>
namespace tt::app::timezone {
void start();
std::string getResultName(const Bundle& bundle);
std::string getResultCode(const Bundle& bundle);
}
@@ -0,0 +1,11 @@
#pragma once
#include <Tactility/Bundle.h>
#include <Tactility/Mutex.h>
#include <Tactility/service/wifi/Wifi.h>
namespace tt::app::wifiapsettings {
void start(const std::string& ssid);
} // namespace
@@ -0,0 +1,14 @@
#pragma once
#include "Tactility/service/wifi/WifiSettings.h"
namespace tt::app::wificonnect {
typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings* settings, bool store, void* context);
typedef struct {
OnConnectSsid onConnectSsid;
void* onConnectSsidContext;
} Bindings;
} // namespace
@@ -0,0 +1,29 @@
#pragma once
#include <Tactility/Mutex.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/service/wifi/WifiSettings.h>
namespace tt::app::wificonnect {
class State {
Mutex lock;
service::wifi::settings::WifiApSettings apSettings = {
.ssid = { 0 },
.password = { 0 },
.auto_connect = false
};
bool connectionError = false;
bool connecting = false;
public:
void setConnectionError(bool error);
bool hasConnectionError() const;
void setApSettings(const service::wifi::settings::WifiApSettings* newSettings);
void setConnecting(bool isConnecting);
bool isConnecting() const;
};
} // namespace
@@ -0,0 +1,47 @@
#pragma once
#include "./Bindings.h"
#include "./State.h"
#include "Tactility/app/AppContext.h"
#include <lvgl.h>
namespace tt::app::wificonnect {
class WifiConnect;
class View {
private:
Bindings* bindings;
State* state;
public:
lv_obj_t* ssid_textarea = nullptr;
lv_obj_t* ssid_error = nullptr;
lv_obj_t* password_textarea = nullptr;
lv_obj_t* password_error = nullptr;
lv_obj_t* connect_button = nullptr;
lv_obj_t* remember_switch = nullptr;
lv_obj_t* connecting_spinner = nullptr;
lv_obj_t* connection_error = nullptr;
lv_group_t* group = nullptr;
View(Bindings* bindings, State* state) :
bindings(bindings),
state(state)
{}
void init(AppContext& app, lv_obj_t* parent);
void update();
void createBottomButtons(lv_obj_t* parent);
void setLoading(bool loading);
void resetErrors();
};
} // namespace
@@ -0,0 +1,55 @@
#pragma once
#include "Tactility/app/App.h"
#include "Tactility/app/wificonnect/Bindings.h"
#include "Tactility/app/wificonnect/State.h"
#include "Tactility/app/wificonnect/View.h"
#include <Tactility/Mutex.h>
#include <Tactility/service/wifi/Wifi.h>
namespace tt::app::wificonnect {
class WifiConnect : public App {
private:
Mutex mutex;
State state;
Bindings bindings = {
.onConnectSsid = nullptr,
.onConnectSsidContext = nullptr
};
View view = View(&bindings, &state);
PubSub::SubscriptionHandle wifiSubscription;
bool view_enabled = false;
public:
WifiConnect();
~WifiConnect();
void lock();
void unlock();
void onShow(AppContext& app, lv_obj_t* parent) override;
void onHide(AppContext& app) override;
State& getState() { return state; }
Bindings& getBindings() { return bindings; }
View& getView() { return view; }
void requestViewUpdate();
};
/**
* Start the app with optional pre-filled fields.
*/
void start(const std::string& ssid = "", const std::string& password = "");
bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid);
bool optPasswordParameter(const std::shared_ptr<const Bundle>& bundle, std::string& password);
} // namespace
@@ -0,0 +1,19 @@
#pragma once
namespace tt::app::wifimanage {
typedef void (*OnWifiToggled)(bool enable);
typedef void (*OnConnectSsid)(const char* ssid);
typedef void (*OnDisconnect)();
typedef void (*OnShowApSettings)(const char* ssid);
typedef void (*OnConnectToHidden)();
struct Bindings{
OnWifiToggled onWifiToggled;
OnConnectSsid onConnectSsid;
OnDisconnect onDisconnect;
OnShowApSettings onShowApSettings;
OnConnectToHidden onConnectToHidden;
};
} // namespace
@@ -0,0 +1,44 @@
#pragma once
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/Mutex.h>
namespace tt::app::wifimanage {
/**
* View's state
*/
class State {
Mutex mutex = Mutex(Mutex::Type::Recursive);
bool scanning = false;
bool scannedAfterRadioOn = false;
service::wifi::RadioState radioState;
std::vector<service::wifi::ApRecord> apRecords;
std::string connectSsid;
public:
State() = default;
void setScanning(bool isScanning);
bool isScanning() const;
bool hasScannedAfterRadioOn() const { return scannedAfterRadioOn; }
void setRadioState(service::wifi::RadioState state);
service::wifi::RadioState getRadioState() const;
void updateApRecords();
template <std::invocable<const std::vector<service::wifi::ApRecord>&> Func>
void withApRecords(Func&& onApRecords) const {
mutex.withLock([&]() {
std::invoke(std::forward<Func>(onApRecords), apRecords);
});
}
void setConnectSsid(const std::string& ssid);
std::string getConnectSsid() const;
};
} // namespace
@@ -0,0 +1,42 @@
#pragma once
#include "./Bindings.h"
#include "./State.h"
#include "Tactility/app/AppContext.h"
#include <lvgl.h>
namespace tt::app::wifimanage {
class View {
private:
Bindings* bindings;
State* state;
std::unique_ptr<app::Paths> paths;
lv_obj_t* root = nullptr;
lv_obj_t* enable_switch = nullptr;
lv_obj_t* enable_on_boot_switch = nullptr;
lv_obj_t* scanning_spinner = nullptr;
lv_obj_t* networks_list = nullptr;
lv_obj_t* connect_to_hidden = nullptr;
void updateWifiToggle();
void updateEnableOnBootToggle();
void updateScanning();
void updateNetworkList();
void updateConnectToHidden();
void createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting);
public:
View(Bindings* bindings, State* state) : bindings(bindings), state(state) {}
void init(const AppContext& app, lv_obj_t* parent);
void update();
};
} // namespace
@@ -0,0 +1,41 @@
#pragma once
#include "./View.h"
#include "./State.h"
#include "Tactility/app/App.h"
#include <Tactility/PubSub.h>
#include <Tactility/Mutex.h>
#include <Tactility/service/wifi/Wifi.h>
namespace tt::app::wifimanage {
class WifiManage : public App {
private:
PubSub::SubscriptionHandle wifiSubscription = nullptr;
Mutex mutex;
Bindings bindings = { };
State state;
View view = View(&bindings, &state);
bool isViewEnabled = false;
public:
WifiManage();
void lock();
void unlock();
void onShow(AppContext& app, lv_obj_t* parent) override;
void onHide(AppContext& app) override;
Bindings& getBindings() { return bindings; }
State& getState() { return state; }
void requestViewUpdate();
};
} // namespace
@@ -0,0 +1,9 @@
#pragma once
#include <Tactility/hal/Configuration.h>
namespace tt::lvgl {
void init(const hal::Configuration& config);
} // namespace
@@ -0,0 +1,46 @@
#pragma once
#include <Tactility/MessageQueue.h>
#include <Tactility/Mutex.h>
#include <Tactility/PubSub.h>
#include <Tactility/service/gui/Gui.h>
#include <cstdio>
#include <lvgl.h>
namespace tt::service::gui {
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_EXIT (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
/** Gui structure */
struct Gui {
// Thread and lock
Thread* thread = nullptr;
Mutex mutex = Mutex(Mutex::Type::Recursive);
PubSub::SubscriptionHandle loader_pubsub_subscription = nullptr;
// Layers and Canvas
lv_obj_t* appRootWidget = nullptr;
lv_obj_t* statusbarWidget = nullptr;
// App-specific
std::shared_ptr<app::AppContext> appToRender = nullptr;
lv_obj_t* _Nullable keyboard = nullptr;
lv_group_t* keyboardGroup = nullptr;
};
/** Update GUI, request redraw */
void requestDraw();
/** Lock GUI */
void lock();
/** Unlock GUI */
void unlock();
} // namespace
@@ -0,0 +1,52 @@
#pragma once
#include "Tactility/app/AppInstance.h"
#include "Tactility/app/AppManifest.h"
#include "Tactility/service/loader/Loader.h"
#include <Tactility/DispatcherThread.h>
#include <Tactility/EventFlag.h>
#include <Tactility/MessageQueue.h>
#include <Tactility/PubSub.h>
#include <Tactility/RtosCompatSemaphore.h>
#include <Tactility/Thread.h>
#include <stack>
#include <utility>
namespace tt::service::loader {
// region LoaderMessage
class LoaderMessageAppStart {
public:
std::string id;
std::shared_ptr<const Bundle> _Nullable parameters;
LoaderMessageAppStart() = default;
LoaderMessageAppStart(LoaderMessageAppStart& other) :
id(other.id),
parameters(other.parameters) {}
LoaderMessageAppStart(const std::string& id, std::shared_ptr<const Bundle> parameters) :
id(id),
parameters(std::move(parameters))
{}
~LoaderMessageAppStart() = default;
};
// endregion LoaderMessage
struct Loader {
std::shared_ptr<PubSub> pubsubExternal = std::make_shared<PubSub>();
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::stack<std::shared_ptr<app::AppInstance>> appStack;
/** The dispatcher thread needs a callstack large enough to accommodate all the dispatched methods.
* This includes full LVGL redraw via Gui::redraw()
*/
std::unique_ptr<DispatcherThread> dispatcherThread = std::make_unique<DispatcherThread>("loader_dispatcher", 6144); // Files app requires ~5k
};
} // namespace
@@ -0,0 +1,55 @@
#pragma once
#include "Tactility/TactilityConfig.h"
#if TT_FEATURE_SCREENSHOT_ENABLED
#include "ScreenshotTask.h"
#include <Tactility/Mutex.h>
#include <Tactility/service/Service.h>
namespace tt::service::screenshot {
enum class Mode {
None,
Timed,
Apps
};
class ScreenshotService final : public Service {
private:
Mutex mutex;
std::unique_ptr<ScreenshotTask> task;
Mode mode = Mode::None;
public:
bool isTaskStarted();
/** The state of the service. */
Mode getMode() const;
/** @brief Start taking screenshot whenever an app is started
* @param[in] path the path to store the screenshots at
*/
void startApps(const std::string& path);
/** @brief Start taking screenshots after a certain delay
* @param[in] path the path to store the screenshots at
* @param[in] delayInSeconds the delay before starting (and between successive screenshots)
* @param[in] amount 0 = indefinite, >0 for a specific
*/
void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount);
/** @brief Stop taking screenshots */
void stop();
};
std::shared_ptr<ScreenshotService> _Nullable optScreenshotService();
} // namespace
#endif
@@ -0,0 +1,62 @@
#include "Tactility/TactilityConfig.h"
#if TT_FEATURE_SCREENSHOT_ENABLED
#pragma once
#include <Tactility/Thread.h>
#include <Tactility/Mutex.h>
namespace tt::service::screenshot {
#define TASK_WORK_TYPE_DELAY 1
#define TASK_WORK_TYPE_APPS 2
class ScreenshotTask {
struct ScreenshotTaskWork {
int type = TASK_WORK_TYPE_DELAY ;
uint8_t delay_in_seconds = 0;
uint8_t amount = 0;
std::string path;
};
Thread* thread = nullptr;
Mutex mutex = Mutex(Mutex::Type::Recursive);
bool interrupted = false;
bool finished = false;
ScreenshotTaskWork work;
public:
ScreenshotTask() = default;
~ScreenshotTask();
/** @brief Start taking screenshots after a certain delay
* @param[in] path the path to store the screenshots at
* @param[in] delayInSeconds the delay before starting (and between successive screenshots)
* @param[in] amount 0 = indefinite, >0 for a specific
*/
void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount);
/** @brief Start taking screenshot whenever an app is started
* @param[in] path the path to store the screenshots at
*/
void startApps(const std::string& path);
/** @brief Stop taking screenshots */
void stop();
void taskMain();
bool isFinished();
private:
bool isInterrupted();
void setFinished();
void taskStart();
};
}
#endif