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
2 changes: 1 addition & 1 deletion Data/system/app_icon/tactility.maps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Data/system/app_icon/tactility.maps_mono.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions Devices/simulator/Source/SimulatorProfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ constexpr auto* DISPLAY_SETTINGS_PATH = "data/tactility/appdata/tactility.displa
constexpr auto* ORIENTATION_KEY = "orientation=";

static const Profile PROFILES[PROFILE_COUNT] = {
{ "Default", 640, 320, LV_DPI_DEF, false, false, false, true, true },
{ "CL-32", 384, 168, 139, false, true, true, true, false },
{ "LilyGO T-Deck", 320, 240, 143, false, false, false, true, true },
{ "LilyGO T-Lora Pager", 480, 222, 150, false, false, false, true, false },
{ "M5Stack Cardputer", 240, 135, 139, false, true, false, true, false },
{ "M5Stack Tab5", 1280, 720, 250, true, false, false, true, true },
{ "Default", 640, 320, LV_DPI_DEF, false, 100, 100, false, true, true },
{ "CL-32", 384, 168, 139, false, 40, 0, true, true, false },
{ "LilyGO T-Deck", 320, 240, 143, false, 40, 60, false, true, true },
{ "LilyGO T-Lora Pager", 480, 222, 150, false, 40, 0, false, true, false },
{ "M5Stack Cardputer", 240, 135, 139, false, 20, 0, false, true, false },
{ "M5Stack Tab5", 1280, 720, 250, true, 100, 100, false, true, true },
};

static size_t activeProfileIndex = 0;
Expand Down
6 changes: 4 additions & 2 deletions Devices/simulator/Source/SimulatorProfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ struct Profile {
uint16_t dpi; // lvgl.dpi from device.properties (if set), otherwise display.dpi
/** The default rotation: the display is taller than wide. The width and height values may be given in either order. */
bool portrait;
/** Compact UI density */
bool compact;
/** LVGL UI spacing, from lvgl.uiSpacing of the device */
uint8_t uiSpacing;
/** LVGL UI size, from lvgl.uiSize of the device */
uint8_t uiSize;
/** Greyscale display, which makes the monochrome theme apply */
bool mono;
bool keyboard;
Expand Down
4 changes: 2 additions & 2 deletions Devices/simulator/Source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ extern "C" {

static error_t start() {
simulator::initActiveProfile();
const uint8_t density = simulator::getProfile(simulator::getActiveProfileIndex()).compact ? 0 : 100;
lvgl_theme_set_default_density(density, density);
const auto& profile = simulator::getProfile(simulator::getActiveProfileIndex());
lvgl_theme_set_default_density(profile.uiSpacing, profile.uiSize);
error_t error = app_manager_add(&simulator::profileAppManifest);
if (error != ERROR_NONE) {
return error;
Expand Down
Binary file removed Documentation/pics/screenshot-Files.png
Binary file not shown.
Binary file removed Documentation/pics/screenshot-Launcher.png
Binary file not shown.
Binary file removed Documentation/pics/screenshot-Settings.png
Binary file not shown.
Binary file removed Documentation/pics/screenshot-Terminal.png
Binary file not shown.
143 changes: 143 additions & 0 deletions Modules/coreutils-module/source/apps/log.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include <app/manifest.h>

#include <tactility/concurrent/mutex.h>
#include <tactility/log.h>
#include <tactility/memory.h>

#include <atomic>
#include <csignal>
#include <cstdio>
#include <new>
#include <poll.h>
#include <unistd.h>

namespace coreutils::log {

constexpr size_t RING_CAPACITY = 2048;
constexpr size_t CHUNK_SIZE = 512;
constexpr int POLL_INTERVAL_MS = 50;

/** Hands lines from the log drain task to the app's task. It only holds what wasn't printed yet. */
struct Ring {
Mutex mutex;
size_t head = 0;
size_t count = 0;
/** Lines that were dropped, also when the callback couldn't get the mutex */
std::atomic<size_t> dropped { 0 };
char data[RING_CAPACITY];
};

static volatile sig_atomic_t stopRequested = 0;

static void onSignal(int) {
stopRequested = 1;
}

/** Never waits, so logging isn't held up. Whole lines only: a line that doesn't fit, or arrives while the ring is busy, is dropped and counted. */
static void onLogLine(const char* line, size_t length, void* context) {
auto* ring = static_cast<Ring*>(context);
if (!mutex_try_lock(&ring->mutex, 0)) {
ring->dropped++;
return;
}
if (length > RING_CAPACITY - ring->count) {
ring->dropped++;
} else {
size_t tail = (ring->head + ring->count) % RING_CAPACITY;
for (size_t i = 0; i < length; i++) {
ring->data[(tail + i) % RING_CAPACITY] = line[i];
}
ring->count += length;
}
mutex_unlock(&ring->mutex);
}

/** Writes at most one ring's worth, so the main loop keeps checking for input while logging is busy */
static void drain(Ring* ring) {
char chunk[CHUNK_SIZE];
size_t written = 0;
while (written < RING_CAPACITY) {
size_t length = 0;
mutex_lock(&ring->mutex);
length = ring->count < sizeof(chunk) ? ring->count : sizeof(chunk);
for (size_t i = 0; i < length; i++) {
chunk[i] = ring->data[(ring->head + i) % RING_CAPACITY];
}
ring->head = (ring->head + length) % RING_CAPACITY;
ring->count -= length;
mutex_unlock(&ring->mutex);

const size_t dropped = ring->dropped.exchange(0);

if (dropped > 0) {
printf("[%zu log lines dropped]\n", dropped);
}
if (length == 0) {
break;
}
fwrite(chunk, 1, length, stdout);
written += length;
}
fflush(stdout);
}

/** Returns true when any key was typed or stdin was closed */
static bool waitForInput() {
pollfd descriptor = { .fd = STDIN_FILENO, .events = POLLIN, .revents = 0 };
const int result = poll(&descriptor, 1, POLL_INTERVAL_MS);
if (result <= 0) {
return false;
}
if (descriptor.revents & (POLLHUP | POLLERR)) {
return true;
}
char input[16];
read(STDIN_FILENO, input, sizeof(input));
return true;
}

static int32_t main(int, char*[]) {
MemoryPolicy policy = { .required = 0, .desired = MEMORY_CAPABILITY_EXTERNAL, .alignment = 0 };
auto* ring = static_cast<Ring*>(memory_alloc_with_policy(sizeof(Ring), &policy));
if (ring == nullptr) {
fprintf(stderr, "log: out of memory\n");
return 1;
}
new (ring) Ring();
mutex_construct(&ring->mutex);

stopRequested = 0;
signal(SIGINT, onSignal);
signal(SIGTERM, onSignal);
signal(SIGHUP, onSignal);

if (!log_subscribe(onLogLine, ring)) {
fprintf(stderr, "log: too many log listeners\n");
mutex_destruct(&ring->mutex);
memory_free(ring);
return 1;
}

while (!stopRequested) {
if (waitForInput()) {
break;
}
drain(ring);
}

log_unsubscribe(onLogLine, ring);
mutex_destruct(&ring->mutex);
memory_free(ring);
return 0;
}

extern const ::AppManifest manifest = {
.id = "log",
.name = "log",
.category = APP_CATEGORY_SYSTEM,
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(main) },
.flags = APP_MANIFEST_FLAG_HEADLESS,
.stack = { .depth = 4096, .desired_memory_capability = 0 },
};

} // namespace coreutils::log
3 changes: 3 additions & 0 deletions Modules/coreutils-module/source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace coreutils {
namespace free { extern const ::AppManifest manifest; }
namespace head { extern const ::AppManifest manifest; }
namespace help { extern const ::AppManifest manifest; }
namespace log { extern const ::AppManifest manifest; }
namespace ls { extern const ::AppManifest manifest; }
namespace mkdir { extern const ::AppManifest manifest; }
namespace mv { extern const ::AppManifest manifest; }
Expand All @@ -40,6 +41,7 @@ static error_t start() {
if ((error = app_manager_add(&coreutils::free::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::head::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::help::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::log::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::ls::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::mkdir::manifest)) != ERROR_NONE) return error;
if ((error = app_manager_add(&coreutils::mv::manifest)) != ERROR_NONE) return error;
Expand All @@ -62,6 +64,7 @@ static error_t stop() {
app_manager_remove(coreutils::free::manifest.id);
app_manager_remove(coreutils::head::manifest.id);
app_manager_remove(coreutils::help::manifest.id);
app_manager_remove(coreutils::log::manifest.id);
app_manager_remove(coreutils::ls::manifest.id);
app_manager_remove(coreutils::mkdir::manifest.id);
app_manager_remove(coreutils::mv::manifest.id);
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
Tactility is an operating system that focuses on the ESP32 microcontroller family.
It can load apps from SD card that were built with native C/C++ SDKs.

See [https://tactilityproject.org](https://tactilityproject.org) for more information.
Links:

<div align="center">
<div> <img src="Documentation/pics/screenshot-Launcher.png" alt="screenshot of launcher app" border="1"> &nbsp; <img src="Documentation/pics/screenshot-Settings.png" alt="screenshot of settings app" border="1"> </div>
<div> <img src="Documentation/pics/screenshot-Terminal.png" alt="screenshot of terminal app" border="1"> &nbsp; <img src="Documentation/pics/screenshot-Files.png" alt="screenshot of files app" border="1"> </div>
</div>
- [Main website](https://tactilityproject.org)
- [Installer](https://install.tactilityproject.org)
- [Documentation](https://docs.tactilityproject.org)

## License

Expand Down
20 changes: 17 additions & 3 deletions Tactility/Private/Tactility/app/terminal/KeyboardInput.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
#include <tactility/drivers/keyboard.h>

#include <tactility/freertos/freertos.h>

#include <vector>
#include <tactility/std/vector.hpp>

/** Tracks every KEYBOARD_TYPE device, re-scanning at most once a second so a USB keyboard
* plugged/unplugged after the terminal starts is picked up without restarting the app. */
Expand All @@ -30,13 +29,28 @@ class KeyboardInput {
private:
static constexpr uint32_t REFRESH_INTERVAL_MS = 1000;

/** Keyboards may keep reporting a held key as pressed, so a key only counts on its press edge */
struct Keyboard {
static constexpr size_t HELD_CAPACITY = 8;

Device* device;
uint32_t held[HELD_CAPACITY] = {};
size_t heldCount = 0;

bool isHeld(uint32_t key) const;
void addHeld(uint32_t key);
void removeHeld(uint32_t key);
void clearHeld() { heldCount = 0; }
};

static bool collect(Device* device, void* context);

void refresh();
void rescan();
void applyExclusive();
void awaitKeyRelease();

std::vector<Device*> devices_;
tt::std::vector<Keyboard> devices_;
TickType_t lastRefresh_ = 0;
bool exclusive_ = false;
};
6 changes: 3 additions & 3 deletions Tactility/Private/Tactility/lvgl/Fonts.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ struct FontConfiguration {
/** Default text font size in pixels, other font sizes are derived from it */
uint16_t defaultSize;
/** TrueType font for regular text, or empty for the system font */
std::string regularFontPath;
::std::string regularFontPath;
/** TrueType font for monospace text, or empty for the system font */
std::string monoFontPath;
::std::string monoFontPath;

bool operator==(const FontConfiguration&) const = default;
};
Expand All @@ -27,7 +27,7 @@ struct FontConfiguration {
constexpr size_t LARGE_FONT_CHARACTER_COUNT = 1000;

/** @return the number of characters in a TrueType font, or 0 when it can't be read */
size_t getTtfCharacterCount(const std::string& path);
size_t getTtfCharacterCount(const ::std::string& path);

/**
* Changing fonts generates them on the device, which needs at least 2 MB of memory in total.
Expand Down
6 changes: 2 additions & 4 deletions Tactility/Source/InitApps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ namespace app {
namespace terminal { extern const ::AppManifest manifest; }
namespace timedatesettings { extern const ::AppManifest manifest; }
namespace meshchat {
extern const ::AppManifest contactsManifest;
extern const ::AppManifest channelsManifest;
extern const ::AppManifest manifest;
extern const ::AppManifest conversationManifest;
extern const ::AppManifest addContactManifest;
}
Expand Down Expand Up @@ -169,8 +168,7 @@ static void registerInternalApps() {
}

if (settings::radio::hasRadio()) {
app_manager_add(&app::meshchat::contactsManifest);
app_manager_add(&app::meshchat::channelsManifest);
app_manager_add(&app::meshchat::manifest);
app_manager_add(&app::meshchat::conversationManifest);
app_manager_add(&app::meshchat::addContactManifest);
app_manager_add(&app::meshsettings::manifest);
Expand Down
2 changes: 1 addition & 1 deletion Tactility/Source/app/TileGrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ PageLayout computePageLayout(lv_obj_t* grid) {
const auto pad = icon_size / 16;
// The focus ring is drawn inside the frame, at most 2dp wide, and leaves 1dp to the icon
const auto frame_pad = LV_DPX(2) + LV_DPX(1);
const auto label_gap = LV_DPX(2);
const auto label_gap = lvgl_theme_lerp(LV_DPX(1), LV_DPX(4), lvgl_theme_get_spacing());
const auto gap = TILE_GAP;
const auto text_height = lv_font_get_line_height(lvgl_get_text_font(FONT_SIZE_SMALL));
// The label needs room for a few characters, also when the icon font is smaller (e.g. a fallback)
Expand Down
1 change: 1 addition & 0 deletions Tactility/Source/app/files/View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ View::View(const std::shared_ptr<State>& state, TaskEventGroup* eventGroup) :
.resolveImage = resolveImage,
.resolveIcon = resolveIcon,
.onPageChanged = onPageChanged,
.onTilesCleared = nullptr,
.userData = this
}) {
check(task_event_group_claim_bit(eventGroup, &pasteBit) == ERROR_NONE);
Expand Down
3 changes: 1 addition & 2 deletions Tactility/Source/app/launcher/AppsMode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ const BuiltInIconAsset BUILT_IN_ICONS[] = {
BUILT_IN_ICON("tactility.files", tactility_files),
BUILT_IN_ICON("tactility.i2cscanner", tactility_i2cscanner),
BUILT_IN_ICON("tactility.maps", tactility_maps),
BUILT_IN_ICON("tactility.meshchannels", tactility_meshchannels),
BUILT_IN_ICON("tactility.meshcontacts", tactility_meshcontacts),
BUILT_IN_ICON("tactility.mesh", tactility_mesh),
BUILT_IN_ICON("tactility.notes", tactility_notes),
BUILT_IN_ICON("tactility.systeminfo", tactility_systeminfo),
BUILT_IN_ICON("tactility.terminal", tactility_terminal),
Expand Down
Loading
Loading