crates/veilvoice-gui/src/app.rs
veilvoice-gui · 3584 lines · read the source here · or on GitHub
The VeilVoice desktop application: seven tabs, one window, no menus.
One window, seven tabs, no menus and no settings file to hunt for. This file owns the window: the tab strip, the state behind it, and the rules about what the user is allowed to do before they have answered the questions that matter. The tabs themselves live partly here and partly in siblings -- crate::security draws the lock tab and the unlock screen, crate::prefs draws settings.
The tabs, and why these
One row, in the order Tab::ALL lists them, which is the order they are drawn in and the order this table is in.
| Tab | What it is | |---|---| | anonymise file | Process a recording on disk. The default path. | | group | One recording with several people in it, each given their own voice. | | studio | Scramble a microphone in real time, and keep what was said if it was asked for. | | browser | What is in the recording vault, without opening any of it. | | monitor | Which applications currently hold the microphone and camera. | | lock | The app lock, and a plain statement of what it is worth. | | verify | Check a download against the signed list of hashes. | | settings | Colour scheme, animation, and where those choices are kept. | | install | Whether this copy is portable or installed, and the optional companions. | | about | Versions, licence, and the honest scope. |
Roadmap item 130 took one row out of this table rather than adding one. Live scramble was a tab, and everything it did the Studio also did, through the same session, with the devices the other tab happened to be set to. Two screens for one act, and two starters for one microphone.
There is no "advanced" tab and no hidden pane. Everything the program can do is reachable in one click from the strip, because a privacy tool whose important controls are buried is a privacy tool whose important controls do not get used.
Nothing slow runs on the UI thread
VeilVoiceApp::start_job spawns a worker and hands back an std::sync::mpsc receiver; VeilVoiceApp::poll_job drains it with try_recv once per frame. The window keeps painting while a job runs.
That split is not tidiness. A long recording takes real time to process, and sealing it runs Argon2id at 256 MiB, which is deliberately slow -- that is the whole point of a memory-hard KDF. Doing either on the UI thread means a frozen window and an operating system offering to kill the application, in the middle of the operation the user cares most about completing.
poll_job handles all three channel outcomes, including Disconnected -- a worker that panicked. The user is told the thread stopped rather than watching a progress state that will never finish.
The at-rest choice is enforced here, not merely offered
Recordings are encrypted at rest by default (locked decision 4.10), and a job cannot start until the user has answered the modal that appears if they try to turn that off. The rule is asserted by a test in this file rather than left as a property of the layout code, because "the button was disabled" is a claim about pixels and "the job refuses to start" is a claim about behaviour.
The worker encodes the WAV in memory and seals it before anything is written, so a recording that is going to be encrypted never touches the disk in the clear -- not even briefly, not even in a temporary file that would be deleted afterwards. Deleting a file does not remove its contents from a flash device; not writing it does.
Nothing that talks to the operating system runs on this thread
The device monitor is the one that got this wrong and shipped. It was polled straight from update, and asking Windows which applications hold the microphone cost about a hundred and ninety subprocesses -- so the window froze for seconds at a time, every two seconds. Both halves are fixed: veilvoice-watch now costs two subprocesses, and crate::watchfeed keeps even that on a thread of its own.
The rule this file keeps, and the reason the defect is worth a paragraph: update may read state and paint it, and may start work, and may never wait for any. A job, a lock operation, a monitor scan and an install all go to a worker and come back through a channel.
The monitor indicator
VeilVoiceApp::watch_indicator shows, in the header, whether anything is holding the microphone or camera right now, and clicking it goes to the monitor tab. It is polled on a timer rather than watched continuously, because the underlying platform code enumerates processes and doing that every frame would cost more than the rest of the window put together.
What it reports is bounded by what the platform allows, and veilvoice_watch::support() states that bound rather than letting an empty list imply an empty machine. The indicator must never present "we could not see" as "nothing is there".
A policy tightens the controls, and the tightening is not the drawing code
crate::policy::InForce holds whatever veilvoice policy fixed on this machine. Fixed controls are drawn disabled with the reason underneath, but that is a courtesy: the values a job actually uses come from VeilVoiceApp::posture, which applies the policy every time it is asked. A policy that held only while a checkbox was drawn would not be a policy, and this file already keeps that rule for the at-rest choice.
Where the honest limits are stated
The about tab carries the scope text, and the lock tab carries veilvoice_crypto::lock::SCOPE. Neither is decoration: tests fail the build if that wording is softened, because a user who over-trusts the app lock is left worse off than one who never had it. If you are editing text in this file and a test starts failing, it is that rule, and it is working.
In plain words
The window itself: the tabs along the top, what each one shows, and the state they all share.
One window with tabs, no menus, and no settings file to go hunting for. Everything VeilVoice can do is reachable from something visible.
The one rule this file follows without exception is that painting the window never waits for anything. Reading a recording or running the engine takes seconds; if that happened here the window would stop responding, so it is started on another thread and the answer is collected later.
WHAT THIS FILE CONTAINS
3584 lines defining 34 functions (3 public), 3 types and 1 constant. Everything below is read out of the source, so it cannot disagree with the code.
The types it owns.
enum Tabline 141 · The things VeilVoice does.enum JobDoneline 216 · Result of a background file job.struct VeilVoiceAppline 227 · Application state.
What happens when it runs. These are the ways in: public, and nothing else in this file calls them, so they are what an outside caller reaches first.
Tab::keyline 172 · The name this tab answers to on the command line.VeilVoiceApp::newline 725 · Build the application, ready for its first frame.
reachestab_from_arguments,from_key
WHAT CALLS WHAT
The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one. 22 of 34 functions are drawn; the diagram is bounded at 22 so it stays readable.
The same graph as Mermaid source
%%{init: {"theme":"base","themeVariables":{"background":"#1a1b26","primaryColor":"#1f2335","primaryTextColor":"#c0caf5","primaryBorderColor":"#7aa2f7","secondaryColor":"#16161e","tertiaryColor":"#16161e","lineColor":"#737aa2","textColor":"#c0caf5","mainBkg":"#1f2335","nodeBorder":"#7aa2f7","clusterBkg":"#16161e","clusterBorder":"#2f3549","fontFamily":"ui-monospace, SFMono-Regular, Consolas, monospace","fontSize":"14px"}}}%%
flowchart TD
n_from_key["Tab::from_key<br/>line 202"]
n_preferred_output["preferred_output<br/>line 398"]
n_preferred_input["preferred_input<br/>line 407"]
n_count_frames["VeilVoiceApp::count_frames<br/>line 431"]
n_frame_readout["VeilVoiceApp::frame_readout<br/>line 464"]
n_frame_rate_detail["VeilVoiceApp::<br/>frame_rate_detail<br/>line 493"]
n_without_devices["VeilVoiceApp::without_devices<br/>line 513"]
n_default["VeilVoiceApp::default<br/>line 581"]
n_integrity_panel["VeilVoiceApp::integrity_panel<br/>line 620"]
n_tab_from_arguments["VeilVoiceApp::<br/>tab_from_arguments<br/>line 711"]
n_new(["VeilVoiceApp::new<br/>line 725"])
n_apply_policy["VeilVoiceApp::apply_policy<br/>line 822"]
n_posture["VeilVoiceApp::posture<br/>line 847"]
n_config["VeilVoiceApp::config<br/>line 858"]
n_fit_to_the_screen["VeilVoiceApp::<br/>fit_to_the_screen<br/>line 887"]
n_header_button["header_button<br/>line 929"]
n_ui["VeilVoiceApp::ui<br/>line 952"]
n_poll_job["VeilVoiceApp::poll_job<br/>line 1513"]
n_settings["VeilVoiceApp::settings<br/>line 1549"]
n_file_tab["VeilVoiceApp::file_tab<br/>line 1661"]
n_start_job["VeilVoiceApp::start_job<br/>line 1771"]
n_guest_list["VeilVoiceApp::guest_list<br/>line 1882"]
n_apply_policy --> n_posture
n_config --> n_posture
n_default --> n_preferred_input
n_default --> n_preferred_output
n_default --> n_without_devices
n_file_tab --> n_settings
n_file_tab --> n_start_job
n_frame_readout --> n_frame_rate_detail
n_new --> n_tab_from_arguments
n_settings --> n_config
n_start_job --> n_config
n_start_job --> n_posture
n_tab_from_arguments --> n_from_key
n_ui --> n_config
n_ui --> n_count_frames
n_ui --> n_file_tab
n_ui --> n_fit_to_the_screen
n_ui --> n_frame_readout
n_ui --> n_header_button
n_ui --> n_integrity_panel
n_ui --> n_poll_job
click n_from_key href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L202" "open the source"
click n_preferred_output href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L398" "open the source"
click n_preferred_input href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L407" "open the source"
click n_count_frames href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L431" "open the source"
click n_frame_readout href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L464" "open the source"
click n_frame_rate_detail href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L493" "open the source"
click n_without_devices href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L513" "open the source"
click n_default href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L581" "open the source"
click n_integrity_panel href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L620" "open the source"
click n_tab_from_arguments href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L711" "open the source"
click n_new href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L725" "open the source"
click n_apply_policy href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L822" "open the source"
click n_posture href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L847" "open the source"
click n_config href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L858" "open the source"
click n_fit_to_the_screen href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L887" "open the source"
click n_header_button href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L929" "open the source"
click n_ui href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L952" "open the source"
click n_poll_job href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L1513" "open the source"
click n_settings href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L1549" "open the source"
click n_file_tab href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L1661" "open the source"
click n_start_job href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L1771" "open the source"
click n_guest_list href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/app.rs#L1882" "open the source"
classDef entry fill:#1f2335,stroke:#7aa2f7,color:#c0caf5
class n_new entry
classDef api fill:#1f2335,stroke:#7dcfff,color:#c0caf5
class n_from_key api
classDef helper fill:#1f2335,stroke:#bb9af7,color:#c0caf5
class n_preferred_output,n_preferred_input,n_count_frames,n_frame_readout,n_frame_rate_detail,n_without_devices,n_default,n_integrity_panel,n_tab_from_arguments,n_apply_policy,n_posture,n_config,n_fit_to_the_screen,n_header_button,n_ui,n_poll_job,n_settings,n_file_tab,n_start_job,n_guest_list helper
This site loads no third-party script, so it cannot run Mermaid; the diagram above is the same nodes and edges drawn by the generator instead. GitHub renders the source below directly.
ITEMS
| Item | Line | Documentation |
|---|---|---|
Tab pub(crate) enum | 141 | The things VeilVoice does. |
Tab::key pub fn | 172 | The name this tab answers to on the command line. |
Tab::ALL pub const | 188 | Every tab, in the order the window shows them. |
Tab::from_key pub fn | 202 | The tab with this name, if it is one. |
JobDone enum | 216 | Result of a background file job. |
VeilVoiceApp pub struct | 227 | Application state. |
preferred_output fn | 398 | Pick the output to start on: a virtual cable if the machine has one, because routing there is what lets other applications hear the veiled voice at all; otherwise the system default. |
preferred_input fn | 407 | Pick the input to start on: the system default, else whatever is first. |
VeilVoiceApp::count_frames fn | 431 | Frames per second, to stderr, when VEILVOICE_FRAME_LOG is set. |
VeilVoiceApp::frame_readout fn | 464 | Roadmap item 148. |
VeilVoiceApp::frame_rate_detail fn | 493 | The sentence behind the readout, and the one the About tab prints. |
VeilVoiceApp::without_devices fn | 513 | The application with no devices enumerated. |
VeilVoiceApp::default fn | 581 | |
VeilVoiceApp::integrity_panel fn | 620 | Build the app, applying theme and fonts to ctx. |
VeilVoiceApp::tab_from_arguments fn | 711 | The tab --tab= asks for, so a capture can open one screen directly. |
VeilVoiceApp::new pub fn | 725 | Build the application, ready for its first frame. |
VeilVoiceApp::apply_policy fn | 822 | Bring the controls into line with the policy, once, at startup. |
VeilVoiceApp::posture fn | 847 | The settings as they will actually be used, after the policy. |
VeilVoiceApp::config fn | 858 | The de-identification settings the panels currently describe. |
VeilVoiceApp::fit_to_the_screen fn | 887 | Open at a size this screen can actually show, once, on the first frame. |
header_button fn | 929 | A small-text button drawn to the size of the control beside it. |
VeilVoiceApp::on_exit fn | 942 | |
VeilVoiceApp::ui fn | 952 | |
VeilVoiceApp::poll_job fn | 1513 | Take the result of a finished job without ever waiting for one. |
VeilVoiceApp::settings fn | 1549 | Draw the settings panel, with a policy floor shown as a floor rather than as a value somebody can move. |
VeilVoiceApp::file_tab fn | 1661 | Draw the File tab: pick a recording, veil it, write it somewhere else. |
VeilVoiceApp::start_job fn | 1771 | Hand the work to a thread, so the window keeps drawing while it runs. |
VeilVoiceApp::guest_list fn | 1882 | The Recording Studio: the voice first, then the take. |
VeilVoiceApp::studio_tab fn | 1959 | Draw the Recording Studio: record into the vault, veiled on the way in. |
VeilVoiceApp::start_preview fn | 2322 | Veil to this machine's own output, and say where it is going. |
VeilVoiceApp::check_failsafe fn | 2374 | Ask the safety catch what it makes of what is holding a microphone. |
VeilVoiceApp::watch_indicator fn | 2450 | Re-scan on a timer rather than every frame. |
VeilVoiceApp::watch_tab fn | 2482 | Draw the Watch tab: what is recording the screen, and what is allowed to. |
VeilVoiceApp::report_a_fault fn | 2572 | Say so if the last run ended badly, and offer the file. |
VeilVoiceApp::about_tab fn | 2589 | Draw the About tab: versions, where files live, and the companion list. |
paths_section fn | 2719 | Where this copy is keeping things, on this machine. |
device_picker fn | 2779 | A dropdown of devices that keeps working when the chosen one disappears. |
field fn | 2805 | One labelled read-only value, in the shape the About tab uses throughout. |
header_layout_tests mod | 2813 |