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
4 changes: 4 additions & 0 deletions .github/workflows/v2-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ jobs:
working-directory: v2
run: npm run test:remote-paste

- name: Playback and expert shell regressions
working-directory: v2
run: npm run test:playback-shell

frontend-mobile:
name: Mobile frontend type check
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ These are load-bearing — break them and the safety story falls over.
- **App lists live in JSON, not in code.** Add/edit packages in `v2/crates/core/data/app-lists/{common,shield,googletv}.json`. They are embedded at compile time via `include_str!` in `crates/core/src/commands/loader.rs` — editing the JSON requires a rebuild. Never hard-code packages in Rust.
- **Snapshots are versioned.** `schema_version == 0` is rejected. `schema_version > current` is rejected. Bump the constant in `engine/snapshot.rs` when the schema changes and write an explicit migration.
- **Snapshot reads are path-confined** to `snapshot_dir` via `canonicalize` + `starts_with`. Keep that check on any new read path that takes a user-supplied snapshot location — zip-slip / traversal protection is the same pattern in `adb/install.rs`.
- **The do-not-disable list is mandatory.** `engine::safety::classify` / `is_never_disable` must gate every disable code path (apply-snapshot, optimize wizard, memory-table Disable button, stock-launcher wizard, panic-recovery's inverse, …). Bypassing it bricks devices.
- **The do-not-disable list is mandatory for curated actions.** `engine::safety::classify` / `is_never_disable` must gate every typed disable code path (apply-snapshot, optimize wizard, memory-table Disable button, stock-launcher wizard, panic-recovery's inverse, …). Desktop expert shell is an explicit opt-in exception: arbitrary shell expressions can bypass its lexical checks. It must remain bounded, clearly warn about destructive effects, and never claim the curated actions' safety guarantee. Mobile does not expose it.
- **Tauri commands return `Result<T, String>`.** The error type is serialized to the frontend. Convert with `.map_err(|e| e.to_string())`.
- **Frontend is Svelte 5 runes** (`$state`, `$derived`, `$effect`, `$props`) on SvelteKit in SPA mode (`adapter-static`). No legacy stores.

Expand Down Expand Up @@ -92,7 +92,7 @@ The Linux runner needs `libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev libayatana
The README walkthrough (`v2/screenshots/gallery.gif`) is generated, not hand-captured. **When you change v2 UI in a way that alters any captured screen, regenerate it and commit the result** — don't let the gallery drift from the real app:

```
cd v2 && npm run screenshots # captures all 12 screens (demo data, dark theme) + rebuilds the GIF
cd v2 && npm run screenshots # captures 15 screens in both themes and rebuilds both GIFs
```

It runs offline against the demo fixture layer (`src/lib/demo-mock.ts`, gated behind `VITE_DEMO=1`) — no device needed. Most changes (CSS, layout, copy, rows) flow through with no tooling edits. Two cases need a touch-up:
Expand Down
12 changes: 7 additions & 5 deletions v2/ATVTOOLS-PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ Research notes: aTV Tools is a **phone/tablet companion app** (Android 8.1+/iOS
| Screen recording | ✅ (Pro, no DRM content) | ❌ |
| Remote control / D-pad / mouse | ✅ (Pro) | partial (D-pad + typing, no mouse) |
| **Send text to TV (type from keyboard)** | ✅ | ✅ |
| Permissions grant/revoke | ✅ | |
| Permissions grant/revoke | ✅ | |
| Bulk cache clear | ✅ | ✅ |
| Running apps + force-stop | ✅ | ✅ |
| Resource monitor | CPU/RAM/net/storage | RAM/temp/storage/display (no CPU/net) |
| Shell runner with bookmarks | ✅ | |
| Resource monitor | CPU/RAM/net/storage | ✅ CPU/RAM/net/temp/storage/display |
| Shell runner with bookmarks | ✅ | |
| Screen mirroring / gamepad / media remote | ✅ (phone-centric) | — (out of scope for desktop) |
| Open source / free | ❌ | ✅ |

**Playback report** (Playback tab, unreleased) shows codec configuration, reported HDR/display modes, and audio policy. These observations do not establish runtime decoder support, acceleration, or playback quality.

**Bottom line:** we beat aTV Tools on the *debloat/optimize/safety* core, they beat us on *general device utilities*. The gaps worth closing are the utilities that complement debloating; the phone-centric features aren't our product.

## Prioritized roadmap
Expand All @@ -57,9 +59,9 @@ Shape: `adb -s X exec-out screencap -p > local.png`, save to a user folder, show

**5. Force-stop** on memory-table rows (`am force-stop <pkg>`) — trivial, pairs with the existing Disable button.
**6. Send text to TV** — `input text '<escaped>'` for typing Wi-Fi passwords/searches from the desktop keyboard. Small input box on the device header. (Escape carefully; relates to the package-validation work.)
**7. Shell runner with bookmarks** — an "Advanced" tab: command input → runs via the driver, shows combined output; bookmark list persisted locally. The catch-all that made aTV Tools sticky.
**7. Shell runner with bookmarks** — implemented on the unreleased desktop Shell tab. Explicit expert acknowledgment → editor → Run → bounded stdout/stderr. Presets/bookmarks populate the editor only. The lexical gate catches obvious protected-package commands but can be bypassed by shell expressions; this is an explicit exception to curated-action safety. Mobile shell is deferred.
**8. Bulk cache clear** — `pm trim-caches 999999999999` (one call, no per-app loop).
**9. CPU + network monitor** — add `top -n1` / `/proc/stat` parse and `/proc/net/dev` deltas to the Health report.
**9. CPU + network monitor** — implemented by `resource_sample` on the unreleased desktop and mobile apps: two device-side samples with `/proc/uptime` elapsed time and per-interface network rates. Independent of the health report; mobile sampling is explicitly requested, not polled.

### P3 — Evaluate later
**10. Screen recording** — `screenrecord` (3-min cap, no DRM), pull + save. Nice demo material.
Expand Down
47 changes: 43 additions & 4 deletions v2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,49 @@ When you add a new section, put it at the top; older releases go below.

## v2-2.2.0-beta.1

Four reported bugs reach users for the first time, plus safer defaults and a
companion-app overhaul. Three of these fixes were written without the
reporter's hardware in hand — if you filed one of them, please say whether this
build works for you.
Four reported bugs reach users for the first time, plus a Playback tab, an
expert shell, and a companion-app overhaul. Three of those fixes were written
without the reporter's hardware in hand — if you filed one of them, please say
whether this build works for you.

### Playback

- **A new Playback tab** reports what the TV says it can do: which video
decoders it advertises, which display modes and HDR formats it lists, and
what its audio passthrough settings are set to. It reads configuration, and
it says so — an entry here is not proof that a codec is registered at
runtime, hardware-accelerated, or good enough to play a given file.
- **Tweaks gained an Audio Passthrough control** — Auto / Never / Always /
Manual, with the per-format allow-list when Manual is selected. A format code
the app doesn't have a name for is kept rather than quietly dropped from the
device's list, one change is written at a time, and every row is re-read from
the device afterwards, so what you see is what the TV reports, not what was
asked for.
- The Health tab now samples CPU load and per-interface network throughput.
Rates are computed from the device's own clock over the sampling window, so
a slow round-trip doesn't inflate them. The companion app samples on demand
rather than polling; reads stay Free and writes need Pro.

### Expert shell

- **A Shell tab for everything the curated screens don't cover**, off until you
turn it on and acknowledge what it is. Output and runtime are bounded (256 KiB
per stream, 30 seconds), a non-zero exit and whatever output arrived first are
both kept, and presets and bookmarks only fill the editor — nothing runs until
you press Run.
- It refuses the obvious ways to break a TV from a pasted command, but that
check is an anti-footgun, not a guarantee: a shell expression can get around
it. This is the one place in the app where the do-not-disable list is not
enforced, and it says so on screen. The companion app does not have it.

### Snapshots

- **A setting that was never set is now restored as never set.** Snapshots used
to record "unset" and "empty" the same way, so restoring one could not put a
setting back to the device's own default. New snapshots record the difference
and the preview shows what will be reset. Snapshots taken by earlier versions
behave exactly as they did before — an omission in an old file still means
"leave it alone", never "delete it".

### Devices and pairing

Expand Down
1 change: 1 addition & 0 deletions v2/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ This directory is the v2 workspace. v1 (`Shield-Optimizer.ps1` at the repo root)
- Lists ADB devices with friendly model names + device-type detection
- Full device management: profile, health report, launcher wizard (install/set-default/disable stock), optimize wizard with per-app defaults, full app list with disable/enable/force-stop/uninstall, Tweaks (HDMI-CEC, animations, display scaling), APK sideload, APK backup and cross-device clone, file manager, device screenshots, reboot + recovery, and snapshot save/preview/apply

**Remaining feature gaps vs. aTV Tools:** see [`ATVTOOLS-PARITY.md`](ATVTOOLS-PARITY.md). Headline items not yet shipped: screen recording, shell runner, permissions viewer, CPU + network monitor.
**Remaining feature gaps vs. aTV Tools:** see [`ATVTOOLS-PARITY.md`](ATVTOOLS-PARITY.md). Screen recording and a permissions viewer remain unimplemented. The unreleased desktop adds playback configuration reports, CPU/per-interface network samples, and an opt-in expert shell. Playback and resource reports plus audio policy controls are also available in the mobile companion; mobile does not expose arbitrary shell.

Expert shell can erase data or make a device unusable. Its obvious-command checks are not a security boundary. Execution stops the local ADB client after 30 seconds or 256 KiB from either output stream, which does not guarantee remote work has stopped. Saved bookmarks stay in local app preferences; commands and output are not added to diagnostics logs.

The behavior spec is at [`docs/FEATURES.md`](../docs/FEATURES.md). The ATV Optimizer Android app plan is in [`ATV-OPTIMIZER-ANDROID-PLAN.md`](ATV-OPTIMIZER-ANDROID-PLAN.md).

Expand Down
1 change: 1 addition & 0 deletions v2/crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tokio = { version = "1", features = ["io-util", "time", "macros", "rt-multi-thre
anyhow = "1"
thiserror = "1"
regex = "1"
quick-xml = "0.39.4"
chrono = { version = "0.4", features = ["serde"] }
base64 = "0.22"
ed25519-dalek = "2"
Expand Down
51 changes: 51 additions & 0 deletions v2/crates/core/src/adb/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ pub struct AdbOutput {
pub exit_code: Option<i32>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ShellTermination {
Completed,
OutputLimit,
Timeout,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundedShellOutput {
pub stdout: String,
pub stderr: String,
pub exit_code: Option<i32>,
pub termination: ShellTermination,
}

impl AdbOutput {
/// Returns `true` if exit code was 0.
pub fn success(&self) -> bool {
Expand Down Expand Up @@ -103,6 +119,13 @@ pub trait AdbDriver: Send + Sync {
/// Run `adb -s <serial> shell <command>`.
async fn shell(&self, serial: &str, command: &str) -> AdbResult<AdbOutput>;

/// Expert shell execution must bound output and runtime at the transport.
async fn shell_bounded(&self, _serial: &str, _command: &str) -> AdbResult<BoundedShellOutput> {
Err(AdbError::Unsupported {
operation: "shell_bounded",
})
}

/// Run `adb <args...>` and return raw stdout bytes — for binary output
/// like `exec-out screencap -p`, where UTF-8 conversion would corrupt the
/// data. Default reports unsupported so mocks without binary needs don't
Expand Down Expand Up @@ -135,3 +158,31 @@ pub trait AdbDriver: Send + Sync {
Err(AdbError::Unsupported { operation: "spawn" })
}
}

#[cfg(test)]
mod bounded_shell_tests {
use super::*;

struct UnboundedDriver;

#[async_trait]
impl AdbDriver for UnboundedDriver {
async fn raw(&self, _: &[&str]) -> AdbResult<AdbOutput> {
panic!("bounded shell must not delegate to raw")
}

async fn shell(&self, _: &str, _: &str) -> AdbResult<AdbOutput> {
panic!("bounded shell must not delegate to shell")
}
}

#[tokio::test]
async fn drivers_without_bounded_execution_are_unsupported() {
assert!(matches!(
UnboundedDriver.shell_bounded("serial", "id").await,
Err(AdbError::Unsupported {
operation: "shell_bounded"
})
));
}
}
13 changes: 8 additions & 5 deletions v2/crates/core/src/adb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ pub mod remote_input;

pub use batch::{batch_command, split_batch, BATCH_SEPARATOR};
pub use batch::{checked_batch_command, parse_checked_batch};
pub use driver::{AdbByteStream, AdbDriver, AdbError, AdbOutput, AdbResult};
pub use driver::{
AdbByteStream, AdbDriver, AdbError, AdbOutput, AdbResult, BoundedShellOutput, ShellTermination,
};
pub use parse::{
parse_active_audio_device, parse_device_list, parse_disabled_packages_output,
parse_display_mode, parse_dumpsys_meminfo, parse_hardware_properties_temp,
parse_display_mode, parse_display_modes, parse_dumpsys_meminfo, parse_hardware_properties_temp,
parse_installed_packages_output, parse_ls_output, parse_mdns_services, parse_meminfo_summary,
parse_permission_granted, parse_storage_info, parse_thermal_max_celsius,
parse_total_pss_by_process, parse_usage_stats, AppUsage, DisplayMode, FileEntry, MdnsService,
RamInfo, StorageInfo, MDNS_SERVICE_CONNECT, MDNS_SERVICE_LEGACY, MDNS_SERVICE_PAIRING,
parse_net_dev, parse_permission_granted, parse_proc_stat, parse_storage_info,
parse_thermal_max_celsius, parse_total_pss_by_process, parse_usage_stats, AppUsage, CpuSample,
DisplayMode, FileEntry, MdnsService, NetSample, RamInfo, StorageInfo, MDNS_SERVICE_CONNECT,
MDNS_SERVICE_LEGACY, MDNS_SERVICE_PAIRING,
};
pub use remote_input::RemoteInputSession;
Loading
Loading