diff options
| author | 1992414357@qq.com <1992414357@qq.com> | 2025-06-09 01:44:36 +0800 |
|---|---|---|
| committer | 1992414357@qq.com <1992414357@qq.com> | 2025-06-09 01:44:36 +0800 |
| commit | 49192dbb98e0ab1f2a66b4786fac86d63f69be64 (patch) | |
| tree | 7cdf27c7cab5fb6b1aabc2ac7c44b7b492558945 /console | |
| parent | c9dbba0d288becb7f05cebe526be25c76e5a850a (diff) | |
重构所有部分
Diffstat (limited to 'console')
| -rw-r--r-- | console/Cargo.toml | 7 | ||||
| -rw-r--r-- | console/src/bin/nogpadc.rs | 387 | ||||
| -rw-r--r-- | console/src/bin/nogpads.rs | 299 | ||||
| -rw-r--r-- | console/src/bin/padc.rs | 684 | ||||
| -rw-r--r-- | console/src/lib.rs | 1 | ||||
| -rw-r--r-- | console/src/utils.rs | 107 |
6 files changed, 797 insertions, 688 deletions
diff --git a/console/Cargo.toml b/console/Cargo.toml index 4751571..b241b6b 100644 --- a/console/Cargo.toml +++ b/console/Cargo.toml @@ -5,10 +5,13 @@ edition = "2024" version.workspace = true [dependencies] +nogamepads = { path = "../../NoGamepads" } nogamepads-core = { path = "../core" } clap = { version = "4.5.38", features = ["derive"] } rand = { version = "0.9.1", features = [] } -rpassword = "7.4.0" serde = { version = "1.0.219", features = ["derive"] } serde_yaml = "0.9.34" -prettytable-rs = "0.10.0"
\ No newline at end of file +prettytable-rs = "0.10.0" +tokio = { version = "1.45.0", features = ["full"] } +crossterm = "0.29.0" +log = "0.4.27"
\ No newline at end of file diff --git a/console/src/bin/nogpadc.rs b/console/src/bin/nogpadc.rs deleted file mode 100644 index 9a5418e..0000000 --- a/console/src/bin/nogpadc.rs +++ /dev/null @@ -1,387 +0,0 @@ -use crate::AccountCommands::{Add, Customize, List, Remove}; -use crate::Commands::{Account, Connect}; -use clap::{Args, Parser, Subcommand}; -use prettytable::{row, Table}; -use rand::Rng; -use std::env::current_dir; -use std::fs::{create_dir_all, remove_file, File}; -use std::io::{BufReader, Write}; -use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; -use std::process::exit; -use std::str::FromStr; -use nogamepads_lib_rs::DEFAULT_PORT; -use nogamepads_lib_rs::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; -use nogamepads_lib_rs::pad_service::client::nogamepads_client::PadClient; - -/// NoGamePads Console - Client Cli -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct NoGamepadClientCli { - #[command(subcommand)] - command: Commands, -} - -/// 主要命令 -#[derive(Subcommand, Debug)] -enum Commands { - - // 账户设置 - #[command(subcommand, about = "Operation of player account")] - Account(AccountCommands), - - // 连接到服务器 - #[command(about = "Connect a player to server")] - Connect(ConnectArgs) -} - -/// 账户设置 命令 -#[derive(Subcommand, Debug)] -enum AccountCommands { - - // 列出所有账号 - #[command(about = "List all local players")] - List(ListAccountArgs), - - // 添加账号 - #[command(about = "Add a local player")] - Add(AddAccountArgs), - - // 移除账号 - #[command(about = "Remove a local player")] - Remove(RemoveAccountArgs), - - // 自定义账号显示信息 - #[command(about = "Customize how the player appears")] - Customize(CustomizeAccountArgs) -} - -/// 列出所有账号 参数 -#[derive(Args, Debug)] -struct ListAccountArgs { } - -/// 添加账号 参数 -#[derive(Args, Debug)] -struct AddAccountArgs { - - // 注册的角色 ID - #[arg(value_name = "NAME")] - id: String -} - -/// 移除账号 参数 -#[derive(Args, Debug)] -struct RemoveAccountArgs { - - // 删除的角色 ID - #[arg(value_name = "NAME")] - id: String -} - -/// 自定义账号显示信息 参数 -#[derive(Args, Debug)] -struct CustomizeAccountArgs { - - // 定制的角色 ID - #[arg(value_name = "WHO")] - id: String, - - // 昵称 - #[arg(short, long, help = "Nickname")] - name: Option<String>, - - // 颜色 - #[arg(short = 'H', long = "hsv", num_args = 3, value_names = ["H", "S", "V"], help = "Player color, h(0 - 360), s(0 - 1), v(0 - 1)")] - hsv: Option<Vec<f64>> -} - -/// 连接到服务器 -#[derive(Args, Debug)] -struct ConnectArgs { - - // 连接的玩家 - #[arg(value_name = "WHO")] - id: String, - - // 目标服务器 - #[arg(value_name = "WHERE", default_value = "127.0.0.1")] - target: String, - - // 目标端口 - #[arg(short, long, default_value = "5989")] // <---- DEFAULT_PORT - port: Option<u16>, - - // 启用调试 - #[arg(long)] - debug: bool, -} - -/// 配置文件后缀名称 -const EXTENSION_NAME : &str = "yaml"; - -fn main() { - - // 初始化 - let root = get_config_folder_path(); - if ! root.exists() { - create_dir_all(root.as_path()).unwrap(); - } - - // 命令行 - let cli = NoGamepadClientCli::parse(); - match cli.command { - - // 账户设置部分 - Account(commands) => { - match commands { - - // 添加账号 - Add(args) => { - if is_account_exist(&args.id) { - println!("Account already exists!"); - } else { - // 账号 ID 和 配置路径 - let account_id = process_inputted_text(args.id); - let account_config_path = get_account_config_path(&account_id); - - // 为新号准备的配置文件 - let mut new_info = PlayerInfo::new(); - - // 密码输入和密码验证 - let password = rpassword::prompt_password("Type password: ").unwrap(); - let password_confirm = rpassword::prompt_password("Confirm password: ").unwrap(); - if ! password_confirm.eq(&password) { - println!("Password does not match!"); - exit(1); - } - - // 随机生成 色调 值 - let mut rng = rand::rng(); - let random_hue: i32 = rng.random_range(0..=360); - - // 建立账号信息并预填入信息 - new_info.setup_account_info(account_id.as_str(), password.as_str()); - new_info.set_nickname(account_id.as_str()); - new_info.set_customize_color_hsv(random_hue, 0.8, 0.8); - - // 新配置信息的文本 - let new_info_yaml = serde_yaml::to_string(&new_info); - - // 将信息写入文件系统 - let mut buffer = File::create(account_config_path).unwrap(); - buffer.write_all(new_info_yaml.unwrap().as_bytes()).unwrap(); - - println!("Account created."); - } - }, - - // 移除账号 - Remove(args) => { - if ! is_account_exist(&args.id) { - println!("Account not found!"); - } else { - // 账号 ID 和 配置路径 - let account_id = process_inputted_text(args.id); - let account_config_path = get_account_config_path(&account_id); - - // 删除文件 - remove_file(account_config_path).unwrap(); - - println!("Account removed."); - } - }, - - // 列出所有账号 - List(_args) => { - // 账号信息文件夹 - let folder_path = get_config_folder_path(); - - // 输出表格 - let mut info_table = Table::new(); - - // 表头 - info_table.add_row(row!["ACCOUNT_ID", "NICKNAME", "COLOR", "HASH"]); - - // 遍历目录下文件,将信息逐一填入表格 - for item in folder_path.read_dir().unwrap() { - if let Ok(path) = item { - // 文件名 - let file_name = path.file_name().into_string().unwrap(); - let ext = format!(".{}", EXTENSION_NAME); - - // 判断是否为指定后缀 - if file_name.contains(ext.as_str()) { - - // 去除后缀内容,截取为 ID - let id = file_name.replace(ext.as_str(), ""); - - // 读取并加载其中的玩家信息 - let file = File::open(get_account_config_path(&id)).unwrap(); - let reader = BufReader::new(file); - let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); - - // 填入表格 - info_table.add_row(row![ - &id, // ACCOUNT_ID - info.customize.nickname, // NICKNAME - hsv_to_hex( // COLOR - info.customize.color_hue, - info.customize.color_saturation, - info.customize.color_value), - info.account.player_hash // HASH - ]); - } - } - } - println!("{}", info_table.to_string()) - }, - - // 自定义账号显示信息 - Customize(args) => { - - // 加载配置文件 - let file = File::open(get_account_config_path(&args.id)).unwrap(); - let reader = BufReader::new(file); - let mut info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); - - // HSV 参数 - if args.hsv.is_some() { - let hsv = args.hsv.unwrap(); - let hue = hsv[0].round().clamp(0.0, 360.0); - let sat = hsv[1].clamp(0.0, 1.0); - let val = hsv[2].clamp(0.0, 1.0); - - info.customize.color_hue = hue as i32; - info.customize.color_saturation = sat; - info.customize.color_value = val; - - println!("Set {}'s HSV color to: {}, {}, {}.", &args.id, hue, sat, val); - } - - // 昵称 参数 - if args.name.is_some() { - let name = args.name.unwrap(); - info.customize.nickname = name.clone(); - - println!("Set {}'s display name to: {}.", &args.id, name); - } - - // 写入配置文件 - let yaml_content = serde_yaml::to_string(&info); - let mut buffer = File::create(get_account_config_path(&args.id)).unwrap(); - buffer.write_all(yaml_content.unwrap().as_bytes()).unwrap(); - }, - } - }, - - // 连接到服务器 - Connect(args) => { - if ! is_account_exist(&args.id) { - println!("Player not found!"); - exit(1); - } - - // 加载配置文件 - let file = File::open(get_account_config_path(&args.id)).unwrap(); - let reader = BufReader::new(file); - let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap(); - - // 从参数获得 Ip 地址 (或默认) - let addr : IpAddr; - match IpAddr::from_str(&args.target) { - Ok(result) => { addr = result; } - Err(_err) => { - addr = IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)); - } - } - - // 从参数获得端口地址 (或默认) - let port : u16 = if args.port.is_some() { args.port.unwrap() } else { DEFAULT_PORT } - .clamp(0, 65535); - - // 绑定目标地址 - let mut client = PadClient::bind_addr_with_port(addr, port); - - // 启动调试模式 ? - if args.debug { - println!("- DEBUG MODE -"); - client.enable_console(); - } - - // 写入玩家信息 - client.bind_player(info); - - // 连接 - client.connect(); - println!("Connected {} to {}:{}", args.id, addr.to_string(), port.to_string()); - } - } -} - -fn process_inputted_text(input: String) -> String { - // 截取前后文本,并转换为小写 - let s = input.trim().to_lowercase(); - let mut result = String::new(); - - // 处理其中的特殊符号,部分用于分割的符号需要转换为下划线 - for c in s.chars() { - match c { - '\n' | '_' => continue, - '-' | '.' | ',' | ' ' => result.push('_'), - _ => result.push(c), - } - } - - // 仅保留 ASCII 字符 - result.chars() - .filter(|&c| c.is_ascii_alphanumeric() || c == '_') - .collect() -} - -fn hsv_to_hex(h: i32, s: f64, v: f64) -> String { - let h = (h as f64).clamp(0.0, 360.0); - let s = s.clamp(0.0, 1.0); - let v = v.clamp(0.0, 1.0); - - let c = v * s; - let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); - let m = v - c; - - let (r, g, b) = match (h / 60.0) as usize { - 0 => (c, x, 0.0), - 1 => (x, c, 0.0), - 2 => (0.0, c, x), - 3 => (0.0, x, c), - 4 => (x, 0.0, c), - 5 => (c, 0.0, x), - _ => (0.0, 0.0, 0.0), - }; - - let r = ((r + m) * 255.0).round() as u8; - let g = ((g + m) * 255.0).round() as u8; - let b = ((b + m) * 255.0).round() as u8; - format!("#{:02X}{:02X}{:02X}", r, g, b) -} - -fn get_config_folder_path() -> PathBuf { - current_dir().unwrap().join(".nogpadc") -} - -fn get_account_config_path(id: &str) -> PathBuf { - get_config_folder_path().join(format!("{}.{}", process_inputted_text(id.to_string()), EXTENSION_NAME)) -} - -fn is_account_exist(id: &str) -> bool { - let id = process_inputted_text(id.to_string()); - let path = get_config_folder_path(); - let dir = path.as_path().read_dir().unwrap(); - let mut found = false; - for item in dir { - if let Ok(path) = item { - if path.file_name().eq(format!("{}.{}", id, EXTENSION_NAME).as_str()) { - found = true; - } - } - } - found -}
\ No newline at end of file diff --git a/console/src/bin/nogpads.rs b/console/src/bin/nogpads.rs deleted file mode 100644 index 14df488..0000000 --- a/console/src/bin/nogpads.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::collections::HashMap; -use clap::{arg, Args, Parser, Subcommand}; -use serde::{Deserialize, Serialize}; -use std::env::current_dir; -use std::fs::{create_dir, File}; -use std::io::{BufReader, Write}; -use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; -use nogamepads_lib_rs::DEFAULT_PORT; -use nogamepads_lib_rs::pad_data::game_profile::game_profile::GameProfile; -use nogamepads_lib_rs::pad_data::layout::layout_data::LayoutKeyRegisters; -use nogamepads_lib_rs::pad_service::server::nogamepads_server::PadServer; - -/// NoGamePads Console - Server Cli -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct NoGamepadServerCli { - #[command(subcommand)] - command: Commands, -} - -/// 主要命令 -#[derive(Subcommand, Debug)] -enum Commands { - - #[command(subcommand, about = "Manage buttons")] - Button(ManageCommands), - - #[command(subcommand, about = "Manage directions")] - Direction(ManageCommands), - - #[command(subcommand, about = "Manage axes")] - Axis(ManageCommands), - - // 服务端配置 - #[command(about = "Configure the server")] - Config(ConfigArgs), - - // 运行服务端 - #[command(about = "Run the server")] - Run(RunArgs) -} - -/// 服务端配置 参数 -#[derive(Args, Debug)] -struct ConfigArgs { - - // 绑定的端口号 - #[arg(short, long, help = "Server port (0 = Default)")] // <---- DEFAULT_PORT - port: Option<u16>, - - // 游戏名称 - #[arg(short ='n', long = "name")] - game_name: Option<String>, - - // 游戏描述 - #[arg(short = 'd', long = "description")] - game_description: Option<String>, - - // 游戏组织 - #[arg(short = 'o', long = "organization")] - organization: Option<String>, - - // 游戏版本 - #[arg(short = 'v', long = "version")] - version: Option<String>, - - // 工作室 & 游戏 主页 - #[arg(short = 'w', long = "website")] - website: Option<String>, - - // 交流邮箱 - #[arg(short = 'e', long = "email")] - email: Option<String> -} - -/// 运行服务端 参数 -#[derive(Args, Debug)] -struct RunArgs { - - // 调试模式 - #[arg(long)] - debug: bool, -} - -/// 管理键 -#[derive(Subcommand, Debug)] -enum ManageCommands { - - #[command(about = "Add or rename a key")] - Add(AddKeyArgs), - - #[command(about = "Remove a key")] - Remove(RemoveKeyArgs), - - #[command(about = "List all")] - List -} - -/// 添加键 -#[derive(Args, Debug)] -struct AddKeyArgs{ - - // 添加的键 - #[arg(value_name = "KEY")] - key: u8, - - // 事件编号 - #[arg(value_name = "NAME")] - name: String -} - -/// 移除键 -#[derive(Args, Debug)] -struct RemoveKeyArgs{ - - // 删除的键 - #[arg(value_name = "KEY")] - key: u8, -} - -/// 本地存储的配置信息 -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct ServerConfig { - port: u16, - registered_keys: LayoutKeyRegisters, - profile: GameProfile, -} - -impl Default for ServerConfig { - fn default() -> Self { - ServerConfig { - port: DEFAULT_PORT, - registered_keys: Default::default(), - profile: GameProfile::default(), - } - } -} - -/// # 快速生成 更新服务端信息 的宏 -macro_rules! update_config { - ($config:expr, $args:expr, $($field:ident),+) => { - $( - if let Some(ref value) = $args.$field { - $config.profile.$field = value.clone(); - println!("Changed profile \"{}\" to \"{}\"", - stringify!($field), - value - ); - } - )+ - }; -} - -fn main () { - - // 命令行 - let cli = NoGamepadServerCli::parse(); - - // 读取服务端配置 - let mut config = read_config(); - - match cli.command { - - // 服务端配置 - Commands::Config(args) => { - - // 端口信息配置: - // 端口数值被限定在 0 - 65535,但是若端口参数为 0,则会被设置为默认端口 - if args.port.is_some() { - let port = args.port.unwrap_or(DEFAULT_PORT).clamp(0, 65535); - config.port = if port == 0 { DEFAULT_PORT } else { port }; - } - - // 其他信息配置 - update_config!( - config, args, - game_name, - game_description, - organization, - version, - website, - email - ); - }, - - // 运行服务端 - Commands::Run(args) => { - // 读取服务端配置 - let config = read_config(); - - // 根据调试选项启动服务端 - if args.debug { - println!("- DEBUG MODE -"); - println!("Server started!"); - PadServer::default() - .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port) - .put_profile(config.profile) - .register_keys(config.registered_keys) - .enable_console() - .build() - .start_server(); - } else { - println!("Server started!"); - PadServer::default() - .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port) - .put_profile(config.profile) - .register_keys(config.registered_keys) - .build() - .start_server(); - } - } - - Commands::Button(manage) => { - process_manage_command("btn", manage, &mut config.registered_keys.button_keys); - } - - Commands::Direction(manage) => { - process_manage_command("dir", manage, &mut config.registered_keys.direction_keys); - } - - Commands::Axis(manage) => { - process_manage_command("ax", manage, &mut config.registered_keys.axis_keys); - } - } - - // 写入配置 - write_config(config); -} - -fn process_manage_command(prefix: &str, manage: ManageCommands, map: &mut HashMap<u8, String>) { - match manage { - ManageCommands::Add(args) => { - map.entry(args.key) - .or_insert_with(|| args.name.clone()); - println!("Added(Renamed) key {}_{} : \"{}\".", prefix, args.key, args.name); - } - ManageCommands::Remove(args) => { - let removed = map.remove(&args.key); - if removed.is_some() { - println!("Removed key {}_{}.", prefix, removed.is_some()) - } else { - println!("Removed key failed: Cannot found key {}", args.key); - } - } - ManageCommands::List => { - for button_key in map { - println!("{}_{} : \"{}\"", prefix, button_key.0, button_key.1) - } - } - } -} - -#[allow(dead_code)] -fn get_config_folder_path () -> PathBuf { - current_dir().unwrap().join(".nogpads") -} - -#[allow(dead_code)] -fn get_config_file_path () -> PathBuf { - get_config_folder_path().join("config.yaml") -} - -#[allow(dead_code)] -fn get_layout_file_path () -> PathBuf { - get_config_folder_path().join("layout.yaml") -} - -#[allow(dead_code)] -fn get_assets_package_path () -> PathBuf { - get_config_folder_path().join("assets.zip") -} - -fn read_config () -> ServerConfig { - let config_folder_path = get_config_folder_path(); - let config_file_path = get_config_file_path(); - - if ! config_folder_path.exists() { - create_dir(&config_folder_path).unwrap(); - } - - if ! config_file_path.exists() { - let config = ServerConfig::default(); - let config_text = serde_yaml::to_string(&config).unwrap(); - File::create(&config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap(); - config - } else { - let config_file = File::open(&config_file_path).unwrap(); - let config_reader = BufReader::new(config_file); - serde_yaml::from_reader(config_reader).unwrap() - } -} - -fn write_config (config: ServerConfig) { - let config_file_path = get_config_file_path(); - - let config_text = serde_yaml::to_string(&config).unwrap(); - File::create(config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap(); -}
\ No newline at end of file diff --git a/console/src/bin/padc.rs b/console/src/bin/padc.rs new file mode 100644 index 0000000..742b6c2 --- /dev/null +++ b/console/src/bin/padc.rs @@ -0,0 +1,684 @@ +use clap::{Args, CommandFactory, Parser, Subcommand}; +use nogamepads::string_utils::process_id_text; +use nogamepads_console::utils::{confirm, read_password, read_password_and_confirm}; +use nogamepads_core::data::game::structs::GameData; +use nogamepads_core::data::player::structs::Player; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::env::current_dir; +use std::fs::File; +use std::io::{BufReader, Write}; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::process::exit; +use std::str::FromStr; +use std::sync::Arc; +use log::LevelFilter; +use nogamepads::logger_utils::logger_build; +use nogamepads_core::data::controller::cli::cli_command::{process_controller_cli, ControllerCli}; +use nogamepads_core::data::controller::structs::ControllerData; +use nogamepads_core::data::game::cli::cli_command::{process_game_cli, GameCli}; +use nogamepads_core::run_services; +use nogamepads_core::service::cli_addition::runtime_consoles::RuntimeConsole; +use nogamepads_core::service::service_runner::{NoGamepadsService, ServiceRunner}; +use nogamepads_core::service::tcp_network::DEFAULT_PORT; +use nogamepads_core::service::tcp_network::pad_client::structs::PadClientNetwork; +use nogamepads_core::service::tcp_network::pad_server::structs::PadServerNetwork; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct ConsoleCli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + + #[command(subcommand, about = "Manage accounts")] + Account(AccountCommands), + + #[command(about = "List accounts")] + Accounts, + + #[command(subcommand, about = "Manage game infos")] + Game(GameCommands), + + #[command(about = "List all games")] + Games, + + #[command(about = "Connect to a service")] + Connect(ConnectArgs), + + #[command(about = "Start a service")] + Listen(ListenArgs) +} + +#[derive(Subcommand, Debug)] +enum AccountCommands { + + #[command(about = "Add a account")] + Add(AccountArgs), + + #[command(about = "Remove a account")] + Remove(AccountArgs), + + #[command(about = "Edit the profile of account")] + Edit(EditAccountArgs) +} + +#[derive(Args, Debug)] +struct AccountArgs{ + + #[arg(value_name = "Account")] + account: String, + + #[arg(short, long, value_name = "Password")] + password: Option<String> +} + +#[derive(Args, Debug)] +struct EditAccountArgs{ + + #[arg(value_name = "Account")] + account: String, + + #[arg(short, long, help = "Nickname")] + nickname: Option<String>, + + #[arg(short = 'c', long = "color", num_args = 3, value_names = ["H", "S", "V"], help = "Player color, h(0 - 360), s(0 - 1), v(0 - 1)")] + color: Option<Vec<f64>> +} + +#[derive(Subcommand, Debug)] +enum GameCommands { + + #[command(about = "Add a game")] + Add(GameArgs), + + #[command(about = "Remove a game")] + Remove(GameArgs), + + #[command(about = "Edit the profile of game")] + Edit(EditGameArgs), + + #[command(subcommand, about = "Register keys to game")] + Register(RegisterKeysCommands) +} + +#[derive(Args, Debug)] +struct GameArgs { + + #[arg(value_name = "Game Name")] + name: String, + + #[arg(short, long)] + confirm: bool +} + +#[derive(Args, Debug)] +struct EditGameArgs { + + #[arg(value_name = "Game Name")] + name: String, + + #[arg(value_name = "Key")] + key: String, + + #[arg(value_name = "Value")] + value: String, +} + +#[derive(Subcommand, Debug)] +enum RegisterKeysCommands { + + #[command(subcommand, about = "Register a button")] + Button(KeyManageCommands), + + #[command(subcommand, about = "Register a axis")] + Axis(KeyManageCommands), + + #[command(subcommand, about = "Register a direction")] + Direction(KeyManageCommands) +} + +#[derive(Subcommand, Debug)] +enum KeyManageCommands { + + #[command(about = "Add or rename a key")] + Add(AddKeyArgs), + + #[command(about = "Remove a key")] + Remove(RemoveKeyArgs), + + #[command(about = "List all")] + List(ListKeyArgs) +} + +#[derive(Args, Debug)] +struct AddKeyArgs{ + + #[arg(value_name = "Game Name")] + name: String, + + #[arg(value_name = "Key")] + key: u8, + + #[arg(value_name = "Name")] + key_name: String +} + +#[derive(Args, Debug)] +struct RemoveKeyArgs{ + + #[arg(value_name = "Game Name")] + name: String, + + #[arg(value_name = "Key")] + key: u8, +} + +#[derive(Args, Debug)] +struct ListKeyArgs{ + + #[arg(value_name = "Game Name")] + name: String +} + +#[derive(Args, Debug)] +struct ConnectArgs { + + #[arg(short, long, value_name = "Account")] + account: Option<String>, + + #[arg(short, long, value_name = "Address")] + tcp_addr: Option<String>, + + #[arg(short, long, value_name = "Methods")] + method: Option<String>, + + #[arg(long)] + cmd: bool, + + #[arg(long)] + gui: bool, + + #[arg(long)] + debug: bool, +} + +#[derive(Args, Debug)] +struct ListenArgs { + + #[arg(value_name = "Game")] + game: String, + + #[arg(short = 'a', long, value_name = "Address")] + tcp_addr: Option<String>, + + #[arg(long)] + cmd: bool, + + #[arg(long)] + gui: bool, + + #[arg(long)] + tcp: bool, + + #[arg(long)] + bluetooth: bool, + + #[arg(long)] + usb: bool, + + #[arg(long)] + debug: bool, +} + +#[derive(Default, Serialize, Deserialize, PartialEq, Debug)] +struct LocalData { + game_data: LocalGameData, + controller_data: LocalControllerData, +} + +#[derive(Default, Serialize, Deserialize, PartialEq, Debug)] +struct LocalGameData { + games: HashMap<String, GameData> +} + +#[derive(Default, Serialize, Deserialize, PartialEq, Debug)] +struct LocalControllerData { + players: HashMap<String, Player> +} + +fn main () { + let cli = ConsoleCli::parse(); + let mut data = read(); + + match cli.command { + Commands::Account(cmds) => { + match cmds { + AccountCommands::Add(args) => { + add_player(&mut data, args.account, args.password); + } + + AccountCommands::Remove(args) => { + remove_player(&mut data, args.account, args.password); + } + + AccountCommands::Edit(args) => { + edit_player(&mut data, args); + } + } + } + + Commands::Accounts => { + for player in data.controller_data.players.values() { + let id = &player.account.id; + if player.customize.is_some() { + let nickname = &player.clone().customize.unwrap().nickname; + println!("{}({})", id, nickname); + } else { + println!("{}", id); + } + } + } + + Commands::Game(cmds) => { + match cmds { + GameCommands::Add(args) => { + let name = process_id_text(args.name); + data.game_data.games.entry(name.clone()) + .or_insert_with(GameData::default); + println!("Game configuration added or reset: \"{}\"", name.clone()); + } + + GameCommands::Remove(args) => { + if ! args.confirm { + if ! confirm("Confirm ") { + exit(1); + } + } + + let name = process_id_text(args.name); + let game = data.game_data.games.remove(&name); + if game.is_none() { + eprintln!("Removal of the game \"{}\" failed: game not found.", name.clone()); + } else { + println!("The game \"{}\" has been removed.", name.clone()); + } + } + + GameCommands::Edit(args) => { + let name = process_id_text(args.name); + let game = data.game_data.games.get_mut(&name); + if game.is_none() { + eprintln!("Edit the game \"{}\" failed: game not found.", name.clone()); + } else { + let mut game = game.unwrap(); + game.info(args.key.clone(), args.value.clone()); + println!("Set the game info \"{}\" to \"{}\".", args.key, args.value); + } + } + + GameCommands::Register(cmds) => { + match cmds { + RegisterKeysCommands::Button(cmds) => { + manage_keys(&mut data, cmds, + |game| &mut game.control.button_keys); + } + + RegisterKeysCommands::Axis(cmds) => { + manage_keys(&mut data, cmds, + |game| &mut game.control.axis_keys); + } + + RegisterKeysCommands::Direction(cmds) => { + manage_keys(&mut data, cmds, + |game| &mut game.control.direction_keys); + } + } + } + } + } + + Commands::Games => { + for (key, _) in data.game_data.games.iter() { + println!("{}", key); + } + } + + Commands::Connect(args) => { + connect(&mut data, args); + } + + Commands::Listen(args) => { + listen(&mut data, args); + } + } + + write(data); +} + +fn connect(data: &mut LocalData, args: ConnectArgs) { + + let mut result: Option<&Player> = None; + if args.account.is_some() { + // Account specified + let id = &args.account.unwrap(); + let player = data.controller_data.players.get(id); + + // Account not found + if player.is_none() { + eprintln!("Account not found: \"{}\"", id); + exit(1); + } else { + // Account exists + result = Some(player.unwrap()); + } + } else { + // Account not specified + for found in data.controller_data.players.values() { + // Found a replaceable account + result = Some(found); + println!("Account not specified! Using account \"{}\" instead!", found.account.id); + break; + } + // No replaceable account found + if result.is_none() { + eprintln!("Cannot find any replaceable account! Please ensure at least one account exists locally!"); + exit(1); + } + } + + let player = result.unwrap().clone(); + + let mut controller = ControllerData::default(); + controller.bind_player(player); + + let runtime = controller.runtime(); + + let method = args.method.unwrap_or("tcp".to_string()); + let mut entry: Option<NoGamepadsService> = None; + match method.as_str() { + "tcp" => { + let mut client = PadClientNetwork::build(Arc::clone(&runtime)); + let addr = args.tcp_addr.unwrap_or(format!("127.0.0.1:{}", DEFAULT_PORT)); + client.bind_addr(SocketAddr::from_str(&addr).unwrap_or( + SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)), + )); + + entry = Some(client.build_entry()); + }, + + "bluetooth" => { + // TODO :: BLUETOOTH METHOD + }, + + "usb" => { + // TODO :: USB METHOD + }, + + _ => { + eprintln!("Unknown connection method: {}", method); + exit(1); + } + } + + if let Some(entry) = entry { + let mut services = Vec::new(); + services.push(entry); + + if args.cmd { + services.push(RuntimeConsole::build( + ControllerCli::command(), + "ControllerCli".to_string(), + Arc::clone(&runtime), + |runtime, cmd| { + process_controller_cli(runtime, cmd); + } + ).build_entry()); + } + + if args.gui { + + } + + if args.debug { + logger_build(LevelFilter::Trace); + } else { + logger_build(LevelFilter::Info); + } + + ServiceRunner::run(services); + } +} + +fn listen(data: &mut LocalData, args: ListenArgs) { + let id = process_id_text(args.game); + let game = data.game_data.games.get(&id); + if game.is_none() { + eprintln!("Game not found: \"{}\"", id); + exit(1); + } + + let game_data = game.unwrap().clone(); + + let runtime = game_data.runtime(); + + let mut services = Vec::new(); + + if args.tcp { + let mut server = PadServerNetwork::build(Arc::clone(&runtime)); + if args.tcp_addr.is_some() { + let addr = SocketAddr::from_str(&args.tcp_addr.unwrap()) + .unwrap_or(SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT))); + server.bind_ip(addr.ip()); + server.bind_port(addr.port()); + } + services.push(server.build_entry()); + println!("Setup TCP Service!") + } + + if args.bluetooth { + + } + + if args.usb { + + } + + if args.cmd { + services.push(RuntimeConsole::build( + GameCli::command(), + "GameCli".to_string(), + Arc::clone(&runtime), + |runtime, cmd| { + process_game_cli(runtime, cmd); + } + ).build_entry()); + println!("Setup Command Line!") + } + + if args.gui { + + } + + if args.debug { + logger_build(LevelFilter::Trace); + } else { + logger_build(LevelFilter::Info); + } + + ServiceRunner::run(services); +} + +fn add_player(data: &mut LocalData, account_args: String, password_args: Option<String>) { + if data.controller_data.players.contains_key(process_id_text(account_args.clone()).as_str()) { + eprintln!("This account already exists. Please do not create it again."); + exit(1); + } else { + // Read password + let mut password = "".to_string(); + if password_args.is_none() { + let input = read_password_and_confirm("Enter password: ", "Confirm: "); + if input.is_some() { + password = input.unwrap(); + } + } else if password_args.is_some() { + password = password_args.unwrap(); + } + + // Create player + let player = Player::register(account_args, password); + let player_key = player.clone().account.id; + + data.controller_data.players.insert(player_key, player.clone()); + } +} + +fn remove_player(data: &mut LocalData, account_args: String, password_args: Option<String>) { + // Read password + let mut password = "".to_string(); + if password_args.is_none() { + password = read_password("Enter password: ").unwrap_or("".to_string()); + } else if password_args.is_some() { + password = password_args.unwrap(); + } + + // Remove + let player_id = process_id_text(account_args.clone()); + + let player = data.controller_data.players.get(&player_id); + if player.is_none() { + eprintln!("Failed to remove account \"{}\": Account not found.", account_args.clone()); + exit(1); + } + let player = player.unwrap(); + + if player.check(password.clone()) { + data.controller_data.players.remove(player_id.as_str()); + println!("The account \"{}\" has been removed!", player_id); + } else { + eprintln!("Failed to remove account \"{}\": Password is incorrect!", player_id) + } +} + +fn edit_player(data: &mut LocalData, args: EditAccountArgs) { + let account_id = process_id_text(args.account); + if ! data.controller_data.players.contains_key(&account_id) { + eprintln!("Edit failed: Account \"{}\" not found!", account_id); + exit(1); + } + + let player = data.controller_data.players.get_mut(&account_id).unwrap(); + + if args.nickname.is_some() { + let nickname = args.nickname.unwrap(); + player.nickname(&nickname); + println!("Change the nickname of account \"{}\" to \"{}\"", account_id, &nickname); + } + + if args.color.is_some() { + let color = args.color.unwrap(); + let hue = color[0].round().clamp(0.0, 360.0); + let sat = color[1].clamp(0.0, 1.0); + let val = color[2].clamp(0.0, 1.0); + player.hsv(hue as i32, sat, val); + println!("Change the color of account \"{}\" to \"{}\"", account_id, hsv_to_hex(hue as i32, sat, val)); + } +} + +macro_rules! check_game { + ($data:expr, $args:expr, |$game:ident| $code:block) => { + let name = process_id_text($args.name); + let mut game = $data.game_data.games.get_mut(&name); + if game.is_none() { + eprintln!("Edit the game \"{}\" failed: game not found.", name.clone()); + exit(1); + } else { + let mut $game = game.unwrap(); + $code + } + }; +} + +fn manage_keys(data: &mut LocalData, cmds: KeyManageCommands, get_map: fn(game: &mut GameData) -> &mut HashMap<u8, String>) { + match cmds { + KeyManageCommands::Add(args) => { + check_game!(data, args, |game| { + get_map(game).entry(args.key) + .or_insert_with(|| args.key_name.clone()); + println!("Registered key \"{}\"", args.key_name); + }); + } + + KeyManageCommands::Remove(args) => { + check_game!(data, args, |game| { + let result = get_map(game).remove(&args.key); + if result.is_some() { + println!("Removed key \"{}\"", result.unwrap()); + } + }); + } + + KeyManageCommands::List(args) => { + check_game!(data, args, |game| { + for (key, key_name) in get_map(game).iter() { + println!("{} - \"{}\"", key, key_name); + } + }); + } + } +} + +fn local_config() -> PathBuf { + current_dir().unwrap().join("nogamepads.yaml") +} + +fn read() -> LocalData { + let file_path = local_config(); + + if ! file_path.exists() { + let data = LocalData::default(); + let content = serde_yaml::to_string(&data).unwrap(); + File::create(&file_path).unwrap().write_all(content.as_bytes()).unwrap(); + data + } else { + let file = File::open(&file_path).unwrap(); + let reader = BufReader::new(file); + serde_yaml::from_reader(reader).unwrap() + } +} + +fn write(config: LocalData) { + let file_path = local_config(); + let content = serde_yaml::to_string(&config).unwrap(); + + File::create(file_path).unwrap().write_all(content.as_bytes()).unwrap(); +} + +fn hsv_to_hex(h: i32, s: f64, v: f64) -> String { + let h = (h as f64).clamp(0.0, 360.0); + let s = s.clamp(0.0, 1.0); + let v = v.clamp(0.0, 1.0); + + let c = v * s; + let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); + let m = v - c; + + let (r, g, b) = match (h / 60.0) as usize { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + 5 => (c, 0.0, x), + _ => (0.0, 0.0, 0.0), + }; + + let r = ((r + m) * 255.0).round() as u8; + let g = ((g + m) * 255.0).round() as u8; + let b = ((b + m) * 255.0).round() as u8; + format!("#{:02X}{:02X}{:02X}", r, g, b) +} diff --git a/console/src/lib.rs b/console/src/lib.rs index e69de29..fab870e 100644 --- a/console/src/lib.rs +++ b/console/src/lib.rs @@ -0,0 +1 @@ +pub mod utils;
\ No newline at end of file diff --git a/console/src/utils.rs b/console/src/utils.rs new file mode 100644 index 0000000..1e65f0e --- /dev/null +++ b/console/src/utils.rs @@ -0,0 +1,107 @@ +use std::io::{self, Read, Write}; +use crossterm::terminal; +use tokio::task; + +pub fn read_password(prompt: &str) -> Option<String> { + print!("{}", prompt); + io::stdout().flush().unwrap(); + + let raw_mode_enabled = terminal::enable_raw_mode().is_ok(); + if !raw_mode_enabled { + eprintln!("Warning: Password will be displayed in plain text"); + } + + let mut buffer = Vec::new(); + loop { + let mut byte = [0]; + if io::stdin().read(&mut byte).is_err() || byte[0] == b'\n' || byte[0] == b'\r' { + break; + } + + match byte[0] { + 8 | 127 => { + if buffer.pop().is_some() { + print!("\x08 \x08"); + io::stdout().flush().unwrap(); + } + } + 3 => { + println!(); + return None; + } + _ => { + buffer.push(byte[0]); + print!("*"); + io::stdout().flush().unwrap(); + } + } + } + + if raw_mode_enabled { + terminal::disable_raw_mode().unwrap(); + } + println!(); + + Some(String::from_utf8_lossy(&buffer).into_owned()) +} + +pub fn read_password_and_confirm( + prompt: &str, + confirm_prompt: &str +) -> Option<String> { + loop { + let pw1 = read_password(prompt).unwrap(); + let pw2 = read_password(confirm_prompt).unwrap(); + + if pw1 == pw2 { + return Some(pw1); + } + eprintln!("The passwords entered twice do not match, please try again."); + } +} + +pub fn confirm(prompt: &str) -> bool { + let prompt = format!("{} [Y/n]: ", prompt); + loop { + let input = read_password(&prompt).unwrap_or_default(); + match input.to_lowercase().as_str() { + "y" | "yes" | "" => return true, + "n" | "no" => return false, + _ => eprintln!("Invalid input, please enter Y or n."), + } + } +} + +pub async fn read_password_async(prompt: &str) -> Option<String> { + let prompt = prompt.to_owned(); + task::spawn_blocking(move || read_password(&prompt)) + .await + .unwrap_or_else(|_| None) +} + +pub async fn read_password_and_confirm_async( + prompt: &str, + confirm_prompt: &str +) -> Option<String> { + loop { + let pw1 = read_password_async(prompt).await.unwrap(); + let pw2 = read_password_async(confirm_prompt).await.unwrap(); + + if pw1 == pw2 { + return Some(pw1); + } + eprintln!("The passwords entered twice do not match, please try again."); + } +} + +pub async fn confirm_async(prompt: &str) -> bool { + let prompt = format!("{} [Y/n]: ", prompt); + loop { + let input = read_password_async(&prompt).await.unwrap_or_default(); + match input.to_lowercase().as_str() { + "y" | "yes" | "" => return true, + "n" | "no" => return false, + _ => eprintln!("Invalid input, please enter Y or n."), + } + } +}
\ No newline at end of file |
