1pub(crate) mod client_init;
2mod ui;
3
4use super::{char_selection::CharSelectionState, dummy_scene::Scene, server_info::ServerInfoState};
5#[cfg(feature = "singleplayer")]
6use crate::singleplayer::SingleplayerState;
7use crate::{
8 Direction, GlobalState, PlayState, PlayStateResult, hud,
9 render::{Drawer, GlobalsBindGroup},
10 session::SessionState,
11 settings::Settings,
12 window::Event,
13};
14use chrono::{DateTime, Local, Utc};
15use client::{
16 Client, ClientInitStage, ServerInfo,
17 addr::ConnectionArgs,
18 error::{InitProtocolError, NetworkConnectError, NetworkError},
19};
20use client_init::{ClientInit, Error as InitError, InitOutMsg};
21use common::{comp, event::UpdateCharacterMetadata};
22use common_base::span;
23use common_net::msg::ClientType;
24#[cfg(feature = "plugins")]
25use common_state::plugin::PluginMgr;
26use credentialstore::*;
27use i18n::{LocalizationGuard, LocalizationHandle, fluent_args};
28#[cfg(feature = "singleplayer")]
29use server::ServerInitStage;
30#[cfg(any(feature = "singleplayer", feature = "plugins"))]
31use specs::WorldExt;
32use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc};
33use tokio::{
34 runtime,
35 sync::mpsc,
36 time::{Duration, interval},
37};
38use tracing::error;
39use ui::{Event as MainMenuEvent, MainMenuUi};
40
41pub use ui::rand_bg_image_spec;
42
43pub enum CredentialResponse {
44 Status(CredentialStatus),
45 Password(Result<String, CredentialError>),
46}
47
48#[derive(Debug)]
49pub enum DetailedInitializationStage {
50 #[cfg(feature = "singleplayer")]
51 Singleplayer,
52 #[cfg(feature = "singleplayer")]
53 SingleplayerServer(ServerInitStage),
54 StartingMultiplayer,
55 Client(ClientInitStage),
56 CreatingRenderPipeline(usize, usize),
57}
58
59enum InitState {
60 None,
61 Client(ClientInit),
63 Pipeline(Box<Client>, hud::PersistedHudState),
65}
66
67impl InitState {
68 fn client(&self) -> Option<&ClientInit> {
69 if let Self::Client(client_init) = &self {
70 Some(client_init)
71 } else {
72 None
73 }
74 }
75}
76
77pub struct MainMenuState {
78 main_menu_ui: MainMenuUi,
79 init: InitState,
80 scene: Scene,
81 secure_service: SecureService,
82 secure_service_rx: Option<mpsc::UnboundedReceiver<CredentialResponse>>,
83 secure_service_tx: Option<mpsc::Sender<String>>,
84 pending_credentials: Option<Credentials>,
85}
86
87impl MainMenuState {
88 pub fn new(global_state: &mut GlobalState) -> Self {
90 Self {
91 main_menu_ui: MainMenuUi::new(global_state),
92 init: InitState::None,
93 scene: Scene::new(global_state.window.renderer_mut()),
94 secure_service: SecureService::default(),
95 secure_service_rx: None,
96 secure_service_tx: None,
97 pending_credentials: None,
98 }
99 }
100}
101
102impl PlayState for MainMenuState {
103 fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
104 if global_state.settings.audio.output.is_enabled() && global_state.audio.music_enabled() {
106 global_state.audio.play_title_music();
107 }
108
109 #[cfg(feature = "singleplayer")]
111 {
112 global_state.singleplayer = SingleplayerState::None;
113 }
114
115 self.main_menu_ui
117 .update_language(global_state.i18n, &global_state.settings);
118 self.main_menu_ui
120 .set_scale_mode(global_state.settings.interface.ui_scale);
121
122 #[cfg(feature = "discord")]
123 global_state.discord.enter_main_menu();
124 }
125
126 fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
127 span!(_guard, "tick", "<MainMenuState as PlayState>::tick");
128
129 if let Some(receiver) = &mut self.secure_service_rx
132 && let Some(transmitter) = &mut self.secure_service_tx
133 {
134 while let Ok(response) = receiver.try_recv() {
135 match response {
136 CredentialResponse::Status(status) => {
137 self.main_menu_ui.update_secure_service_status(status);
138 if status == CredentialStatus::Ready
139 && global_state.settings.interface.use_secure_service
140 {
141 let _ = transmitter.try_send(self.main_menu_ui.username());
142 }
143 },
144 CredentialResponse::Password(Ok(password)) => {
145 self.main_menu_ui.set_password(password)
146 },
147 CredentialResponse::Password(Err(error)) => {
148 tracing::warn!(?error, "{error}")
149 },
150 }
151 }
152 }
153
154 let localized_strings = &global_state.i18n.read();
156
157 #[cfg(feature = "singleplayer")]
159 {
160 if let Some(singleplayer) = global_state.singleplayer.as_running() {
161 if let Ok(stage_update) = singleplayer.init_stage_receiver.try_recv() {
162 self.main_menu_ui.update_stage(
163 DetailedInitializationStage::SingleplayerServer(stage_update),
164 );
165 }
166
167 match singleplayer.receiver.try_recv() {
168 Ok(Ok(())) => {
169 attempt_login(
171 ConnectionArgs::Mpsc(14004),
172 &mut self.init,
173 &global_state.tokio_runtime,
174 global_state.settings.language.send_to_server.then_some(
175 global_state.settings.language.selected_language.clone(),
176 ),
177 &global_state.config_dir,
178 global_state.args.client_type.0,
179 );
180 },
181 Ok(Err(e)) => {
182 error!(?e, "Could not start server");
183 global_state.singleplayer = SingleplayerState::None;
184 self.init = InitState::None;
185 self.main_menu_ui.cancel_connection();
186 let server_err = match e {
187 server::Error::NetworkErr(e) => localized_strings
188 .get_msg_ctx("main-servers-network_error", &i18n::fluent_args! {
189 "raw_error" => e.to_string()
190 })
191 .into_owned(),
192 server::Error::ParticipantErr(e) => localized_strings
193 .get_msg_ctx(
194 "main-servers-participant_error",
195 &i18n::fluent_args! {
196 "raw_error" => e.to_string()
197 },
198 )
199 .into_owned(),
200 server::Error::StreamErr(e) => localized_strings
201 .get_msg_ctx("main-servers-stream_error", &i18n::fluent_args! {
202 "raw_error" => e.to_string()
203 })
204 .into_owned(),
205 server::Error::DatabaseErr(e) => localized_strings
206 .get_msg_ctx("main-servers-database_error", &i18n::fluent_args! {
207 "raw_error" => e.to_string()
208 })
209 .into_owned(),
210 server::Error::PersistenceErr(e) => localized_strings
211 .get_msg_ctx(
212 "main-servers-persistence_error",
213 &i18n::fluent_args! {
214 "raw_error" => e.to_string()
215 },
216 )
217 .into_owned(),
218 server::Error::RtsimError(e) => localized_strings
219 .get_msg_ctx("main-servers-rtsim_error", &i18n::fluent_args! {
220 "raw_error" => e.to_string(),
221 })
222 .into_owned(),
223 server::Error::Other(e) => localized_strings
224 .get_msg_ctx("main-servers-other_error", &i18n::fluent_args! {
225 "raw_error" => e,
226 })
227 .into_owned(),
228 };
229 global_state.info_message = Some(
230 localized_strings
231 .get_msg_ctx(
232 "main-servers-singleplayer_error",
233 &i18n::fluent_args! {
234 "sp_error" => server_err
235 },
236 )
237 .into_owned(),
238 );
239 },
240 Err(_) => (),
241 }
242 }
243 }
244 for event in events {
246 if self.main_menu_ui.handle_event(event.clone()) {
248 continue;
249 }
250
251 if matches!(event, Event::Close) {
253 return PlayStateResult::Shutdown;
254 }
255 }
256
257 if let Some(client_stage_update) = self.init.client().and_then(|init| init.stage_update()) {
258 self.main_menu_ui
259 .update_stage(DetailedInitializationStage::Client(client_stage_update));
260 }
261
262 if let Some(clientinit) = self.init.client() {
264 match clientinit.poll() {
265 Some(InitOutMsg::Done(Ok(mut client))) => {
266 self.secure_service_rx = None;
268 self.secure_service_tx = None;
269 if let Some(credentials) = self.pending_credentials.take() {
270 let service = self.secure_service.clone();
271 global_state.tokio_runtime.spawn(async move {
272 let _ = service
273 .save(credentials)
274 .await
275 .map_err(|error| tracing::warn!(?error, "{error}"));
276 });
277 }
278 #[cfg(feature = "plugins")]
280 for path in client.take_local_plugins().drain(..) {
281 if let Err(e) = client
282 .state_mut()
283 .ecs_mut()
284 .write_resource::<PluginMgr>()
285 .load_server_plugin(path)
286 {
287 tracing::error!(?e, "load local plugin");
288 }
289 }
290 crate::ecs::init(client.state_mut().ecs_mut());
292 self.init =
293 InitState::Pipeline(Box::new(client), hud::PersistedHudState::default());
294 },
295 Some(InitOutMsg::Done(Err(e))) => {
296 self.init = InitState::None;
297 error!(?e, "Client Init failed raw error");
298 let e = get_client_init_msg_error(e, &global_state.i18n);
299 self.secure_service_rx = None;
301 self.secure_service_tx = None;
302 error!(?e, "Client Init failed");
305 global_state.info_message = Some(
306 localized_strings
307 .get_msg_ctx("main-login-client_init_failed", &i18n::fluent_args! {
308 "init_fail_reason" => e
309 })
310 .into_owned(),
311 );
312 },
313 Some(InitOutMsg::IsAuthTrusted(auth_server)) => {
314 if global_state
315 .settings
316 .networking
317 .trusted_auth_servers
318 .contains(&auth_server)
319 {
320 clientinit.auth_trust(auth_server, true);
321 } else {
322 self.main_menu_ui.auth_trust_prompt(auth_server);
324 }
325 },
326 Some(InitOutMsg::RequestCredentials(auth_server)) => {
327 if self.secure_service.is_supported()
329 && let Some(auth_server) = auth_server.clone()
330 {
331 let service = self.secure_service.clone();
332 let (command_tx, mut command_rx) = mpsc::channel::<String>(1);
333 let (updates_tx, updates_rx) =
334 mpsc::unbounded_channel::<CredentialResponse>();
335 self.secure_service_rx = Some(updates_rx);
336 self.secure_service_tx = Some(command_tx);
337 global_state.tokio_runtime.spawn(async move {
338 let mut interval = interval(Duration::from_secs(2));
339 let mut request = String::new();
340 let mut status = CredentialStatus::Disabled;
341 while !updates_tx.is_closed() {
342 tokio::select! {
343 _ = interval.tick() => {
344 let new_status = service.status().await;
345 if new_status != status {
346 status = new_status;
347 let _ = updates_tx.send(CredentialResponse::Status(status));
348 }
349 },
350 command = command_rx.recv() => {
351 match command {
352 Some(username) => {
353 if request != username {
354 let password = service.load(auth_server.clone(), username.clone()).await;
355 if password.is_ok() {
356 request = username;
357 }
358 let _ = updates_tx.send(CredentialResponse::Password(password));
359 }
360 },
361 None => break,
362 }
363 }
364 }
365 }
366 });
367 }
368
369 self.main_menu_ui.credentials_prompt(auth_server);
370 },
371 None => {},
372 }
373 }
374
375 if let InitState::Pipeline(client, _) = &mut self.init {
377 match client.tick(
378 comp::ControllerInputs::default(),
379 global_state.clock.game_dt(),
380 ) {
381 Ok(events) => {
382 for event in events {
383 match event {
384 client::Event::SetViewDistance(_vd) => {},
385 client::Event::Disconnect => {
386 global_state.info_message = Some(
387 localized_strings
388 .get_msg("main-login-server_shut_down")
389 .into_owned(),
390 );
391 self.init = InitState::None;
392 },
393 client::Event::Chat(m) => {
394 if let InitState::Pipeline(client, persisted_state) = &mut self.init
395 {
396 persisted_state.message_backlog.new_message(
397 client,
398 &global_state.profile,
399 m,
400 )
401 }
402 },
403 client::Event::MapMarker(marker_event) => {
404 if let InitState::Pipeline(_client, persisted_state) =
405 &mut self.init
406 {
407 persisted_state.location_markers.update(marker_event);
408 }
409 },
410 #[cfg_attr(not(feature = "plugins"), expect(unused_variables))]
411 client::Event::PluginDataReceived(data) => {
412 #[cfg(feature = "plugins")]
413 {
414 tracing::info!("plugin data {}", data.len());
415 if let InitState::Pipeline(client, _) = &mut self.init {
416 let hash = client
417 .state()
418 .ecs()
419 .write_resource::<PluginMgr>()
420 .cache_server_plugin(&global_state.config_dir, data);
421 match hash {
422 Ok(hash) => {
423 if client.plugin_received(hash) == 0 {
424 client.load_character_list();
427 }
428 },
429 Err(e) => tracing::error!(?e, "cache_server_plugin"),
430 }
431 }
432 }
433 },
434 _ => {},
435 }
436 }
437 },
438 Err(err) => {
439 error!(?err, "[main menu] Failed to tick the client");
440 global_state.info_message =
441 Some(get_client_msg_error(err, None, &global_state.i18n.read()));
442 self.init = InitState::None;
443 },
444 }
445 }
446
447 if let InitState::Pipeline(..) = &self.init {
449 if let Some((done, total)) = &global_state.window.renderer().pipeline_creation_status()
450 {
451 self.main_menu_ui.update_stage(
452 DetailedInitializationStage::CreatingRenderPipeline(*done, *total),
453 );
454 } else {
456 if let InitState::Pipeline(mut client, persisted_state) =
458 core::mem::replace(&mut self.init, InitState::None)
459 {
460 self.main_menu_ui.connected();
461
462 if client.client_type().can_spectate()
465 && !client.client_type().can_enter_character()
466 {
467 client.request_spectate(global_state.settings.graphics.view_distances());
468
469 return PlayStateResult::Push(Box::new(SessionState::new(
470 global_state,
471 UpdateCharacterMetadata::default(),
472 Rc::new(RefCell::new(*client)),
473 Rc::new(RefCell::new(persisted_state)),
474 )));
475 }
476
477 let server_info = client.server_info().clone();
478 let server_description = client.server_description().clone();
479
480 let char_select = CharSelectionState::new(
481 global_state,
482 Rc::new(RefCell::new(*client)),
483 Rc::new(RefCell::new(persisted_state)),
484 );
485
486 let new_state = ServerInfoState::try_from_server_info(
487 global_state,
488 self.main_menu_ui.bg_img_spec(),
489 char_select,
490 server_info,
491 server_description,
492 false,
493 )
494 .map(|s| Box::new(s) as _)
495 .unwrap_or_else(|s| Box::new(s) as _);
496
497 return PlayStateResult::Push(new_state);
498 }
499 }
500 }
501
502 for event in self
504 .main_menu_ui
505 .maintain(global_state, global_state.clock.real_dt())
506 {
507 match event {
508 MainMenuEvent::LoginAttempt { server_address } => {
509 let net_settings = &mut global_state.settings.networking;
510 let use_srv = net_settings.use_srv;
511 let use_quic = net_settings.use_quic;
512 let validate_tls = net_settings.validate_tls;
513 net_settings.default_server.clone_from(&server_address);
514 if !server_address.is_empty() && !net_settings.servers.contains(&server_address)
515 {
516 net_settings.servers.push(server_address.clone());
517 }
518 global_state
519 .settings
520 .save_to_file_warn(&global_state.config_dir);
521
522 let connection_args = if use_srv {
523 ConnectionArgs::Srv {
524 hostname: server_address,
525 prefer_ipv6: false,
526 validate_tls,
527 use_quic,
528 }
529 } else if use_quic {
530 ConnectionArgs::Quic {
531 hostname: server_address,
532 prefer_ipv6: false,
533 validate_tls,
534 }
535 } else {
536 ConnectionArgs::Tcp {
537 hostname: server_address,
538 prefer_ipv6: false,
539 }
540 };
541 attempt_login(
542 connection_args,
543 &mut self.init,
544 &global_state.tokio_runtime,
545 global_state
546 .settings
547 .language
548 .send_to_server
549 .then_some(global_state.settings.language.selected_language.clone()),
550 &global_state.config_dir,
551 global_state.args.client_type.0,
552 );
553 },
554 MainMenuEvent::CancelLoginAttempt => {
555 self.secure_service_rx = None;
557 self.secure_service_tx = None;
558 #[cfg(feature = "singleplayer")]
562 {
563 global_state.singleplayer = SingleplayerState::None;
564 }
565 self.init = InitState::None;
566 self.main_menu_ui.cancel_connection();
567 },
568 MainMenuEvent::ChangeLanguage(new_language) => {
569 global_state.settings.language.selected_language =
570 new_language.language_identifier;
571 global_state.i18n = LocalizationHandle::load_expect(
572 &global_state.settings.language.selected_language,
573 );
574 global_state
575 .i18n
576 .set_english_fallback(global_state.settings.language.use_english_fallback);
577 self.main_menu_ui
578 .update_language(global_state.i18n, &global_state.settings);
579 },
580 #[cfg(feature = "singleplayer")]
581 MainMenuEvent::StartSingleplayer => {
582 global_state.singleplayer.run(
583 &global_state.tokio_runtime,
584 &global_state.settings.language.selected_language,
585 &global_state.i18n,
586 );
587 },
588 #[cfg(feature = "singleplayer")]
589 MainMenuEvent::InitSingleplayer => {
590 global_state.singleplayer = SingleplayerState::init();
591 },
592 #[cfg(feature = "singleplayer")]
593 MainMenuEvent::SinglePlayerChange(change) => {
594 if let SingleplayerState::Init(ref mut init) = global_state.singleplayer {
595 match change {
596 ui::WorldsChange::SetActive(world) => init.current = world,
597 ui::WorldsChange::Delete(world) => init.remove(world),
598 ui::WorldsChange::Regenerate(world) => init.delete_map_file(world),
599 ui::WorldsChange::AddNew => init.new_world(),
600 ui::WorldsChange::CurrentWorldChange(change) => {
601 if let Some(world) = init.current.map(|i| &mut init.worlds[i]) {
602 change.apply(world);
603 init.save_current_meta();
604 }
605 },
606 }
607 }
608 },
609 MainMenuEvent::Quit => return PlayStateResult::Shutdown,
610 MainMenuEvent::AuthServerTrust(auth_server, trust) => {
615 if trust {
616 global_state
617 .settings
618 .networking
619 .trusted_auth_servers
620 .insert(auth_server.clone());
621 global_state
622 .settings
623 .save_to_file_warn(&global_state.config_dir);
624 }
625 self.init
626 .client()
627 .map(|init| init.auth_trust(auth_server, trust));
628 },
629 MainMenuEvent::CredentialsProvided(auth_server, credentials) => {
630 let net_settings = &mut global_state.settings.networking;
633
634 self.pending_credentials = if global_state.settings.interface.use_secure_service
635 && let Some(auth_server) = auth_server.as_ref()
636 && let Ok(client::Credentials::UsernamePassword(username, password)) =
637 credentials.as_ref()
638 {
639 Some(Credentials {
640 auth_server: auth_server.clone(),
641 username: username.clone(),
642 password: password.clone(),
643 })
644 } else {
645 None
646 };
647
648 let _ = credentials.as_ref().map(|credentials| {
649 let username = match credentials {
650 client::Credentials::Username(username) => username,
651 client::Credentials::UsernamePassword(username, _) => username,
652 };
653 net_settings.username.clone_from(username);
654 });
655 global_state
656 .settings
657 .save_to_file_warn(&global_state.config_dir);
658
659 self.init
660 .client()
661 .map(|init| init.provide_credentials(auth_server, credentials));
662 },
663 MainMenuEvent::DeleteServer { server_index } => {
664 let net_settings = &mut global_state.settings.networking;
665 net_settings.servers.remove(server_index);
666
667 global_state
668 .settings
669 .save_to_file_warn(&global_state.config_dir);
670 },
671 MainMenuEvent::UseSecureService(enabled) => {
672 global_state.settings.interface.use_secure_service = enabled;
673 global_state
674 .settings
675 .save_to_file_warn(&global_state.config_dir);
676 if enabled
679 && let Some(transmitter) = &mut self.secure_service_tx
680 && let Err(error) = transmitter.try_send(self.main_menu_ui.username())
681 {
682 tracing::warn!(?error, "Credential store is overloaded")
683 }
684 },
685 }
686 }
687
688 if let Some(info) = global_state.info_message.take() {
689 self.main_menu_ui.show_info(info);
690 }
691
692 PlayStateResult::Continue
693 }
694
695 fn name(&self) -> &'static str { "Title" }
696
697 fn capped_fps(&self) -> bool { true }
698
699 fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
700
701 fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
702 let mut third_pass = drawer.third_pass();
704 if let Some(mut ui_drawer) = third_pass.draw_ui() {
705 self.main_menu_ui.render(&mut ui_drawer);
706 };
707 }
708
709 fn egui_enabled(&self) -> bool { false }
710}
711
712pub(crate) fn get_client_msg_error(
713 error: client::Error,
714 mismatched_server_info: Option<ServerInfo>,
715 localization: &LocalizationGuard,
716) -> String {
717 let net_error = |error: String, mismatched_server_info: Option<ServerInfo>| -> String {
721 if let Some(server_info) = mismatched_server_info.filter(|info| {
722 info.git_hash != *common::util::GIT_HASH
723 || info.git_timestamp != *common::util::GIT_TIMESTAMP
724 }) {
725 format!(
726 "{} {}: {} {}: {}",
727 localization.get_msg("main-login-network_wrong_version"),
728 localization.get_msg("main-login-client_version"),
729 *common::util::DISPLAY_VERSION,
730 localization.get_msg("main-login-server_version"),
731 common::util::make_display_version(server_info.git_hash, server_info.git_timestamp),
732 )
733 } else {
734 format!(
735 "{}: {}",
736 localization.get_msg("main-login-network_error"),
737 error
738 )
739 }
740 };
741
742 use client::Error;
743 match error {
744 Error::SpecsErr(e) => {
745 format!(
746 "{}: {}",
747 localization.get_msg("main-login-internal_error"),
748 e
749 )
750 },
751 Error::AuthErr(e) => format!(
752 "{}: {}",
753 localization.get_msg("main-login-authentication_error"),
754 e
755 ),
756 Error::Kicked(reason) => localization
757 .get_msg_ctx("main-login-kicked", &fluent_args! {
758 "reason" => reason,
759 })
760 .into(),
761 Error::TooManyPlayers => localization.get_msg("main-login-server_full").into(),
762 Error::AuthServerNotTrusted => localization
763 .get_msg("main-login-untrusted_auth_server")
764 .into(),
765 Error::CredentialsNotProvided => localization
766 .get_msg("main-login-credentials_not_provided")
767 .into(),
768 Error::ServerTimeout => localization.get_msg("main-login-timeout").into(),
769 Error::ServerShutdown => localization.get_msg("main-login-server_shut_down").into(),
770 Error::NotOnWhitelist => localization.get_msg("main-login-not_on_whitelist").into(),
771 Error::Banned(ban_info) => if let Some(end_time) = ban_info
772 .until
773 .and_then(|timestamp| DateTime::<Utc>::from_timestamp(timestamp, 0))
774 {
775 let end_date = end_time.with_timezone(&Local);
776 let end_date_str = end_date.format("%Y-%m-%d %H:%M").to_string();
777
778 localization.get_msg_ctx("main-login-banned_until", &fluent_args! {
779 "reason" => ban_info.reason,
780 "end_date" => end_date_str,
781 })
782 } else {
783 localization.get_msg_ctx("main-login-banned", &fluent_args! {
784 "reason" => ban_info.reason
785 })
786 }
787 .into(),
788 Error::InvalidCharacter => localization.get_msg("main-login-invalid_character").into(),
789 Error::NetworkErr(NetworkError::ConnectFailed(NetworkConnectError::Handshake(
790 InitProtocolError::WrongVersion(_),
791 ))) => net_error(
792 localization
793 .get_msg("main-login-network_wrong_version")
794 .into_owned(),
795 mismatched_server_info,
796 ),
797 Error::NetworkErr(e) => net_error(e.to_string(), mismatched_server_info),
798 Error::ParticipantErr(e) => net_error(e.to_string(), mismatched_server_info),
799 Error::StreamErr(e) => net_error(e.to_string(), mismatched_server_info),
800 Error::RustlsErr(e) => net_error(e.to_string(), mismatched_server_info),
801 Error::HostnameLookupFailed(e) => {
802 format!(
803 "{}: {}",
804 localization.get_msg("main-login-server_not_found"),
805 e
806 )
807 },
808 Error::Other(e) => {
809 format!("{}: {}", localization.get_msg("common-error"), e)
810 },
811 Error::AuthClientError(e) => match e {
812 client::AuthClientError::RequestError(e) => format!(
814 "{}: {}",
815 localization.get_msg("main-login-failed_sending_request"),
816 e
817 ),
818 client::AuthClientError::ResponseError(e) => format!(
819 "{}: {}",
820 localization.get_msg("main-login-failed_sending_request"),
821 e
822 ),
823 client::AuthClientError::CertificateLoad(e) => format!(
824 "{}: {}",
825 localization.get_msg("main-login-failed_sending_request"),
826 e
827 ),
828 client::AuthClientError::JsonError(e) => format!(
829 "{}: {}",
830 localization.get_msg("main-login-failed_sending_request"),
831 e
832 ),
833 client::AuthClientError::InsecureSchema => localization
834 .get_msg("main-login-insecure_auth_scheme")
835 .into(),
836 client::AuthClientError::ServerError(_, e) => String::from_utf8_lossy(&e).into(),
837 },
838 Error::AuthServerUrlInvalid(e) => {
839 format!(
840 "{}: https://{}",
841 localization.get_msg("main-login-failed_auth_server_url_invalid"),
842 e
843 )
844 },
845 }
846}
847
848fn get_client_init_msg_error(
849 error: client_init::Error,
850 localized_strings: &LocalizationHandle,
851) -> String {
852 let localization = localized_strings.read();
853
854 match error {
855 InitError::ClientError {
856 error,
857 mismatched_server_info,
858 } => get_client_msg_error(error, mismatched_server_info, &localization),
859 InitError::ClientCrashed => localization.get_msg("main-login-client_crashed").into(),
860 InitError::ServerNotFound => localization.get_msg("main-login-server_not_found").into(),
861 }
862}
863
864fn attempt_login(
865 connection_args: ConnectionArgs,
866 init: &mut InitState,
867 runtime: &Arc<runtime::Runtime>,
868 locale: Option<String>,
869 config_dir: &Path,
870 client_type: ClientType,
871) {
872 if let InitState::None = init {
874 *init = InitState::Client(ClientInit::new(
875 connection_args,
876 Arc::clone(runtime),
877 locale,
878 config_dir,
879 client_type,
880 ));
881 }
882}