mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 16:57:00 +01:00
A second launch hands its request to the open window and exits: over a Unix socket in the user's runtime directory, or a named pipe on Windows whose name carries a random key from the user's config directory, so another account cannot take the name first. The open window comes forward, and a share link given as the first such argument opens the Add form with it filled in. Setup failures are logged and the app runs without the lock.
79 lines
3.5 KiB
Rust
79 lines
3.5 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<()> {
|
|
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}"))
|
|
}
|