mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 08:56:59 +01:00
- Desktop shows Proton's CAPTCHA instead of failing the sign-in: a wry webview in a child process of the app (`pstr-app::verify`), confined to https://*.proton.me, hands the token back and the sign-in is retried. Linux packages and CI gain WebKitGTK 4.1; the Windows zip and MSI ship WebView2Loader.dll, without which a -gnu build does not start. - The desktop keyring splits a secret too long for one entry across several, so a Proton session with many key salts survives Windows' 1280-unit cap. Short secrets stay one entry, so every existing secret still reads. - `WatchSync` runs against a small `HistoryDrive` trait and is tested end to end against an in-memory Drive: two devices, newer wins, no re-upload of an unchanged history, a missing file written again, an unreadable own file never overwritten, a newer-format file left alone. - An installation's file unwritten for 90 days is retired by the next device to sync, after its entries are merged and carried in that device's file. - Removing a share keeps its watch state: sync would bring it back from the other devices anyway, and re-adding the share picks up where it left off. - Watch history syncs once more on desktop exit (bounded to 3 s) and, on Android, through a WorkManager job enqueued when the app leaves the screen. - The second-factor prompts say security keys are not supported.
85 lines
3.7 KiB
Rust
85 lines
3.7 KiB
Rust
//! `proton-stream` — the desktop application.
|
|
//!
|
|
//! Everything is in the library beside this file; what is here is the process:
|
|
//! logging, the tokio runtime, the window, and the app inside it. See
|
|
//! [`pstr_app`] for how the three threads divide the work.
|
|
|
|
// A GUI binary linked against the Windows *console* subsystem gets a console
|
|
// allocated for it, so launching from the Start menu flashes up a cmd window
|
|
// that lives as long as the app. Only release builds are switched: with the
|
|
// windows subsystem there is no stdout for `tracing_subscriber` to write to, and
|
|
// a developer running `cargo run` on Windows wants the log more than the tidy
|
|
// window. `pstr-cli` stays on the console subsystem, which is correct for it.
|
|
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
|
|
|
use anyhow::Context;
|
|
use pstr_core::config::AppDirs;
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
// Before anything else: a verification window is a child process of the
|
|
// app, and must not claim the single instance or open a library.
|
|
if let Some(code) = pstr_app::verify::run_if_requested() {
|
|
std::process::exit(code);
|
|
}
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "pstr_app=info,pstr_core=info,pstr_stream=info".into()),
|
|
)
|
|
.init();
|
|
|
|
let runtime = std::sync::Arc::new(
|
|
tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.context("build the tokio runtime")?,
|
|
);
|
|
let dirs = AppDirs::ensure().context("resolve app directories")?;
|
|
|
|
// Before the window: a second launch hands its request to the first and
|
|
// never opens one.
|
|
let request = pstr_app::instance::Request::from_args(std::env::args().skip(1));
|
|
let instance = match pstr_app::instance::claim(&runtime, &dirs, &request) {
|
|
pstr_app::instance::Claim::First(listener) => listener,
|
|
pstr_app::instance::Claim::Handed => {
|
|
tracing::info!("handed over to the window already open");
|
|
return Ok(());
|
|
}
|
|
};
|
|
|
|
let window = pstr_app::window::WindowState::load(&dirs.window_file());
|
|
let mut viewport = egui::ViewportBuilder::default();
|
|
// X11 and Windows take the icon from the window. Wayland ignores this and
|
|
// looks the app id up in the installed desktop entry instead.
|
|
match eframe::icon_data::from_png_bytes(include_bytes!("../assets/icon-256.png")) {
|
|
Ok(icon) => viewport = viewport.with_icon(icon),
|
|
Err(error) => tracing::debug!("window icon: {error}"),
|
|
}
|
|
let mut options = eframe::NativeOptions {
|
|
viewport: viewport
|
|
.with_title("proton-stream")
|
|
.with_inner_size(window.map_or([1180.0, 780.0], |state| [state.width, state.height]))
|
|
.with_maximized(window.is_some_and(|state| state.maximized))
|
|
.with_min_inner_size([720.0, 480.0])
|
|
.with_app_id("io.narl.proton-stream"),
|
|
..Default::default()
|
|
};
|
|
// Off on Wayland, where a swap on a window nobody is looking at never
|
|
// returns and the compositor decides the app has hung. `pstr_app::pacing`
|
|
// has the whole of it, and caps the frame rate in vsync's place.
|
|
options.glow_options.vsync = pstr_app::pacing::vsync();
|
|
|
|
eframe::run_native(
|
|
"proton-stream",
|
|
options,
|
|
Box::new(move |cc| {
|
|
pstr_app::app::App::new(cc, runtime, dirs, instance, request)
|
|
.map(|app| Box::new(app) as Box<dyn eframe::App>)
|
|
.map_err(|error| error.into())
|
|
}),
|
|
)
|
|
// `eframe::Error` is not `Send + Sync`, which `anyhow` wants of a source.
|
|
.map_err(|error| anyhow::anyhow!("run the window: {error}"))
|
|
}
|