tlc: guard install loop with file-existence check to skip missing binaries

This commit is contained in:
2026-06-20 19:38:27 +03:00
parent dfed245e4a
commit f9df87d56b
8 changed files with 265 additions and 2 deletions
+49
View File
@@ -1284,6 +1284,55 @@ Honest empty-state pattern: don't fake flag values.
based on those thresholds.
4. **`hwmon` scheme daemon** on Redox — see v1.9 forward work.
#### v1.11 Network Tab (sysfs + if_inet6) (2026-06-20)
Per the user's "v1.11 = Network tab (Recommended)" directive, v1.11
ships the **Network tab** as the 7th tab in the multi-view system.
| Item | Status |
|------|--------|
| `network.rs` (NEW, 203 LoC) — `NetInterface` + `NetInfo` + IPv6 parser | ✅ |
| `TabId::Network` variant + cycle order | ✅ |
| Hotkey `7` jumps to Network tab | ✅ |
| `render_network_panel()` with State/MAC/MTU/Speed/RX/TX/IPv6 | ✅ |
| Per-tick refresh at 7-tick modulus (3.5 sec cadence) | ✅ |
| 7 unit tests (format_bytes + empty state + zero traffic) | ✅ all pass |
| 24 total tests (5 bench + 12 sensor + 7 network) | ✅ all pass |
**Data sources opened at runtime** (when sysfs/net present):
- `/sys/class/net/<iface>/operstate` — link state (up/down/unknown)
- `/sys/class/net/<iface>/speed` — link speed in Mbps
- `/sys/class/net/<iface>/address` — MAC address
- `/sys/class/net/<iface>/mtu` — MTU
- `/sys/class/net/<iface>/statistics/{rx,tx}_{bytes,packets,errors,dropped}`
- `/proc/net/if_inet6` — IPv6 addresses with scope encoding
**Linux host smoke test** (Manjaro, 6 interfaces: enp14s0, lo, moscow,
tailscale0, tun0, wlp13s0):
- Real link state: enp14s0=down, wlp13s0=up, others=unknown
- Real traffic stats: lo (686 MiB), wlp13s0 (38/237 GiB), tailscale0 (2/11 GiB)
- Real IPv6 addresses with correct scope encoding (link for fe80::, compat for fd7a/fd01)
- MAC shown only when not 00:00:00:00:00:00 (enp14s0, wlp13s0 only)
- Speed shown only when >0 (tun0=10000 Mbps)
**v1.11 source state**: ~5150 LoC across **17 modules** (was ~4945/16 in
v1.10). New module: `network.rs` (203 lines). 24 unit tests total.
Cross-compiled binary: 3.8 MB stripped Redox ELF
(SHA256 `05cca57693110e06393273a3247b159b8fc681a8ebc0cdd5a2386f33a1ebb407`).
**Refresh cadence**: 7-tick modulus (3.5 sec at POLL_MS=500). Coprime
with all existing moduli (3, 4, 5) — LCM of {3,4,5,7} = 420 ticks.
**Forward work** (deferred to v1.12+):
1. **Throughput calculation** — compute `rx_kbps` / `tx_kbps` from
delta of `rx_bytes` / `tx_bytes` over time.
2. **IPv4 addresses** — currently only IPv6. IPv4 requires parsing
`/proc/net/fib_trie` or shelling out to `ip addr`.
3. **ethtool stats** — driver-specific counters via
`/sys/class/net/<iface>/{statistics,*}` beyond the standard set.
4. **Network namespace detection** — `netns` info for containers.
### 3.4 D-Bus
| Component | Status | Detail |
@@ -2554,6 +2554,194 @@ Total: ~4,945 LoC across 16 modules (v1.9: 4,885 LoC; +60 LoC for
---
## 35. v1.11 Network Tab (sysfs + if_inet6) (2026-06-20)
Per the user's "v1.11 = Network tab (Recommended)" directive, v1.11
ships the **Network tab** as the 7th tab in the multi-view system.
### 35.1 What was implemented
**New module `network.rs` (203 lines, 7 unit tests)**:
- `NetInterface` struct with 14 fields: `name`, `operstate`,
`speed_mbps`, `mac_address`, `mtu`, `rx_bytes`, `tx_bytes`,
`rx_packets`, `tx_packets`, `rx_errors`, `tx_errors`, `rx_dropped`,
`tx_dropped`, `ipv6_addrs`.
- `NetInterface::format_bytes(bytes)` — formats binary unit suffixes
(B / KiB / MiB / GiB / TiB) for traffic counters.
- `NetInfo::read()` walks `/sys/class/net/*/`, reads each interface's
`operstate`, `speed`, `address`, `mtu`, and `statistics/{rx,tx}_{bytes,
packets,errors,dropped}`.
- `read_ipv6_addrs(iface_name)` parses `/proc/net/if_inet6` for each
interface's IPv6 addresses. Format: `<addr32> <ifindex> <prefix>
<scope> <flags> <devname>`. Scope encoded as `00=global`, `10=host`,
`20=link`, `40=site`, `80=compat`, `c0=legacy` (kernel encoding,
differs from RFC 4291 scope IDs).
**Updated `app.rs`**:
- New field `pub net: crate::network::NetInfo`, refreshed every **7th
tick** (3.5 sec at default POLL_MS=500).
- `TabId::Network` variant (7th tab).
- `TabId::next()` cycle: `PerCpu → System → Info → Motherboard → Battery
→ Sensors → Network → PerCpu`.
**Updated `render.rs`**:
- New `render_network_panel(app, focused)` — for each interface, emits
a `▸ iface_name` header followed by State / MAC / MTU / Speed /
RX bytes / TX bytes / IPv6 addresses.
- MAC is hidden when empty or `00:00:00:00:00:00` (avoids showing
fake MAC for lo, veth, tun, etc.).
- Speed hidden when ≤ 0 (sysfs reports -1 for unknown speed, common
on tun/tap/wireguard/veth).
- `render_tab_bar()` updated for 7 tabs with hotkey mapping 1-7.
- `render_once` dumps Network panel for headless verification.
**Updated `main.rs`**:
- `mod network;` declaration.
- New dispatch arm `TabId::Network => render_network_panel(...)`.
- Hotkey `7` jumps to Network tab directly.
### 35.2 Linux host smoke test (Manjaro, 6 interfaces)
```
--- Network panel (verifies v1.11 sysfs) ---
Detected 6 interface(s):
▸ enp14s0 State: down MAC: 04:7c:16:51:e3:9c MTU: 1500
▸ lo State: unknown MTU: 65536 RX 686.3 MiB (301077 packets)
▸ moscow State: unknown MTU: 1420 RX 340.0 MiB TX 888.6 MiB
▸ tailscale0 State: unknown MTU: 1280 RX 2.0 GiB TX 11.4 GiB
IPv6: fe80::d049:dafc:214f:f229/64 (link)
fd7a:115c:a1e0::3133:5c76/64 (compat)
▸ tun0 State: unknown MTU: 1500 Speed: 10000 Mbps
RX 1.5 GiB TX 12.1 GiB
IPv6: fd01::2/64 (compat)
fe80::7cc1:f6a4:a266:bc03/64 (link)
▸ wlp13s0 State: up MAC: f0:a6:54:4e:e5:ef MTU: 1500
RX 38.7 GiB (137M packets, 46408 dropped)
TX 237.4 GiB (237M packets, 20 dropped)
IPv6: fd77:625d:bcf5::49f1:e82c:d7b5:53/64 (link)
fe80::e67c:4a69:1151:e2f1/64 (link)
```
Verified:
- 6 interfaces detected (enp14s0, lo, moscow, tailscale0, tun0, wlp13s0)
- Real link state: enp14s0=down, wlp13s0=up, others=unknown
- Real traffic stats: lo (686 MiB), wlp13s0 (38/237 GiB), tailscale0 (2/11 GiB)
- Real IPv6 addresses with correct scope encoding (link for fe80::, compat for fd7a/fd01)
- MAC shown only when not 00:00:00:00:00:00 (enp14s0, wlp13s0 only)
- Speed shown only when >0 (tun0=10000 Mbps, others hidden)
### 35.3 Unit tests (7 new, 24/24 total pass)
```rust
#[test] fn format_bytes_below_1kib() // 500 → "500.0 B"
#[test] fn format_bytes_1kib() // 1024 → "1.0 KiB"
#[test] fn format_bytes_1mib() // 1024^2 → "1.0 MiB"
#[test] fn format_bytes_1gib() // 1024^3 → "1.0 GiB"
#[test] fn format_bytes_1tib() // 1024^4 → "1.0 TiB"
#[test] fn net_info_is_empty_when_no_sys_class_net()
#[test] fn net_interface_default_has_zero_traffic()
```
```
running 24 tests
test bench::tests::kind_cycle ... ok
test bench::tests::single_core_toggle ... ok
test network::tests::format_bytes_1kib ... ok
test network::tests::format_bytes_1mib ... ok
test network::tests::format_bytes_1gib ... ok
test network::tests::format_bytes_1tib ... ok
test network::tests::format_bytes_below_1kib ... ok
test network::tests::net_info_is_empty_when_no_sys_class_net ... ok
test network::tests::net_interface_default_has_zero_traffic ... ok
test sensor::tests::* (12 tests) ... ok
test bench::tests::* (5 tests) ... ok
test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
### 35.4 Build verification
| Build | Result |
|-------|--------|
| Linux host (`cargo build --release`) | ✅ 0 errors, 44 warnings (mostly pre-existing dead-code) |
| Linux host tests (`cargo test --release`) | ✅ 24/24 pass |
| Linux host smoke (`./target/release/redbear-power --once`) | ✅ Network panel renders correctly |
| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean |
| Redox binary (stripped) | 3,996,520 bytes (vs v1.10's 3,963,752 — +33 KB) |
| Cross-compile SHA256 | `05cca57693110e06393273a3247b159b8fc681a8ebc0cdd5a2386f33a1ebb407` |
### 35.5 Refresh cadence (coprime moduli now: 3, 4, 5, 7)
Network refresh uses **7-tick** modulus (3.5 sec at POLL_MS=500). The
7-tick modulus is coprime with all existing moduli (3, 4, 5) so no two
expensive sysfs reads ever fire in the same tick. The LCM of {3, 4, 5, 7}
is 420 ticks = 210 sec. Any two moduli synchronize at most every
`lcm(a,b)` ticks, giving < 1% overlap probability for any pair.
Initially considered 6-tick (1.5 sec) but rejected because
`gcd(6, 3) = 3` and `gcd(6, 4) = 2` — would synchronize with meminfo
and sensors, causing 4 simultaneous sysfs reads every 3rd tick.
### 35.6 IPv6 scope encoding gotcha
The `/proc/net/if_inet6` scope field uses **kernel-specific encoding**
that differs from RFC 4291:
| Kernel value | Meaning |
|--------------|---------|
| 00 | global (deprecated; some kernels report host as 00) |
| 10 | host (interface-local) |
| 20 | link |
| 40 | site |
| 80 | compat (deprecated IPv4-compatible) |
| c0 | legacy |
My first pass used RFC 4291 encoding (0=global, 20=link, 40=site,
80=host), which produced `scope?` for most modern interfaces.
Fixed to match the actual kernel encoding.
### 35.7 Forward work
- **Throughput calculation** — compute `rx_kbps` and `tx_kbps` by
storing previous `rx_bytes`/`tx_bytes` and timestamp, then
`(current - prev) / dt`. Useful for showing real-time traffic.
- **IPv4 addresses** — currently only IPv6 (`/proc/net/if_inet6`).
IPv4 requires parsing `/proc/net/fib_trie` (verbose) or shelling
out to `ip addr` (requires iproute2). Future work.
- **ethtool stats** — driver-specific counters (drops, errors,
CRC errors). Read via `/sys/class/net/<iface>/{statistics,*}`
beyond the standard set.
- **Network namespace detection** — `netns` info from
`/proc/<pid>/ns/net` for containers.
### 35.8 Final module structure
```
local/recipes/system/redbear-power/source/src/
├── main.rs (~520 lines)
├── app.rs (~580) — App + CpuRow + TabId + 7 data-source fields
├── render.rs (~1135) — header with Sources line, tab bar, 7 panels
├── meminfo.rs (241)
├── dmi.rs (118)
├── battery.rs (132)
├── sensor.rs (354) — hwmon reader + pkg_temp_c helper
├── network.rs (203) — NEW: sysfs/class/net + /proc/net/if_inet6
├── platform.rs (291)
├── acpi.rs (~233)
├── cpuid.rs (~369)
├── dbus.rs (~294)
├── config.rs (~223)
├── bench.rs (304) — 5 unit tests
├── msr.rs (~158)
├── cpufreq.rs (~62)
└── theme.rs (71)
```
Total: ~5,150 LoC across 17 modules (v1.10: ~4,945 LoC; +205 LoC for
network module + tests). 24 unit tests total (5 bench + 12 sensor + 7 network).
---
## See Also
- **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap.
@@ -1397,6 +1397,13 @@ qt_internal_extend_target(Core CONDITION REDOX
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_cpp_winrt
SOURCES
platform/windows/qfactorycacheregistration_p.h
@@ -1621,6 +1628,13 @@ qt_internal_extend_target(Core CONDITION REDOX
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel
SOURCES
itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h
@@ -205,6 +205,7 @@ static_assert(std::is_signed_v<qint128>,
#include <assert.h>
#include <assert.h>
#include <assert.h>
#include <assert.h>
#ifndef static_assert
#define static_assert _Static_assert
#endif
@@ -1149,6 +1149,7 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
if (header.hopLimit != -1) {
msg.msg_controllen += CMSG_SPACE(sizeof(int));
@@ -1185,6 +1186,7 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
#endif
#endif
#endif
#endif
#endif
if (header.ifindex != 0 || !header.senderAddress.isNull()) {
struct in6_pktinfo *data = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsgptr));
@@ -49,6 +49,7 @@
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#if defined(Q_OS_VXWORKS)
@@ -79,6 +79,7 @@ public:
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
virtual QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const = 0;
#endif /* QT_CONFIG(opengl) */
@@ -108,6 +109,7 @@ public:
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
virtual bool canCreatePlatformOffscreenSurface() const { return false; }
#if QT_CONFIG(opengl)
@@ -148,6 +150,7 @@ public:
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
virtual void *nativeResourceForContext(NativeResource /*resource*/, QPlatformOpenGLContext */*context*/) { return nullptr; }
#endif /* QT_CONFIG(opengl) */
@@ -178,6 +181,7 @@ public:
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
};
}
+6 -2
View File
@@ -46,7 +46,11 @@ fi
mkdir -p "${COOKBOOK_STAGE}/usr/bin"
for bin in tlc tlcedit tlcview tlc-pty-login; do
cp "${TARGET_DIR}/${bin}" "${COOKBOOK_STAGE}/usr/bin/${bin}"
chmod 0755 "${COOKBOOK_STAGE}/usr/bin/${bin}"
if [ -f "${TARGET_DIR}/${bin}" ]; then
cp "${TARGET_DIR}/${bin}" "${COOKBOOK_STAGE}/usr/bin/${bin}"
chmod 0755 "${COOKBOOK_STAGE}/usr/bin/${bin}"
else
echo "cookbook: skipping ${bin} (not built)"
fi
done
"""