265 lines
8.9 KiB
Rust
265 lines
8.9 KiB
Rust
#![cfg_attr(
|
|
all(not(debug_assertions), target_os = "windows"),
|
|
windows_subsystem = "windows"
|
|
)]
|
|
#![recursion_limit = "256"]
|
|
|
|
use freya::prelude::*;
|
|
use freya::radio::use_init_radio_station;
|
|
use oneclient_app::ipc::{self, Claim};
|
|
use oneclient_app::state::{AppChannel, AppState, LauncherInit};
|
|
use oneclient_app::{
|
|
Actions, ConfirmLinkOverlay, EssentialConfirmOverlay, EssentialGuardState, EventPump,
|
|
LinkConfirmState, StartMaximizedState, cli, constants, events, microsoft_java, platform,
|
|
router, theme, use_provide_actions, use_provide_essential_guard, use_provide_link_confirm,
|
|
use_provide_start_maximized,
|
|
};
|
|
use std::cell::Cell;
|
|
use tokio::runtime::Builder;
|
|
|
|
struct OneClientApp {
|
|
needs_location: bool,
|
|
start_maximized: bool,
|
|
boot_launch: Cell<Option<String>>,
|
|
}
|
|
|
|
impl App for OneClientApp {
|
|
fn render(&self) -> impl IntoElement {
|
|
let needs_location = self.needs_location;
|
|
|
|
// Radio state is `!Send` so every writer runs on the UI thread `spawn_forever`
|
|
let station = use_init_radio_station::<AppState, AppChannel>(move || AppState {
|
|
launcher: LauncherInit {
|
|
needs_location,
|
|
..LauncherInit::default()
|
|
},
|
|
..AppState::default()
|
|
});
|
|
|
|
let boot_launch = self.boot_launch.take();
|
|
|
|
let actions = use_hook(move || {
|
|
let (signals_tx, signals_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
let (events_bus, events_rx) = oneclient_events::EventBus::channel();
|
|
let actions = Actions::new(station, signals_tx, events_bus.clone());
|
|
|
|
spawn_forever(
|
|
EventPump {
|
|
events: events_rx,
|
|
signals: signals_rx,
|
|
station,
|
|
}
|
|
.run(),
|
|
);
|
|
|
|
if !needs_location {
|
|
let startup = actions.clone();
|
|
let rescue_bus = events_bus.clone();
|
|
spawn_forever(async move {
|
|
match events::start_launcher(station, events_bus).await {
|
|
// Must follow startup `sync_bundles` needs the launcher handle and firing
|
|
// it early leaves `syncing_bundles` stuck disabling every launch button
|
|
Ok(()) => {
|
|
startup.sync_bundles();
|
|
Actions::check_sessions();
|
|
microsoft_java::spawn_auto_install();
|
|
}
|
|
Err(err) => {
|
|
events::report_startup_failure(&station, &err);
|
|
oneclient_app::updater::spawn_update_check(false, rescue_bus);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if let Some(folder) = boot_launch {
|
|
actions.request_launch_by_folder(folder);
|
|
}
|
|
|
|
actions
|
|
});
|
|
|
|
use_provide_actions(&actions);
|
|
|
|
let link_confirm = use_state(|| None::<String>);
|
|
use_provide_link_confirm(LinkConfirmState(link_confirm));
|
|
|
|
let essential_guard = use_state(|| None);
|
|
use_provide_essential_guard(EssentialGuardState(essential_guard));
|
|
|
|
use_provide_start_maximized(StartMaximizedState(self.start_maximized));
|
|
|
|
rect()
|
|
.width(Size::fill())
|
|
.height(Size::fill())
|
|
.child(ConfirmLinkOverlay)
|
|
.child(EssentialConfirmOverlay)
|
|
.child(router())
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
#[cfg(target_os = "linux")]
|
|
unsafe {
|
|
gtk_sys::gtk_disable_setlocale();
|
|
}
|
|
|
|
let cli = cli::parse();
|
|
|
|
let mut builder = Builder::new_multi_thread();
|
|
builder.enable_all().max_blocking_threads(64);
|
|
|
|
// Debug builds emit unoptimized async code that can overflow the default 2MB stack
|
|
#[cfg(debug_assertions)]
|
|
builder.thread_stack_size(3 * 1024 * 1024);
|
|
|
|
let rt = builder.build().unwrap();
|
|
let _tokio_guard = rt.enter();
|
|
|
|
let adopted = rt.block_on(oneclient_core::relocate::adopt_legacy_dir());
|
|
|
|
// no settings file is the sign of a fresh install, but not proof of one
|
|
let never_set_up = oneclient_common::paths::settings_file()
|
|
.map(|path| !path.exists())
|
|
.unwrap_or(false);
|
|
let has_database = oneclient_common::paths::database_file()
|
|
.map(|path| path.exists())
|
|
.unwrap_or(false);
|
|
let was_damaged = oneclient_common::paths::damaged_settings_file()
|
|
.map(|path| path.exists())
|
|
.unwrap_or(false);
|
|
let needs_location = never_set_up && !has_database && !was_damaged;
|
|
|
|
let mut unprotected = None;
|
|
match rt.block_on(ipc::claim(&cli)) {
|
|
Claim::Forwarded => return,
|
|
Claim::Primary(listener) => ipc::listen(listener),
|
|
Claim::Solo(reason) => unprotected = Some(reason),
|
|
}
|
|
|
|
let settings = rt.block_on(oneclient_core::settings::store::load_settings(None));
|
|
|
|
oneclient_common::consent::init(settings.declined_tos);
|
|
|
|
let abandoned_move = settings
|
|
.data_dir
|
|
.clone()
|
|
.filter(|dir| oneclient_core::relocate::incomplete(dir));
|
|
|
|
if let Some(dir) = settings.data_dir.clone()
|
|
&& abandoned_move.is_none()
|
|
{
|
|
oneclient_common::paths::set_data_dir(dir);
|
|
}
|
|
|
|
if settings.log_debug {
|
|
oneclient_core::logger::init_debug()
|
|
} else {
|
|
oneclient_core::logger::init()
|
|
}
|
|
.expect("Failed to initialize logger");
|
|
|
|
match adopted {
|
|
Ok(Some(from)) => {
|
|
tracing::info!(from = %from.display(), "moved out of the old launcher folder")
|
|
}
|
|
Ok(None) => {}
|
|
Err(err) => tracing::error!("{err}"),
|
|
}
|
|
|
|
if let Some(reason) = unprotected {
|
|
tracing::warn!("no single-instance endpoint, a second launcher can start: {reason}");
|
|
}
|
|
|
|
match oneclient_app::shortcut::launcher_exe()
|
|
.and_then(|exe| oneclient_app::protocol::register(&exe))
|
|
{
|
|
Ok(()) => {}
|
|
Err(err) => tracing::warn!(
|
|
"could not register the {}:// handler: {err:#}",
|
|
oneclient_app::protocol::SCHEME
|
|
),
|
|
}
|
|
|
|
if let Some(dir) = abandoned_move {
|
|
tracing::error!(
|
|
path = %dir.display(),
|
|
"a move of the game data folder never finished; staying on the old folder"
|
|
);
|
|
}
|
|
|
|
let _sentry_guard = oneclient_core::reporting::init(settings.crash_reporting);
|
|
|
|
#[cfg(target_os = "macos")]
|
|
oneclient_app::platform::macos::loop_memory_collector();
|
|
|
|
let start_maximized = settings.start_maximized;
|
|
let show_tray_icon = settings.show_tray_icon;
|
|
|
|
let window_config = WindowConfig::new_app(OneClientApp {
|
|
needs_location,
|
|
start_maximized,
|
|
boot_launch: Cell::new(cli.launch),
|
|
})
|
|
.with_title(constants::WINDOW_TITLE)
|
|
.with_app_id(constants::WINDOW_APP_ID)
|
|
.with_icon(LaunchConfig::window_icon(include_bytes!(
|
|
"../icons/128x128.png"
|
|
)))
|
|
.with_size(1200., 800.)
|
|
.with_min_size(800., 600.)
|
|
.with_transparency(false)
|
|
.with_background(Color::TRANSPARENT)
|
|
// A half-copied data folder is unrecoverable, so the window refuses to
|
|
// close while one is being moved; the move screen says as much
|
|
.with_on_close(|_, _| {
|
|
if oneclient_core::relocate::in_progress() {
|
|
tracing::warn!("close request ignored, the data folder is still being moved");
|
|
} else {
|
|
ipc::send(ipc::IpcCommand::Close);
|
|
}
|
|
CloseDecision::KeepOpen
|
|
});
|
|
|
|
#[cfg(target_os = "macos")]
|
|
let window_config =
|
|
window_config
|
|
.with_decorations(true)
|
|
.with_window_attributes(move |attrs, _| {
|
|
use freya::winit::platform::macos::WindowAttributesExtMacOS;
|
|
attrs
|
|
.with_title_hidden(true)
|
|
.with_titlebar_transparent(true)
|
|
.with_titlebar_buttons_hidden(true)
|
|
.with_fullsize_content_view(true)
|
|
.with_has_shadow(true)
|
|
.with_maximized(start_maximized)
|
|
});
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
let window_config = window_config
|
|
.with_decorations(false)
|
|
.with_window_attributes(move |attrs, _| attrs.with_maximized(start_maximized));
|
|
|
|
let mut launch_config = LaunchConfig::new()
|
|
.with_window(window_config)
|
|
.with_gpu_resource_cache_limit(
|
|
std::env::var("ONECLIENT_GPU_CACHE_BYTES")
|
|
.ok()
|
|
.and_then(|v| v.parse::<usize>().ok())
|
|
.unwrap_or(96 * 1024 * 1024),
|
|
)
|
|
.with_plugin(freya::borderless::BorderlessPlugin::new())
|
|
.with_plugin(freya::metrics::MetricsPlugin::default())
|
|
.with_default_font(theme::DEFAULT_FONT);
|
|
|
|
if show_tray_icon && platform::tray::available() {
|
|
launch_config = launch_config.with_tray(platform::tray::build, platform::tray::handle);
|
|
}
|
|
|
|
for (font, bytes) in theme::load_fonts() {
|
|
launch_config = launch_config.with_font(font, bytes);
|
|
}
|
|
|
|
launch(launch_config);
|
|
}
|