From 30b3f8d7b10cff63d537510c189a5bc8ca58901c Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 15:33:12 +0100 Subject: [PATCH 01/25] Add BestItemMode enum and update run_detection to support item selection mode - Introduced a new `BestItemMode` enum in `config.rs` to allow users to specify how the best item is determined (Default, Platinum, Ducats). - Updated the `run_detection` function in `main.rs` to accept an `Arguments` struct, enabling the selection of the best item mode based on user input. - Modified item evaluation logic to accommodate the new best item mode options. - Updated the `Arguments` struct to include a new command-line argument for best item mode with a default value. --- src/bin/main.rs | 28 ++++++++++++++++++++-------- src/config.rs | 9 +++++++++ src/lib.rs | 1 + 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 src/config.rs diff --git a/src/bin/main.rs b/src/bin/main.rs index bd7ebdb..4c8ce92 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -20,9 +20,10 @@ use wfinfo::{ database::Database, ocr::{normalize_string, reward_image_to_reward_names, OCR}, utils::fetch_prices_and_items, + config::BestItemMode, }; -fn run_detection(capturer: &Window, db: &Database) { +fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { let frame = capturer.capture_image().unwrap(); info!("Captured"); let image = DynamicImage::ImageRgba8(frame); @@ -37,8 +38,12 @@ fn run_detection(capturer: &Window, db: &Database) { .iter() .map(|item| { item.map(|item| { - item.platinum - .max(item.ducats as f32 / 10.0 + item.platinum / 100.0) + match arguments.best_item_mode { + BestItemMode::Default => item.platinum + .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), + BestItemMode::Platinum => item.platinum, + BestItemMode::Ducats => item.ducats as f32 / 10.0, + } }) .unwrap_or(0.0) }) @@ -49,7 +54,7 @@ fn run_detection(capturer: &Window, db: &Database) { for (index, item) in items.iter().enumerate() { if let Some(item) = item { info!( - "{}\n\t{}\t{}\t{}", + "Name: {}\n\tPlatinum: {}\tDucats: {}\tBest: {}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, @@ -163,13 +168,20 @@ struct Arguments { /// some systems may require the window name to be specified (e.g. when using gamescope) #[arg(short, long, default_value = "Warframe")] window_name: String, + /// Best item mode + /// + /// - `default`: Platinum + Ducats + /// - `platinum`: Platinum + /// - `ducats`: Ducats + #[arg(short, long, default_value = "default")] + best_item_mode: BestItemMode, } fn main() -> Result<(), Box> { let arguments = Arguments::parse(); let default_log_path = PathBuf::from_str(&std::env::var("HOME").unwrap()).unwrap().join(PathBuf::from_str(".local/share/Steam/steamapps/compatdata/230410/pfx/drive_c/users/steamuser/AppData/Local/Warframe/EE.log")?); - let log_path = arguments.game_log_file_path.unwrap_or(default_log_path); - let window_name = arguments.window_name; + let log_path = arguments.game_log_file_path.as_ref().unwrap_or(&default_log_path); + let window_name = arguments.window_name.clone(); let env = Env::default() .filter_or("WFINFO_LOG", "info") .write_style_or("WFINFO_STYLE", "always"); @@ -198,12 +210,12 @@ fn main() -> Result<(), Box> { let (event_sender, event_receiver) = channel(); - log_watcher(log_path, event_sender.clone()); + log_watcher(log_path.clone(), event_sender.clone()); hotkey_watcher("F12".parse()?, event_sender); while let Ok(()) = event_receiver.recv() { info!("Capturing"); - run_detection(warframe_window, &db); + run_detection(warframe_window, &db, &arguments); } drop(OCR.lock().unwrap().take()); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..f8cd602 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,9 @@ +use clap::ValueEnum; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] +pub enum BestItemMode { + #[default] + Default, + Platinum, + Ducats, +} diff --git a/src/lib.rs b/src/lib.rs index d04d889..82218e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod testing; pub mod theme; pub mod utils; pub mod wfinfo_data; +pub mod config; \ No newline at end of file From 866db27accb9efea944bef775aa63f451fc77e95 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 15:43:45 +0100 Subject: [PATCH 02/25] cargo fmt --- src/bin/main.rs | 20 +++++++++++--------- src/lib.rs | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 4c8ce92..a34db90 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -17,10 +17,10 @@ use notify::{watcher, RecursiveMode, Watcher}; use xcap::Window; use wfinfo::{ + config::BestItemMode, database::Database, ocr::{normalize_string, reward_image_to_reward_names, OCR}, utils::fetch_prices_and_items, - config::BestItemMode, }; fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { @@ -37,13 +37,12 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { let best = items .iter() .map(|item| { - item.map(|item| { - match arguments.best_item_mode { - BestItemMode::Default => item.platinum - .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), - BestItemMode::Platinum => item.platinum, - BestItemMode::Ducats => item.ducats as f32 / 10.0, - } + item.map(|item| match arguments.best_item_mode { + BestItemMode::Default => item + .platinum + .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), + BestItemMode::Platinum => item.platinum, + BestItemMode::Ducats => item.ducats as f32 / 10.0, }) .unwrap_or(0.0) }) @@ -180,7 +179,10 @@ struct Arguments { fn main() -> Result<(), Box> { let arguments = Arguments::parse(); let default_log_path = PathBuf::from_str(&std::env::var("HOME").unwrap()).unwrap().join(PathBuf::from_str(".local/share/Steam/steamapps/compatdata/230410/pfx/drive_c/users/steamuser/AppData/Local/Warframe/EE.log")?); - let log_path = arguments.game_log_file_path.as_ref().unwrap_or(&default_log_path); + let log_path = arguments + .game_log_file_path + .as_ref() + .unwrap_or(&default_log_path); let window_name = arguments.window_name.clone(); let env = Env::default() .filter_or("WFINFO_LOG", "info") diff --git a/src/lib.rs b/src/lib.rs index 82218e3..531d5ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod database; pub mod ocr; pub mod statistics; @@ -5,4 +6,3 @@ pub mod testing; pub mod theme; pub mod utils; pub mod wfinfo_data; -pub mod config; \ No newline at end of file From ec90e1332d4b110c82fa3e8cc731e26a81893485 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:34:15 +0100 Subject: [PATCH 03/25] Enhance item tracking with volume data and new InfoMode enum - Introduced `InfoMode` enum in `config.rs` to toggle between default and detailed item information display. - Updated `Item` struct in `database.rs` to include `yesterday_vol` and `today_vol` for tracking item volume. - Modified price data deserialization in `wfinfo_data.rs` to accommodate new volume fields. - Enhanced logging in `main.rs` to display volume information based on the selected `InfoMode`. - Updated command-line arguments to include `info_mode` for user-defined output preferences. --- src/bin/main.rs | 33 +++++++++++++++++++++++++-------- src/config.rs | 7 +++++++ src/database.rs | 28 ++++++++++++++++++++++++++-- src/wfinfo_data.rs | 4 ++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index a34db90..7909e44 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -17,7 +17,7 @@ use notify::{watcher, RecursiveMode, Watcher}; use xcap::Window; use wfinfo::{ - config::BestItemMode, + config::{BestItemMode, InfoMode}, database::Database, ocr::{normalize_string, reward_image_to_reward_names, OCR}, utils::fetch_prices_and_items, @@ -52,13 +52,24 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { for (index, item) in items.iter().enumerate() { if let Some(item) = item { - info!( - "Name: {}\n\tPlatinum: {}\tDucats: {}\tBest: {}", - item.drop_name, - item.platinum, - item.ducats as f32 / 10.0, - if Some(index) == best { "<----" } else { "" } - ); + match arguments.info_mode { + InfoMode::Default => info!( + "Name: {}\n\tPlatinum: {}\tDucats: {}\tBest: {}", + item.drop_name, + item.platinum, + item.ducats as f32 / 10.0, + if Some(index) == best { "<----" } else { "" } + ), + InfoMode::All => info!( + "Name: {}\n\tPlatinum: {}\tDucats: {}\t{}\nYesterday Vol: {}\tToday Vol: {}", + item.drop_name, + item.platinum, + item.ducats as f32 / 10.0, + if Some(index) == best { "<----" } else { "" }, + item.yesterday_vol, + item.today_vol + ), + } } else { warn!("Unknown item\n\tUnknown"); } @@ -174,6 +185,12 @@ struct Arguments { /// - `ducats`: Ducats #[arg(short, long, default_value = "default")] best_item_mode: BestItemMode, + /// Info mode + /// + /// - `default`: Default + /// - `all`: All + #[arg(short, long, default_value = "default")] + info_mode: InfoMode, } fn main() -> Result<(), Box> { diff --git a/src/config.rs b/src/config.rs index f8cd602..7de1736 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,3 +7,10 @@ pub enum BestItemMode { Platinum, Ducats, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] +pub enum InfoMode { + #[default] + Default, + All, +} diff --git a/src/database.rs b/src/database.rs index 55f1d21..22c9383 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,6 +24,8 @@ pub struct Item { pub drop_name: String, pub platinum: f32, pub ducats: usize, + pub yesterday_vol: usize, + pub today_vol: usize, } impl Database { @@ -32,10 +34,17 @@ impl Database { let text = read_to_string(prices.unwrap_or_else(|| Path::new("prices.json"))).unwrap(); let price_list: Vec = serde_json::from_str(&text).unwrap(); let price_table: HashMap = price_list + .clone() .into_iter() .map(|item| (item.name, item.custom_avg)) .collect(); + let price_table_vol: HashMap = price_list + .clone() + .into_iter() + .map(|item| (item.name, (item.yesterday_vol, item.today_vol))) + .collect(); + let text = read_to_string(filtered_items.unwrap_or_else(|| Path::new("filtered_items.json"))) .unwrap(); @@ -68,14 +77,25 @@ impl Database { }; let platinum = *match price_table .get(name) - .or_else(|| price_table.get(&format!("{name} Blueprint"))) - { + .or_else(|| price_table.get(&format!("{name} Blueprint"))) { Some(plat) => plat, None => { println!("Failed to find price for item: {name}"); return None; } }; + + let (yesterday_vol, today_vol) = match price_table_vol + .get(name) + .or_else(|| price_table_vol.get(&format!("{name} Blueprint"))) + { + Some(&vol) => vol, + None => { + println!("Failed to find volume for item: {name}"); + return None; + } + }; + let ducats = ducat_item.ducats; Some(Item { @@ -83,6 +103,8 @@ impl Database { drop_name, platinum, ducats, + yesterday_vol, + today_vol, }) }) }) @@ -91,6 +113,8 @@ impl Database { drop_name: name.to_owned(), platinum: 0.0, ducats: 0, + yesterday_vol: 0, + today_vol: 0, })) .collect(); diff --git a/src/wfinfo_data.rs b/src/wfinfo_data.rs index 8495886..5225918 100644 --- a/src/wfinfo_data.rs +++ b/src/wfinfo_data.rs @@ -9,6 +9,10 @@ pub mod price_data { pub name: String, #[serde(deserialize_with = "deserialize_number_from_string")] pub custom_avg: f32, + #[serde(deserialize_with = "deserialize_number_from_string")] + pub yesterday_vol: usize, + #[serde(deserialize_with = "deserialize_number_from_string")] + pub today_vol: usize, } } From 23edbf7748c8eb8dd48e4909f21130c9095dea89 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:38:08 +0100 Subject: [PATCH 04/25] cargo fmt --- src/database.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database.rs b/src/database.rs index 22c9383..7005f89 100644 --- a/src/database.rs +++ b/src/database.rs @@ -77,7 +77,8 @@ impl Database { }; let platinum = *match price_table .get(name) - .or_else(|| price_table.get(&format!("{name} Blueprint"))) { + .or_else(|| price_table.get(&format!("{name} Blueprint"))) + { Some(plat) => plat, None => { println!("Failed to find price for item: {name}"); From 9f2f9b752644646412ff4d5ee7d8680647be708a Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:42:11 +0100 Subject: [PATCH 05/25] Add Volatility mode to BestItemMode enum and update detection logic - Introduced `Volatility` variant to the `BestItemMode` enum in `config.rs`. - Updated the item evaluation logic in `run_detection` function of `main.rs` to calculate item value based on the new `Volatility` mode (Platinum * (Yesterday Vol + Today Vol)). - Enhanced documentation for command-line arguments to reflect the addition of the `volatility` mode. --- src/bin/main.rs | 4 +++- src/config.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 7909e44..4f6d838 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -43,6 +43,7 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), BestItemMode::Platinum => item.platinum, BestItemMode::Ducats => item.ducats as f32 / 10.0, + BestItemMode::Volatility => (item.yesterday_vol + item.today_vol) as f32 * item.platinum, }) .unwrap_or(0.0) }) @@ -180,9 +181,10 @@ struct Arguments { window_name: String, /// Best item mode /// - /// - `default`: Platinum + Ducats + /// - `default`: Platinum + Ducats (Platinum / 100 + Ducats / 10) /// - `platinum`: Platinum /// - `ducats`: Ducats + /// - `volatility`: Volatility (Platinum * (Yesterday Vol + Today Vol)) #[arg(short, long, default_value = "default")] best_item_mode: BestItemMode, /// Info mode diff --git a/src/config.rs b/src/config.rs index 7de1736..d7f4209 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ pub enum BestItemMode { Default, Platinum, Ducats, + Volatility, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] From bd08a1c9808ceb1f7eb2aff45b45291eb1b5d06b Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:42:44 +0100 Subject: [PATCH 06/25] cargo fmt --- src/bin/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 4f6d838..c1474a5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -43,7 +43,9 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), BestItemMode::Platinum => item.platinum, BestItemMode::Ducats => item.ducats as f32 / 10.0, - BestItemMode::Volatility => (item.yesterday_vol + item.today_vol) as f32 * item.platinum, + BestItemMode::Volatility => { + (item.yesterday_vol + item.today_vol) as f32 * item.platinum + } }) .unwrap_or(0.0) }) From 0a9009286ff8f288a9c7664c6150dbb679c8aa27 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:52:28 +0100 Subject: [PATCH 07/25] Enhance command-line argument documentation for BestItemMode and InfoMode --- src/bin/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index c1474a5..617b632 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -188,12 +188,14 @@ struct Arguments { /// - `ducats`: Ducats /// - `volatility`: Volatility (Platinum * (Yesterday Vol + Today Vol)) #[arg(short, long, default_value = "default")] + #[clap(verbatim_doc_comment)] best_item_mode: BestItemMode, /// Info mode /// - /// - `default`: Default - /// - `all`: All + /// - `default`: Default (Shows platinum and ducats) + /// - `all`: All (Shows today and yesterday's volumes) #[arg(short, long, default_value = "default")] + #[clap(verbatim_doc_comment)] info_mode: InfoMode, } From abaecb1a0d31788d5d20e6d2b42c16f50edb0bcb Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 16:53:56 +0100 Subject: [PATCH 08/25] Clarification on "all" info mode --- src/bin/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 617b632..1d908f2 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -193,7 +193,7 @@ struct Arguments { /// Info mode /// /// - `default`: Default (Shows platinum and ducats) - /// - `all`: All (Shows today and yesterday's volumes) + /// - `all`: All (Also shows today and yesterday's volumes) #[arg(short, long, default_value = "default")] #[clap(verbatim_doc_comment)] info_mode: InfoMode, From c3a814bcbf78c99483306dc4c6f50f10e9c020de Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:11:04 +0100 Subject: [PATCH 09/25] Refactor BestItemMode and InfoMode enums to use 'Combined' as default Also updated volatility formula to better reflect market changes --- src/bin/main.rs | 21 ++++++++++++--------- src/config.rs | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 1d908f2..113b778 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -38,14 +38,17 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { .iter() .map(|item| { item.map(|item| match arguments.best_item_mode { - BestItemMode::Default => item + BestItemMode::Combined => item .platinum .max(item.ducats as f32 / 10.0 + item.platinum / 100.0), BestItemMode::Platinum => item.platinum, BestItemMode::Ducats => item.ducats as f32 / 10.0, BestItemMode::Volatility => { - (item.yesterday_vol + item.today_vol) as f32 * item.platinum - } + // Calculate sales volume: max(volume_yesterday - volume_today, 0) + let sales_volume = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32; + // Calculate volatility: sales_volume * platinum + sales_volume * item.platinum + }, }) .unwrap_or(0.0) }) @@ -56,7 +59,7 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { for (index, item) in items.iter().enumerate() { if let Some(item) = item { match arguments.info_mode { - InfoMode::Default => info!( + InfoMode::Combined => info!( "Name: {}\n\tPlatinum: {}\tDucats: {}\tBest: {}", item.drop_name, item.platinum, @@ -183,18 +186,18 @@ struct Arguments { window_name: String, /// Best item mode /// - /// - `default`: Platinum + Ducats (Platinum / 100 + Ducats / 10) + /// - `combined`: Platinum + Ducats (Platinum / 100 + Ducats / 10) /// - `platinum`: Platinum /// - `ducats`: Ducats - /// - `volatility`: Volatility (Platinum * (Yesterday Vol + Today Vol)) - #[arg(short, long, default_value = "default")] + /// - `volatility`: Volatility (Max(volume_yesterday - volume_today, 0) * Platinum) + #[arg(short, long, default_value = "combined")] #[clap(verbatim_doc_comment)] best_item_mode: BestItemMode, /// Info mode /// - /// - `default`: Default (Shows platinum and ducats) + /// - `combined`: Combined (Shows platinum and ducats) /// - `all`: All (Also shows today and yesterday's volumes) - #[arg(short, long, default_value = "default")] + #[arg(short, long, default_value = "combined")] #[clap(verbatim_doc_comment)] info_mode: InfoMode, } diff --git a/src/config.rs b/src/config.rs index d7f4209..c832719 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use clap::ValueEnum; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] pub enum BestItemMode { #[default] - Default, + Combined, Platinum, Ducats, Volatility, @@ -12,6 +12,6 @@ pub enum BestItemMode { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] pub enum InfoMode { #[default] - Default, + Combined, All, } From 1dfdcb0abfe23a3ec34c0c039eccc1388d18c692 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:15:15 +0100 Subject: [PATCH 10/25] Fix logging format in run_detection to improve item information display --- src/bin/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 113b778..252d51e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -60,7 +60,7 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { if let Some(item) = item { match arguments.info_mode { InfoMode::Combined => info!( - "Name: {}\n\tPlatinum: {}\tDucats: {}\tBest: {}", + "Name: {}\n\tPlatinum: {}\tDucats: {}\t{}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, From ccb4ee826350765d00e7b6473e27a141615afb44 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:27:06 +0100 Subject: [PATCH 11/25] Add Minimal InfoMode variant and update default value in command-line arguments - Introduced a new `Minimal` variant to the `InfoMode` enum in `config.rs` for simplified item information display. - Updated the default value of `info_mode` in the `Arguments` struct to `minimal`, changing the output format to show only the item name, platinum, and ducats. - Enhanced documentation for `info_mode` to reflect the new `minimal` option. --- src/bin/main.rs | 12 ++++++++++-- src/config.rs | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 252d51e..a483cc6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -59,6 +59,13 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { for (index, item) in items.iter().enumerate() { if let Some(item) = item { match arguments.info_mode { + InfoMode::Minimal => info!( + "{}\n\t{}\t{}\t{}", + item.drop_name, + item.platinum, + item.ducats as f32 / 10.0, + if Some(index) == best { "<----" } else { "" } + ), InfoMode::Combined => info!( "Name: {}\n\tPlatinum: {}\tDucats: {}\t{}", item.drop_name, @@ -195,9 +202,10 @@ struct Arguments { best_item_mode: BestItemMode, /// Info mode /// - /// - `combined`: Combined (Shows platinum and ducats) + /// - `minimal`: Minimal (Shows only the name, platinum, and ducats) + /// - `combined`: Combined (Shows platinum and ducats, with labels) /// - `all`: All (Also shows today and yesterday's volumes) - #[arg(short, long, default_value = "combined")] + #[arg(short, long, default_value = "minimal")] #[clap(verbatim_doc_comment)] info_mode: InfoMode, } diff --git a/src/config.rs b/src/config.rs index c832719..c30bb1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ pub enum BestItemMode { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] pub enum InfoMode { #[default] + Minimal, Combined, All, } From 2d93de4c4ed0637248a0c488df5040276d1743a6 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:28:42 +0100 Subject: [PATCH 12/25] Fix logging format in `run_detection` to include item volume data in `InfoMode::Combined` and `InfoMode::All`. Updated output strings to ensure proper display of item name, platinum, ducats, and volume information, maintaining consistency across different info modes. --- src/bin/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index a483cc6..a1a473a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -67,20 +67,20 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { if Some(index) == best { "<----" } else { "" } ), InfoMode::Combined => info!( - "Name: {}\n\tPlatinum: {}\tDucats: {}\t{}", + "{}\n\tPlatinum: {}\tDucats: {}\t{}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, if Some(index) == best { "<----" } else { "" } ), InfoMode::All => info!( - "Name: {}\n\tPlatinum: {}\tDucats: {}\t{}\nYesterday Vol: {}\tToday Vol: {}", + "{}\n\tPlatinum: {}\tDucats: {}\tYesterday Vol: {}\tToday Vol: {}\t{}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, - if Some(index) == best { "<----" } else { "" }, item.yesterday_vol, - item.today_vol + item.today_vol, + if Some(index) == best { "<----" } else { "" } ), } } else { From de6b798692fb5f70d9143905747e968b9ba47141 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:49:55 +0100 Subject: [PATCH 13/25] Rename `InfoMode` to `InfoDisplayMode` for clarity and update related references in `main.rs`. --- src/bin/main.rs | 14 +++++++------- src/config.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index a1a473a..811d1ee 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -17,7 +17,7 @@ use notify::{watcher, RecursiveMode, Watcher}; use xcap::Window; use wfinfo::{ - config::{BestItemMode, InfoMode}, + config::{BestItemMode, InfoDisplayMode}, database::Database, ocr::{normalize_string, reward_image_to_reward_names, OCR}, utils::fetch_prices_and_items, @@ -58,22 +58,22 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { for (index, item) in items.iter().enumerate() { if let Some(item) = item { - match arguments.info_mode { - InfoMode::Minimal => info!( + match arguments.info_display_mode { + InfoDisplayMode::Minimal => info!( "{}\n\t{}\t{}\t{}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, if Some(index) == best { "<----" } else { "" } ), - InfoMode::Combined => info!( + InfoDisplayMode::Combined => info!( "{}\n\tPlatinum: {}\tDucats: {}\t{}", item.drop_name, item.platinum, item.ducats as f32 / 10.0, if Some(index) == best { "<----" } else { "" } ), - InfoMode::All => info!( + InfoDisplayMode::All => info!( "{}\n\tPlatinum: {}\tDucats: {}\tYesterday Vol: {}\tToday Vol: {}\t{}", item.drop_name, item.platinum, @@ -200,14 +200,14 @@ struct Arguments { #[arg(short, long, default_value = "combined")] #[clap(verbatim_doc_comment)] best_item_mode: BestItemMode, - /// Info mode + /// Info display mode /// /// - `minimal`: Minimal (Shows only the name, platinum, and ducats) /// - `combined`: Combined (Shows platinum and ducats, with labels) /// - `all`: All (Also shows today and yesterday's volumes) #[arg(short, long, default_value = "minimal")] #[clap(verbatim_doc_comment)] - info_mode: InfoMode, + info_display_mode: InfoDisplayMode, } fn main() -> Result<(), Box> { diff --git a/src/config.rs b/src/config.rs index c30bb1d..f8ce82d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,7 +10,7 @@ pub enum BestItemMode { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)] -pub enum InfoMode { +pub enum InfoDisplayMode { #[default] Minimal, Combined, From 9ab4f18bd0af72441157eed996ae38e0398d0f0a Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Sun, 22 Dec 2024 17:50:23 +0100 Subject: [PATCH 14/25] cargo fmt and clippy --- src/bin/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 811d1ee..bf04d4d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -48,7 +48,7 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { let sales_volume = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32; // Calculate volatility: sales_volume * platinum sales_volume * item.platinum - }, + } }) .unwrap_or(0.0) }) From d2ff1d51a911e1fa80333a824d88d2dd18b27521 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Mon, 23 Dec 2024 12:03:22 +0100 Subject: [PATCH 15/25] Added Forma multiplier argument --- src/bin/image.rs | 2 +- src/bin/main.rs | 13 +++++++++---- src/bin/relics.rs | 2 +- src/bin/theme_tune.rs | 2 +- src/database.rs | 16 ++++++++++------ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/bin/image.rs b/src/bin/image.rs index b3ead13..71e47c0 100644 --- a/src/bin/image.rs +++ b/src/bin/image.rs @@ -21,7 +21,7 @@ fn main() { let text: Vec<_> = detections.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); for item in items.iter() { if let Some(item) = item { diff --git a/src/bin/main.rs b/src/bin/main.rs index bf04d4d..114962d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -208,6 +208,11 @@ struct Arguments { #[arg(short, long, default_value = "minimal")] #[clap(verbatim_doc_comment)] info_display_mode: InfoDisplayMode, + /// Forma platinum multiplier + /// + /// The default is 1.0 + #[arg(short, long, default_value = "1.0")] + forma_platinum_multiplier: f32, } fn main() -> Result<(), Box> { @@ -240,7 +245,7 @@ fn main() -> Result<(), Box> { ); let (prices, items) = fetch_prices_and_items()?; - let db = Database::load_from_file(Some(&prices), Some(&items)); + let db = Database::load_from_file(Some(&prices), Some(&items), Some(arguments.forma_multiplier)); info!("Loaded database"); @@ -282,7 +287,7 @@ mod test { let text = reward_image_to_reward_names(image, None); let text = text.iter().map(|s| normalize_string(s)); println!("{:#?}", text); - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let items: Vec<_> = text.map(|s| db.find_item(&s, None)).collect(); println!("{:#?}", items); @@ -318,7 +323,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); @@ -353,7 +358,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); diff --git a/src/bin/relics.rs b/src/bin/relics.rs index 82b2ddf..8f5652d 100644 --- a/src/bin/relics.rs +++ b/src/bin/relics.rs @@ -72,7 +72,7 @@ fn best_trace_dump(database: &Database) { } fn main() { - let database = Database::load_from_file(None, None); + let database = Database::load_from_file(None, None, Some(1.0)); let mut args = std::env::args().skip(1); let relics = match args .next() diff --git a/src/bin/theme_tune.rs b/src/bin/theme_tune.rs index ac48648..fdf2bbf 100644 --- a/src/bin/theme_tune.rs +++ b/src/bin/theme_tune.rs @@ -78,7 +78,7 @@ fn spawn_ocr_thread( let images = images.to_owned(); thread::spawn(move || { - let database = Database::load_from_file(None, None); + let database = Database::load_from_file(None, None, Some(1.0)); loop { let (mut index, mut last_request): (usize, HslRange) = request_receiver.recv().unwrap(); diff --git a/src/database.rs b/src/database.rs index 7005f89..3f7a705 100644 --- a/src/database.rs +++ b/src/database.rs @@ -29,7 +29,11 @@ pub struct Item { } impl Database { - pub fn load_from_file(prices: Option<&Path>, filtered_items: Option<&Path>) -> Database { + pub fn load_from_file( + prices: Option<&Path>, + filtered_items: Option<&Path>, + forma_multiplier: Option, + ) -> Database { // download file from: https://api.warframestat.us/wfinfo/prices let text = read_to_string(prices.unwrap_or_else(|| Path::new("prices.json"))).unwrap(); let price_list: Vec = serde_json::from_str(&text).unwrap(); @@ -120,7 +124,7 @@ impl Database { .collect(); if let Some(item) = items.iter_mut().find(|item| item.name == "Forma Blueprint") { - item.platinum = 35.0 / 3.0; + item.platinum = forma_multiplier.unwrap_or(1.0) * (35.0 / 3.0); }; let relics = filtered_items.relics; @@ -279,12 +283,12 @@ mod test { #[test] pub fn can_load_database() { - Database::load_from_file(None, None); + Database::load_from_file(None, None, Some(1.0)); } #[test] pub fn can_find_items() { - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let item = db .find_item("TitaniaPrimeBlueprint", Some(0)) @@ -299,7 +303,7 @@ mod test { #[test] pub fn can_find_fuzzy_items() { - let db = Database::load_from_file(None, None); + let db = Database::load_from_file(None, None, Some(1.0)); let item = db .find_item("Akstlett Prlme Recver", None) @@ -319,7 +323,7 @@ mod test { #[test] fn validate_shared_relic_values() { - let database = Database::load_from_file(None, None); + let database = Database::load_from_file(None, None, Some(1.0)); for (name, relic) in database.relics.lith.iter() { println!("{} {:#?}", name, relic); From 17eea1256f68c692745f0e9d6a777d1cf09d1324 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Mon, 23 Dec 2024 12:05:58 +0100 Subject: [PATCH 16/25] Silly mistake fixed --- src/bin/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 114962d..b618fcb 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -245,7 +245,11 @@ fn main() -> Result<(), Box> { ); let (prices, items) = fetch_prices_and_items()?; - let db = Database::load_from_file(Some(&prices), Some(&items), Some(arguments.forma_multiplier)); + let db = Database::load_from_file( + Some(&prices), + Some(&items), + Some(arguments.forma_platinum_multiplier), + ); info!("Loaded database"); From 8f25269f6a04a94c370be1b4afe829d4bcc0ca2e Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Mon, 23 Dec 2024 17:55:39 +0100 Subject: [PATCH 17/25] feat(config): add forma value and hotkey customization --- src/bin/image.rs | 2 +- src/bin/main.rs | 21 ++++++++++++++++----- src/bin/relics.rs | 2 +- src/bin/theme_tune.rs | 2 +- src/database.rs | 11 ++++++----- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/bin/image.rs b/src/bin/image.rs index 71e47c0..fa13801 100644 --- a/src/bin/image.rs +++ b/src/bin/image.rs @@ -21,7 +21,7 @@ fn main() { let text: Vec<_> = detections.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); for item in items.iter() { if let Some(item) = item { diff --git a/src/bin/main.rs b/src/bin/main.rs index b618fcb..79fd4ed 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -210,9 +210,19 @@ struct Arguments { info_display_mode: InfoDisplayMode, /// Forma platinum multiplier /// - /// The default is 1.0 + /// The multiplier to use for Forma's platinum value #[arg(short, long, default_value = "1.0")] forma_platinum_multiplier: f32, + /// Forma platinum value + /// + /// The base platinum value for Forma + #[arg(short = 'v', long, default_value = "11.666667")] // 35.0/3.0 + forma_platinum_value: f32, + /// Detection hotkey + /// + /// The hotkey to use for detection + #[arg(short, long, default_value = "F12")] + detection_hotkey: HotKey, } fn main() -> Result<(), Box> { @@ -249,6 +259,7 @@ fn main() -> Result<(), Box> { Some(&prices), Some(&items), Some(arguments.forma_platinum_multiplier), + Some(arguments.forma_platinum_value), ); info!("Loaded database"); @@ -256,7 +267,7 @@ fn main() -> Result<(), Box> { let (event_sender, event_receiver) = channel(); log_watcher(log_path.clone(), event_sender.clone()); - hotkey_watcher("F12".parse()?, event_sender); + hotkey_watcher(arguments.detection_hotkey, event_sender); while let Ok(()) = event_receiver.recv() { info!("Capturing"); @@ -291,7 +302,7 @@ mod test { let text = reward_image_to_reward_names(image, None); let text = text.iter().map(|s| normalize_string(s)); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let items: Vec<_> = text.map(|s| db.find_item(&s, None)).collect(); println!("{:#?}", items); @@ -327,7 +338,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); @@ -362,7 +373,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); diff --git a/src/bin/relics.rs b/src/bin/relics.rs index 8f5652d..83fae52 100644 --- a/src/bin/relics.rs +++ b/src/bin/relics.rs @@ -72,7 +72,7 @@ fn best_trace_dump(database: &Database) { } fn main() { - let database = Database::load_from_file(None, None, Some(1.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let mut args = std::env::args().skip(1); let relics = match args .next() diff --git a/src/bin/theme_tune.rs b/src/bin/theme_tune.rs index fdf2bbf..0c9f54b 100644 --- a/src/bin/theme_tune.rs +++ b/src/bin/theme_tune.rs @@ -78,7 +78,7 @@ fn spawn_ocr_thread( let images = images.to_owned(); thread::spawn(move || { - let database = Database::load_from_file(None, None, Some(1.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); loop { let (mut index, mut last_request): (usize, HslRange) = request_receiver.recv().unwrap(); diff --git a/src/database.rs b/src/database.rs index 3f7a705..0ae7fea 100644 --- a/src/database.rs +++ b/src/database.rs @@ -33,6 +33,7 @@ impl Database { prices: Option<&Path>, filtered_items: Option<&Path>, forma_multiplier: Option, + forma_platinum_value: Option, ) -> Database { // download file from: https://api.warframestat.us/wfinfo/prices let text = read_to_string(prices.unwrap_or_else(|| Path::new("prices.json"))).unwrap(); @@ -124,7 +125,7 @@ impl Database { .collect(); if let Some(item) = items.iter_mut().find(|item| item.name == "Forma Blueprint") { - item.platinum = forma_multiplier.unwrap_or(1.0) * (35.0 / 3.0); + item.platinum = forma_multiplier.unwrap_or(1.0) * (forma_platinum_value.unwrap_or(35.0 / 3.0)); }; let relics = filtered_items.relics; @@ -283,12 +284,12 @@ mod test { #[test] pub fn can_load_database() { - Database::load_from_file(None, None, Some(1.0)); + Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); } #[test] pub fn can_find_items() { - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let item = db .find_item("TitaniaPrimeBlueprint", Some(0)) @@ -303,7 +304,7 @@ mod test { #[test] pub fn can_find_fuzzy_items() { - let db = Database::load_from_file(None, None, Some(1.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); let item = db .find_item("Akstlett Prlme Recver", None) @@ -323,7 +324,7 @@ mod test { #[test] fn validate_shared_relic_values() { - let database = Database::load_from_file(None, None, Some(1.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); for (name, relic) in database.relics.lith.iter() { println!("{} {:#?}", name, relic); From e2afd01a64deb051770439eb9eb6de374d8d4c02 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Tue, 24 Dec 2024 15:52:46 +0100 Subject: [PATCH 18/25] feat(snapit): add screen selection tool for individual item price checks --- Cargo.lock | 672 ++++++++++++++++++++++++++++++++++++------ Cargo.toml | 4 +- src/bin/image.rs | 6 +- src/bin/main.rs | 326 +++++++++++++++++--- src/bin/relics.rs | 2 +- src/bin/theme_tune.rs | 6 +- src/database.rs | 11 +- src/ocr.rs | 232 +++++++++++++-- 8 files changed, 1095 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da820a5..ea72255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -133,6 +139,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "arboard" version = "2.1.1" @@ -145,11 +157,22 @@ dependencies = [ "objc-foundation", "objc_id", "parking_lot", - "thiserror", + "thiserror 1.0.57", "winapi 0.3.9", "x11rb", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -162,6 +185,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -180,6 +209,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec 0.7.6", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec 0.7.6", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -237,19 +289,31 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + [[package]] name = "bumpalo" version = "3.15.3" @@ -273,7 +337,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -282,6 +346,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.7.2" @@ -298,7 +368,7 @@ dependencies = [ "log", "nix 0.25.1", "slotmap", - "thiserror", + "thiserror 1.0.57", "vec_map", ] @@ -308,6 +378,8 @@ version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -326,6 +398,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -398,10 +480,10 @@ version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -486,7 +568,7 @@ dependencies = [ "bitflags 1.3.2", "block", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", "libc", "objc", ] @@ -529,7 +611,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys 0.8.7", "libc", ] @@ -541,9 +633,9 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" @@ -577,7 +669,7 @@ checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types 0.3.2", "libc", ] @@ -590,7 +682,20 @@ checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] @@ -606,6 +711,17 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "libc", +] + [[package]] name = "core-text" version = "20.1.0" @@ -669,7 +785,7 @@ checksum = "3eb5a3822b594afc99b503cc1859b94686d3c3efdd60507a28587dab80ee1071" dependencies = [ "cocoa 0.25.0", "core-foundation 0.9.4", - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", "core-graphics 0.23.1", "core-text", "dwrote", @@ -997,7 +1113,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -1052,7 +1168,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -1248,7 +1364,7 @@ dependencies = [ "crossbeam-channel", "keyboard-types", "once_cell", - "thiserror", + "thiserror 1.0.57", "windows-sys 0.52.0", "x11-dl", ] @@ -1377,6 +1493,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1517,11 +1639,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1551,22 +1673,43 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +dependencies = [ + "byteorder-lite", + "quick-error", ] +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indexmap" version = "1.9.3" @@ -1620,6 +1763,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1635,6 +1789,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1651,7 +1814,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.57", "walkdir", ] @@ -1661,14 +1824,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -1695,7 +1864,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "serde", "unicode-segmentation", ] @@ -1732,7 +1901,7 @@ checksum = "649bf19e77d43f7b8d2acc2e9a6ccabc373773f395205890f1ecc950fad7606c" dependencies = [ "leptonica-sys", "libc", - "thiserror", + "thiserror 1.0.57", ] [[package]] @@ -1764,10 +1933,19 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ - "cc", "pkg-config", ] +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1785,7 +1963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164" dependencies = [ "cfg-if 1.0.0", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1810,6 +1988,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1819,6 +2006,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if 1.0.0", + "rayon", +] + [[package]] name = "memchr" version = "2.7.1" @@ -1959,7 +2156,7 @@ dependencies = [ "jni-sys", "ndk-sys 0.3.0", "num_enum", - "thiserror", + "thiserror 1.0.57", ] [[package]] @@ -1973,7 +2170,7 @@ dependencies = [ "ndk-sys 0.4.1+23.1.7779620", "num_enum", "raw-window-handle 0.5.2", - "thiserror", + "thiserror 1.0.57", ] [[package]] @@ -2055,6 +2252,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.22.3" @@ -2109,6 +2312,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify" version = "4.0.17" @@ -2128,12 +2337,44 @@ dependencies = [ ] [[package]] -name = "ntapi" -version = "0.4.1" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "winapi 0.3.9", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", ] [[package]] @@ -2216,7 +2457,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if 1.0.0", "foreign-types 0.3.2", "libc", @@ -2233,7 +2474,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -2330,6 +2571,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -2372,7 +2619,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -2415,6 +2662,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2422,18 +2678,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn 2.0.91", +] + [[package]] name = "qoi" version = "0.4.1" @@ -2443,6 +2718,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.30.0" @@ -2467,19 +2748,82 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", "serde", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ + "getrandom", "serde", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec 0.7.6", + "av1-grain", + "bitstream-io", + "built", + "cfg-if 1.0.0", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.57", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.4.3" @@ -2497,9 +2841,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2613,6 +2957,12 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "ring" version = "0.17.8" @@ -2646,7 +2996,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2758,7 +3108,7 @@ checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", "libc", "security-framework-sys", ] @@ -2769,7 +3119,7 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", "libc", ] @@ -2801,7 +3151,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -2815,6 +3165,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2870,6 +3229,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -2985,9 +3353,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", @@ -3003,28 +3371,13 @@ dependencies = [ "futures-core", ] -[[package]] -name = "sysinfo" -version = "0.30.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6746919caf9f2a85bff759535664c060109f21975c5ac2e8652e60102bd4d196" -dependencies = [ - "cfg-if 1.0.0", - "core-foundation-sys 0.8.6", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows", -] - [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3035,10 +3388,29 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "core-foundation-sys 0.8.6", + "core-foundation-sys 0.8.7", "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.12", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.12.0" @@ -3060,7 +3432,7 @@ checksum = "b419c2568ceb602121d4ea2495e4b214ac7f32d5009b74b1ce67765a89c4da54" dependencies = [ "tesseract-plumbing", "tesseract-sys", - "thiserror", + "thiserror 1.0.57", ] [[package]] @@ -3071,7 +3443,7 @@ checksum = "a25fbbb95169954a9262a565fbfb001c4d9dad271d48142e6632a3e2b7314b35" dependencies = [ "leptonica-plumbing", "tesseract-sys", - "thiserror", + "thiserror 1.0.57", ] [[package]] @@ -3092,7 +3464,16 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.57", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -3103,7 +3484,18 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", ] [[package]] @@ -3124,7 +3516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.5.2", "bytemuck", "cfg-if 1.0.0", "png", @@ -3215,11 +3607,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.12", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3229,7 +3636,20 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.2.5", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap 2.2.5", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.20", ] [[package]] @@ -3319,6 +3739,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -3331,6 +3762,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -3383,7 +3820,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", "wasm-bindgen-shared", ] @@ -3417,7 +3854,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3642,11 +4079,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.52.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -3659,6 +4096,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -3922,6 +4394,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "wio" version = "0.2.2" @@ -3976,27 +4457,26 @@ dependencies = [ [[package]] name = "xcap" -version = "0.0.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776c1b371ea7c73bc98a40ae5c6a69c648669ff2e6d8a03fc8b80ddc58a0498" +checksum = "8022d36d08fb7ebfefedcb4be098cf165d442280994449d0becd11ac476e0fc6" dependencies = [ - "core-foundation 0.9.4", - "core-graphics 0.23.1", + "core-foundation 0.10.0", + "core-graphics 0.24.0", "dbus", "image", "log", "percent-encoding", - "sysinfo", - "thiserror", + "thiserror 2.0.9", "windows", "xcb", ] [[package]] name = "xcb" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d27b37e69b8c05bfadcd968eb1a4fe27c9c52565b727f88512f43b89567e262" +checksum = "f1e2f212bb1a92cd8caac8051b829a6582ede155ccb60b5d5908b81b100952be" dependencies = [ "bitflags 1.3.2", "libc", @@ -4021,6 +4501,7 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -4032,7 +4513,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.91", ] [[package]] @@ -4041,6 +4522,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -4049,3 +4536,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index b9b962c..d94f3d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "4.5.1", features = ["derive"] } eframe = "0.19.0" egui_extras = "0.19.0" global-hotkey = "0.4.2" -image = "0.24.3" +image = "0.25.5" indexmap = { version = "1.9.1", features = ["serde"] } lazy_static = "1.4.0" levenshtein = "1.0.5" @@ -33,7 +33,7 @@ serde = { version = "1.0.144", features = ["serde_derive"] } serde-aux = "3.1.0" serde_json = "1.0.85" tesseract = "0.12.0" -xcap = "0.0.4" +xcap = "0.2.0" log = "0.4.22" env_logger = "0.11.5" reqwest = { version = "0.12.7", features = ["blocking"] } diff --git a/src/bin/image.rs b/src/bin/image.rs index fa13801..717d876 100644 --- a/src/bin/image.rs +++ b/src/bin/image.rs @@ -1,6 +1,6 @@ use std::{fs::write, path::PathBuf}; -use image::io::Reader; +use image::ImageReader; use indexmap::IndexMap; use wfinfo::{ database::Database, @@ -13,7 +13,7 @@ fn main() { for argument in std::env::args().skip(1) { let filepath = PathBuf::from(argument); - let image = Reader::open(&filepath).unwrap().decode().unwrap(); + let image = ImageReader::open(&filepath).unwrap().decode().unwrap(); let detections = reward_image_to_reward_names(image.clone(), None); println!("{:#?}", detections); @@ -21,7 +21,7 @@ fn main() { let text: Vec<_> = detections.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); for item in items.iter() { if let Some(item) = item { diff --git a/src/bin/main.rs b/src/bin/main.rs index 79fd4ed..cdddbad 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,3 +1,4 @@ +use std::process::Command; use std::thread::sleep; use std::time::Duration; use std::{error::Error, str::FromStr}; @@ -19,7 +20,10 @@ use xcap::Window; use wfinfo::{ config::{BestItemMode, InfoDisplayMode}, database::Database, - ocr::{normalize_string, reward_image_to_reward_names, OCR}, + ocr::{ + extract_part, normalize_string, reward_image_to_reward_names, selection_to_part_name, + slop_to_selection, SelectionParams, OCR, + }, utils::fetch_prices_and_items, }; @@ -89,6 +93,157 @@ fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { } } +fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option { + // Capture the window + let frame = window.capture_image().ok()?; + let image = DynamicImage::ImageRgba8(frame); + debug!("Captured window image"); + + // Run slop to get the selection + let slop_output = Command::new("slop") + .args(["-b", "3", "-c", "1,0,0,0.8"]) + .output() + .ok()?; + let slop_output = String::from_utf8_lossy(&slop_output.stdout); + debug!("Slop output: {}", slop_output); + + // Parse the selection coordinates + let selection = slop_to_selection(&slop_output)?; + debug!( + "Selection: {}x{} at {},{}", + selection.width, selection.height, selection.x, selection.y + ); + + // Get window position + let window_x = window.x(); + let window_y = window.y(); + + // Save selection to Desktop for debugging + let desktop_path = PathBuf::from(std::env::var("HOME").unwrap()) + .join("Desktop") + .join("wfinfo_selection.png"); + debug!("Saving selection to: {}", desktop_path.display()); + + let cropped = extract_part( + &image, + (selection.width, selection.height), + (selection.x - window_x, selection.y - window_y), + arguments.ocr_brightness, + arguments.ocr_contrast, + ); + + if let Err(e) = cropped.save(&desktop_path) { + error!("Failed to save selection: {}", e); + } else { + debug!("Successfully saved selection image"); + } + + // Convert the selection to a part name + let params = SelectionParams { + abs_x: selection.x, + abs_y: selection.y, + width: selection.width, + height: selection.height, + monitor_x: window_x, + monitor_y: window_y, + brightness: arguments.ocr_brightness, + contrast: arguments.ocr_contrast, + }; + + let text = selection_to_part_name(image.clone(), params)?; + + // Look up the item in the database + let item = db.find_item(&normalize_string(&text), None); + if let Some(item) = item { + match arguments.info_display_mode { + InfoDisplayMode::Minimal => { + let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { + BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), + BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), + BestItemMode::Combined => { + if item.platinum > item.ducats as f32 / 10.0 { + ("\x1b[1;32m", "\x1b[0m") + } else { + ("\x1b[0m", "\x1b[1;32m") + } + } + BestItemMode::Volatility => ("\x1b[0m", "\x1b[0m"), + }; + info!( + "{}:\n\t{}{}p\x1b[0m, {}{}d\x1b[0m{}", + item.drop_name, + plat_fmt, item.platinum, + ducat_fmt, item.ducats as f32 / 10.0, + if arguments.best_item_mode == BestItemMode::Volatility { + format!("\n\tVolatility: \x1b[1;32m{}\x1b[0m", volatility) + } else { + String::new() + } + ); + } + InfoDisplayMode::Combined => { + let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { + BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), + BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), + BestItemMode::Combined => { + if item.platinum > item.ducats as f32 / 10.0 { + ("\x1b[1;32m", "\x1b[0m") + } else { + ("\x1b[0m", "\x1b[1;32m") + } + } + BestItemMode::Volatility => ("\x1b[0m", "\x1b[0m"), + }; + info!( + "{}:\n\tPlatinum: {}{}p\x1b[0m\tDucats: {}{}d\x1b[0m{}", + item.drop_name, + plat_fmt, item.platinum, + ducat_fmt, item.ducats as f32 / 10.0, + if arguments.best_item_mode == BestItemMode::Volatility { + format!("\n\tVolatility: \x1b[1;32m{}\x1b[0m", volatility) + } else { + String::new() + } + ); + } + InfoDisplayMode::All => { + let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { + BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), + BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), + BestItemMode::Combined => { + if item.platinum > item.ducats as f32 / 10.0 { + ("\x1b[1;32m", "\x1b[0m") + } else { + ("\x1b[0m", "\x1b[1;32m") + } + } + BestItemMode::Volatility => ("\x1b[0m", "\x1b[0m"), + }; + info!( + "{}:\n\tPlatinum: {}{}p\x1b[0m\tDucats: {}{}d\x1b[0m\tYesterday Vol: {}\tToday Vol: {}\t{}", + item.drop_name, + plat_fmt, item.platinum, + ducat_fmt, item.ducats as f32 / 10.0, + item.yesterday_vol, + item.today_vol, + if arguments.best_item_mode == BestItemMode::Volatility { + format!("\n\tVolatility: \x1b[1;32m{}\x1b[0m", volatility) + } else { + String::new() + } + ); + } + } + } else { + info!("No item found"); + } + + Some(text) +} + fn log_watcher(path: PathBuf, event_sender: mpsc::Sender<()>) { debug!("Path: {}", path.display()); let mut position = File::open(&path) @@ -149,21 +304,6 @@ fn log_watcher(path: PathBuf, event_sender: mpsc::Sender<()>) { }); } -fn hotkey_watcher(hotkey: HotKey, event_sender: mpsc::Sender<()>) { - debug!("watching hotkey: {hotkey:?}"); - thread::spawn(move || { - let manager = GlobalHotKeyManager::new().unwrap(); - manager.register(hotkey).unwrap(); - - while let Ok(event) = GlobalHotKeyEvent::receiver().recv() { - debug!("{:?}", event); - if event.state == HotKeyState::Pressed { - event_sender.send(()).unwrap(); - } - } - }); -} - #[allow(dead_code)] fn benchmark() -> Result<(), Box> { for _ in 0..10 { @@ -179,9 +319,9 @@ fn benchmark() -> Result<(), Box> { Ok(()) } -#[derive(Parser)] -#[command(version, about, long_about = None)] -struct Arguments { +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Arguments { /// Path to the `EE.log` file located in the game installation directory /// /// Most likely located at `~/.local/share/Steam/steamapps/compatdata/230410/pfx/drive_c/users/steamuser/AppData/Local/Warframe/EE.log` @@ -190,7 +330,12 @@ struct Arguments { /// /// some systems may require the window name to be specified (e.g. when using gamescope) #[arg(short, long, default_value = "Warframe")] - window_name: String, + pub window_name: String, + + /// Skip window confirmation (debug only) + #[arg(long)] + pub skip_window_confirmation: bool, + /// Best item mode /// /// - `combined`: Platinum + Ducats (Platinum / 100 + Ducats / 10) @@ -199,7 +344,8 @@ struct Arguments { /// - `volatility`: Volatility (Max(volume_yesterday - volume_today, 0) * Platinum) #[arg(short, long, default_value = "combined")] #[clap(verbatim_doc_comment)] - best_item_mode: BestItemMode, + pub best_item_mode: BestItemMode, + /// Info display mode /// /// - `minimal`: Minimal (Shows only the name, platinum, and ducats) @@ -207,22 +353,99 @@ struct Arguments { /// - `all`: All (Also shows today and yesterday's volumes) #[arg(short, long, default_value = "minimal")] #[clap(verbatim_doc_comment)] - info_display_mode: InfoDisplayMode, + pub info_display_mode: InfoDisplayMode, + /// Forma platinum multiplier /// /// The multiplier to use for Forma's platinum value #[arg(short, long, default_value = "1.0")] - forma_platinum_multiplier: f32, + pub forma_platinum_multiplier: f32, + /// Forma platinum value /// /// The base platinum value for Forma #[arg(short = 'v', long, default_value = "11.666667")] // 35.0/3.0 - forma_platinum_value: f32, + pub forma_platinum_value: f32, + /// Detection hotkey /// /// The hotkey to use for detection #[arg(short, long, default_value = "F12")] + pub detection_hotkey: HotKey, + + /// Snap-it hotkey + /// + /// The hotkey to use for the snap-it feature + #[arg(short, long, default_value = "F10")] + pub snapit_hotkey: HotKey, + + /// Default X11 display + #[arg(short, long)] + pub x11_display: Option, + + /// Default Wayland display + #[arg(short = 'y', long)] + pub wayland_display: Option, + + /// Sleep duration between checks in milliseconds + /// + /// Controls how often to check for hotkey events. Lower values increase responsiveness but use more CPU. + #[arg(long, default_value = "10")] + pub sleep_duration: u64, + + /// OCR brightness adjustment (-255 to 255) + #[arg(long, default_value = "30")] + pub ocr_brightness: i32, + + /// OCR contrast adjustment (0.0 to 10.0) + #[arg(long, default_value = "2.0")] + pub ocr_contrast: f32, +} + +fn setup_hotkeys( detection_hotkey: HotKey, + snapit_hotkey: HotKey, + detection_sender: mpsc::Sender<()>, + snapit_sender: mpsc::Sender<()>, +) -> Result> { + let hotkey_manager = GlobalHotKeyManager::new()?; + debug!( + "Registering hotkeys - F12: {}, F10: {}", + detection_hotkey.id(), + snapit_hotkey.id() + ); + + hotkey_manager.register(detection_hotkey)?; + hotkey_manager.register(snapit_hotkey)?; + + let detection_id = detection_hotkey.id(); + let snapit_id = snapit_hotkey.id(); + + // Single thread for handling both hotkeys + let receiver = GlobalHotKeyEvent::receiver(); + thread::spawn(move || { + while let Ok(event) = receiver.recv() { + if event.state == HotKeyState::Pressed { + match event.id { + id if id == detection_id => { + debug!("Detection hotkey pressed"); + if let Err(e) = detection_sender.send(()) { + error!("Failed to send detection event: {}", e); + } + } + id if id == snapit_id => { + debug!("Snapit hotkey pressed"); + if let Err(e) = snapit_sender.send(()) { + error!("Failed to send snapit event: {}", e); + } + } + _ => {} + } + } + } + }); + + Ok(hotkey_manager) } fn main() -> Result<(), Box> { @@ -249,7 +472,7 @@ fn main() -> Result<(), Box> { }; debug!( - "Capture source resolution: {:?}x{:?}", + "Found window: {}x{}", warframe_window.width(), warframe_window.height() ); @@ -264,14 +487,39 @@ fn main() -> Result<(), Box> { info!("Loaded database"); - let (event_sender, event_receiver) = channel(); + let (detection_sender, detection_receiver) = channel(); + let (snapit_sender, snapit_receiver) = channel(); + + log_watcher(log_path.clone(), detection_sender.clone()); - log_watcher(log_path.clone(), event_sender.clone()); - hotkey_watcher(arguments.detection_hotkey, event_sender); + // Setup hotkeys + let _hotkey_manager = setup_hotkeys( + arguments.detection_hotkey, + arguments.snapit_hotkey, + detection_sender, + snapit_sender, + )?; - while let Ok(()) = event_receiver.recv() { - info!("Capturing"); - run_detection(warframe_window, &db, &arguments); + loop { + // Check detection receiver + match detection_receiver.try_recv() { + Ok(()) => { + run_detection(warframe_window, &db, &arguments); + } + Err(mpsc::TryRecvError::Empty) => {} + Err(mpsc::TryRecvError::Disconnected) => break, + } + + // Check snapit receiver + match snapit_receiver.try_recv() { + Ok(()) => { + run_snapit(warframe_window, &db, &arguments); + } + Err(mpsc::TryRecvError::Empty) => { + thread::sleep(Duration::from_millis(arguments.sleep_duration)); + } + Err(mpsc::TryRecvError::Disconnected) => break, + } } drop(OCR.lock().unwrap().take()); @@ -283,7 +531,7 @@ mod test { use std::collections::BTreeMap; use std::fs::read_to_string; - use image::io::Reader; + use image::ImageReader; use indexmap::IndexMap; use rayon::prelude::*; use tesseract::Tesseract; @@ -295,14 +543,14 @@ mod test { #[test] fn single_image() { - let image = Reader::open(format!("test-images/{}.png", 1)) + let image = ImageReader::open(format!("test-images/{}.png", 1)) .unwrap() .decode() .unwrap(); let text = reward_image_to_reward_names(image, None); let text = text.iter().map(|s| normalize_string(s)); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let items: Vec<_> = text.map(|s| db.find_item(&s, None)).collect(); println!("{:#?}", items); @@ -330,7 +578,7 @@ mod test { let labels: IndexMap = serde_json::from_str(&read_to_string("WFI test images/labels.json").unwrap()).unwrap(); for (filename, label) in labels { - let image = Reader::open("WFI test images/".to_string() + &filename) + let image = ImageReader::open("WFI test images/".to_string() + &filename) .unwrap() .decode() .unwrap(); @@ -338,7 +586,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); @@ -365,7 +613,7 @@ mod test { let success_count: usize = labels .into_par_iter() .map(|(filename, label)| { - let image = Reader::open("WFI test images/".to_string() + &filename) + let image = ImageReader::open("WFI test images/".to_string() + &filename) .unwrap() .decode() .unwrap(); @@ -373,7 +621,7 @@ mod test { let text: Vec<_> = text.iter().map(|s| normalize_string(s)).collect(); println!("{:#?}", text); - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let items: Vec<_> = text.iter().map(|s| db.find_item(s, None)).collect(); println!("{:#?}", items); println!("{}", filename); @@ -401,7 +649,7 @@ mod test { fn images() { let tests = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; for i in tests { - let image = Reader::open(format!("test-images/{}.png", i)) + let image = ImageReader::open(format!("test-images/{}.png", i)) .unwrap() .decode() .unwrap(); diff --git a/src/bin/relics.rs b/src/bin/relics.rs index 83fae52..d951923 100644 --- a/src/bin/relics.rs +++ b/src/bin/relics.rs @@ -72,7 +72,7 @@ fn best_trace_dump(database: &Database) { } fn main() { - let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let mut args = std::env::args().skip(1); let relics = match args .next() diff --git a/src/bin/theme_tune.rs b/src/bin/theme_tune.rs index 0c9f54b..d2204f6 100644 --- a/src/bin/theme_tune.rs +++ b/src/bin/theme_tune.rs @@ -11,7 +11,7 @@ use eframe::{ epaint::ColorImage, }; use egui_extras::RetainedImage; -use image::{io::Reader, DynamicImage, Rgb}; +use image::{DynamicImage, ImageReader, Rgb}; use palette::{FromColor, Hsl, Srgb}; use wfinfo::{ database::Database, @@ -44,7 +44,7 @@ impl Default for MyApp { fn default() -> Self { let original_images = std::env::args() .skip(1) - .map(|name| Reader::open(name).unwrap().decode().unwrap()) + .map(|name| ImageReader::open(name).unwrap().decode().unwrap()) .collect(); let settings = HslRange { saturation: 0.50..1.0, @@ -78,7 +78,7 @@ fn spawn_ocr_thread( let images = images.to_owned(); thread::spawn(move || { - let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); loop { let (mut index, mut last_request): (usize, HslRange) = request_receiver.recv().unwrap(); diff --git a/src/database.rs b/src/database.rs index 0ae7fea..51be95a 100644 --- a/src/database.rs +++ b/src/database.rs @@ -125,7 +125,8 @@ impl Database { .collect(); if let Some(item) = items.iter_mut().find(|item| item.name == "Forma Blueprint") { - item.platinum = forma_multiplier.unwrap_or(1.0) * (forma_platinum_value.unwrap_or(35.0 / 3.0)); + item.platinum = + forma_multiplier.unwrap_or(1.0) * (forma_platinum_value.unwrap_or(35.0 / 3.0)); }; let relics = filtered_items.relics; @@ -284,12 +285,12 @@ mod test { #[test] pub fn can_load_database() { - Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); } #[test] pub fn can_find_items() { - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let item = db .find_item("TitaniaPrimeBlueprint", Some(0)) @@ -304,7 +305,7 @@ mod test { #[test] pub fn can_find_fuzzy_items() { - let db = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let db = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); let item = db .find_item("Akstlett Prlme Recver", None) @@ -324,7 +325,7 @@ mod test { #[test] fn validate_shared_relic_values() { - let database = Database::load_from_file(None, None, Some(1.0), Some(35.0/3.0)); + let database = Database::load_from_file(None, None, Some(1.0), Some(35.0 / 3.0)); for (name, relic) in database.relics.lith.iter() { println!("{} {:#?}", name, relic); diff --git a/src/ocr.rs b/src/ocr.rs index 0c793b6..13322c5 100644 --- a/src/ocr.rs +++ b/src/ocr.rs @@ -4,16 +4,179 @@ use std::f32::consts::PI; use std::{collections::HashMap, sync::Mutex}; use tesseract::Tesseract; +use image::imageops::colorops; use image::{DynamicImage, GenericImageView, Pixel, Rgb}; use log::debug; use crate::theme::Theme; +// Constants for reward screen dimensions in pixels at 1080p resolution const PIXEL_REWARD_WIDTH: f32 = 968.0; const PIXEL_REWARD_HEIGHT: f32 = 235.0; const PIXEL_REWARD_YDISPLAY: f32 = 316.0; const PIXEL_REWARD_LINE_HEIGHT: f32 = 48.0; +/// Represents a selected region on the screen +pub struct Selection { + pub x: i32, // Absolute X coordinate + pub y: i32, // Absolute Y coordinate + pub width: i32, // Width of selection + pub height: i32, // Height of selection +} + +/// Converts raw slop output (WxH+X+Y format) into a Selection struct +/// Example input: "100x200+300+400" -> Selection of 100x200 at (300,400) +pub fn slop_to_selection(slop_output: &str) -> Option { + // Parse the WxH+X+Y format, trimming any whitespace/newlines + let (dimensions, coordinates) = slop_output.trim().split_once('+')?; + let (width, height) = dimensions.split_once('x')?; + let (x, y) = coordinates.split_once('+')?; + + let x = x.parse().ok()?; + let y = y.parse().ok()?; + let width = width.parse().ok()?; + let height = height.parse().ok()?; + + debug!("Selection: {}x{} at {},{}", width, height, x, y); + Some(Selection { + x, + y, + width, + height, + }) +} + +/// Extracts a part of an image and enhances it for OCR +/// +/// # Arguments +/// * `image` - Source image to extract from +/// * `sel_size` - (width, height) of the selection +/// * `sel_pos` - (x, y) coordinates relative to the image +/// * `brightness` - Brightness adjustment (-255 to 255) +/// * `contrast` - Contrast adjustment (0.0 to 10.0) +pub fn extract_part( + image: &DynamicImage, + sel_size: (i32, i32), + sel_pos: (i32, i32), + brightness: i32, + contrast: f32, +) -> DynamicImage { + debug!("Processing image {}x{}", image.width(), image.height()); + + // Convert coordinates and ensure they're within bounds + let x = sel_pos.0.max(0) as u32; + let y = sel_pos.1.max(0) as u32; + let width = sel_size.0.max(0) as u32; + let height = sel_size.1.max(0) as u32; + + let width = width.min(image.width() - x); + let height = height.min(image.height() - y); + + debug!( + "Cropping region: x={}, y={}, w={}, h={}", + x, y, width, height + ); + let cropped = image.crop_imm(x, y, width, height); + + // Two-step image enhancement for better OCR: + // 1. Brighten to make text more visible + // 2. Increase contrast to separate text from background + let rgb = cropped.into_rgb8(); + let brightened = colorops::brighten(&rgb, brightness); + let enhanced = colorops::contrast(&brightened, contrast); + let enhanced = DynamicImage::ImageRgb8(enhanced); + + // Save debug image for troubleshooting + if let Err(e) = enhanced.save("debug_cropped.png") { + debug!("Failed to save debug image: {}", e); + } + + enhanced +} + +/// Performs OCR on an image using Tesseract +/// +/// # Arguments +/// * `tesseract` - Mutable reference to the Tesseract instance +/// * `image` - Image to perform OCR on +pub fn image_to_string(tesseract: &mut Option, image: &DynamicImage) -> String { + debug!("Running OCR on {}x{} image", image.width(), image.height()); + let mut ocr = tesseract.take().unwrap(); + + // Convert image to format required by Tesseract + let buffer = image.as_flat_samples_u8().unwrap(); + ocr = ocr + .set_frame( + buffer.samples, + image.width() as i32, + image.height() as i32, + 3, // RGB format (3 channels) + 3 * image.width() as i32, // Bytes per row (RGB * width) + ) + .expect("Failed to set image"); + + let result = ocr.get_text().expect("Failed to get text"); + debug!("OCR result: {}", result.trim()); + tesseract.replace(ocr); + + result +} + +/// Parameters for OCR selection processing +pub struct SelectionParams { + pub abs_x: i32, + pub abs_y: i32, + pub width: i32, + pub height: i32, + pub monitor_x: i32, + pub monitor_y: i32, + pub brightness: i32, + pub contrast: f32, +} + +/// Converts absolute screen coordinates to image-relative coordinates and performs OCR +/// +/// # Arguments +/// * `image` - Source image +/// * `params` - Selection parameters including coordinates, dimensions, and OCR settings +pub fn selection_to_part_name(image: DynamicImage, params: SelectionParams) -> Option { + // Convert absolute screen coordinates to image-relative coordinates + let x = params.abs_x - params.monitor_x; + let y = params.abs_y - params.monitor_y; + debug!( + "Processing selection: {}x{} at {},{}", + params.width, params.height, x, y + ); + + let text = part_image_to_part_name( + image, + None, + (params.width, params.height), + (x, y), + params.brightness, + params.contrast, + ); + if text.trim().is_empty() { + debug!("No text detected in selection"); + return None; + } + + Some(text) +} + +/// Removes all non-ASCII-alphabetic characters from a string +/// Used to normalize item names for database lookup +pub fn normalize_string(string: &str) -> String { + string.replace(|c: char| !c.is_ascii_alphabetic(), "") +} + +/// Detects the theme of a reward screen by analyzing pixel colors +/// +/// This function works by: +/// 1. Calculating screen scaling based on resolution +/// 2. Sampling pixels in the expected reward area +/// 3. Comparing colors to known theme colors +/// 4. Returning the most prevalent theme pub fn detect_theme(image: &DynamicImage) -> Theme { let screen_scaling = if image.width() * 9 > image.height() * 16 { image.height() as f32 / 1080.0 @@ -59,6 +222,14 @@ pub fn detect_theme(image: &DynamicImage) -> Theme { .to_owned() } +/// Extracts individual reward parts from a reward screen image +/// +/// This function: +/// 1. Calculates screen scaling to handle different resolutions +/// 2. Crops the image to the reward area +/// 3. Analyzes pixel rows to find text regions +/// 4. Uses scaling detection to handle UI size variations +/// 5. Returns a vector of cropped images, one for each reward pub fn extract_parts(image: &DynamicImage, theme: Theme) -> Vec { image.save("input.png").unwrap(); let screen_scaling = if image.width() * 9 > image.height() * 16 { @@ -248,6 +419,13 @@ pub fn extract_parts(image: &DynamicImage, theme: Theme) -> Vec { filter_and_separate_parts_from_part_box(partial_screenshot, theme) } +/// Filters and separates individual parts from a reward box +/// +/// This function: +/// 1. Converts the image to black and white based on theme colors +/// 2. Uses cosine analysis to detect reward spacing +/// 3. Determines if there are 3 or 4 rewards +/// 4. Splits the image into individual reward sections pub fn filter_and_separate_parts_from_part_box( image: DynamicImage, theme: Theme, @@ -328,35 +506,19 @@ pub fn filter_and_separate_parts_from_part_box( images } -pub fn normalize_string(string: &str) -> String { - string.replace(|c: char| !c.is_ascii_alphabetic(), "") -} - -pub fn image_to_string(tesseract: &mut Option, image: &DynamicImage) -> String { - let mut ocr = tesseract.take().unwrap(); - let buffer = image.as_flat_samples_u8().unwrap(); - ocr = ocr - .set_frame( - buffer.samples, - image.width() as i32, - image.height() as i32, - 3, - 3 * image.width() as i32, - ) - .expect("Failed to set image"); - - let result = ocr.get_text().expect("Failed to get text"); - tesseract.replace(ocr); - - result -} - lazy_static! { pub static ref OCR: Mutex> = Mutex::new(Some( Tesseract::new(None, Some("eng")).expect("Could not initialize Tesseract") )); } +/// Processes a reward screen image and returns the names of all rewards +/// +/// This function: +/// 1. Detects the theme if not provided +/// 2. Extracts individual reward parts +/// 3. Performs OCR on each part +/// 4. Returns a vector of reward names pub fn reward_image_to_reward_names(image: DynamicImage, theme: Option) -> Vec { let theme = theme.unwrap_or_else(|| detect_theme(&image)); let parts = extract_parts(&image, theme); @@ -367,3 +529,27 @@ pub fn reward_image_to_reward_names(image: DynamicImage, theme: Option) - .map(|image| image_to_string(&mut OCR.lock().unwrap(), image)) .collect() } + +/// Processes a single part image and returns its name +/// +/// # Arguments +/// * `image` - Source image +/// * `theme` - Optional theme for color processing +/// * `sel_size` - Selection dimensions (width, height) +/// * `sel_pos` - Selection position (x, y) +/// * `brightness` - OCR brightness adjustment (-255 to 255) +/// * `contrast` - OCR contrast adjustment (0.0 to 10.0) +pub fn part_image_to_part_name( + image: DynamicImage, + _theme: Option, + sel_size: (i32, i32), + sel_pos: (i32, i32), + brightness: i32, + contrast: f32, +) -> String { + let part = extract_part(&image, sel_size, sel_pos, brightness, contrast); + debug!("Extracted part image"); + + let text = image_to_string(&mut OCR.lock().unwrap(), &part); + text +} From a436796796608d1dd56358a3d77cb965dddb36c7 Mon Sep 17 00:00:00 2001 From: TYTheBeast Date: Tue, 24 Dec 2024 16:18:32 +0100 Subject: [PATCH 19/25] Remove debug image saving functionality from OCR and main modules to streamline code and reduce unnecessary file generation. --- src/bin/main.rs | 12 ------------ src/ocr.rs | 5 ----- 2 files changed, 17 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index cdddbad..785e1ca 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -118,12 +118,6 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option Option Date: Thu, 26 Dec 2024 19:07:34 +0100 Subject: [PATCH 20/25] refactor: unify event handling with AppEvent enum and centralized event loop --- src/app_events.rs | 6 ++ src/bin/main.rs | 157 ++++++++++++++++++++-------------------------- src/lib.rs | 1 + 3 files changed, 74 insertions(+), 90 deletions(-) create mode 100644 src/app_events.rs diff --git a/src/app_events.rs b/src/app_events.rs new file mode 100644 index 0000000..d28825e --- /dev/null +++ b/src/app_events.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone, Copy)] +pub enum AppEvent { + Detection, + Snapit, + LogTrigger, +} diff --git a/src/bin/main.rs b/src/bin/main.rs index 785e1ca..2ae32cd 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -21,10 +21,11 @@ use wfinfo::{ config::{BestItemMode, InfoDisplayMode}, database::Database, ocr::{ - extract_part, normalize_string, reward_image_to_reward_names, selection_to_part_name, - slop_to_selection, SelectionParams, OCR, + normalize_string, reward_image_to_reward_names, selection_to_part_name, slop_to_selection, + SelectionParams, OCR, }, utils::fetch_prices_and_items, + app_events::AppEvent, }; fn run_detection(capturer: &Window, db: &Database, arguments: &Arguments) { @@ -118,14 +119,6 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option Option { - let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let volatility = + (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), @@ -161,8 +155,10 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option Option { - let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let volatility = + (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), @@ -187,8 +184,10 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option Option { - let volatility = (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; + let volatility = + (item.yesterday_vol.saturating_sub(item.today_vol)) as f32 * item.platinum; let (plat_fmt, ducat_fmt) = match arguments.best_item_mode { BestItemMode::Platinum => ("\x1b[1;32m", "\x1b[0m"), BestItemMode::Ducats => ("\x1b[0m", "\x1b[1;32m"), @@ -232,7 +232,7 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option) { +fn log_watcher(path: PathBuf, event_sender: mpsc::Sender) { debug!("Path: {}", path.display()); let mut position = File::open(&path) .unwrap_or_else(|_| panic!("Couldn't open file {}", path.display())) @@ -277,7 +277,7 @@ fn log_watcher(path: PathBuf, event_sender: mpsc::Sender<()>) { if reward_screen_detected { info!("Detected, waiting..."); sleep(Duration::from_millis(1500)); - event_sender.send(()).unwrap(); + event_sender.send(AppEvent::LogTrigger).unwrap(); } position = f.metadata().unwrap().len(); @@ -292,6 +292,37 @@ fn log_watcher(path: PathBuf, event_sender: mpsc::Sender<()>) { }); } +fn setup_hotkeys( + detection_hotkey: HotKey, + snapit_hotkey: HotKey, + event_sender: mpsc::Sender, +) { + debug!("Setting up hotkeys"); + thread::spawn(move || { + let manager = GlobalHotKeyManager::new().unwrap(); + + manager.register(detection_hotkey).unwrap(); + manager.register(snapit_hotkey).unwrap(); + + let event_receiver = GlobalHotKeyEvent::receiver(); + while let Ok(event) = event_receiver.recv() { + debug!("{:?}", event); + if event.state == HotKeyState::Pressed { + let app_event = if event.id == detection_hotkey.id() { + debug!("Detection triggered"); + AppEvent::Detection + } else if event.id == snapit_hotkey.id() { + debug!("Snap-it triggered"); + AppEvent::Snapit + } else { + continue; + }; + event_sender.send(app_event).unwrap(); + } + } + }); +} + #[allow(dead_code)] fn benchmark() -> Result<(), Box> { for _ in 0..10 { @@ -390,52 +421,6 @@ pub struct Arguments { pub ocr_contrast: f32, } -fn setup_hotkeys( - detection_hotkey: HotKey, - snapit_hotkey: HotKey, - detection_sender: mpsc::Sender<()>, - snapit_sender: mpsc::Sender<()>, -) -> Result> { - let hotkey_manager = GlobalHotKeyManager::new()?; - debug!( - "Registering hotkeys - F12: {}, F10: {}", - detection_hotkey.id(), - snapit_hotkey.id() - ); - - hotkey_manager.register(detection_hotkey)?; - hotkey_manager.register(snapit_hotkey)?; - - let detection_id = detection_hotkey.id(); - let snapit_id = snapit_hotkey.id(); - - // Single thread for handling both hotkeys - let receiver = GlobalHotKeyEvent::receiver(); - thread::spawn(move || { - while let Ok(event) = receiver.recv() { - if event.state == HotKeyState::Pressed { - match event.id { - id if id == detection_id => { - debug!("Detection hotkey pressed"); - if let Err(e) = detection_sender.send(()) { - error!("Failed to send detection event: {}", e); - } - } - id if id == snapit_id => { - debug!("Snapit hotkey pressed"); - if let Err(e) = snapit_sender.send(()) { - error!("Failed to send snapit event: {}", e); - } - } - _ => {} - } - } - } - }); - - Ok(hotkey_manager) -} - fn main() -> Result<(), Box> { let arguments = Arguments::parse(); let default_log_path = PathBuf::from_str(&std::env::var("HOME").unwrap()).unwrap().join(PathBuf::from_str(".local/share/Steam/steamapps/compatdata/230410/pfx/drive_c/users/steamuser/AppData/Local/Warframe/EE.log")?); @@ -475,38 +460,30 @@ fn main() -> Result<(), Box> { info!("Loaded database"); - let (detection_sender, detection_receiver) = channel(); - let (snapit_sender, snapit_receiver) = channel(); - - log_watcher(log_path.clone(), detection_sender.clone()); + let (event_sender, event_receiver) = channel(); - // Setup hotkeys - let _hotkey_manager = setup_hotkeys( + log_watcher(log_path.clone(), event_sender.clone()); + + setup_hotkeys( arguments.detection_hotkey, arguments.snapit_hotkey, - detection_sender, - snapit_sender, - )?; - - loop { - // Check detection receiver - match detection_receiver.try_recv() { - Ok(()) => { - run_detection(warframe_window, &db, &arguments); - } - Err(mpsc::TryRecvError::Empty) => {} - Err(mpsc::TryRecvError::Disconnected) => break, - } + event_sender, + ); - // Check snapit receiver - match snapit_receiver.try_recv() { - Ok(()) => { + while let Ok(event) = event_receiver.recv() { + debug!("Processing event: {:?}", event); + match event { + AppEvent::Snapit => { + info!("Snapping it"); run_snapit(warframe_window, &db, &arguments); } - Err(mpsc::TryRecvError::Empty) => { - thread::sleep(Duration::from_millis(arguments.sleep_duration)); + AppEvent::Detection => { + info!("Capturing"); + run_detection(warframe_window, &db, &arguments) + } + AppEvent::LogTrigger => { + run_detection(warframe_window, &db, &arguments) } - Err(mpsc::TryRecvError::Disconnected) => break, } } diff --git a/src/lib.rs b/src/lib.rs index 531d5ac..6964249 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ pub mod testing; pub mod theme; pub mod utils; pub mod wfinfo_data; +pub mod app_events; \ No newline at end of file From 581b85f96cdbeb22323c0d44e564bdabb21057d9 Mon Sep 17 00:00:00 2001 From: xNefas Date: Sun, 23 Feb 2025 12:33:31 +0100 Subject: [PATCH 21/25] chore: update .gitignore to include Nix configuration files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c487e7..ce77ef0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target *.png filtered_items.json -prices.json \ No newline at end of file +prices.json +*.nix \ No newline at end of file From 0167c4269ae48ef32ed51eb1e8fcd51e2cdb748a Mon Sep 17 00:00:00 2001 From: xNefas Date: Fri, 28 Feb 2025 14:11:54 +0100 Subject: [PATCH 22/25] chore: ignore direnv configuration files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ce77ef0..b74518e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ *.png filtered_items.json prices.json -*.nix \ No newline at end of file +*.nix +.envrc +.direnv \ No newline at end of file From 7740acc241f29f813a6c4607dc6bdda71242a529 Mon Sep 17 00:00:00 2001 From: xNefas Date: Tue, 4 Mar 2025 23:22:08 +0100 Subject: [PATCH 23/25] chore: remove direnv and Nix configuration files from .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index b74518e..737cb18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,3 @@ *.png filtered_items.json prices.json -*.nix -.envrc -.direnv \ No newline at end of file From 66627aff782043771b22ee251c06aeb77ecfaf47 Mon Sep 17 00:00:00 2001 From: xNefas Date: Thu, 6 Mar 2025 20:43:48 +0100 Subject: [PATCH 24/25] feat: improve error handling for slop command and image processing --- src/bin/main.rs | 15 ++++++++++++--- src/ocr.rs | 5 ++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 2ae32cd..3d7ee99 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -101,10 +101,19 @@ fn run_snapit(window: &Window, db: &Database, arguments: &Arguments) -> Option output, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + error!("Could not find 'slop' command. Please install slop using your system package manager (e.g., 'sudo apt install slop' or 'nix-env -iA slop')"); + return None; + } + Err(e) => { + error!("Failed to run slop: {}", e); + return None; + } + }; let slop_output = String::from_utf8_lossy(&slop_output.stdout); debug!("Slop output: {}", slop_output); diff --git a/src/ocr.rs b/src/ocr.rs index b823417..d561d5f 100644 --- a/src/ocr.rs +++ b/src/ocr.rs @@ -84,9 +84,8 @@ pub fn extract_part( let rgb = cropped.into_rgb8(); let brightened = colorops::brighten(&rgb, brightness); let enhanced = colorops::contrast(&brightened, contrast); - let enhanced = DynamicImage::ImageRgb8(enhanced); - - enhanced + + DynamicImage::ImageRgb8(enhanced) } /// Performs OCR on an image using Tesseract From 037245df35e53bfdade7def38abcf24491a7274c Mon Sep 17 00:00:00 2001 From: xNefas Date: Thu, 6 Mar 2025 20:43:59 +0100 Subject: [PATCH 25/25] chore: restore .gitignore entries for development environment tools --- .envrc | 1 + .gitignore | 10 ++++++++++ shell.nix | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 .envrc create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index 737cb18..669811b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,13 @@ *.png filtered_items.json prices.json + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..631f52b --- /dev/null +++ b/shell.nix @@ -0,0 +1,34 @@ +{}: let + rust-overlay = (import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz")); + pkgs = (import { + overlays = [ rust-overlay ]; + }); +in + pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + (rust-bin.stable.latest.default.override { + extensions = [ "rust-src" ]; + }) + pkg-config + cmake + rustPlatform.bindgenHook + ]; + + buildInputs = with pkgs; [ + pkg-config + cmake + rustPlatform.bindgenHook + openssl + dbus + fontconfig + leptonica + openssl + # slop + tesseract + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + xorg.libXtst + ]; + }