Technical Manual

Version1.0.0
DateOctober 2026
Chapter 1

Introduction and concepts

1.1 · What Phonestra is

Phonestra brings the apps of an Android phone to a Linux PC, each in its own window, over Wi-Fi and without installing anything on the phone. SPECIFICATION.md says what it does; this manual explains how.

Each app window is a virtual display on the phone, as large as the window; the drawer, the main window, also shows the phone's real screen. Video, audio, touches, keys and clipboard go through a component written from scratch, copied to the phone at every connection and deleted at the end.

Linux PC (Rust, GTK4)Drawerapps, notifications, phone screenApp windowvideo, mouse, keyboardApp windowone for each open appADB over Wi-Fione encrypted connectionAndroid 14+ phone (Java)Mirrorthe main screenVirtual displaythe window's appVirtual displayanother appFigure 1.1 — Phonestra: each PC window shows one phone screen, all over a single connection

After reading this manual you should know where to work in every file, how to build the program and the phone component, build the AppImage, run the tests, and add a message or a channel without breaking the rest.

The manual assumes you know Rust and a little Java. Android and ADB concepts are explained where they are needed; the ch. 19 “Glossary” sums them up. The reason behind every choice is in the notes/ folder: here you will find the right reference when it matters (for example, “prove §49” is section 49 of notes/connection-tests.md).

ItemValue
Version1.0.0
LanguagesRust 2024 on the PC, Java on the phone
Size16,462 lines of Rust, 7,058 of Java
TargetLinux x86_64 (AppImage) · Android 14 and later
Table 1.1 — Phonestra at a glance

1.2 · Basic concepts

A few concepts recur throughout the manual. This table introduces each of them in one line, with the chapter that covers it in depth.

ConceptWhat it isWhereMore
ConnectionThe active phone and its ADB connection, kept up as long as Phonestra is open; it starts over at every dropcollegamento.rs3.2 “Life of a connection”
ADB clientThe ADB protocol written by us: all channels over a single encrypted TCP connectionsrc/adb/ch. 4 “The ADB client”
ComponentThe Java jar copied to the phone; the service is its long-running process, one per connectionandroid/helper/, componente.rsch. 5 “The on-phone component”
Video sessionA virtual display for an app window, or the mirror of the main screen for the drawer, with its video:<id> channelvideo_nostro/6.1 “Sessions: virtual display and mirror”
GuardianA shell process that restores the phone when whatever it is tied to endscollegamento.rs, Custode.java5.6 “The two guardians”
Panel “in hand”The physical screen turns off while the phone is used from the PC and turns back on when the user picks the phone upPannello.java, collegamento.rs7.3 “The phone in hand”
DrawerThe main window: apps, notifications, phones, tools and the drawn phone screencassetto.rs10.2 “The drawer”
HelperThe same jar, used for short commands that print and exit (app list, wallpaper, thumbnails)app.rs, Aiuto.java5.1 “What the component is”
Table 1.2 — Basic concepts
i
One phone at a time. Phonestra uses one phone at a time; the other configured phones wait in the drawer's sidebar (3.5 “Multiple phones”).

1.3 · Subsystems at a glance

The code is split into cohesive parts. This is the map that the manual explores chapter by chapter; the line counts of every file are in ch. 17 “Appendix B — File map”.

src/bin/phonestra.rs, src/collegamento.rs
main, the life of the connection, the 3-second round, shutdown, data on the PC (ch. 3 “Startup and life cycle”).
src/adb/, src/rete.rs
The ADB client: messages, TLS, channels, shell,v2, sync:, pairing with the code, mDNS discovery (ch. 4 “The ADB client”).
android/helper/src/phonestra/, src/componente.rs
The component: service, channels, command channel, heartbeat, guardians, self-test, and its PC side (ch. 5 “The on-phone component”).
src/video_nostro/, SessioneVideo.java, Codifica.java
Virtual displays, mirror, H.264 encoder, resizing, app events (ch. 6 “Video”).
Pannello.java
The physical screen turned off during use from the PC, calls, drops (ch. 7 “The phone's panel”).
src/audio_nostro.rs, CanaleAudio.java
Loopback capture, AAC, playback and recording (ch. 8 “Audio”).
src/input_nostro.rs, src/appunti.rs, Input.java, Appunti.java
Touches, keys, text and clipboard in both directions (ch. 9 “Input and clipboard”).
src/cassetto.rs, src/finestra.rs, src/prepara.rs, src/procedura.rs, src/avvisi.rs
Drawer, app windows, preferences, first connection, notifications (ch. 10 “The user interface”).
src/azioni.rs, src/ricevi.rs
Installing apps, sending and receiving files (ch. 11 “Apps and files”).
src/bin/prova.rs, tests/
Tests on the PC and on the phone (ch. 12 “Testing and diagnostics”).
packaging/, android/helper/build.sh
The compiled component, the AppImage, releases (ch. 13 “Build and release”).

1.4 · The repository

The nic-fio/PHONESTRA repository is the only complete copy of the project: code, compiled component, documents, mockups and measurements.

PHONESTRA/
├── Cargo.toml  # the phonestra package: one library and two executables
├── Cargo.lock  # the exact versions of the Rust crates
├── Makefile  # make, make test, make clippy, make docs, make docs-check, make dist, make helper, make clean
├── src/  # the PC program
│   ├── adb/  # the ADB client
│   ├── video_nostro/  # PC side of the component's video
│   └── bin/  # phonestra.rs (the program) and prova.rs (phonestra-prova)
├── android/
│   ├── helper/src/phonestra/  # the phone component, in Java
│   ├── helper/stub/  # fake Android classes, for the compiler only
│   ├── helper/build.sh  # rebuilds the jar (make helper)
│   ├── phonestra-helper.jar  # the compiled component, embedded in the executable
│   └── README.md  # what runs on the phone and how it is built
├── data/instructions.toml  # per-brand instructions for the cable procedure
├── data/en/  # English translations of the interface, one table per module
├── vendor/rusb/  # rusb without the built-in libusb (dynamic linking, vendor/README.md)
├── packaging/  # container, scripts and AppRun of the AppImage, third-party licenses
├── docs/  # the two manuals and their sources (docs/sources/), index.html, README.md
├── notes/  # decisions, studies, measurements, issue log
├── tests/  # integration tests (tests/manual.rs)
├── tools/  # setup-dev.sh (what a fresh clone needs), backup.sh (the whole project as a git bundle)
├── logos/  # the Phonestra logo: icons/, icons-with-text/
├── mockup/  # design proposals, icons and the sources of the interface canvas
├── site/  # the website phonestra.nicfio.it: landing page, publish.sh
├── experiments/  # small test scripts outside the program
├── .github/workflows/ci.yml  # CI at every push: make all, make test, make docs-check
├── README.md  # the project in brief
├── CLAUDE.md  # working rules for Claude Code
├── SPECIFICATION.md  # what Phonestra does; the code's “§” references point here
├── NOTICE.md  # copyright and third-party components with their licenses
├── LICENSE.md  # Phonestra Freeware Licence, from the version after 1.0.0-rc.8
└── LICENSE-1.0.0-rc.8-and-earlier.md  # free personal use, up to 1.0.0-rc.8
Figure 1.2 — The repository's folders
PathContents
src/The PC program: interface, connection, ADB client (src/adb/), PC side of the component (componente.rs, audio_nostro.rs, video_nostro/, input_nostro.rs).
android/helper/The phone component: Java sources in src/phonestra/, fake Android classes for the compiler in stub/, build.sh.
android/phonestra-helper.jarThe compiled component (dex inside a jar). It is in the repository and is embedded in the executable.
data/instructions.tomlInstructions for each brand family for the cable procedure, embedded at build time (format in 10.5 “The first connection”).
docs/Technical Manual.html and User Manual.html (generated, never by hand) and their sources in docs/sources/: build.py, style.css, manual.js, one file per chapter in technical/ and user/.
packaging/Containerfile, build.sh, collect.sh, rust-licenses.py, test-distributions.sh and AppRun: the AppImage and the third-party licenses it carries (ch. 13 “Build and release”).
notes/Decisions, studies, measurements, issue log: the reasons behind the project.
tests/Integration tests; tests/manual.rs checks the manuals.
tools/setup-dev.sh says what a fresh clone lacks (packages, Rust, git identity) and, with --install, installs the packages; backup.sh saves the repository as a git bundle.
logos/The Phonestra logo in several versions, with the icons (icons/) and the icons with the name (icons-with-text/).
mockup/Interface proposals, icons and the sources of the design canvas.
MakefileThe usual commands: make, make test, make clippy, make docs, make docs-check, make dist (AppImage), make helper (jar), make clean.
.github/workflows/ci.ymlThe CI on GitHub: at every push and pull request it builds, runs make test and make docs-check, without a phone.
SPECIFICATION.mdWhat Phonestra does, section by section; the code's “§” numbers point here.
NOTICE.mdCopyright, the third-party components (Rust crates, the AppImage's libraries) and their licenses, where their texts are in the AppImage and where to get their sources.
LICENSE.mdThe Phonestra Freeware Licence, from the first version after 1.0.0-rc.8: free use, also at work, and free redistribution of the unchanged AppImage; no sale, no inclusion in commercial products, no modification. The licence of the versions up to 1.0.0-rc.8 (free personal use) stays in LICENSE-1.0.0-rc.8-and-earlier.md.
site/The website, https://phonestra.nicfio.it: the landing page (landing/), publish.sh that builds it with the manuals, the licence page and the download, and copies it to the server.
Table 1.3 — What is in the repository

1.5 · The project in numbers

How big Phonestra is, part by part. The lines are counted from the sources every time the manual is generated.

PartWhereLinesContents
Tests and measurements (PC)src/bin/prova*, prova_input.rs, misura_audio.rs, video_nostro/prova.rs, adb/misura.rs, adb/prove.rs, tests/2,968the phonestra-prova tool, the study's measurements, the integration tests
Tests and measurements (phone)VideoProva.java, InputProva.java, Codificatori.java1,478measurement tools and test commands of the component
Interfacecassetto.rs, finestra.rs, ricevi.rs, prepara.rs, procedura.rs, avvisi.rs, foto.rs, bin/phonestra.rs6,513drawer, app windows, receiving files, first connection, alerts
ADB clientsrc/adb/1,491messages, TLS, channels, shell,v2, sync:, pairing
Component, PC sidecomponente.rs, audio_nostro.rs, video_nostro/, input_nostro.rs, appunti.rs3,123service, dispatching, audio, video, input, clipboard
Connection and datacollegamento.rs, rete.rs, configurazione.rs, telefono.rs, usb.rs, notifiche.rs, azioni.rs, app.rs, lib.rs2,367connection, panel, mDNS, configuration, cable, notifications, actions, app list
On-phone componentandroid/helper/src/phonestra/5,580service, guardian, audio, video, input, clipboard, panel, thumbnails
Build and toolspackaging/, android/helper/build.sh, docs/sources/4,820container, AppImage, jar, manual generator
Total28,340
Table 1.4 — The project's parts and their line counts

The numbers in this table and in ch. 17 “Appendix B — File map” are not written by hand, and cargo test fails if the published manual no longer matches the sources (12.5 “The manuals and their checks”).

Chapter 2

Overall architecture

2.1 · Architectural principles

Phonestra grew out of a few rules, written before the code and followed in every piece. They explain choices that would otherwise look odd, such as an ADB client written from scratch or a guardian for every changed setting.

PrincipleWhat it means
Nothing on the phoneNo app installed. The component is copied to /data/local/tmp at every connection and deleted at the end.
The phone goes back to how it wasEvery changed setting has a guardian that restores it, even if the PC disappears or the service dies suddenly (5.6 “The two guardians”).
Our own ADBNo adb to install: TLS, pairing with the code, mDNS and multiple channels are written here (ch. 4 “The ADB client”).
Our own codeThe phone component is written from scratch: no scrcpy, no third-party code to depend on.
Measure before writingStudy, then measurements on the real phone, then code one piece at a time. The measurements stay in notes/.
One file, no tracesOne AppImage that contains everything; on the PC only ~/.config/Phonestra and ~/.cache/Phonestra (3.6 “Configuration and data on the PC”).
Table 2.1 — The principles that shaped Phonestra

2.2 · The two sides and the layers

Phonestra has two sides: the program on the PC, in Rust, and the component on the phone, in Java. They talk to each other only through ADB, over a single encrypted Wi-Fi connection.

Linux PC (Rust)Android 14+ phone (Java)Interfacecassetto · finestra · riceviPC-side piecesaudio, video, input, clipboardConnectioncollegamento.rsComponentcomponente.rs (Condiviso)mDNSrete.rsADB clientsrc/adb: TCP + TLS, channelsAudio · Video · Input · Clipboard · Panelservice classesServiceapp_process, uid 2000Guardiansh with setsidadbdWireless debuggingTLSFigure 2.1 — The parts of Phonestra and who calls whom

On the PC each layer calls only the ones below it: the interface does not talk to ADB, and the ADB client knows nothing about video or audio. On the phone there is a single process per connection, the service, which serves all windows, the drawer's screen, audio and clipboard. The guardian is a separate shell process that stays alive even when the service dies; the dashed line is the pipe through which the service passes it the restore actions.

LayerResponsibilityMain modules
InterfaceThe GTK windows: drawer, app windows, first connection, system alertscassetto.rs, finestra.rs, ricevi.rs, prepara.rs, procedura.rs, avvisi.rs
ConnectionFinding the phone, keeping it connected, the 3-second round, the connection guardian, shutdowncollegamento.rs, rete.rs, notifiche.rs, configurazione.rs
PC-side piecesAudio, video, input and clipboard over the service's channelsaudio_nostro.rs, video_nostro/, input_nostro.rs, appunti.rs
ComponentService startup, command channel, message dispatchingcomponente.rs
ADB clientMessages, TLS, channels, flow controlsrc/adb/
PhoneService, guardian, classes of the piecesandroid/helper/src/phonestra/
Table 2.2 — The program's layers and their modules
i
Where the state lives. the state shared by the interface and the connection lives in the Collegamento, in watch channels: stato(), guasto(), info(), notifiche(), adb(), componente(). Drawer and windows subscribe and restart on their own when a value changes (3.2 “Life of a connection”).

2.3 · Threads and tasks

Three worlds run together in the Phonestra process: the GTK thread, the tokio runtime and GStreamer. Each has its own job, and they talk to each other only through channels.

WhereWhat runsHow they talk
GTK main threadThe whole interface; glib::spawn_future_local for tasks that touch widgetsReads the watch channels of the Collegamento, sends commands over mpsc channels
tokio runtime (phonestra::esecutore())Connection, ADB client, component, audio, video, inputwatch for states, mpsc for messages, Notify for wake-ups
GStreamerVideo and audio decoding, playback, recordingappsrc elements fed by tokio tasks
Table 2.3 — Threads and tasks
i
Rule of thumb. a Widget never leaves the GTK thread; an Adb, a Condiviso or a Mittente is cloned and goes wherever it is needed.
Chapter 3

Startup and life cycle

3.1 · Program startup

The program starts from main, in src/bin/phonestra.rs: it initializes GStreamer, creates the GTK application and hands the configured phone over to a Collegamento, which lives in a tokio task of its own.

mainbin/phonestra.rsadw::Applicationone per sessionCollegamentocollegamento.rsDrawercassetto.rsgst::init()application_id io.github.nic_fio.Phonestraprimo_telefono()with no phones configured: prepara::apri (“Add a phone”) and stopesecutore().spawn(mantieni())cassetto::aprisubscribes to stato, guasto, info, notifichelast window closed → Collegamento::chiudi, waits for Stato::Chiuso, then exitsFigure 3.1 — From main to the drawer

A second launch does not open a second connection: adw::Application is unique per session and connect_activate brings the drawer back to the foreground (reopening it if it was closed). With a package name as an argument (phonestra com.android.chrome) that app opens right away too, but only on the first launch: the instance already running does not receive the argument.

Ctrl+C and SIGTERM close the windows just as the user would, so the phone is restored. When the drawer asks to switch phones (3.5 “Multiple phones”), main relaunches the program at the end: $APPIMAGE if set, otherwise the current executable.

3.2 · Life of a connection

Collegamento::mantieni is the heart of the program. It runs in a tokio task as long as Phonestra stays open and starts over at every drop.

  1. Finds the phone: first the last address that worked, then the mDNS search for the _adb-tls-connect._tcp service with the saved serial number (rete::indirizzo_attivo). The port changes every time Wireless debugging is turned on, so it must always be read again.
  2. Opens the connection: Adb::wifi (TCP, STLS, TLS with Phonestra's certificate), with a 30 s timeout.
  3. Reads and saves the user's values (screen timeout and media volume), also in telefoni.toml. They are read only once for all reconnections; at every reconnection Phonestra checks whether the user has changed the screen timeout in the meantime.
  4. Reads the screen's density and shape (wm density; wm size, first the “Override” values chosen by the user, then the “Physical” ones): the density is needed by the virtual displays, the aspect ratio by the column of portrait-only apps.
  5. Starts the connection guardian: a shell exec: that raises screen timeout and volume to the maximum and restores them when the channel closes (5.6 “The two guardians”).
  6. Publishes the Adb (watch) and the Collegato state: drawer and windows restart on their own.
  7. Starts the component in a task (gira_componente): service on the phone, then Condiviso published for windows and drawer, then audio (after the mirror and 5 s, 8.4 “The startup order”).
  8. Listens to the clipboard of the phone (appunti::ascolta).
  9. 3-second round as long as the connection holds (next section).
  10. Shutdown when the user closes Phonestra (3.4 “Shutdown”).

The state is published as Stato::{Cerco, Collegato, Bloccato, Perso, Chiuso}. Between one attempt and the next the wait grows from 2 to 10 s; “Reconnect now” (Riconnetti ora, riconnetti_ora) interrupts it.

CercomDNS, then Adb::wifiCollegatoapps in useBloccatoapps receive no inputPersoretries on its ownChiusoPhonestra endsconnection openphone lockedunlockeddropfailed, or 30 swait 2 → 10 sthe user closes Phonestra (from any state)Figure 3.2 — The connection states (Stato) and what changes them

3.3 · The 3-second round

A single exec: every 3 s fetches lock state, calls and notifications, and also serves as a check that the phone is responding: if it does not respond within 5 s, the connection is considered dropped. Battery and network and, with the phone “in hand”, lastUserActivityTime are separate commands, with a 5 s timeout each: if they fail, the connection does not drop.

CommandWhenUsed for
dumpsys window | grep -m1 -o 'isKeyguardShowing=[a-z]*'every roundknowing whether the phone is locked (Bloccato state) and whether the user unlocked it by hand
dumpsys telephony.registry | grep -o 'mCallState=[12]'every roundincoming (1) and ongoing (2) calls; with two SIMs there is one line per SIM (7.4 “Calls”)
notifiche::COMANDO_NOTIFICHEevery roundthe drawer's notifications (10.6 “Notifications and alerts”)
notifiche::COMANDO_INFOon the first round and then every 10 (30 s)battery and network for the drawn phone
dumpsys power | grep -m1 lastUserActivityTime=only with the phone “in hand”turning the panel off after the screen timeout with no touches (7.3 “The phone in hand”)
Table 3.1 — The commands of the 3-second round

Protected screens do not go through here: the component reports them, session by session (6.6 “App events”).

3.4 · Shutdown

When the user closes Phonestra, usa leaves the round and restores the phone to how it was, one step at a time, with a timeout for each.

  1. Music or video that is playing is paused (cmd media_session dispatch pause, at most 3 s): otherwise, once the capture is removed, it would resume from the phone's speaker.
  2. The Adb is withdrawn; the windows close their sessions and remove their apps from recents (waiting up to 5 s).
  3. The component receives FINE and shuts down (at most 12 s); its guardian turns the panel back on.
  4. The connection guardian is closed, and it restores volume and screen timeout.
  5. For 3 s Phonestra checks that the screen timeout is no longer Phonestra's; if the guardian has not restored it, Phonestra restores it directly. If even that fails, it stays recorded in telefoni.toml and is restored at the next connection.

3.5 · Multiple phones

Phonestra uses one phone at a time (the option of several phones active together was discarded). The other configured phones appear in the sidebar as “not active” (non attivo); to switch to one of them the program closes and restarts.

  1. Clicking an inactive phone asks for confirmation if apps are open.
  2. Telefoni::metti_primo moves it to the top of telefoni.toml: it is the one opened at startup.
  3. The drawer sets cassetto::RIAVVIA and closes all windows, like a normal shutdown: the previous phone is restored.
  4. main relaunches the program ($APPIMAGE or the current executable), which connects to the new phone.

Forget this phone… (Dimentica questo telefono…) does the same restart if other phones remain; if none are left, Phonestra closes, and at the next start “Add a phone” (Aggiungi un telefono) opens.

3.6 · Configuration and data on the PC

Phonestra keeps its data in two folders on the PC: the configuration in ~/.config/Phonestra, things that can be recreated in ~/.cache/Phonestra. telefoni.toml and preferenze.toml are read and written in configurazione.rs (Telefoni, Preferenze).

FileContents
~/.config/Phonestra/adbkeyPhonestra's private RSA key (permissions 600), different from that of adb: the phone authorizes Phonestra as a separate computer
~/.config/Phonestra/telefoni.tomlPer phone: seriale, nome (the device name, not shown), modello, nome_scelto (from Rename…, Rinomina…), modello_commerciale, tablet (Telefono::nome_mostrato builds the displayed name from them), android, ultimo_indirizzo, spegnimento_originale, volume_originale, preferiti; the first one is opened at startup
~/.config/Phonestra/preferenze.tomlesc_indietro, avvisi, solo_nome_app, app_silenziate, cartella_file, cartella_ricevuti (if missing: the Downloads (Scaricati) folder), lingua (if missing: the system's language); 10.4 “Preferences”
~/.config/Phonestra/icone/App icons for system alerts
~/.cache/Phonestra/GStreamer plugin registry, image loaders and fallback libraries of the AppImage, the logo for the “About” (Informazioni) window (icone/phonestra.png): it can be deleted
Table 3.2 — Phonestra's data on the PC

Deleting ~/.config/Phonestra brings Phonestra back to its initial state. The configuration folder follows $XDG_CONFIG_HOME; screenshots, recordings and received files go to the XDG user folders (Pictures, Videos, Downloads).

i
A new PC. on a new PC the phone must be paired again: the ADB key and the paired phones are not in the repository.
Chapter 4

The ADB client

4.1 · Why our own client

Phonestra speaks the ADB protocol on its own, in src/adb/: no adb server, no programs to install. All channels (commands, audio, the video of every window, shell) travel over a single encrypted TCP connection.

The adb_client library reads all channels from the same connection without dispatching the messages: it handles one command at a time, not video, audio and commands together. The client in src/adb/ has a reader task that dispatches each message to the right channel by local ID, and every channel honors ADB's flow control.

adb_client remains for short commands outside the actual connection: the USB cable (telefono.rs) in the fallback procedure and in phonestra-prova prepara, and the first Wi-Fi connection of “Add a phone” (Aggiungi un telefono), which after pairing reads model and version and removes the expiry from the authorization (telefono::Collegamento::wifi, 10.5 “The first connection”).

4.2 · ADB messages

ADB is made of a few messages, all with the same shape: a 24-byte little-endian header (command, arg0, arg1, data length, byte sum, command xor 0xffffffff) followed by the data (messaggio.rs).

CommandMeaningUse in Phonestra
CNXNHandshake: version, max_payload, featuresOurs announces host::features=shell_v2,cmd,stat_v2 (and delayed_ack if enabled)
STLSSwitch to TLSAlways, on Wireless debugging
OPENOpens a channel to a service (shell,v2,raw:…, sync:, localabstract:…)Adb::apri, with a 10 s timeout for the reply
OKAYChannel accepted, or data acknowledgedFlow control
WRTEData on a channelCanale::scrivi / leggi
CLSEClosing a channelCanale::chiudi, Chiusore
Table 4.1 — ADB messages

4.3 · Wi-Fi connection and TLS

On Wireless debugging the connection starts in clear and switches to TLS right away. Adb::wifi opens it in four steps; the phone recognizes the PC by its authorized public key.

PhonestraAdb::wifiadbdWireless debuggingTCP (5 s, TCP_NODELAY)CNXN in clear: features and max_payloadSTLSSTLS, then TLS with Phonestra's certificateCNXN after TLS: common max_payload, phone's featuresFigure 4.1 — Opening the Wi-Fi connection
  1. TCP to the address found via mDNS (5 s timeout, TCP_NODELAY).
  2. CNXN in clear: the phone reads our features and the max_payload from here, not from the CNXN after TLS.
  3. The phone replies STLS; we reply STLS and TLS starts (tls.rs). The client certificate is self-signed with Phonestra's RSA key (~/.config/Phonestra/adbkey): the phone recognizes the PC by the authorized public key. The phone's certificate is not checked against an authority (it is self-signed): pairing guarantees the identity.
  4. After TLS comes the phone's CNXN: its arg1 is the minimum of the two max_payload values, and its text contains the phone's features.

4.4 · Channels and flow control

All channels of a connection share a single underlying connection. Each channel has its own local ID; the reader dispatches, writers wait their turn.

Command channelthe componentAudio channelone at a timevideo:<id> channelsone per sessionexec: and shell,v2short commands, serviceAdb — cloned and passed around everywhereAdb::inviawrites, one at a time (Mutex)leggi_semprereads and dispatches by IDPostathe OKAYs that cannot waitOne TCP + TLS connectionadbdWireless debuggingFigure 4.2 — Many channels over one connection: solid arrows are writes, dashed arrows the dispatched data

An Adb is cloned and passed around everywhere. One task reads from the socket and dispatches (leggi_sempre); writes go directly to the socket, one at a time under a Mutex (Adb::invia). A second task, the “posta” (mailbox), sends only the acknowledgments that cannot wait for the writers (the OKAY messages acknowledging reads).

Adb::apri(servizio) returns a Canale with scrivi, leggi (cancelable), leggi_esatti, leggi_tutto and chiudi; Canale::chiusore() returns an object that closes the channel from another task. Adb::esegui is the shortcut for a short command (the exec: service) that returns the output.

Without delayed ack each channel has only one WRTE in flight: the next one leaves after the OKAY. The transport parameters are in Trasporto:

ValueDefaultTest variableWhy
delayed_ackoffPHONESTRA_ADB_DELAYED_ACK=1On 28 Sep 2026 adbd rejected every OPEN when it was announced (ch. 18 “Appendix C — Known issues”).
max_payload64 KiBPHONESTRA_ADB_PAYLOAD=1mA single connection for all channels: a 1 MiB WRTE holds the wire for ~200 ms and audio waits behind video; a 64 KiB one for ~13 ms. With 64 KiB audio stays in sync (misure §50).
window256 KiBPHONESTRA_ADB_FINESTRA=512kMatters only with delayed ack: bytes in flight per channel.
Table 4.2 — Transport parameters

The details of delayed ack (balance, 4-byte OKAY, adbd's behavior) are in notes/adb.md and in the pure parts of flusso.rs, tested by adb/prove.rs against a fake in-memory adbd.

4.5 · The shell,v2 service

shell.rs starts a process without a terminal, with input, output, errors and exit code kept separate. Packets id u8 · lunghezza u32 LE · dati travel on the channel.

idDirectionContent
0PC → phoneProcess input
1phone → PCOutput
2phone → PCError output
3phone → PCExit code (1 byte)
4PC → phoneClosing of the input
Table 4.3 — The packets of shell,v2

This is how the Phonestra service starts: the output carries the ready line without mixing with the logs, the input carries the secret, the exit code tells why the service ended, and if the channel drops adbd sends SIGHUP to the process. For short commands Adb::esegui is enough (the exec: service).

4.6 · Copying files: sync:

sync.rs implements the sync: protocol in both directions.

FunctionRequestsUse
invia, invia_a_blocchiSEND with path and permissions, DATA blocks, DONEcopying the component to /data/local/tmp, the files the user sends to the phone and the .apk files to install (with progress and cancellation)
elencaSTA2, then LIS2listing a phone folder for “Receive files…” (Ricevi file…): 72-byte DNT2 entries after the ID, 64-bit sizes
e_cartellaSTA2finding out whether a path exists and is a folder (the places of “Receive files…”, Ricevi file…)
riceviRECVcopying a file to the PC by writing the DATA blocks straight into the file, without holding it in memory
Table 4.4 — The functions of sync.rs
i
Note. in shared storage (FUSE) the listing does not contain “.” and “..”: an empty folder and one that does not exist would give the same answer, which is why STA2 is asked first.

4.7 · Pairing with the code

abbina.rs reimplements Android 11+'s adb pair from the Android and BoringSSL sources: the phone's “Pair device with pairing code” (Associa dispositivo con codice di associazione) screen shows 6 digits, and the user types them into Phonestra.

  1. TLS directly on the pairing port, with Phonestra's certificate.
  2. Password = the 6 digits + 64 bytes exported from TLS (label adb-label\0): nobody can get in the middle.
  3. SPAKE2 over Ed25519 (curve25519-dalek), with Phonestra in the “alice” role.
  4. From the shared key, with HKDF-SHA256, an AES-128-GCM key (ring).
  5. Encrypted exchange of the PeerInfo: we send Phonestra's ADB public key, the phone sends its ID.

From then on the phone accepts Phonestra's key in Wi-Fi connections, as after an “Always allow” (Consenti sempre) over the cable.

4.8 · Network discovery: mDNS

rete.rs builds and parses DNS packets by hand: a PTR query for _adb-tls-connect._tcp.local (connection) or _adb-tls-pairing._tcp.local (the code screen) with the QU bit, i.e. reply sent directly to our socket. It is the method that found the phone when the discovery of adb saw nothing.

RuleWhy
The instance is named adb-<seriale>-<suffisso>The name identifies the saved phone.
The port comes from the SRV recordIt changes every time Wireless debugging restarts.
The query is repeated every secondSome phones only answer the second one.
An SRV with TTL 0 removes the found phoneIt is the “goodbye” of a service that is shutting down.
indirizzo_attivo: last good address (800 ms), then up to three 3 s searchesThe common case is fast; an address is accepted only if the port really answers.
Table 4.5 — How the phone is found on the network
Chapter 5

The on-phone component

5.1 · What the component is

Everything Phonestra does on the phone goes through a Java service written from scratch, started like the ADB shell and deleted at the end. This chapter describes its infrastructure; the following chapters describe the pieces: video, panel, audio, input.

android/phonestra-helper.jar contains a classes.dex. The PC embeds it (app::AIUTO, include_bytes!), copies it to /data/local/tmp and starts it with app_process: it runs with the shell's uid (2000) and its permissions (capturing the screen and audio, injecting events, reading the clipboard). It is not an app, it is not installed, it does not appear in the settings.

The same jar has two modes of use, chosen by the first argument of phonestra.Aiuto: the service, the long-running process, one per connection, which handles audio, video, input, clipboard and panel; and the short helper commands, which print and exit. For each short command the PC copies the jar under a name of its own (phonestra-aiuto.jar.<8 cifre esadecimali>, because several requests may arrive together), runs it and deletes it (app.rs).

CommandWhat it printsWho uses it
app [lato]the list of launcher apps with their PNG icons in base64, closed by a fine\t<n> line; with no arguments it means app 96drawer (app::elenco)
sfondo [larghezza]the phone's wallpaper as PNG (default 540)phone drawn in the drawer
miniature <lato> <percorsi in base64>one line indice\tJPEG in base64 (quality 80) or indice\t- for each file“Receive files…” (Ricevi file…; 11.2 “Receiving files from the phone”)
pannello [0|1]schermi=<quanti> after turning the panel on (1, default) or offthe guardian (5.6 “The two guardians”)
codificatorithe phone's audio and video encodersphonestra-prova codificatori
audio …captured audio, for study (sorgente=submix|loopback|render, formato, priorita, voce)phonestra-prova audio-nostro
video-prova …the video measurement tool of the studyphonestra-prova video-prova
serviziothe ready line, then stays aliveComponente::avvia
Table 5.1 — The jar's commands. An unknown command exits with code 2

Java only where Android requires it: the APIs needed (MediaCodec, VirtualDisplay, AudioPolicy, InputManager) exist only in Java. Audio and video encoding is done by the phone's encoders anyway.

5.2 · Starting the service

The PC starts the service on a shell,v2 channel: it copies the jar, launches it with app_process, passes it the secret on the input and waits for the ready line. Then it opens the command channel and receives the CIAO.

PCComponente::avviaadbdServiceJava, uid 2000Guardianshsync: phonestra-servizio-<random>.jarshell,v2,raw: exec app_process … serviziostart (uid 2000)secret (32 hex digits) on the inputsetsid sh -c … (action pipe)deletes the jar, context, self-testLocalServerSocket phonestra_<32 hex>ready line (process output)localabstract: “comandi” preambleCIAO (versions, self-test)each second, sent both waysBATTITOFigure 5.1 — From startup to the first heartbeat

The ready line is phonestra-servizio pronto protocollo=1 socket=phonestra_<32 hex> pid=<pid>; if startup fails, phonestra-servizio errore <causa> and code 1. The PC waits 20 s for the ready line and 10 s for the CIAO; if the service does not start, the PC deletes the jar itself. exec makes the service take the place of sh, so adbd's SIGHUP reaches it directly; --nice-name makes it appear in ps as phonestra-servizio.

5.3 · Channels and preamble

Each channel is a localabstract:phonestra_<32 hex> opened by the PC. The first bytes are the preamble: segreto (16 byte) · lunghezza del tipo u8 · tipo ASCII. The service reads it with a 3 s timeout and chooses the handler from the part of the type before the colon (Servizio.TIPI).

TypeHandlerContent
comandiServizio.comandiMessages in both directions. Only one per service: a second comandi channel is rejected
audio, audio:aac, audio:pcmCanaleAudio.gestisciAudio packets to the PC (8.1 “The recipe”); an unknown format receives the text errore formato sconosciuto
video:<id>Video.canaleVideo packets of a session (6.3 “The video:<id> channel”)
Table 5.2 — Channel types

Wrong secret, uid other than 2000 or unknown type: the socket is closed without a reply.

5.4 · The command channel

The command channel carries all the short messages between PC and service, in both directions: requests with a reply, spontaneous events, the heartbeat.

Messages with an 8-byte big-endian header, tipo u8 · bandiere u8 · id u16 · lunghezza u32, and the content (16 MB at most: beyond that, the stream is corrupted and the channel is closed). id ties the reply to the request (0 = spontaneous message); the 0x01 flag marks it as a reply. The format is the same in Protocollo.java and componente.rs, checked by a test with the same bytes.

RangeWhoWhere
0x01–0x0fInfrastructurecomponente::tipo, Protocollo.java
0x10–0x1fTesting and diagnosticsPROVA_CUSTODE
0x40–0x4fVideo and panel6.2 “Video messages”
0x50–0x5fInput and clipboard9.1 “Input and clipboard messages”
Table 5.3 — Message type ranges
TypeNameDirectionContent
0x01CIAOservice → PC, first messageLines chiave=valore: protocol, android, sdk, manufacturer, model, pid, avvio_ms, autotest_ms, autotest.<voce>=… (ok or the reason; for the guardian ok setsid, morto setsid, ok senza-setsid)
0x02BATTITOboth directions, every secondempty
0x03FINEPC → service; same replyempty; after the reply the service exits with 0
0x04ERROREservice → PC, as a replytext, for example “tipo sconosciuto 0x2a”
0x10PROVA_CUSTODEPC → servicecreates a file that the guardian removes at the end (tests only)
Table 5.4 — Infrastructure messages

A type the service does not know receives ERRORE: the PC understands that the jar is old. PROTOCOLLO (1 today) changes only if an existing message changes meaning.

5.5 · Heartbeat and exit codes

Any message counts as a sign of life. After 5 s of silence each side considers the other gone: the PC closes the command channel (ricevi returns None; it checks every 200 ms), the service exits. This is needed because adbd on Wireless debugging does not notice by itself that a PC is gone (Wi-Fi lost, PC off).

CodeWhen
0FINE requested by the PC
1Startup error or unhandled exception
3No message from the PC for 5 s
4Command channel closed by the PC, or write impossible
5Secret not received or no command channel within 10 s
128+nKilled by signal n (129 = SIGHUP: startup channel closed; 137 = kill -9)
Table 5.5 — Service exit codes (componente::descrivi_uscita)

The service exits with System.exit and, after 2 s, Runtime.halt if something hangs. Restoring the phone is the guardian's job, which is there both at an orderly end and at a sudden one. The service restores by itself only what it can restore right away: the audio policy (the audio-fine shutdown hook), the panel when it turns it back on, and min_refresh_rate as soon as the panel is off; in those cases it also removes the corresponding action from the guardian.

5.6 · The two guardians

Phonestra changes a few things on the phone and must restore them even when something goes wrong. This is done by two shell processes, each tied to the life of something else:

Connection guardianService guardian
Who starts itThe PC, collegamento.rs, with exec:The service, Custode.java, with setsid sh -c
Tied toThe ADB connection: it ends when adbd closes its input (cat >/dev/null)The service process: it reads a pipe that only the service keeps open
What it restoresScreen timeout, media volumePhysical panel, minimum display refresh rate, test tasks, jar copies
Survives withtrap '' HUP TERM PIPEsetsid and trap '' HUP INT TERM PIPE
Table 5.6 — The two guardians

The screen timeout is set to the maximum (SPEGNIMENTO_LUNGO, i.e. never) while Phonestra is open: with the phone asleep, apps on virtual displays receive no input, and taps from the PC do not count as phone activity (with 30 minutes it fell asleep during use, prove §55). The volume is set to the maximum because with the volume at 0 the Facebook app does not start the audio of reels; audio comes out only from the PC anyway.

Both values are also saved in telefoni.toml: if Phonestra crashes without restoring them, the next connection restores the saved ones. The screen timeout is read from the phone at startup and restored at the end only if it is still Phonestra's value: if the user changes it during the connection, the user's value stays (prove §56). A value under 5 s is considered invalid; 30 minutes (SPEGNIMENTO_LUNGO_VECCHIO, the value of releases up to rc.3) is treated as a leftover of a crashed Phonestra.

The service guardian receives the whole list of actions at every change, between a #inizio line and a #fine line; a half list does not replace the previous one. When the service dies it runs them in ascending order (for equal order, the last added first), one per line with sh -c (one that fails does not stop the others), then deletes the service's jar and forgotten copies older than one minute, and terminates.

OrderActionWho sets it
400Turn the panel back on: app_process … phonestra.Aiuto pannello 1 with a copy of the jar kept for the guardian (phonestra-custode-<pid>.jar); if the copy is missing, the old fallback KEYCODE_SLEEP/WAKEUPPannello.java
410Restore min_refresh_rate to what it was (only while waiting, 1 s at most, before the panel is turned off)Pannello.java
500Remove the tasks started by the input testsInputProva.java
900Remove the PROVA_CUSTODE fileServizio.java
Table 5.7 — The service guardian's actions
i
Why the panel is turned back on in Java. at first the guardian turned the screen back on by putting the phone to sleep and waking it up (KEYCODE_SLEEP/WAKEUP). On Samsung phones this locked the phone and made Wireless debugging drop at every Phonestra restart. Now the guardian calls setDisplayPowerMode like the service (misure §51).

Virtual displays need no action: Android closes them when the process dies. Apps are not removed from recents on a drop, on purpose: when the connection comes back they return to their window with their state. Android removes the audio policy (8.5 “Who removes the capture”).

5.7 · Context, hidden APIs and self-test

Many of the APIs needed are hidden (IDisplayManager, IWindowManager, InputManagerGlobal, IClipboard, AudioPolicy). In app_process the hidden API policy is off: they are called via reflection.

PartHow
HiddenServices from ServiceManager + Stub.asInterface, methods looked up by name and number of parameters among the known variants. Rule: look at what is there, not at the version.
Contextapp_process has no Android context. One step at a time, it sets up the main Looper, a system ActivityThread, its ConfigurationController (without it, on Samsung phones DisplayManagerGlobal fails) and the context of the com.android.shell package: since Android 16 the shell's permissions apply only with the right package.
Self-testAt startup it checks that every hidden API is there and with which signature, without using it. The result goes to the PC in the CIAO (Ciao::mancanti()).
SystemSistema.java gathers the common pieces: the display manager for each virtual display, app launching (startActivityAsUser with 11 parameters, with am start as fallback), togliTask when a window is closed, the internal shell commands.
Table 5.8 — Context and hidden APIs
ItemWhat it checks
contestoShell context ready, com.android.shell package
permessiThe shell permissions the pieces need
display_managercreateVirtualDisplay and DisplayManagerGlobal
capture_displayIWindowManager.captureDisplay and the class of its arguments
task_stack_listenerTaskStackListener and its registration
inject_input_eventinjectInputEvent (2 or 3 parameters), InputEvent.setDisplayId
audio_policyAudioPolicy, AudioMix, createAudioRecordSink, registration
appuntiMethods of IClipboard; presence of semclipboard
custodeGuardian alive, with or without setsid
Table 5.9 — Self-test items

5.8 · Service security

The service runs with the shell's permissions: nobody else must be able to control it, and at the end nothing must be left on the phone.

  • Abstract socket with a random 128-bit name; every channel must come from uid 2000 (getPeerCredentials) and start with the secret (constant-time comparison). No TCP port open on the phone.
  • The secret is passed on the process input, not on the command line: /proc/<pid>/cmdline is readable by other processes with the same uid.
  • No generic command: the PC cannot make the service run arbitrary shell commands. The guardian's actions are decided by the service; test commands accept only packages and actions made of letters, digits, dots and underscores.
  • Nothing is left: jar deleted at startup and by the guardian, service that exits after 5 s without a PC, guardian that terminates after restoring.

5.9 · PC side: Componente and Condiviso

Componente (in componente.rs) is a started service: avvia, apri_canale, manda, richiesta, ricevi, chiudi. In Phonestra it is always used through Condiviso, which keeps it in a task (smista) and can be cloned: one service per connection, many windows. When the last copy of Condiviso goes away, the service is closed.

Messagefrom the serviceRISPOSTA?yesTo the requestersame id; ERRORE → errornoVIDEO_EVENTO?yesTo session id=…evento=fine closes itnoTo the type's subscribersfor example APPUNTI_CAMBIATIFigure 5.2 — Dispatching the command channel's messages
Condiviso methodWhat it is for
domanda(tipo, dati)A request with a reply (5 s timeout; expired requests are checked every 500 ms)
manda(tipo, dati)A message without a reply
apri_sessioneVIDEO_APRI: reply and events of the session; events that arrive before the reply are kept for 2 s
dimentica(sessione)Stop dispatching the events of a closed session
iscrivi(tipo)Receive the spontaneous messages of a type
mittente()A cloneable Mittente: taps and keys go straight into the command channel's queue, without going through the task
apritore()An Apritore to open the audio and video:<id> channels
nome_dispositivo()The model from the CIAO (“telefono”, phone, if missing)
finito(), vivo()Find out whether the service is still alive
chiudi()FINE, waiting for the exit, closing the channels
Table 5.10 — The methods of Condiviso

Collegamento::gira_componente restarts the service if it dies while the phone is still connected, after 2 s. After CADUTE_MASSIME (3) failed starts or drops it stops trying and publishes the reason (Collegamento::guasto): drawer and windows show “Phonestra won't start on the phone” (Phonestra non parte sul telefono) with “Reconnect now” (Riconnetti ora).

Chapter 6

Video

6.1 · Sessions: virtual display and mirror

Every app window is a virtual display on the phone, as large as the window, with the app launched on it. The phone screen drawn in the drawer is instead the mirror of the main screen. The phone encodes in H.264 in hardware, the PC decodes with GStreamer.

Virtual display (an app's window)Mirror (drawer)
How it is createdcreateVirtualDisplay(nome, l, a, dpi, null, flag) with the flags of Sistema.FLAG_PROPOSTI: PUBLIC, PRESENTATION, OWN_CONTENT_ONLY, SUPPORTS_TOUCH, ROTATES_WITH_CONTENT, DESTROY_CONTENT_ON_REMOVAL, TRUSTED, OWN_DISPLAY_GROUP, OWN_FOCUS, TOUCH_FEEDBACK_DISABLEDHidden static DisplayManager.createVirtualDisplay (permission CAPTURE_VIDEO_OUTPUT)
SizeThe one requested by the PC (720×1280 at 320 dpi if missing), aligned to 8 and to the encoder's alignment before creating itThat of the main screen, reduced to 1920 per side
OrientationLocked: cmd window set-ignore-orientation-request and user-rotation lock 0 on the displayFollows the phone: the size is re-read every 500 ms; if it rotates, new encoder and new mirror
ResizableYes (VirtualDisplay.resize)No: the window scales the image; VIDEO_RIDIMENSIONA replies «specchio: misura dello schermo del telefono»
Display numberIn the open reply0 (the main screen); no orientation events, but protected-screen events yes
Table 6.1 — The two kinds of video session

On the PC the size in pixels comes from finestra::pixel: the window size in points (1 PC point = 1 phone dp) times the phone's density, within 2560 per side and in multiples of 8. The display has the same density as the phone, because some apps (Facebook) draw certain elements with the density of the real screen, and with different densities they would come out huge. For portrait-only apps in a wide window the display is a column shaped like the phone (finestra::misura).

6.2 · Video messages

Video messages sit in the 0x40–0x4f range of the command channel, with a content made of chiave=valore lines. Requests are executed in order on a video thread of the service: the command channel (heartbeat, input) never waits for video.

TypeNameRequest (PC → service)Reply
0x40VIDEO_APRIlarghezza altezza dpi codec or specchio=1 lato_massimo codec; optional app=, informazioni=; test switches, valid for the whole service: max_fps=, priorita=, protetta=id display codec larghezza altezza (size already aligned), plus avvio=<esito> if there was app= or informazioni=
0x41VIDEO_CHIUDIid [togli_task=1]empty, once closed
0x42VIDEO_AVVIA_APPid app=<pacchetto> or id informazioni=<pacchetto>outcome of the launch
0x43VIDEO_RIDIMENSIONAid larghezza altezzasize LxA or unchanged size
0x44VIDEO_CHIAVEidempty
0x45VIDEO_PANNELLOacceso=0|1schermi=<quanti>
0x46VIDEO_EVENTO—spontaneous: evento=<nome> id=<sessione> …
Table 6.2 — The video messages

The PC waits for the reply only for APRI and CHIUDI; the other commands do not wait for it and an error ends up in the log. informazioni=<pacchetto> opens the “App info” (Informazioni sull'app) (App info) page of Settings instead of the app. The default codec is h264; the component also accepts h265, used only by the tests.

6.3 · The video:<id> channel

Each session has its own channel, video:<id>, which carries the encoder's packets from the phone to the PC.

The PC opens it right after VIDEO_APRI (within 10 s). The encoder starts when the channel is open, so the first packet is the size, then the parameters, then the first keyframe, and nothing is lost. The PC does not write to it; if it closes it, the session closes (without removing the app from recents). Each packet has a 12-byte big-endian header:

PacketHeaderThen
New size0x80000000 · larghezza u32 · altezza u32nothing
Datapts u64 (µs since the first frame; bit 62 = codec parameters, bit 61 = keyframe) · lunghezza u32the data in Annex B, as it comes out of MediaCodec
Table 6.3 — The packets of the video channel

On the PC side video_nostro::flusso::leggi_pacchetto reads them in a dedicated task: a read interrupted halfway inside a select! would lose bytes. The phone's reader thread writes each whole packet with a single write.

6.4 · Encoder and keyframe

Codifica.java takes the first hardware encoder (not an alias) for the type, with the measured values (misure §43): 8 Mbit/s, 60 frames per second declared, keyframe every 10 s, repeat after 100 ms, real-time priority, limited range, plus prepend-sps-pps-to-idr-frames so that every keyframe carries the parameters in front; max-fps-to-encoder only with the test switch max_fps.

If configure rejects the format, it retries in this order: hardware without prepend-sps-pps-to-idr-frames, then Android's default encoder with and without it.

The keyframe is needed when a window restarts or a recording begins (ricomincia_video → VIDEO_CHIAVE). It is requested with REQUEST_SYNC_FRAME, without recreating anything: about 0.1 s instead of scrcpy's 1–2 s restarts.

i
Still screen: the forced redraw. the Qualcomm encoder (c2.qti.avc.encoder) ignores repeat-previous-frame-after: on a still screen nothing comes out, and the keyframe request would wait for the next change (a freshly opened window would stay black). If the keyframe does not come out within 80 ms, SessioneVideo detaches and reattaches the encoder's Surface (VirtualDisplay.setSurface(null) and then its own again), which makes a frame get composed at once; if still nothing, a second time after another 160 ms.

6.5 · Resizing

When the window changes size, the virtual display follows it: it is resized if that is enough, recreated if the scale changes too much.

  1. The window changes size. A function tied to GTK's redraw sends the new size to the session on every change, without waiting.
  2. The PC decides. During a recording the size does not change (8.6 “Recording”); for the mirror nothing is done. If, with the 2560-pixel cap, the right scale drifts more than 15% from that of the display, resizing the display is not enough: the session ends with FineSessione::Ricrea and is recreated after 300 ms, once the window has stopped changing size. Otherwise VIDEO_RIDIMENSIONA.
  3. The phone resizes. One size = one encoder. If the aligned size does not change, nothing happens. If it changes: the new encoder is prepared, the size is sent to the PC, VirtualDisplay.resize + setSurface, then the old one is closed; its packets still in flight are discarded.

An input event computed on the old size is discarded by the phone (9.2 “Injection”).

6.6 · App events

EventiApp.java registers a TaskStackListener as long as there is at least one session. Each event schedules a check 150 ms later (events arrive in bursts); in addition, a check every 3 s, because a protected window can appear without task events.

EventPairsWhenWhat the PC does
orientamentodisplay verticale=0|1 valore=NAt the first check, then when the app switches from portrait to landscape or vice versa (not on every value change); never for the mirrorFixed-size 9:16 window or column
protettadisplay protetta=0|1At the first check, then when it changesMessage in place of the black image
spostatatask displayA task moves to another screen (app opened on the phone)Nothing (diagnostics only)
rimossotaskA task of the display closesNothing (diagnostics only)
finemotivoThe phone closes the session by itselfThe session restarts as after a drop
Table 6.4 — The app events

The protected screen is recognized without dumpsys (Protetta.java): captureDisplay shrunk to 5 % and containsSecureLayers(), with a 2 s timeout. Phonestra does not bypass protections: it shows a message.

6.7 · PC side: from session to window

On the PC a session is a SessioneNostra: the video channel, the commands and the events. finestra::vista connects it to a GStreamer pipeline that draws in the window.

A video session from the PC
let SessioneNostra { display, video: mut flusso, mut comandi, mut eventi, .. } =
    SessioneNostra::avvia(&servizio, &Opzioni { display: (l, a, dpi), ..Opzioni::default() }).await?;
comandi.avvia_app("com.android.chrome").await?;
while let Ok(p) = leggi_pacchetto(&mut flusso).await { /* Pacchetto::Dimensione or Pacchetto::Dati */ }
comandi.ridimensiona(l, a).await?;
comandi.ricomincia_video().await?;
while let Some(e) = eventi.recv().await { /* Evento::Orientamento, Protetta, Spostata, Rimosso, Fine */ }
comandi.chiudi(true).await?;   // true = remove from recents (the user closed the window)
SessioneVideodisplay → encoderleggi_pacchettotokio taskappsrch264parsedecodebinvideoconvertgtk4paintablesinkGtkPictureFigure 6.1 — The path of a frame, from the phone to the window; from the first keyframe on, a copy goes to the recording (h264parse ! mp4mux)

finestra::vista holds the pipeline and redoes the session when needed: connection dropped, component restarted, display to be recreated, orientation changed (FineSessione::{Chiusa, Ricrea, Caduta}). The codec parameters (SPS/PPS) must be merged with the following frame before the appsrc.

Chapter 7

The phone's panel

7.1 · Turning the panel off

While apps are used from the PC the phone's screen turns off, but the phone stays awake and unlocked: that way the apps on the virtual displays keep running and receiving touches. When the user picks the phone back up, the panel turns back on; when they leave it there, it turns off again.

Pannello.java calls SurfaceControl.setDisplayPowerMode(token, 0|2) on every physical display: it acts only on the compositor, Android believes the screen is on and any change of state (power button) turns it back on. Since Android 14 the tokens live in DisplayControl, inside services.jar, loaded with a class loader on the SYSTEMSERVERCLASSPATH and the library android_servers.

The PC controls it with VIDEO_PANNELLO (6.2 “Video messages”): video_nostro::pannello sends it without waiting for the reply, ComandiVideo::pannello sends it from a session. There is one panel per phone, not one per window: the one that decides whether to turn it off is the Collegamento, with the phone “in hand” rule described below. When the service dies, the guardian's action 400 turns it back on (5.6 “The two guardians”).

7.2 · Display refresh rate when the panel is off

Samsung phones change the display refresh rate by themselves (10–120 Hz): after a moment of calm it is at 24 Hz. When the panel is turned off SurfaceFlinger switches to 60 Hz, but confirms the change only with the panel's vsyncs, which no longer arrive: its model stays at 24 Hz, takes the frames of the virtual displays at that pace, and apps that draw faster remain stuck waiting for it (Facebook, with AV1 reels decoded in software, starves the audio). Prove §59.

  1. Before turning off, Pannello.java reads mDisplayModePtr and mPeriodConfirmationInProgress from dumpsys SurfaceFlinger.
  2. If the model is not confirmed at 60 Hz or more, it sets min_refresh_rate=60 and registers with the guardian action 410, which restores the previous value.
  3. It waits for the confirmation, at most 1 s (ATTESA_60_MS), then turns off.
  4. It immediately restores min_refresh_rate as it was and removes the guardian's action.

7.3 · The phone in hand

The state is a single value in the Collegamento: a_mano, read with Collegamento::pannello_a_mano. With a_mano false, sessions that open turn the panel off; with a_mano true they leave it on. It lasts as long as the process, across reconnections: a drop does not reset it.

Panel offthe phone is used from the PCPanel on, “in hand”Collegamento::pannello_a_mano()manual unlock · incoming callend of call · last session closedreconnection after a locka touch, a key or a click from the PC (usa_dal_pc)idle for the user's screen timeout, with no callssessions that open turn the panel offsessions that open leave it onFigure 7.1 — The two states of the panel and what changes them
EventWhereEffect
The phone, locked during use, is unlockedCollegamento::sbloccato“in hand”: the panel stays on
First check after a connection, phone unlockedCollegamento::sbloccato“in hand” only if there had been a lock before, or a drop with the phone asleep (7.5 “Drops and reconnections”)
Incoming call3-second roundpanel on, “in hand” (7.4 “Calls”)
End of a call with the panel off3-second roundpanel on, “in hand”
Closing of the last sessionfinestra.rs, pannello_acceso_senza_finestrepanel on, “in hand”
Touch, key, scroll, text, paste, zoom, long press or Back from a windowCollegamento::usa_dal_pcif it was “in hand”: panel off, no longer “in hand”
Phone “in hand” idle for the user's screen timeout, with no calls during that time3-second roundpanel off, no longer “in hand”
Table 7.1 — Who turns the panel on and who turns it off

Turning off without touches exists because the phone's screen timeout, during the connection, is at the maximum (5.6 “The two guardians”): a phone unlocked by hand and then left on the table would never turn off. The 3-second round, as long as the phone is “in hand”, asks dumpsys power | grep -m1 lastUserActivityTime= and reads “(N ms ago)” (fermo_da): once the time chosen by the user (the one saved at the start of the connection) has passed, it turns the panel off. This also applies with no windows open: the phone stays awake and unlocked (prove §58 and §60).

!
The mirror counts as a session. the session counter (Collegamento::sessioni) also counts the drawer's mirror. “Last session” therefore means the last window and the drawer: with the drawer open, closing an app's last window does not turn the panel back on. Opening the mirror also turns the panel off, just like opening a window.

7.4 · Calls

The 3-second round reads the call state with dumpsys telephony.registry | grep -o 'mCallState=[12]': 1 means it is ringing, 2 that it is in progress. With two SIMs there is one line per SIM, and one line is enough to count.

MomentRuleWhy
It starts ringingIf the phone is not already “in hand”, panel on and “in hand”To answer with the phone in hand (prove §58).
During the callThe time without touches is counted from the last round with a call (chiamata_alle): the panel does not turn offWith the phone at your ear there are no touches.
It ends, and the phone was not “in hand”Panel on and “in hand”; then turning off without touches appliesAnswered with a click from the PC: during the call Android turns the panel back on by itself (proximity sensor), and Phonestra would believe it off; it stayed on forever (prove §60).
Table 7.2 — Calls and the panel
!
No restarts during a call. the call variables (squillava, in_chiamata, chiamata_alle) start from zero at every connection. Before restarting Phonestra for a test, look at all the mCallState lines: a restart during a call leaves the mirror black (prove §61).

7.5 · Drops and reconnections

On Samsung phones locking the phone drops Wireless debugging: the connection reopens on unlock. But the connection also drops because of the network, with the phone still unlocked on the table. In both cases the panel is on upon return (the guardian or the user turned it back on), and Collegamento::sbloccato must figure out who did it.

  1. On the drop, mantieni notes the time (caduto), only the first time and only if the connection was really open.
  2. At the first lock check after the reconnection, if the phone is unlocked: if before the drop the round had seen the lock (bloccato_durante_uso), the user unlocked it: “in hand”.
  3. Otherwise dumpsys power | grep -m1 mLastSleepTime= tells how long ago the phone fell asleep (dormito_da). If that happened no longer ago than the time elapsed since the drop plus MARGINE_CADUTA (15 s, generous compared with the 8 s within which the PC notices the drop: 5 s of waiting for the reply plus 3 s between checks), the phone has slept and the user unlocked it: “in hand”.
  4. If it slept earlier, the drop was a network one: the panel turns off again as the sessions restart. If the line is missing or the command does not reply, when in doubt “in hand”.

“Unlocked by hand” is decided from the first lock check after the connection, not from the reconnection alone (prove §55 and §57).

7.6 · The change log

Every panel change leaves a line in Phonestra's log, which ends up in the PC's journal:

$ journalctl --user --since today | grep -i phonestra
LineWhen
[collegamento] sbloccato a mano: il pannello resta accesomanual unlock, even after a drop (“unlocked by hand: the panel stays on”)
[collegamento] caduta senza blocco: il pannello si rispegnereconnection after a network drop (“drop without lock: the panel turns off again”)
[collegamento] chiamata in arrivo: pannello accesoit starts ringing (“incoming call: panel on”)
[collegamento] fine della chiamata: pannello acceso, si rispegne senza tocchiend of a call with the panel off (“end of the call: panel on, turns off again without touches”)
[collegamento] telefono in mano non toccato da N s: pannello spentoturning off without touches (“phone in hand not touched for N s: panel off”)
[finestra] usato dal PC: pannello spentofirst touch from the PC with the phone “in hand” (“used from the PC: panel off”)
[finestra] ultima finestra chiusa: pannello accesoclosing of the last session (“last window closed: panel on”)
Table 7.3 — The lines of the panel change log
i
Note. turning off when a session opens does not leave a line: it happens all the time. The service's lines arrive in the same log prefixed by [servizio]: when the display needs to be brought to 60 Hz (7.2 “Display refresh rate when the panel is off”), phonestra-servizio: video: frequenza del display a 60 Hz prima dello spegnimento appears.
Chapter 8

Audio

8.1 · The recipe

The phone's audio plays from the PC's speakers, without interruptions and in sync with the video. The recipe came from measurements: loopback capture, AAC, timestamps from the sample count and a precise startup order.

ChoiceWhy
Loopback capture: AudioPolicy with ROUTE_FLAG_LOOP_BACK on the sound usages (media, games, assistant, navigation, system sounds…: Audio.USI)Meanwhile the phone stays silent; once the policy is removed, it plays again by itself. No setting to restore.
AAC-LC 192 kbit/s, 48 kHz stereo (Android's software encoder)As clean as PCM (misure §42) and much lighter on Wi-Fi. PCM as a test fallback.
Timestamps from the sample count (samples × 10⁶ / 48000)Regular: the encoder's output time comes in bursts (1–3 ms and 30–40 ms instead of 21).
Reading at priority −19, and reading, encoding and sending on separate threadsIf Wi-Fi or the encoder slow down, reading does not stop and no samples are lost.
Table 8.1 — The audio recipe

On the phone (CanaleAudio.java, which uses the classes of the measuring tool Audio.java) there are four threads: audio-lettura, audio-codifica, audio-spedizione (queue of 256 packets, about 5 s, which drops the oldest ones, counted in persi) and audio-sentinella, which reads from the socket only to notice the close. Each thread catches its own errors: an audio problem closes the channel with an errore line, never the service. Only one audio channel at a time: a new one stops the old one and waits (at most 3 s) for it to have removed its policy.

audio-letturapriority −19audio-codificaAAC 192 kbit/sQueue256 packets, ~5 saudio-spedizionewrites to the socketAudio channelto the PCPCM: no encodingaudio-sentinellareads from the socket: sees the closeFigure 8.1 — The four audio threads on the phone (CanaleAudio.java)

8.2 · Audio packets

The audio channel goes only from the phone to the PC: orario u64 BE · lunghezza u32 BE · dati. The PC sends nothing; closing the channel stops the capture.

Flag in the timestampContent
bit 61UTF-8 text of the form chiave=valore …. The first packet is always inizio (formato=aac frequenza=48000 canali=2 bitrate=192000 sorgente=loopback buffer_ms=… registrazione=istanza|statica) or errore …; then lettura tid=… nice=…, misura (one per second), avviso, errore.
bit 62Codec configuration (AudioSpecificConfig, 2 bytes 11 90), before any data.
noneData: an AAC frame of 1024 samples (21.333 ms), or 1024 PCM samples.
Table 8.2 — The packets of the audio channel

8.3 · Playback on the PC

On the PC three pieces give each packet its timestamp and decide when to play it; then GStreamer decodes and plays it.

Flusso::apriaudio channelOrari + Durateregular timestampsMarginewhen to playappsrcraw AAC, codec_dataavdec_aacaudioconvert, resampleautoaudiosinkPC speakersFigure 8.2 — Audio from the channel to the speakers; a copy of the packets goes to the recording
PieceWhat it does
DurateThe duration of each packet from the sample count (21,333 or 21,334 µs, with no accumulated error).
OrariKeeps the timestamps regular and realigns only beyond a 60 ms deviation.
MargineDecides when to play each packet: phone timestamp + an offset fixed from the first packet. It starts at 80 ms; a late packet (less than 10 ms before now) moves everything later (a moment of silence, then no gaps) and widens the margin by 40 ms, up to 300. A phone more than 200 ms ahead beyond the margin triggers a realignment.
Margine::scendiAfter 10 s of calm it reduces the margin by skipping a “silence” packet (less than 40 % of the average bytes). With AAC the packet size is almost constant and it never triggers (ch. 18 “Appendix C — Known issues”).
Table 8.3 — The pieces of playback

audio_nostro::riproduci(apritore) is the function the connection calls; it ends if the channel closes and, when cancelled, closes it. PHONESTRA_AUDIO_CODEC=pcm (or raw) uses PCM instead of AAC.

8.4 · The startup order

Audio capture does not start together with the connection: it waits for the drawer's mirror. It is a rule found through measurements.

!
A rule not to lose. audio capture starts after the mirror of the drawer's screen, and 5 s after the mirror is open (ATTESA_SPECCHIO 10 s at most, ASSESTAMENTO 5 s, in collegamento.rs). If the mirror is recreated, the capture restarts after 300 ms. With the capture started before the connection's initial sessions had started, the Facebook reels player in the windows starved and the audio had micro-interruptions; verified with alternating tests (misure §48–49). Android's internal mechanism is not yet understood: do not change this order without redoing those tests.

The drawer signals the opening of the mirror with Collegamento::specchio_aperto(), which increments a watch counter followed by the audio task. The task only looks at the mirrors opened after the current service started: after a reconnection it waits for the new mirror (prove §53).

8.5 · Who removes the capture

The capture removes itself in every way the service can end, even the most abrupt.

How it endsWho removes the audio policy
The PC closes the channel, or a new one is openedCanaleAudio: stops the recorder and calls unregisterAudioPolicy
The service exits with System.exitA shutdown hook (audio-fine)
The service dies suddenly (kill -9)Android: the policy is tied with linkToDeath to our process
Table 8.4 — Who removes the capture

The guardian has no action for the audio: no shell command removes another process's policy, and none is needed. When Phonestra closes, before detaching the audio, the connection pauses the media that are playing, otherwise they would resume from the phone's speaker (3.4 “Shutdown”).

8.6 · Recording

The “Record the screen” (Registra lo schermo) button in an app's window writes an MP4: the H.264 video as it is (h264parse ! mp4mux) and the AAC audio as it is, without re-encoding. audio_nostro::ascolta() gives a copy of the packets (broadcast), caps_registrazione() the caps with the codec_data of the current audio. It starts from the first keyframe; with no AAC audio in progress the file has no audio.

During the recording the app's display does not change size and is not recreated for orientation (the window scales the image), and the button shows the elapsed time (● m:ss). The file goes to <Video di XDG>/Phonestra/<app> AAAA-MM-GG HH.MM.SS.mp4.

Chapter 9

Input and clipboard

9.1 · Input and clipboard messages

The PC's mouse, touchpad and keyboard become fingers and keys on the app's screen; the clipboard travels in both directions, text only, never passwords.

Range 0x50–0x5f, big-endian. Events have id 0 and no response: the PC does not wait for the phone. larghezza/altezza are the size of the image on which the PC computed the coordinates.

TypeNameContent
0x50TOCCHIdisplay i32 · larghezza u16 · altezza u16 · n u8 · n × (dito i64 · azione u8 · x i32 · y i32 · pressione f32)
0x51ROTELLINAdisplay i32 · x i32 · y i32 · larghezza u16 · altezza u16 · orizzontale f32 · verticale f32
0x52TASTOdisplay i32 · azione u8 · codice u32 · ripetizione u32 · meta u32
0x53TESTOdisplay i32 · testo UTF-8
0x54INDIETROdisplay i32 · azione u8
0x55APPUNTI_SCRIVIdisplay i32 · incolla u8 · testo UTF-8; responds if id is not 0
0x56APPUNTI_LEGGIempty request; response stato u8 · testo
0x57APPUNTI_ASCOLTAattivo u8; responds if id is not 0
0x58APPUNTI_CAMBIATIservice → PC, unsolicited: stato u8 · testo
0x5cCONTEGGIdiagnostics: injected, failed, discarded, clipboard notices, ascolto_appunti, last error
0x5dPROVAtest commands (InputProva.java), on their own thread (input-prova): apri <l> <a> <dpi>, avvia <display> <pacchetto>, azione <display> <azione> [pacchetto], firma <display> (32×56 luminance), chiudi, salva-appunti, ripristina-appunti, esterno <0|1> <testo>
Table 9.1 — Input and clipboard messages

A Rust test in input_nostro.rs reads Input.java and Video.java and checks that no message number is used twice.

9.2 · Injection

On the phone, input becomes Android events injected into the right display, in order, by a single thread.

finestra.rsGTK eventsInputNostroencoding, MittenteCommand channelno responseServiceInput.ricevi“input” threadinjectInputEventFigure 9.1 — The path of a touch, from the PC window to the virtual display
StepHow
QueueThe thread that reads commands does not inject; it queues the messages for the “input” thread, a single one, so order is preserved.
EventInputEvent.setDisplayId always, then injectInputEvent asynchronously. A rejection is counted (falliti); the log records the first error and then one every 100.
FingersEach PC identifier (−1 mouse, −2 generic finger, 10 and 11 for pinch) becomes a finger with a local number 0–9, at most 10. Clicks and drags are fingers (SOURCE_TOUCHSCREEN): dragging scrolls, a long press opens menus. Every event carries all the fingers that are down.
ScalingCoordinates × (display size / PC size). An event computed on a size different from the one declared by the video is discarded: during a resize a click would land in the wrong place. For this reason the PC rounds sizes to multiples of 8, like the phone.
WheelACTION_SCROLL, SOURCE_MOUSE, fractional values, within ±16.
KeysKeyEvent from a virtual keyboard. Text: the virtual key map, one character at a time, in practice ASCII only; the PC sends the rest (accented letters, symbols) with “paste”.
BackKEYCODE_BACK; on the main display when it is off, POWER to turn it back on.
PastePhone clipboard + KEYCODE_PASTE.
Table 9.2 — How the service injects input

9.3 · Keyboard and mouse on the PC

finestra.rs translates GTK events (finestra::tastiera for keys). Modifiers follow the values of KeyEvent.META_* (META_SHIFT, META_CTRL).

On the PCOn the phone
Click, dragFinger
Right clickLong press (selects and opens the Android menu)
Wheel, two fingers on the touchpadScrolling at the pointer position
Ctrl + wheel, Ctrl++ / Ctrl+−, pinch on the touchpadTwo-finger pinch (zoom; with the keys, at the center of the window)
Esc, mouse “back” button, Back buttonBack (Esc can be turned off in the preferences)
↑ / ↓One scroll step; real keys while typing (after a letter, Backspace or Delete)
Page Up / Page DownScrolling by 80% of the window height
Enter, Backspace, Delete, Tab, ← →, Home, EndThe corresponding Android keys (also with Alt: Alt+← is an arrow, not Back)
ASCII lettersTESTO; the rest via paste
Ctrl + letterThe Android shortcut (Ctrl+C, Ctrl+A…), pressed and released
Ctrl+V, Shift+InsertThe PC clipboard goes into the app
Ctrl+R, Ctrl+W, Ctrl+Shift+CStay with the PC: Rotate, Close app, Copy screenshot (10.3 “App windows”)
Alt + otherStays with the desktop (Alt+F4…)
Table 9.3 — Keyboard and mouse

9.4 · Clipboard

The service talks directly to IClipboard, as the package com.android.shell: no ClipboardManager, hence no Looper to run and no detour through the Samsung service semclipboard, which rejects the write with the wrong context. Signatures change between versions: the longest variant is chosen whose parameters, after the fixed ones, are only strings and integers.

DirectionHowWhat does not pass
Phone → PCAPPUNTI_ASCOLTA 1 at startup; on every copy APPUNTI_CAMBIATI (appunti::ascolta) → Collegamento::appunti → GTK clipboardCopies marked as sensitive (state 2) or of unknown sensitivity (3); texts over 200,000 bytes; echoes
PC → phoneOnly with Ctrl+V in a window: APPUNTI_SCRIVI with incolla=1Texts from password managers (x-kde-passwordManagerHint): alert “Password non inviata al telefono” (Password not sent to the phone); texts that are too long: “Testo troppo lungo: usa il trasferimento file” (Text too long: use file transfer)
Table 9.4 — The clipboard in both directions

Echoes. What Phonestra puts into the phone's clipboard must not come back to the PC. The service ignores its own writes (even an identical text within 3 s, because the notice may arrive later) and Samsung sends every notice twice (discarded within 0.5 s); the PC remembers the texts it sent (Collegamento::e_un_rimbalzo). The service rereads the clipboard only if the current clip is its own: reading another app's clip would make the “ha incollato dagli appunti” (pasted from the clipboard) notice appear.

i
Wayland. on GNOME the PC clipboard can only be changed while a window of the program is active. main sets the copy immediately and, to be safe, sets it again the next time a Phonestra window is activated; if something else is copied on the PC in the meantime, the pending one is forgotten.
Chapter 10

The user interface

10.1 · Interface architecture

The interface uses GTK4 and libadwaita, with a style faithful to libadwaita (GNOME title bar, boxed lists, pill buttons). Phonestra follows the system's light or dark theme: segui_tema applies to all windows, the SCURO palette of cassetto.rs to the drawer, “Add a phone” (Aggiungi un telefono) and the wizard (the app windows have rules of their own); the mockups are light only. No .desktop file is installed. The design proposals every screen comes from are in mockup/; the rules in notes/interface.md.

WindowFileWhat it isMore detail
Drawercassetto.rsThe main window: apps, notifications, phones, tools, the phone's screen10.2 “The drawer”
App windowfinestra.rsOne per app, with its own virtual display10.3 “App windows”
“Receive files…” (Ricevi file…)ricevi.rsAn adw::Dialog for choosing files on the phone11.2 “Receiving files from the phone”
“Add a phone” (Aggiungi un telefono)prepara.rsThe first connection without a cable10.5 “The first connection”
Cable wizardprocedura.rsThe USB cable fallback10.5 “The first connection”
System alertsavvisi.rsThe phone's notifications on the desktop, via D-Bus10.6 “Notifications and alerts”
Table 10.1 — Phonestra's windows and their files

10.2 · The drawer

cassetto.rs is the main window, the drawer. It subscribes to four watch of the Collegamento: stato(), guasto(), info() (battery and network) and notifiche(); it reaches the component through finestra::vista.

PartWhat it contains
Title barThe Phonestra symbol, the name and the phone pill with the connection state. When the component has failed, the pill turns red: “Phonestra won't start on the phone” (Phonestra non parte sul telefono).
Pill menuHeader with name, “model · Android N”, Wi-Fi and battery; Reconnect (Riconnetti), Rename… (Rinomina…), Turn off Wireless debugging on exit (Spegni il Debug wireless alla chiusura; disabled, “Coming soon”), Forget this phone… (Dimentica questo telefono…).
SidebarApps (App) and Notifications (Notifiche; with the counter); section My phones (I miei telefoni) with the active phone, the other configured phones (“not active”, non attivo) and Add phone (Aggiungi telefono); section Tools (Strumenti) with Install app… (Installa app…), Send files… (Invia file…), Receive files… (Ricevi file…); at the bottom Preferences (Preferenze) and About (Informazioni; the libadwaita “About” window with the logo).
Apps pageSearch, FAVORITES (PREFERITI) and ALL APPS (TUTTE LE APP). Typing in the drawer searches; Enter opens the first app found.
Notifications pageThe phone's notifications grouped by app, at most 2 per app and then “N more notifications from … ›” (altre N notifiche di … ›); Hide (it stays on the phone) (Nascondi (sul telefono resta)) and Hide all (Nascondi tutte) act only in Phonestra, and a hidden notification reappears if it is updated.
Drawn phoneOn the right: time, Wi-Fi, battery and the phone's real screen (the mirror, interactive, with finestra::vista(…, SCHERMO, …); it receives keys only after a click). Files can be dragged onto it (11.1 “Installing and sending”).
VeilWhen the phone cannot be used, a veil explains why; with the connection lost or the component failed it offers “Reconnect now” (Riconnetti ora).
Table 10.2 — The parts of the drawer

The app list comes from the helper (app::elenco, command app <lato>) and is valid only if it ends with the line fine\t<n>: a list interrupted by a drop is not used and is reread on reconnection (elenco_intero). The app menu (right click) has Open (Apri; or Bring to front, Porta in primo piano, if it is already open), Close app (Chiudi app) if it is open, favorites, App info (Informazioni sull'app) and Uninstall… (Disinstalla…; disabled for system apps).

10.3 · App windows

finestra.rs: one window per app, with finestra::vista in the center (video, mouse, keyboard) and around it the bar with Back (Indietro), Screenshot, Record the screen (Registra lo schermo) and the More commands (Altri comandi; ⋮) menu.

CommandShortcutWhat it does
ScreenshotSaves to <Immagini di XDG>/Phonestra/<app> AAAA-MM-GG HH.MM.SS.png and copies to the clipboard
Copy screenshot (Copia screenshot; ⋮ menu)Ctrl+Shift+CCopies only
Record the screenMP4 in <Video di XDG>/Phonestra; the button becomes ● m:ss (8.6 “Recording”)
Rotate (Ruota; ⋮ menu)Ctrl+RSwaps the window's sides; nothing if it is maximized or recording
Close app (⋮ menu)Ctrl+WCloses the window and removes the app from recents
Table 10.3 — The commands of an app window
  • The virtual display follows the window (6.5 “Resizing”).
  • Portrait-only apps (the orientamento event): fixed-size 9:16 window, no maximizing and no draggable edges; in full screen the app sits in a column shaped like the phone.
  • Connection lost or component failed: the last image stays, blurred, with “Reconnect now” and “Close” (Chiudi); the session restarts on its own when the connection returns and the app reappears where it was. With the phone locked only the subtitle changes.
  • Protected screen: a message instead of the black image.
  • When the window is closed, the app is removed from the phone's recents (ComandiVideo::chiudi(true)); if it was the one controlling playback (YouTube, Facebook), it is paused. When the last session closes, the panel turns back on (7.3 “The phone in hand”).

10.4 · Preferences

The drawer's Preferences page saves the user's choices in preferenze.toml (3.6 “Configuration and data on the PC”). Each item has its own value.

TabItemValue in preferenze.toml
App windowsEsc goes backesc_indietro
NotificationsPop-up alertavvisi
NotificationsApp name onlysolo_nome_app
NotificationsApps allowed to alert (one switch per app)app_silenziate
FilesFiles sent to the phone: one of the 6 folders of azioni::CARTELLE (Download, Documents, Pictures, Camera, Music, Movies; in Italian Download, Documenti, Immagini, Fotocamera, Musica, Video)cartella_file
FilesFiles received from the phone: a folder on the PC; choosing the Downloads (Scaricati) folder saves “nothing”, so it follows the system foldercartella_ricevuti
Phone appsApp list: Update now (Aggiorna ora)—
LanguageInterface language: Automatic (system) (Automatica (del sistema)), Italiano or English; it takes effect at the next start (lingua::attuale)lingua
Table 10.4 — The drawer's Preferences page (configurazione::Preferenze)

10.5 · The first connection

prepara.rs is “Add a phone” (Aggiungi un telefono) without a cable: the list of settings to enable on the phone (same Wi-Fi network, Developer options, any protections, Wireless debugging with “Pair device with pairing code”). For each item, the word to search for in Settings and “Ask Google ↗” (Chiedi a Google ↗), which opens Google's AI Mode with the question already written.

Items visible on the network tick themselves: Wireless debugging turned on (_adb-tls-connect) and the pairing code screen open (_adb-tls-pairing), which enables the 6-digit field. With the code, Phonestra pairs (adb::abbina), connects with adb_client, removes the expiry of the authorization (settings put global adb_allowed_connection_time 0) and saves the phone.

procedura.rs is the USB cable fallback, for Android 10 or earlier (it opens from “Android 10 or earlier? Connect with the cable” (Android 10 o precedente? Collega col cavo)). It advances on its own by checking the cable every second (usb.rs reads /sys/bus/usb/devices without opening the device) and recognizes these cases:

Cable state (usb.rs)How it is recognizedStep shown
SoloRicaricano useful interface, known Android manufacturer (PRODUTTORI_ANDROID)choose “File transfer” (Trasferimento file) from the USB notification
DebugSpentoMTP interface (06/01/01) without ADBenable Developer options and USB debugging
DebugAttivoSoloRicaricaADB (ff/42/01) without MTPin “Charging only” (Solo ricarica) the systemd permission (uaccess) is missing and access may be denied: choose “File transfer” (Trasferimento file)
DebugAttivoMTP and ADB“Always allow” (Consenti sempre), then the switch to Wi-Fi
Table 10.5 — The cable states

After the cable, telefono.rs retries 3 times with a 1 s pause (the error “got AUTH” means consent is still missing), sets adb_allowed_connection_time 0 and adb_wifi_enabled 1 and waits up to 30 s for the user's confirmation. After 20 s stuck at the first step, the “What the PC sees” (Cosa vede il PC) box appears with the list of USB devices, to photograph for whoever is helping remotely.

The per-brand instructions come from data/instructions.toml, embedded at build time. The families are chosen by searching for the words in marche in the manufacturer read from the cable; the last one, “Other phones” (Altri telefoni), has an empty marche and acts as the fallback. The paths are the phone's Settings names as they appear on the phone, in Italian here and in English in data/en/instructions.toml (the one used with the English interface); in the example: “Impostazioni › Informazioni sul telefono › Informazioni sul software” (Settings › About phone › Software information), “Numero build” (Build number), “Impostazioni › Opzioni sviluppatore” (Settings › Developer options) and “Impostazioni › Sicurezza e privacy › Blocco automatico” (Settings › Security and privacy › Auto Blocker).

data/instructions.toml
[[famiglia]]
nome = "Samsung"
marche = ["samsung"]             # words searched in the manufacturer
verificata = true                # paths tested on a real phone
percorso_build = ["Impostazioni", "Informazioni sul telefono", "Informazioni sul software"]
voce_build = "Numero build"
percorso_debug = ["Impostazioni", "Opzioni sviluppatore"]
grigio = "…"                     # what to do if the USB debugging item is grayed out
sicurezza = "…"                  # extra step (Xiaomi: security settings)
[famiglia.prima]                 # a setting to change before anything else
percorso = ["Impostazioni", "Sicurezza e privacy", "Blocco automatico"]
voce = "Blocco automatico"
i
Tests without a phone. with PHONESTRA_PROVA_PASSO both windows show a specific step without a phone, for interface tests: acceso, codice, fatto for prepara.rs; debug, consenti, xiaomi, wifi, fatto for procedura.rs.

10.6 · Notifications and alerts

notifiche.rs reads dumpsys notification --noredact (title and text of private notifications too) in the connection's 3-second round, and battery (dumpsys battery, “charging” with status 2 or 5) and network (cmd wifi status) every 30 s. It discards ongoing notifications (ONGOING_EVENT, FOREGROUND_SERVICE), group summaries (GROUP_SUMMARY) and those with neither title nor text; it sorts them newest first.

avvisi.rs turns new notifications into system notifications with the D-Bus service org.freedesktop.Notifications (GNOME, KDE, Xfce): GTK notifications on GNOME work only for programs with a .desktop file, which Phonestra does not install. “Already seen” notifications are fixed at the first read; today, however, the first read happens with the drawer just opened, when the list is still empty, so at the first real read the notifications already on the phone raise an alert (ch. 18 “Appendix C — Known issues”). Clicking an alert opens the app. The preferences allow turning them off, showing only the app name (the text becomes “New notification”, Nuova notifica) and muting individual apps. The app icons for the alerts are written to ~/.config/Phonestra/icone/<pacchetto>.png.

Chapter 11

Apps and files

11.1 · Installing and sending

azioni.rs uses only the Android shell: no app on the phone, no extra permissions.

ActionFrom whereHow
Installing an appInstall app… (Installa app…), or an .apk dragged onto the drawn phone (with confirmation)Copy with sync: to /data/local/tmp, pm install -r; the reasons for a rejection are translated into plain words (azioni::spiega_installazione)
UninstallingUninstall… (Disinstalla…) in the app menu (user apps only, pm list packages -3), with confirmationpm uninstall; the app leaves the favorites and its window closes
Sending filesSend files… (Invia file…; several files too), or files dragged onto the drawn phoneCopy to /sdcard/<cartella> (the folder from the preferences, Download if not chosen) with a free name, without overwriting, and notification to the media scanner: the file appears in Gallery and Files
Table 11.1 — Installing, uninstalling, sending
  • One transfer at a time: a second one is rejected with the alert “Wait for the current transfer to finish” (Aspetta la fine del trasferimento in corso). The transfer card shows the progress and is canceled with ×.
  • After an installation or uninstallation, the app list is reread.

11.2 · Receiving files from the phone

ricevi.rs is the “Receive files…” (Ricevi file…) window (adw::Dialog): you browse the phone and choose the files to copy to the PC (SPECIFICATION §11.1, mockup receive-files.html).

PlacePhone folderOpens as
Recent (Recenti)the last 7 days of Camera (Fotocamera), Screenshot, Download, Documents (Documenti) and WhatsApp (images, videos, documents, audio), in groups Today, Yesterday, This week (Oggi, Ieri, Questa settimana)list
Camera (Fotocamera)/sdcard/DCIM/Camerathumbnails
Screenshot/sdcard/DCIM/Screenshots or /sdcard/Pictures/Screenshotsthumbnails
Download/sdcard/Downloadlist
WhatsApp/sdcard/Android/media/com.whatsapp/WhatsApp/Media (Android 11+) or /sdcard/WhatsApp/Medialist
Documents (Documenti)/sdcard/Documentslist
Phone storage (Memoria del telefono)/sdcardlist
SD card (Scheda SD)the cards found in /storage (more than one: “SD card 1”, “SD card 2”…)list
Table 11.2 — The places of “Receive files…” (Ricevi file…). Recent (Recenti) and Phone storage (Memoria del telefono) are always there; the other places appear only if they exist (STA2, the first candidate folder that exists wins); SD cards are found by listing /storage (minus emulated and self)
  • Clickable path and “up” button, up to Phone storage or SD card; search by name; sort by date, newest or oldest first; thumbnails or list.
  • Folders are read with sync.rs (4.6 “Copying files: sync:”): they are read in full and shown 200 at a time (“Show N more”, Mostra altri N); hidden files (starting with a dot) do not appear.
  • Checkmarks persist when changing folder; “Select all” / “Deselect all” (Scegli tutti / Togli tutti); a double click receives the file immediately.
  • The thumbnails are made by the helper (miniature <lato> <percorsi in base64>, Miniature.java) in groups of 40, at 192 pixels: each launch costs about one second. Photos are downscaled by BitmapFactory and straightened using EXIF; videos give a frame with MediaMetadataRetriever through a MediaDataSource.

The copy is done by the drawer, with the same transfer card as sending: each file arrives with sync::ricevi in the folder of Preferenze::cartella_ricevuti (the Downloads (Scaricati) folder if not chosen), with a free name and the phone's modification date. At the end an alert says how many files arrived, with Open folder (Apri la cartella; with a single file, the folder opens with the file already highlighted).

i
For tests. PHONESTRA_PROVA_RICEVI=<posto> opens “Receive files…” (Ricevi file…) by itself on connection, at the given place (for example Fotocamera), for interface tests; phonestra-prova file tests listing, receiving and thumbnails from the command line.
Chapter 12

Testing and diagnostics

12.1 · Tests on the PC

Two levels: the tests on the PC, which need no phone and run at every commit, and the tests on the real phone with phonestra-prova.

cargo test tests the pure parts: the formats of the chunked messages (commands, audio, video, input), the preamble, the ready line, CIAO, the heartbeat, shell,v2 packets, dispatching without a network, audio timing and margin, real AAC decoded and written to MP4, flow control against a fake adbd, mDNS, parsing of dumpsys, message numbers matching between Java and Rust, and these manuals (tests/manual.rs, 12.5 “The manuals and their checks”).

12.2 · The phonestra-prova tool

A separate executable, without a user interface, that uses the same code as the program. The component tests are run with the phone unlocked and Phonestra closed; each one cleans up after itself and checks that nothing is left behind on the phone.

CommandWhat it does
Connection
cerca, collega, bannermDNS discovery, connection to the saved phone, features announced by adbd
abbina <codice> [ip:porta]Pairing with the 6-digit code; without an address it finds the pairing-code screen by itself with mDNS
usb, prepara, shell-usb <comando>, proceduraThe cable: status, Wi-Fi preparation, shell, the fallback procedure
shell <comando>A shell command over Wi-Fi (instead of adb shell)
throughput [MB] [--senza-delayed-ack] [--payload N] [--finestra N] [--latenza] [--exec] [--alla-lettura]Throughput and latency of the ADB transport
canaliSeveral commands at once on the same connection and a file copy with sync: (old test)
Helper and drawer
app, sfondo, notifiche, codificatoriHelper commands and drawer reads
file <cartella>, file ricevi <percorso> <destinazione>, file miniature <percorsi>“Receive files…” (Ricevi file…) from the command line: listing, copy to the PC, thumbnails
Component
servizio [secondi] [--sparisci]The skeleton of the component: startup, CIAO, heartbeat, guardian, exit; --sparisci simulates a PC that disappears
custode [abbandona]A test guardian like the connection's one (screen timeout at 1,234 s, without the volume): it closes the channel and checks that the value is restored; with abbandona it exits without closing it, and the restore is checked by hand (shell settings get system screen_off_timeout)
audio-componente <secondi> [aac|pcm] [--ascolta] [--uccidi]Audio from the component into a file (phonestra-prova.aac/.wav), check of timing and policies
video-componente app|schermo [--app P] [--secondi N] [--codec C] [--senza-pannello]Virtual display or mirror, keyframes, resizing, panel, events; saves the stream for ffprobe
input-componente appunti|tocchi|testo|tutte [--misura LxA] [--dpi D] [--app P] [--azione A[:P]] [--tocca X,Y]Clipboard, taps, scroll wheel, pinch, text, verified with the “signature” of the test screen
Study
video-prova schermo|chiave|istanze|protetto|task|permessi|codificatoriThe video measurement tool of the study
audio-nostro <secondi> [submix|loopback|render] [pcm|aac] [senza-priorita] [voce]The audio measurements of the study
Table 12.1 — The commands of phonestra-prova
Examples
$ ./target/debug/phonestra-prova servizio 10
$ ./target/debug/phonestra-prova video-componente app --secondi 15
$ PHONESTRA_DEBUG=1 ./target/debug/phonestra-prova input-componente tutte

12.3 · Rules for tests on the phone

Tests on the real phone follow a few rules: the phone must go back to how it was, and every measurement must stay on record.

  • phonestra-prova first; the system adb only for diagnostics the tool cannot do (it uses a different key: the phone asks for a new authorization).
  • At the end of a test: shut down any adb server that was started and any open sessions; do not leave phone settings changed.
  • Before restarting Phonestra for a test, check all the mCallState lines (one per SIM): never during a call (7.4 “Calls”).
  • Measurements and results go in notes/connection-tests.md, with the section number (§).

12.4 · Diagnostic variables

The PHONESTRA_* variables turn on diagnostics or change a parameter, for tests and measurements.

VariableEffect
PHONESTRA_DEBUG=1Diagnostics on the terminal (works with any value): audio measurements, frames drawn and dropped, clipboard, window scaling
PHONESTRA_FOTO=<cartella>Every window saves itself as PNG 6 s after opening and then every 6 s (foto.rs)
PHONESTRA_PROVA_PASSO=…Shows a step of the first connection without a phone (10.5 “The first connection”)
PHONESTRA_PROVA_RICEVI=<posto>Opens “Receive files…” (Ricevi file…) by itself on connection, at the given place
PHONESTRA_AUDIO_CODEC=pcmPCM audio instead of AAC (also raw)
PHONESTRA_VIDEO_FPS, PHONESTRA_VIDEO_PRIORITA, PHONESTRA_VIDEO_PROTETTA=0Test switches for the encoder and for the protected-screen check (they become max_fps=, priorita=, protetta= of VIDEO_APRI)
PHONESTRA_ADB_DELAYED_ACK, PHONESTRA_ADB_PAYLOAD, PHONESTRA_ADB_FINESTRAADB transport parameters (4.4 “Channels and flow control”)
Table 12.2 — The environment variables

The log of Phonestra started from the AppImage is in the PC's journal: journalctl --user --since today | grep -i phonestra (7.6 “The change log”).

12.5 · The manuals and their checks

Both manuals are generated: the sources are in docs/sources/, one per chapter in technical/ and user/, and build.py assembles them into docs/Technical Manual.html and docs/User Manual.html, two self-contained files, readable even when downloaded on their own. Their style is the common style of the manuals of the seven projects, identical in all of them and embedded in each page: style.css and manual.js (the search box at the top of the sidebar and the Copy button on command blocks) are copied from it as they are, and build.py only adds the few rules for elements found only here. The cover shows the official logo (logos/phonestra-logo-horizontal-light.png, embedded in the page).

tests/manual.rs runs python3 docs/sources/build.py --controlla, which fails if:

  • the published file does not match the sources (the manuals are not edited by hand);
  • the page does not embed the common style.css and manual.js unchanged;
  • a source file is missing from ch. 17 “Appendix B — File map” or the map lists a file that no longer exists;
  • a cited Rust symbol (such as Collegamento::mantieni) does not exist in the sources, or a cited Java method (such as Servizio.comandi) is not in the class's file;
  • a cited file does not exist in the repository;
  • a PHONESTRA_* variable in the code is not documented, or the manuals cite one that does not exist;
  • a command of phonestra-prova or of the helper is not documented;
  • an internal link leads to a section that does not exist, or the version from Cargo.toml does not appear; or the running text of either manual contains Italian sentences (outside code, interface labels with the Italian label in parentheses after them, and program output).
i
What the checks do not see. the checks find names that have disappeared, not behavior that has changed: when you change the way something works, look in the manuals for the section that describes it and update it in the same commit.
Chapter 13

Build and release

13.1 · Required tools

Building Phonestra, its component and the manuals requires these tools. The reference system is Debian 13 “trixie”.

ToolWhat forNotes
Stable Rust (2024 edition)Everything on the PCcargo lives in ~/.cargo/bin: add it to the PATH in commands.
GTK 4.12+, libadwaita 1.5+, GStreamer 1.x (with -dev)Building and running on the development PCRequired plugins: base, good, bad, gstreamer1.0-libav (for avdec_aac) and gst-plugin-gtk4 (gtk4paintablesink).
JDK (javac)Building the componentCompiled with --release 11; any recent JDK will do.
D8 from R8 9.4.26From Java classes to dexIn strumenti/r8.jar (not in the repository: URL and SHA-256 fingerprint in android/helper/build.sh).
podmanBuilding the AppImageContainer phonestra-appimage (13.4 “The AppImage container”).
Python 3 with pygments and PillowGenerating the two manualspython3 docs/sources/build.py writes docs/Technical Manual.html and docs/User Manual.html; packages python3-pygments and python3-pil (Pillow is needed for the official logo on the cover). It is also used by cargo test.
Table 13.1 — The tools needed on the PC

Testing requires a phone with Android 14 or later, Wireless debugging turned on and the PC on the same Wi-Fi network.

i
What a clone does not bring. three things live outside the repository. strumenti/r8.jar is downloaded with the command written in android/helper/build.sh. The image of the phonestra-appimage container is rebuilt with podman build. The developer's configuration (~/.config/Phonestra: ADB key and paired phones) stays on the PC: on a new PC the phone must be paired again. The AppImages published on the site are rebuilt from the code.

13.2 · Everyday commands

A few commands cover everyday work; cargo test also checks that the manuals are in step with the code.

Everyday commands
$ export PATH=$HOME/.cargo/bin:$PATH
$ cargo build   # program and test tool
$ cargo test   # tests on the PC, without a phone; also checks the manuals
$ cargo clippy --all-targets   # no warnings allowed
$ cargo run --bin phonestra   # runs Phonestra from the sources
$ android/helper/build.sh   # rebuilds the phone component
$ python3 docs/sources/build.py   # regenerates the two manuals
+
The everyday loop. change the code → cargo test (seconds) → if you touched the component, android/helper/build.sh and the phonestra-prova test of that piece → the real Phonestra on the phone. Before every commit: cargo build, cargo test and cargo clippy with no warnings.
!
The manuals are generated. the manuals are changed in the sources in docs/sources/, never in the HTML files in docs/: the next generation would wipe out the change, and cargo test fails until the published files match the sources (12.5 “The manuals and their checks”).

13.3 · Building the component

The phone component is built outside cargo, with a script: javac, then D8. The resulting jar goes into the repository.

Building the component
$ curl -L -o strumenti/r8.jar https://dl.google.com/android/maven2/com/android/tools/r8/9.4.26/r8-9.4.26.jar
$ android/helper/build.sh
creato android/phonestra-helper.jar

The script compiles with javac --release 11 the fake Android classes in stub/ (signatures only: they serve the compiler and do not end up in the jar; the phone has the real ones) and the sources in src/phonestra/, then D8 converts them to dex with --min-api 34 and --lib pointing at the JDK.

When you use a new public Android class, add its stub with only the methods used. Hidden APIs are called through reflection and usually have no stub; the exceptions are the hidden classes we extend, which the compiler must know: android.app.TaskStackListener (EventiApp.java) and android.content.IOnPrimaryClipChangedListener (Input.java).

!
The jar goes in the commit. after the build the jar must be committed together with the sources: the program embeds it with include_bytes!, and an old jar makes the phone run the previous code.

13.4 · The AppImage container

A single file that starts on all common distributions: it is built on an old glibc and carries GTK, libadwaita and GStreamer with it, but uses the system's graphics drivers.

packaging/Containerfile starts from Ubuntu 22.04 (glibc 2.35), so the executable requires at most glibc 2.34 and starts even on 2022 systems. The GTK and libadwaita of Ubuntu 22.04 are too old: the container builds into /opt/phonestra wayland 1.22, wayland-protocols 1.36, glib 2.80, graphene 1.10, GTK 4.14 and libadwaita 1.5 (packaging/build.sh, meson), plus gst-plugin-gtk4 0.13.5 from gst-plugins-rs.

Building the AppImage
$ podman build -t phonestra-appimage packaging
$ podman run --rm -v .:/phonestra:Z phonestra-appimage sh -c 'CARGO_TARGET_DIR=target/appimage cargo build --release && packaging/collect.sh'
!
Always both steps. collect.sh on its own packages whatever executable it finds in target/appimage/release, even an old one: after every code change, cargo build --release must be run again in the container first (on 28 Sep 2026 an old version was packaged).

The result is target/appimage/Phonestra-<versione>-x86_64.AppImage; the version comes from Cargo.toml.

13.5 · What collect.sh does

collect.sh takes the executable built in the container and turns it into an AppImage, in five steps.

Executableand libs via lddPluginsGStreamer, images, GIOFallbackusr/lib/riservapatchelf$ORIGINappimagetoolstatic runtimeFigure 13.1 — The steps of collect.sh
  1. Copies the executable and, recursively with ldd, the libraries it uses, except those that must come from the system: glibc, libstdc++, libgcc_s, graphics drivers and the libraries that load them (GL, EGL, DRM, gbm, Vulkan), libX11 and libxcb, fontconfig, freetype, libexpat, PipeWire, ALSA, udev.
  2. Copies the GStreamer plugins that are needed (coreelements, app, typefindfunctions, playback, videoparsersbad, videoconvert, videoscale, libav, opus, audioconvert, audioresample, autodetect, pulseaudio, isomp4, vaapi, va) and gtk4, with gst-plugin-scanner.
  3. Copies the image loaders (PNG, JPEG, SVG), the GSettings schemas, the fallback Adwaita icons and the dconf GIO module: without it, GTK does not read the desktop settings (the window title bar was left with only the X). Then the licenses, in usr/share/doc/phonestra/: LICENSE.md, NOTICE.md and third-party/, with the Ubuntu copyright file of every package a copied file comes from, the license texts they point to, those of the libraries built in the container (saved by build.sh), the crates' ones (rust-licenses.py) and the AppImage runtime's.
  4. Moves into usr/lib/riserva the libraries that the system's drivers also use (wayland, zlib, zstd, libxml2, libffi, libelf, libva, the libxcb-* ones…): they are used only if the system lacks them or has versions that are too old.
  5. Fixes the paths with patchelf ($ORIGIN), adds AppRun, the icon (logos/icons/phonestra-256.png) and a .desktop file with NoDisplay=true (needed by appimagetool, not installed), and creates the AppImage with the static runtime (no libfuse2).

13.6 · AppRun

packaging/AppRun prepares the environment and launches usr/bin/phonestra: the system's libraries and the bundled ones must not mix.

SettingWhy
Only the bundled GStreamer plugins, registry in ~/.cache/PhonestraThe system's plugins are built for a different GStreamer.
Image loaders with the paths of this runThe loaders file has absolute paths (@APPDIR@ replaced at startup).
No system GIO modulesBuilt for a different glib, they break programs.
GSK_RENDERER=glThe GTK 4.14 default gets gradients wrong with older Mesa; a value set by the user wins.
System icons before oursOurs are only a fallback.
Fallback libraries linked into ~/.cache/Phonestra/riserva only when neededFor example a libwayland-client older than 1.21.
Table 13.2 — What AppRun sets up

13.7 · Testing on distributions

packaging/test-distributions.sh starts the AppImage in Ubuntu 22.04, Debian 12, Fedora 43 and Arch containers, connected to the PC's Wayland display and graphics card, with an empty configuration: Phonestra must open “Add a phone” (Aggiungi un telefono). With PHONESTRA_FOTO the window is saved as PNG and the script reports the errors in the log.

13.8 · Publishing a release

A new release is published in four steps, always in the same order.

  1. Version in Cargo.toml (for release candidates: 1.0.0-rc.N), python3 docs/sources/build.py (the version appears in the manuals), commit and push.
  2. AppImage from the container, startup and connection test, SHA-256 fingerprint.
  3. Publication on https://phonestra.nicfio.it with site/publish.sh, which takes the AppImage just built or the published one (see the script's header; first --build alone, to look at site/public/; before publishing, the search-engine check: description, canonical, robots, icons, og.png, structured data, sitemap), then the annotated git tag v<versione>.
  4. Copy of the AppImage into the user's home (~/Phonestra-<versione>-x86_64.AppImage): that is the one the user runs. Since 1.0.0 the site replaces the GitHub Releases (the repository is private).
Chapter 14

Extending Phonestra

14.1 · A new message on the command channel

A new message touches both sides: the Java class of the part and the Rust module that uses it. These are the steps, in order.

  1. Choose the number in the part's range (video 0x40–0x4f, input 0x50–0x5f) or open a new range of 16 for a new part.
  2. Phone: the constant in the part's class (static final int NOME = 0x47; in Video.java), the case in the switch of Servizio.comandi (or Input.nostro for input), the handler. If it is a request, respond with the same id and the RISPOSTA flag, or with ERRORE.
  3. Rebuild the jar with android/helper/build.sh.
  4. PC: the constant in componente::tipo (or input_nostro::tipo), the encoding of the content as a pure function, the method that uses it (Condiviso::domanda for a request, Mittente::manda for an event with no response).
  5. Add the name to the list of the nessun_tipo_usato_da_due_moduli test: it checks that the number is unique, in the right range and the same in Java and in Rust.
  6. An encoding test with the expected bytes, then a test with phonestra-prova.
  7. Describe the message in the table of its chapter in docs/sources/technical/.
i
Compatibility. an old jar responds ERRORE “tipo sconosciuto” (unknown type) to a new message: the PC must know how to handle it. Change PROTOCOLLO only if an existing message changes meaning.

14.2 · A new channel type

A part with a stream of its own, like audio and video, needs its own channel, opened with the preamble.

  1. Phone: a handler static void gestisci(LocalSocket s, String tipo) and its entry in Servizio.TIPI. The handler runs on its own thread: it catches all errors (including the Error of hidden APIs) and closes the socket with shutdownInput/shutdownOutput before close, otherwise a read in progress on another thread keeps the socket open.
  2. PC: Condiviso::apritore().apri("tipo") gives a Canale with the preamble already sent.
  3. If the part changes something on the phone, register the inverse action with the guardian.

14.3 · A new action for the guardian

Everything a part changes on the phone must have its inverse action registered with the service's guardian (5.6 “The two guardians”).

A new action for the guardian
// When you change something on the phone:
Servizio.custode().imposta("mia-azione", 450, "settings put system qualcosa 1");
// When you have put it back yourself:
Servizio.custode().togli("mia-azione");

The command is a shell line run with sh -c after the service dies, without an Android context. Choose the order so that the actions happen in the right sequence (panel 400, refresh rate 410, task 500) and test it with kill -9 on the service (phonestra-prova shell 'kill -9 <pid>').

!
The user's value wins. if the action changes a user setting, restore it only if it still holds Phonestra's value: the user may have changed it in the meantime (this is the screen timeout rule, prove §56).
Chapter 15

Conventions

15.1 · Language and style

Phonestra's code reads like its project documents: in Italian, with the why next to the how. The two manuals are the exception: they are written in English.

  • Italian everywhere else: names, comments, program messages (the English interface takes its texts from data/en/), the documents in the repository (README, SPECIFICATION, notes/). Only the text of the two manuals is in English: the pages in docs/ and the chapters they are generated from in docs/sources/. Plain words, short sentences. The manuals quote the interface labels as the English interface shows them, with the Italian label in parentheses.
  • Comments explain the why and point to the source: SPECIFICATION §7.3, prove §49 (notes/connection-tests.md), notes/component.md.
  • Rust: anyhow for errors, with context saying what was being done; no unwrap where a different phone might answer something else.
  • Java: one class per piece, reflection only in Nascoste and in the pieces that use it, errors caught in the thread that produces them.
  • Third-party code: never copied. scrcpy and AOSP are read as documentation.

15.2 · Before every commit

Every commit leaves the project built, tested and documented.

$ cargo build && cargo test && cargo clippy --all-targets

No clippy warnings. If you changed the component, the rebuilt jar goes into the same commit. If you changed a behavior, update the chapter that describes it in docs/sources/technical/ (and, if the user sees it, in docs/sources/user/) and regenerate the manuals (python3 docs/sources/build.py); if you added a source file, give it a row in the map (ch17_map.py, ch. 17 “Appendix B — File map”): cargo test requires it.

15.3 · Personal data

The repository is public: nothing that identifies people, phones or networks.

!
Personal data. the repository is public. No names of people, serial numbers, Wi-Fi network names, addresses, real screenshots that are not blurred. Tests use fake values (for example R5CT0000000); the files produced by the tests (phonestra-prova.*) are ignored by git.

15.4 · Recording decisions

The project's why lives in notes/, one file per topic. Decisions are written there, together with their why.

FileWhat goes in it
notes/next-session.mdWhere to pick up again: read at the start of every work session
notes/user-decisions.mdThe decisions and their why, including the rejected alternatives
notes/issue-log.mdProblem → cause → solution → status
notes/connection-tests.mdThe measurements on the phone, in numbered sections (§)
notes/component.md, adb.md, api-android.md, study/Design and study of the component and of the transport
notes/interface.mdUser interface rules
Table 15.1 — Where decisions are recorded

Before proposing an alternative, check that it has not already been rejected (for example Flatpak, icons in the system menu, dark themes as a style, several phones active at the same time).

Chapter 16

Appendix A — Data structures and messages

16.1 · Index of data structures

This appendix collects the central data structures of the PC program, with the file that defines them and the section that covers them in depth. They are the contracts that run through Phonestra; the Java classes of the component are listed in ch. 17 “Appendix B — File map”.

StructureDefined inRoleSection
Connection and data
Collegamentocollegamento.rsThe active phone: state, Adb, component, “in hand” panel, notifications3.2 “Life of a connection”
Statocollegamento.rsCerco, Collegato, Bloccato, Perso, Chiuso3.2 “Life of a connection”
Telefoni, Telefonoconfigurazione.rsThe configured phones, in telefoni.toml3.6 “Configuration and data on the PC”
Preferenzeconfigurazione.rsThe user's choices, in preferenze.toml10.4 “Preferences”
Notifica, Infonotifiche.rsNotifications, battery and network read from dumpsys10.6 “Notifications and alerts”
Appapp.rsA launcher app: package, activity, name, icon10.2 “The drawer”
ADB client
Adbadb/mod.rsThe connection: can be cloned, opens channels, runs short commands4.4 “Channels and flow control”
Canale, Chiusoreadb/mod.rsA channel to a service; closing it from another task4.4 “Channels and flow control”
Trasportoadb/mod.rsdelayed_ack, max_payload, window4.4 “Channels and flow control”
Messaggioadb/messaggio.rsAn ADB message: 24-byte header and data4.2 “ADB messages”
ShellV2adb/shell.rsA process on the shell,v2 channel4.5 “The shell,v2 service”
Voceadb/sync.rsAn entry in a phone folder: name, folder or file, size, modification date4.6 “Copying files: sync:”
Component, PC side
Componentecomponente.rsThe started service: command channel, requests, shutdown5.9 “PC side: Componente and Condiviso”
Condivisocomponente.rsThe component held in a task and cloneable: one service, many windows5.9 “PC side: Componente and Condiviso”
Mittente, Apritorecomponente.rsMessages without a reply queued on the command channel; opening of the audio and video:<id> channels5.9 “PC side: Componente and Condiviso”
Pronto, Ciaocomponente.rsThe service's ready line; its CIAO5.2 “Starting the service”
Messaggiocomponente.rsA command-channel message: type, flags, id, content5.4 “The command channel”
SessioneNostra, ComandiVideovideo_nostro/mod.rsA video session and its commands6.7 “PC side: from session to window”
Eventovideo_nostro/mod.rsThe events of a session: orientation, protected, moved, removed, end6.6 “App events”
Opzioni, Pacchettovideo_nostro/flusso.rsThe display options; the packets of the video channel6.3 “The video:<id> channel”
InputNostroinput_nostro.rsTaps, scroll wheel, keys, text and clipboard encoded for the Mittente9.1 “Input and clipboard messages”
Durate, Orari, Margineaudio_nostro.rsDuration, timestamp and playback time of each audio packet8.3 “Playback on the PC”
Vistafinestra.rsVideo, mouse and keyboard: the heart of an app window, also used for the screen in the drawer10.3 “App windows”
Table 16.1 — The main data structures

16.2 · Index of messages

All the command-channel messages in one table, by number. The format of each is described in the section indicated.

TypeNameDirectionSection
Infrastructure (0x01–0x0f) and tests (0x10–0x1f)
0x01CIAOservice → PC5.4 “The command channel”
0x02BATTITOboth directions5.5 “Heartbeat and exit codes”
0x03FINEPC → service5.4 “The command channel”
0x04ERROREservice → PC, as a reply5.4 “The command channel”
0x10PROVA_CUSTODEPC → service5.4 “The command channel”
Video and panel (0x40–0x4f)
0x40–0x45VIDEO_APRI, VIDEO_CHIUDI, VIDEO_AVVIA_APP, VIDEO_RIDIMENSIONA, VIDEO_CHIAVE, VIDEO_PANNELLOPC → service6.2 “Video messages”
0x46VIDEO_EVENTOservice → PC, unsolicited6.6 “App events”
Input and clipboard (0x50–0x5f)
0x50–0x54TOCCHI, ROTELLINA, TASTO, TESTO, INDIETROPC → service, no reply9.1 “Input and clipboard messages”
0x55–0x57APPUNTI_SCRIVI, APPUNTI_LEGGI, APPUNTI_ASCOLTAPC → service9.4 “Clipboard”
0x58APPUNTI_CAMBIATIservice → PC, unsolicited9.4 “Clipboard”
0x5c, 0x5dCONTEGGI, PROVAPC → service (diagnostics and tests)9.1 “Input and clipboard messages”
Table 16.2 — Index of the command-channel messages
i
Note. the test nessun_tipo_usato_da_due_moduli checks that every number is unique, in the right range and the same in Java and in Rust (14.1 “A new message on the command channel”).

16.3 · Stream headers

Every byte stream in Phonestra starts with a fixed header. Here they are side by side.

StreamHeaderByte orderSection
ADB message24 bytes: command, arg0, arg1, length, checksum, command xor 0xfffffffflittle-endian4.2 “ADB messages”
shell,v2 packetid u8 · lunghezza u32little-endian4.5 “The shell,v2 service”
Preamble of a service channelsegreto (16 byte) · lunghezza del tipo u8 · tipo ASCII—5.3 “Channels and preamble”
Command channel8 bytes: tipo u8 · bandiere u8 · id u16 · lunghezza u32big-endian5.4 “The command channel”
video:<id> channel12 bytes: pts u64 · lunghezza u32, or 0x80000000 and the new sizebig-endian6.3 “The video:<id> channel”
Audio channel12 bytes: orario u64 · lunghezza u32big-endian8.2 “Audio packets”
Table 16.3 — Stream headers
Chapter 17

Appendix B — File map

17.1 · The project's files

Every source file with its line count, recounted each time the manual is generated. If a file is missing from the map, or the map lists a file that no longer exists, generation stops and cargo test fails.

FileLinesRole
User interface
src/avvisi.rs93Phone notifications as system notifications (D-Bus), app icons
src/bin/phonestra.rs171main: one process for all windows, connection, drawer, PC clipboard, Ctrl+C and SIGTERM, shutdown and restart to switch phones
src/cassetto.rs2,162The drawer: phone pill, Apps, Notifications and Preferences pages, phones, tools, phone drawn with the mirror, transfers
src/finestra.rs1,637An app's window: view (video, mouse, keyboard, zoom), sessions, panel, recording, screenshot
src/foto.rs36PHONESTRA_FOTO: window images for user interface tests
src/prepara.rs557“Add a phone” (Aggiungi un telefono) without a cable: settings list, checkmarks from mDNS, 6-digit code
src/procedura.rs734The fallback cable procedure, by brand family
src/ricevi.rs1,123“Receive files…” (Ricevi file…): places, Recents, listing and thumbnails of the phone's folders, file selection
Connection and data
src/app.rs177The embedded jar (AIUTO), the helper, app list with the fine row, wallpaper, thumbnails, encoders
src/azioni.rs138Installing, uninstalling, sending files
src/collegamento.rs816The active phone: discovery, connection, connection guardian, component, 3-second round, panel “in hand”, calls, shutdown
src/configurazione.rs354XDG folders, ADB key, telefoni.toml, preferenze.toml
src/lib.rs60The library modules, esecutore(), diagnostics
src/notifiche.rs177Reading notifications, battery and network from dumpsys
src/rete.rs191Hand-written mDNS: phones with Wireless debugging, the code screen
src/telefono.rs144Cable or Wi-Fi connection with adb_client, for the fallback procedure and the first connection
src/usb.rs149Cable-connected phones read from sysfs, without permissions
ADB client
src/adb/abbina.rs317Pairing with the code: TLS, SPAKE2, HKDF, AES-GCM
src/adb/flusso.rs210Pure parts of flow control: banner, OPEN, OKAY, delayed ack balance
src/adb/messaggio.rs88ADB messages: 24-byte header, reading and writing
src/adb/mod.rs479Adb, Canale, Trasporto: Wi-Fi connection, dispatching, flow control
src/adb/shell.rs125The shell,v2 service: input, output, errors, exit code
src/adb/sync.rs202The sync: protocol: copying files to the phone, listing its folders, receiving files
src/adb/tls.rs70Wireless debugging TLS: client certificate from Phonestra's key
Component, PC side
src/appunti.rs61Copies made on the phone → PC clipboard, with limits and bounce-backs
src/audio_nostro.rs880Audio channel, packets, timestamps, margin, GStreamer playback, copies for recording
src/componente.rs1,290Componente and Condiviso: startup, preamble, CIAO, heartbeat, dispatching, leftovers
src/lingua.rs161Interface language, Italian or English: t!, the English tables of data/en/
src/input_nostro.rs430InputNostro: encoding of touches, scroll wheel, keys, text, clipboard
src/video_nostro/flusso.rs87Screen options and reading of video packets
src/video_nostro/mod.rs375SessioneNostra, ComandiVideo, events, panel
Component on the phone
android/helper/src/phonestra/Aiuto.java193Jar entry point: short helper commands and the service
android/helper/src/phonestra/Appunti.java173Direct IClipboard: reading, writing, sensitive items, listener
android/helper/src/phonestra/Audio.java670Audio capture, reading and encoding; measurement tool
android/helper/src/phonestra/Autotest.java176Startup check of the hidden APIs, without using them
android/helper/src/phonestra/CanaleAudio.java311The audio channel: loopback capture, AAC, four threads
android/helper/src/phonestra/Codifica.java243Hardware MediaCodec from a Surface, keyframe on demand
android/helper/src/phonestra/Contesto.java89Android context for app_process, package com.android.shell
android/helper/src/phonestra/Custode.java164The service guardian: sh script, list of actions
android/helper/src/phonestra/EventiApp.java275TaskStackListener: orientation, protected screen, tasks moved or closed
android/helper/src/phonestra/Input.java738Input messages, queue, injection, fingers, scaling
android/helper/src/phonestra/Miniature.java154Photo and video thumbnails for “Receive files…” (downsampled BitmapFactory, EXIF, video frame)
android/helper/src/phonestra/Nascoste.java220Adapters for the hidden APIs, via reflection
android/helper/src/phonestra/Pannello.java241Physical panel on or off, refresh rate at 60 Hz before turning off, restore via the guardian
android/helper/src/phonestra/Protetta.java121Protected screen with captureDisplay and containsSecureLayers
android/helper/src/phonestra/Protocollo.java128Message format of the command channel and the preamble
android/helper/src/phonestra/Pulizia.java79Teardown in reverse order at the end of a test
android/helper/src/phonestra/Servizio.java406The service: secret, socket, channels, heartbeat, exit codes, TIPI
android/helper/src/phonestra/SessioneVideo.java531Virtual display or mirror, encoder, resizing, forced redraw
android/helper/src/phonestra/Sistema.java437Shared pieces: display manager for virtual displays, app launching, togliTask, internal commands, images
android/helper/src/phonestra/Video.java231Video messages, session registry, video:<id> channel
Tests and measurements (PC)
src/adb/misura.rs368phonestra-prova throughput: transport speed and latency
src/adb/prove.rs165The ADB client against a fake in-memory adbd
src/bin/prova/audio_componente.rs301phonestra-prova audio-componente
src/bin/prova.rs793phonestra-prova: the command-line test commands
src/misura_audio.rs253Analysis of the studio audio: measurement lines, levels, WAV
src/prova_input.rs545phonestra-prova input-componente: clipboard, touches, text
src/video_nostro/prova.rs402phonestra-prova video-componente
tests/lingua.rs115Every t! text has its English translation, with the same placeholders
tests/manual.rs26This manual kept in step with the sources (runs build.py --controlla)
Tests and measurements (phone)
android/helper/src/phonestra/Codificatori.java129List of the phone's audio and video encoders
android/helper/src/phonestra/InputProva.java300Input test commands: test screen, signature, clipboard
android/helper/src/phonestra/VideoProva.java1,049Measurement tool for the studio video
Build and tools
packaging/AppRun57AppImage launcher: GTK and GStreamer variables, fallback libraries
packaging/Containerfile70Ubuntu 22.04 container with GTK 4.14, libadwaita 1.5, gst-plugin-gtk4
packaging/build.sh18Builds a library from a tarball with meson, inside the container, and keeps its license files
packaging/test-distributions.sh41The AppImage on Ubuntu, Debian, Fedora and Arch in containers
packaging/collect.sh226Gathers the executable and libraries into the AppDir and creates the AppImage
packaging/rust-licenses.py129License texts of the Rust crates built into an executable, from cargo tree and the crates' sources
android/helper/build.sh20Builds the component: javac, D8, phonestra-helper.jar
docs/sources/build.py633Generates the two manuals: functions for text, tables and SVG figures, numbering, checks
docs/sources/technical/ch01_introduction.py207Technical Manual, chapter 1: Introduction and concepts
docs/sources/technical/ch02_architecture.py84Technical Manual, chapter 2: Overall architecture
docs/sources/technical/ch03_startup.py140Technical Manual, chapter 3: Startup and life cycle
docs/sources/technical/ch04_adb.py152Technical Manual, chapter 4: The ADB client
docs/sources/technical/ch05_component.py246Technical Manual, chapter 5: The on-phone component
docs/sources/technical/ch06_video.py140Technical Manual, chapter 6: Video
docs/sources/technical/ch07_panel.py132Technical Manual, chapter 7: The phone's panel
docs/sources/technical/ch08_audio.py109Technical Manual, chapter 8: Audio
docs/sources/technical/ch09_input.py106Technical Manual, chapter 9: Input and clipboard
docs/sources/technical/ch10_interface.py166Technical Manual, chapter 10: The user interface
docs/sources/technical/ch11_files.py57Technical Manual, chapter 11: Apps and files
docs/sources/technical/ch12_testing.py114Technical Manual, chapter 12: Testing and diagnostics
docs/sources/technical/ch13_build.py154Technical Manual, chapter 13: Build and release
docs/sources/technical/ch14_extending.py50Technical Manual, chapter 14: Extending Phonestra
docs/sources/technical/ch15_conventions.py50Technical Manual, chapter 15: Conventions
docs/sources/technical/ch16_structures.py100Technical Manual, chapter 16: Appendix A — Data structures and messages
docs/sources/technical/ch17_map.py126Technical Manual, chapter 17: Appendix B — File map
docs/sources/technical/ch18_problems.py26Technical Manual, chapter 18: Appendix C — Known issues
docs/sources/technical/ch19_glossary.py72Technical Manual, chapter 19: Glossary
docs/sources/user/ch01_welcome.py68User Manual, chapter 1: Welcome
docs/sources/user/ch02_installation.py74User Manual, chapter 2: Installation and first start
docs/sources/user/ch03_connecting.py141User Manual, chapter 3: Connecting the phone
docs/sources/user/ch04_drawer.py166User Manual, chapter 4: The drawer at a glance
docs/sources/user/ch05_first_steps.py52User Manual, chapter 5: First steps
docs/sources/user/ch06_windows.py138User Manual, chapter 6: App windows
docs/sources/user/ch07_mouse_keyboard.py77User Manual, chapter 7: Mouse, keyboard and clipboard
docs/sources/user/ch08_audio.py47User Manual, chapter 8: Audio, calls and camera
docs/sources/user/ch09_notifications.py57User Manual, chapter 9: Notifications
docs/sources/user/ch10_apps_files.py123User Manual, chapter 10: Apps and files
docs/sources/user/ch11_screenshot.py36User Manual, chapter 11: Screenshots and recording
docs/sources/user/ch12_screen.py67User Manual, chapter 12: The phone's screen
docs/sources/user/ch13_phones.py51User Manual, chapter 13: Multiple phones
docs/sources/user/ch14_preferences.py42User Manual, chapter 14: Preferences
docs/sources/user/ch15_privacy.py99User Manual, chapter 15: Phone, PC and privacy
docs/sources/user/ch16_problems.py123User Manual, chapter 16: Troubleshooting
docs/sources/user/ch17_glossary.py34User Manual, chapter 17: Glossary
Table 17.1 — The project's files
Chapter 18

Appendix C — Known issues

18.1 · Open issues

Open as of version 1.0.0; the up-to-date status is in notes/issue-log.md.

IssueStatus
Original volume saved as 15 instead of the user's value: on shutdown the phone may be left at maximumaccepted by the user: the volume is turned down by hand (Collegamento::volume_originale)
The audio margin grows but does not shrink: Margine::scendi does not trigger because AAC packets are of almost constant sizeto do silence marked by the phone
With delayed ack announced, adbd rejects every channeloff to be investigated (notes/adb.md)
Service memory about 145 MB (ART startup cost)to measure
Why the audio startup order mattersrule found through measurements; the Android mechanism is still to be understood
PC Wi-Fi drop, recording with AAC audio on different phonesto test
Real call after rc.7: panel on for the incoming call and off again after it endsto test
“Receive files…” (Ricevi file…): SD card, cancellation, large folders, dark themeto test
System alerts: on the first read after the drawer opens, the notifications already present on the phone raise an alert, because the “already seen” set is fixed while the list is still empty (avvisa_nuove in cassetto.rs)to verify probable bug, found by rereading the code
Locked phone: on Samsung phones, locking drops Wireless debuggingworked around the connection reopens on unlock and the apps go back to where they were
Table 18.1 — Known issues
Chapter 19

Glossary

19.1 · Terms A–L

Definitions of the technical terms used in the manual. The code of Phonestra is written in Italian, so terms that are also Italian names in the code are listed under the Italian word, followed by the English term this manual uses. The cross-references point to the section with the details.

adbd
The phone's ADB daemon: it accepts connections from the PC and starts the services (shell, sync:, localabstract:). See ch. 4 “The ADB client”.
Aiutante (helper)
The component's jar when used for short commands (app list, wallpaper, thumbnails, measurements). See 5.1 “What the component is”.
Annex B
The H.264/H.265 stream format with the start code 00 00 00 01 in front of every unit. See 6.3 “The video:<id> channel”.
app_process
The Android program that starts Java code outside an app; the shell uses it too. See 5.1 “What the component is”.
ART
Android Runtime: the virtual machine that runs the dex.
AudioPolicy
Hidden API for routing audio; with loopback it sends the apps' sound to a recorder instead of the loudspeaker. See 8.1 “The recipe”.
Battito (heartbeat)
The BATTITO message, sent every second in both directions: after 5 s of silence each side considers the other gone. See 5.5 “Heartbeat and exit codes”.
Canale (channel)
A logical connection inside the ADB connection, to a service on the phone; all channels share the same connection. See 4.4 “Channels and flow control”.
Collegamento (connection)
The active phone and its ADB connection (collegamento.rs), kept alive as long as Phonestra stays open. See 3.2 “Life of a connection”.
Componente (component)
Everything Phonestra runs on the phone: the jar phonestra-helper.jar. See ch. 5 “The on-phone component”.
Custode (guardian)
A shell process that puts the phone back in order when whatever it is tied to ends. See 5.6 “The two guardians”.
Delayed ack
ADB extension with more data in flight per channel and acknowledgements that carry the number of bytes received. See 4.4 “Channels and flow control”.
dex
The format of compiled Java code for Android (classes.dex), produced by D8. See 13.3 “Building the component”.
Drawer
Phonestra's main window with the phone's apps (cassetto.rs). See 10.2 “The drawer”.
Giro dei 3 s (3-second round)
The periodic check of the connection: lock, calls and notifications in a single command. See 3.3 “The 3-second round”.
In mano (in hand)
Panel state: the user is using the phone with their hands, so the panel stays on. See 7.3 “The phone in hand”.
Loopback
Capture of the audio coming out of the apps, while the phone itself stays silent. See 8.1 “The recipe”.

19.2 · Terms M–Z

mDNS
DNS on the local network without a server: this is how the phone announces Wireless debugging. See 4.8 “Network discovery: mDNS”.
Pannello (panel)
The phone's physical screen, turned off while the apps are used from the PC. See 7.1 “Turning the panel off”.
Preambolo (preamble)
The first bytes of every channel of the service: the secret and the channel type. See 5.3 “Channels and preamble”.
Schermo virtuale (virtual display)
An extra display, created by the component, where the app of a window runs. See 6.1 “Sessions: virtual display and mirror”.
Servizio (service)
The component's long-running process, one per connection (phonestra-servizio). See 5.2 “Starting the service”.
Sessione (session)
A virtual display or the mirror, with its video:<id> channel. See 6.1 “Sessions: virtual display and mirror”.
Specchio (mirror)
The copy of the phone's main screen, drawn in the drawer. See 6.1 “Sessions: virtual display and mirror”.
Surface
The graphics buffer the virtual display draws into and the encoder reads from. See 6.4 “Encoder and keyframe”.
uid 2000
The ADB shell user: its permissions are the component's permissions. See 5.8 “Service security”.
Wireless debugging (Debug wireless)
ADB over Wi-Fi in Android 11+, with TLS and pairing by code. See 4.3 “Wi-Fi connection and TLS”.