mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-03 03:11:57 +00:00
fix(companion): pack and history files from the card checked, contact loops skip the reserved slots, small UI fixes
- L2 vector packs: a damaged count can't wrap the index size, and an entry pointing past the file is refused. - L2 map share-target list: sized for 40 long channel names and 16 favourites (it could overflow), off the stack. - L2 SD history: a file with an unreadable header is set aside as .bad instead of written over; Delete message history takes those too. - Contact lookups by key or hash start after the reserved anon slots (an empty one matched a 00 hop hash with a blank name); MessagesScreen uses contactctl::hopName(). - Nearby keeps its selection by public key: deleting a contact shifts the table. - ui-new Live Share target cycle holds every channel and favourite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1491,7 +1491,7 @@ public:
|
||||
}
|
||||
}
|
||||
} else if (prefix) {
|
||||
for (int idx = 0; ; idx++) {
|
||||
for (int idx = MAX_ANON_CONTACTS; ; idx++) {
|
||||
ContactInfo c;
|
||||
if (!the_mesh.getContactByIdx(idx, c)) break;
|
||||
if (memcmp(c.id.pub_key, prefix, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) {
|
||||
@@ -1673,7 +1673,7 @@ public:
|
||||
_task->openChannelHistory(pfx[0]);
|
||||
return true;
|
||||
}
|
||||
for (int idx = 0; ; idx++) {
|
||||
for (int idx = MAX_ANON_CONTACTS; ; idx++) {
|
||||
ContactInfo c2;
|
||||
if (!the_mesh.getContactByIdx(idx, c2)) break;
|
||||
if (memcmp(c2.id.pub_key, pfx, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) {
|
||||
|
||||
@@ -157,14 +157,17 @@ public:
|
||||
}
|
||||
|
||||
void cycleTarget(int dir) {
|
||||
struct T { uint8_t type, ch; uint8_t prefix[NodePrefs::FAVOURITE_PREFIX_LEN]; } list[24];
|
||||
// Every channel and every dial favourite: a cap under MAX_GROUP_CHANNELS
|
||||
// (40) left the favourites, listed last, out of reach.
|
||||
static const int LIST_MAX = MAX_GROUP_CHANNELS + NodePrefs::FAVOURITES_COUNT;
|
||||
struct T { uint8_t type, ch; uint8_t prefix[NodePrefs::FAVOURITE_PREFIX_LEN]; } list[LIST_MAX];
|
||||
int n = 0;
|
||||
ChannelDetails ch;
|
||||
for (int i = 0; i < 64 && n < 24; i++) {
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS && n < LIST_MAX; i++) {
|
||||
if (!the_mesh.getChannel(i, ch) || ch.name[0] == '\0') continue;
|
||||
list[n].type = 0; list[n].ch = (uint8_t)i; n++;
|
||||
}
|
||||
for (int i = 0; i < NodePrefs::FAVOURITES_COUNT && n < 24; i++) {
|
||||
for (int i = 0; i < NodePrefs::FAVOURITES_COUNT && n < LIST_MAX; i++) {
|
||||
const uint8_t* pre = _prefs->favourite_contacts[i];
|
||||
bool empty = true;
|
||||
for (int b = 0; b < NodePrefs::FAVOURITE_PREFIX_LEN; b++) if (pre[b]) { empty = false; break; }
|
||||
|
||||
@@ -195,13 +195,13 @@ public:
|
||||
none.kind = 2; none.lat = 0; none.lon = 0; none.ts = 0; none.live = false; none.fav = false;
|
||||
memset(none.key, 0, 6);
|
||||
snprintf(none.name, sizeof(none.name), "(none)");
|
||||
for (int idx = 0; _target_n < TARGET_MAX; idx++) {
|
||||
for (int idx = MAX_ANON_CONTACTS; _target_n < TARGET_MAX; idx++) {
|
||||
ContactInfo c;
|
||||
if (!the_mesh.getContactByIdx(idx, c)) break;
|
||||
if (!contactctl::favourite(c)) continue;
|
||||
addPersonTarget(c.id.pub_key, c.name, /*require_position=*/false, true);
|
||||
}
|
||||
for (int idx = 0; _target_n < TARGET_MAX; idx++) {
|
||||
for (int idx = MAX_ANON_CONTACTS; _target_n < TARGET_MAX; idx++) {
|
||||
ContactInfo c;
|
||||
if (!the_mesh.getContactByIdx(idx, c)) break;
|
||||
addPersonTarget(c.id.pub_key, c.name, /*require_position=*/true, contactctl::favourite(c));
|
||||
|
||||
@@ -383,24 +383,6 @@ class MessagesScreen : public UIScreen {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve one hop's path hash to a contact name, falling back to a short hex
|
||||
// tag ("?AABB") when no known contact matches -- an unnamed/unknown repeater,
|
||||
// or one whose contact card has since been removed.
|
||||
void resolveHopName(const uint8_t* hash, uint8_t hash_size, char* out, size_t out_sz) {
|
||||
for (int idx = 0; ; idx++) {
|
||||
ContactInfo c;
|
||||
if (!the_mesh.getContactByIdx(idx, c)) break;
|
||||
if (c.id.isHashMatch(hash, hash_size)) {
|
||||
snprintf(out, out_sz, "%s", c.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
char hex[9] = {0};
|
||||
uint8_t n = hash_size > 4 ? 4 : hash_size;
|
||||
for (uint8_t b = 0; b < n; b++) snprintf(hex + b * 2, 3, "%02X", hash[b]);
|
||||
snprintf(out, out_sz, "?%s", hex);
|
||||
}
|
||||
|
||||
// Opens the path/relay detail popup for the entry stashed by buildFsMenu().
|
||||
// Incoming: hops listed oldest-hop-first (the order they were appended as the
|
||||
// message travelled). Outgoing channel: each distinct repeater heard echoing
|
||||
@@ -423,7 +405,7 @@ class MessagesScreen : public UIScreen {
|
||||
uint8_t max_hops = MAX_HIST_PATH_BYTES / hash_size; // matches capturePath()/markChannelRelayed()'s write-side bound
|
||||
if (hop_count > max_hops) hop_count = max_hops;
|
||||
for (uint8_t i = 0; i < hop_count; i++) {
|
||||
resolveHopName(&path[i * hash_size], hash_size, _path_detail_names[i], sizeof(_path_detail_names[i]));
|
||||
contactctl::hopName(path_len_packed, path, i, _path_detail_names[i], sizeof(_path_detail_names[i]));
|
||||
}
|
||||
_ctx_menu.begin(path_is_relay ? "Relayed by" : "Path", hop_count);
|
||||
for (uint8_t i = 0; i < hop_count; i++) _ctx_menu.addItem(_path_detail_names[i]);
|
||||
@@ -724,7 +706,7 @@ class MessagesScreen : public UIScreen {
|
||||
DisplayDriver::translateUTF8Static(nm + 1, chanctl::bareName(ch.name), sizeof(nm) - 1);
|
||||
}
|
||||
} else {
|
||||
for (int idx = 0; ; idx++) {
|
||||
for (int idx = MAX_ANON_CONTACTS; ; idx++) {
|
||||
ContactInfo c2;
|
||||
if (!the_mesh.getContactByIdx(idx, c2)) break;
|
||||
if (memcmp(c2.id.pub_key, pfx, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) {
|
||||
|
||||
@@ -105,20 +105,22 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
void refresh() { refreshModel(); clampSelection(); }
|
||||
|
||||
// Rebuild the stored list (re-merge live shares + re-sort) while keeping the
|
||||
// currently highlighted node selected across the rebuild — by contact index
|
||||
// currently highlighted node selected across the rebuild — by public key
|
||||
// for a stored node, or by name for a non-contact live sender. Returns false
|
||||
// if that node is no longer in the list (caller decides whether to drop the
|
||||
// detail/nav view). Shared by the list, detail and navigate refresh paths.
|
||||
bool refreshKeepingSelection() {
|
||||
int saved_idx = (_sel < _count) ? _entries[_sel].contact_idx : -1;
|
||||
bool saved_live = (_sel < _count) && _entries[_sel].is_live && saved_idx < 0;
|
||||
bool saved_contact = (_sel < _count) && _entries[_sel].contact_idx >= 0;
|
||||
bool saved_live = (_sel < _count) && _entries[_sel].is_live && !saved_contact;
|
||||
uint8_t saved_key[PUB_KEY_SIZE];
|
||||
if (saved_contact) memcpy(saved_key, _entries[_sel].pub_key, PUB_KEY_SIZE);
|
||||
char saved_name[sizeof(_entries[0].name)]; saved_name[0] = '\0';
|
||||
if (_sel < _count) {
|
||||
strncpy(saved_name, _entries[_sel].name, sizeof(saved_name) - 1);
|
||||
saved_name[sizeof(saved_name) - 1] = '\0';
|
||||
}
|
||||
refreshStored();
|
||||
int i = saved_idx >= 0 ? findContact(saved_idx)
|
||||
int i = saved_contact ? findContact(saved_key)
|
||||
: (saved_live && saved_name[0]) ? findLiveByName(saved_name) : -1;
|
||||
if (i >= 0) { _sel = i; return true; }
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user