diff --git a/binaries/geph5-client-gui/src/assets/IosevkaAile-Bold.ttf b/binaries/geph5-client-gui/src/assets/IosevkaAile-Bold.ttf new file mode 100644 index 0000000..b4c9a33 Binary files /dev/null and b/binaries/geph5-client-gui/src/assets/IosevkaAile-Bold.ttf differ diff --git a/binaries/geph5-client-gui/src/assets/IosevkaAile-Regular.ttf b/binaries/geph5-client-gui/src/assets/IosevkaAile-Regular.ttf new file mode 100644 index 0000000..738df16 Binary files /dev/null and b/binaries/geph5-client-gui/src/assets/IosevkaAile-Regular.ttf differ diff --git a/binaries/geph5-client-gui/src/assets/chars.txt b/binaries/geph5-client-gui/src/assets/chars.txt index 976c6ab..16983b1 100644 --- a/binaries/geph5-client-gui/src/assets/chars.txt +++ b/binaries/geph5-client-gui/src/assets/chars.txt @@ -17,6 +17,7 @@ > ? [ +\ ] _ { @@ -76,6 +77,7 @@ x y z а +б в В г @@ -84,6 +86,7 @@ z е Ж з +З и И й @@ -103,6 +106,7 @@ z у ф х +ц ч ы ь @@ -114,6 +118,7 @@ z پ ت ج +ح خ د ذ @@ -139,35 +144,45 @@ z ی 上 下 +中 于 仪 传 -你 保 +信 关 出 +列 +加 务 助 协 取 +口 器 -好 +在 存 +定 已 +帐 帮 度 开 志 态 +息 +户 接 断 日 时 服 +正 流 消 状 用 +的 盘 置 表 @@ -177,6 +192,7 @@ z 连 迷 退 +选 通 速 量 diff --git a/binaries/geph5-client-gui/src/assets/subset.ttf b/binaries/geph5-client-gui/src/assets/chinese.ttf similarity index 75% rename from binaries/geph5-client-gui/src/assets/subset.ttf rename to binaries/geph5-client-gui/src/assets/chinese.ttf index a2baaff..1420a0d 100644 Binary files a/binaries/geph5-client-gui/src/assets/subset.ttf and b/binaries/geph5-client-gui/src/assets/chinese.ttf differ diff --git a/binaries/geph5-client-gui/src/assets/normal.ttf b/binaries/geph5-client-gui/src/assets/normal.ttf new file mode 100644 index 0000000..e67e5c6 Binary files /dev/null and b/binaries/geph5-client-gui/src/assets/normal.ttf differ diff --git a/binaries/geph5-client-gui/src/assets/subset.sh b/binaries/geph5-client-gui/src/assets/subset.sh index 3eff468..095ab35 100755 --- a/binaries/geph5-client-gui/src/assets/subset.sh +++ b/binaries/geph5-client-gui/src/assets/subset.sh @@ -1,3 +1,4 @@ #!/bin/bash find ../ -type f -name '*.rs' -o -name '*.csv' | xargs cat | grep -o . | sort -u > chars.txt -pyftsubset SarasaUiSC-Regular.ttf --text-file=chars.txt --output-file=subset.ttf \ No newline at end of file +pyftsubset IosevkaAile-Regular.ttf --text-file=chars.txt --output-file=normal.ttf +pyftsubset SarasaUiSC-Regular.ttf --text-file=chars.txt --output-file=chinese.ttf \ No newline at end of file diff --git a/binaries/geph5-client-gui/src/daemon.rs b/binaries/geph5-client-gui/src/daemon.rs new file mode 100644 index 0000000..79af628 --- /dev/null +++ b/binaries/geph5-client-gui/src/daemon.rs @@ -0,0 +1,4 @@ +use egui::mutex::Mutex; +use once_cell::sync::Lazy; + +pub static DAEMON: Lazy>> = Lazy::new(|| Mutex::new(None)); diff --git a/binaries/geph5-client-gui/src/dashboard.rs b/binaries/geph5-client-gui/src/dashboard.rs index d04b756..3c172f1 100644 --- a/binaries/geph5-client-gui/src/dashboard.rs +++ b/binaries/geph5-client-gui/src/dashboard.rs @@ -1,81 +1,42 @@ -use std::time::Duration; +use crate::{daemon::DAEMON, l10n::l10n, settings::get_config}; -use anyhow::Context as _; -use futures_util::{future::Shared, FutureExt}; -use geph5_broker_protocol::{BrokerClient, ExitList}; - -use smol::Task; -use smol_timeout::TimeoutExt as _; - -use crate::{l10n::l10n, settings::get_config}; - -pub struct Dashboard { - selected_server: Option<(String, String)>, - server_list: Shared>, -} +pub struct Dashboard {} impl Dashboard { pub fn new() -> Self { - Self { - selected_server: None, - server_list: smolscale::spawn(get_server_list()).shared(), - } + Self {} } pub fn render(&mut self, ui: &mut egui::Ui) -> anyhow::Result<()> { - ui.label(l10n("selected_server")); - egui::ComboBox::from_label("") - .selected_text(render_exit_selection(&self.selected_server)) - .show_ui(ui, |ui| { - if let Some(list) = self.server_list.peek() { - } else { - ui.label(l10n("loading_exit_list")); - } - // ui.selectable_value(&mut self.selected_server, "Apple".to_string(), "Apple"); - // ui.selectable_value(&mut self.selected_server, "Pear".to_string(), "Pear"); - // ui.selectable_value(&mut self.selected_server, "Orange".to_string(), "Orange"); - }); - anyhow::Ok(()) - } -} + let mut daemon = DAEMON.lock(); -fn render_exit_selection(selection: &Option<(String, String)>) -> String { - if let Some((country, city)) = selection.as_ref() { - format!("{country}/{city}") - } else { - "Auto".into() - } -} + ui.columns(2, |columns| { + columns[0].label(l10n("status")); + if daemon.is_none() { + columns[1].colored_label(egui::Color32::DARK_RED, l10n("disconnected")); + } + columns[0].label(l10n("connection_time")); + columns[0].label(l10n("data_used")); + }); + ui.add_space(10.); + ui.vertical_centered(|ui| { + if daemon.is_none() { + if ui.button(l10n("connect")).clicked() { + tracing::warn!("connect clicked"); + *daemon = Some(geph5_client::Client::start(get_config()?)); + } + } else if ui.button(l10n("disconnect")).clicked() { + tracing::warn!("disconnect clicked"); + *daemon = None; + } + anyhow::Ok(()) + }) + .inner?; -async fn get_server_list() -> ExitList { - loop { - let fallible = async { - let broker_client = get_broker_client() - .await? - .context("no broker client available")?; - let exits = broker_client - .get_exits() - .timeout(Duration::from_secs(10)) - .await - .context("timeout")?? - .map_err(|e| anyhow::anyhow!(e))? - .inner; - anyhow::Ok(exits) - }; - match fallible.await { - Ok(res) => return res, - Err(err) => { - tracing::warn!(err = debug(err), "error getting server list"); - smol::Timer::after(Duration::from_secs(1)).await; + if let Some(daemon) = daemon.as_ref() { + if let Err(err) = daemon.check_dead() { + ui.colored_label(egui::Color32::RED, format!("{:?}", err)); } } - } -} - -async fn get_broker_client() -> anyhow::Result> { - let config = get_config()?; - if let Some(src) = config.broker { - Ok(Some(BrokerClient::from(src.rpc_transport()))) - } else { - Ok(None) + Ok(()) } } diff --git a/binaries/geph5-client-gui/src/l10n.csv b/binaries/geph5-client-gui/src/l10n.csv index a07efde..0e2dea0 100644 --- a/binaries/geph5-client-gui/src/l10n.csv +++ b/binaries/geph5-client-gui/src/l10n.csv @@ -4,12 +4,12 @@ account_info,Account Info,帐户信息,Информация об аккаунт cancel,Cancel,取消,Отмена,لغو connect,Connect,连接,Подключиться,اتصال connected,Connected,已连接,Подключено,متصل -connection_time,Connection Time,连接时间,Время подключения,زمان اتصال +connection_time,Connection time,连接时间,Время подключения,زمان اتصال dashboard,Dashboard,仪表盘,Панель,داشبورد -data_used,Data Used,已用流量,Использовано данных,داده مصرف شده +data_used,Data used,已用流量,Использовано данных,داده مصرف شده disconnect,Disconnect,断开连接,Отключиться,قطع اتصال disconnected,Disconnected,已断开连接,Отключено,قطع اتصال -download_speed,Download Speed,下载速度,Скорость скачивания,سرعت دانلود +download_speed,Download speed,下载速度,Скорость скачивания,سرعت دانلود exit,Exit,退出,Выход,خروج geph,Geph,迷雾通,Геф,گف help,Help,帮助,Справка,راهنما diff --git a/binaries/geph5-client-gui/src/main.rs b/binaries/geph5-client-gui/src/main.rs index eb96008..9f1554a 100644 --- a/binaries/geph5-client-gui/src/main.rs +++ b/binaries/geph5-client-gui/src/main.rs @@ -1,3 +1,4 @@ +mod daemon; mod dashboard; mod l10n; mod prefs; @@ -23,7 +24,7 @@ fn main() { ) .with( EnvFilter::builder() - .with_default_directive("geph5=debug".parse().unwrap()) + .with_default_directive("geph5".parse().unwrap()) .from_env_lossy(), ) .init(); @@ -36,8 +37,9 @@ fn main() { let native_options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([400.0, 300.0]) - .with_min_inner_size([400.0, 300.0]), + .with_inner_size([300.0, 300.0]) + .with_min_inner_size([300.0, 300.0]) + .with_max_inner_size([300.0, 300.0]), ..Default::default() }; @@ -67,23 +69,24 @@ impl App { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { // light mode cc.egui_ctx.set_visuals( - Visuals::light() - .tap_mut(|vis| vis.widgets.noninteractive.fg_stroke.color = Color32::BLACK), + Visuals::light(), // .tap_mut(|vis| vis.widgets.noninteractive.fg_stroke.color = Color32::BLACK), ); - cc.egui_ctx.set_zoom_factor(1.1); - // set up fonts. currently this uses SC for CJK, but this can be autodetected instead. let mut fonts = FontDefinitions::default(); fonts.font_data.insert( - "sarasa_sc".into(), - FontData::from_static(include_bytes!("assets/subset.ttf")), + "normal".into(), + FontData::from_static(include_bytes!("assets/normal.ttf")), + ); + fonts.font_data.insert( + "chinese".into(), + FontData::from_static(include_bytes!("assets/chinese.ttf")), ); fonts .families .get_mut(&FontFamily::Proportional) .unwrap() - .insert(0, "sarasa_sc".into()); + .insert(0, "chinese".into()); cc.egui_ctx.set_fonts(fonts); Self { diff --git a/binaries/geph5-client/src/client.rs b/binaries/geph5-client/src/client.rs index a7ee72f..cb47eb9 100644 --- a/binaries/geph5-client/src/client.rs +++ b/binaries/geph5-client/src/client.rs @@ -1,9 +1,13 @@ use anyctx::AnyCtx; use clone_macro::clone; -use futures_util::TryFutureExt; +use futures_util::{ + future::{FusedFuture, Shared}, + task::noop_waker, + FutureExt, TryFutureExt, +}; use geph5_broker_protocol::{Credential, ExitList}; use smol::future::FutureExt as _; -use std::{net::SocketAddr, path::PathBuf, time::Duration}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc, task::Context, time::Duration}; use serde::{Deserialize, Serialize}; use smolscale::immortal::{Immortal, RespawnStrategy}; @@ -30,20 +34,36 @@ pub struct Config { } pub struct Client { - task: smol::Task>, + task: Shared>>>, } impl Client { /// Starts the client logic in the loop, returnign the handle. pub fn start(cfg: Config) -> Self { let ctx = AnyCtx::new(cfg); - let task = smolscale::spawn(client_main(ctx)); - Client { task } + let task = smolscale::spawn(client_main(ctx).map_err(Arc::new)); + Client { + task: task.shared(), + } } /// Wait until there's an error. pub async fn wait_until_dead(self) -> anyhow::Result<()> { - self.task.await + self.task.await.map_err(|e| anyhow::anyhow!(e)) + } + + /// Check for an error. + pub fn check_dead(&self) -> anyhow::Result<()> { + match self + .task + .clone() + .poll(&mut Context::from_waker(&noop_waker())) + { + std::task::Poll::Ready(val) => val.map_err(|e| anyhow::anyhow!(e))?, + std::task::Poll::Pending => {} + } + + Ok(()) } }