From 82e92d3cd70a3223075ff0ec2762bfb0f70dfcca Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:56:12 +0100 Subject: [PATCH 01/65] init --- Cargo.lock | 2 + Cargo.toml | 21 ++++--- src/app.rs | 160 ++++++++++++++++++++++++++++++++++++++++-------- src/commands.rs | 40 +++++++----- src/main.rs | 23 +++---- src/utils.rs | 79 ++++++++++++++++++++++++ 6 files changed, 264 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35240ed..3c8ac70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3411,6 +3411,8 @@ dependencies = [ "serde", "tokio", "toml", + "windows 0.58.0", + "winreg", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8d32950..06a8cae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,20 @@ name = "rustcast" version = "0.1.0" edition = "2024" + +[target.'cfg(target_os = "windows")'.dependencies] +winreg = "0.52" +windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] } + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6.3" +objc2-app-kit = "0.3.2" +objc2-application-services = { version = "0.3.2", default-features = false, features = [ + "HIServices", + "Processes", +] } +objc2-foundation = { version = "0.3.2", features = ["NSString"] } + [dependencies] anyhow = "1.0.100" applications = "0.3.1" @@ -11,13 +25,6 @@ global-hotkey = "0.7.0" iced = { version = "0.14.0", features = ["image", "smol", "tokio"] } icns = "0.3.1" image = "0.25.9" -objc2 = "0.6.3" -objc2-app-kit = "0.3.2" -objc2-application-services = { version = "0.3.2", default-features = false, features = [ - "HIServices", - "Processes", -] } -objc2-foundation = { version = "0.3.2", features = ["NSString"] } rand = "0.9.2" rayon = "1.11.0" serde = { version = "1.0.228", features = ["derive"] } diff --git a/src/app.rs b/src/app.rs index 93f88f8..29c338c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,5 @@ use crate::commands::Function; use crate::config::Config; -use crate::macos::{focus_this_app, transform_process_to_ui_element}; -use crate::{macos, utils::get_installed_apps}; - use global_hotkey::{GlobalHotKeyEvent, HotKeyState}; use iced::futures::SinkExt; use iced::{ @@ -17,9 +14,22 @@ use iced::{ }, window::{self, Id, Settings}, }; +use std::path::Path; +#[cfg(target_os = "macos")] +use crate::macos::{focus_this_app, transform_process_to_ui_element}; +#[cfg(target_os = "macos")] +use crate::{macos, utils::get_installed_apps}; +#[cfg(target_os = "macos")] use objc2::rc::Retained; +#[cfg(target_os = "macos")] use objc2_app_kit::NSRunningApplication; + +#[cfg(target_os = "windows")] +use windows::Win32::Foundation::HWND; +#[cfg(target_os = "windows")] +use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}; + use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::slice::ParallelSliceMut; @@ -135,6 +145,9 @@ pub fn default_settings() -> Settings { } } +#[derive(Debug, Clone)] +pub struct Temp {} + #[derive(Debug, Clone)] pub struct Tile { theme: iced::Theme, @@ -145,7 +158,10 @@ pub struct Tile { options: Vec, visible: bool, focused: bool, + #[cfg(target_os = "macos")] frontmost: Option>, + #[cfg(target_os = "windows")] + frontmost: Option, config: Config, open_hotkey_id: u32, } @@ -156,27 +172,46 @@ impl Tile { let (id, open) = window::open(default_settings()); let open = open.discard().chain(window::run(id, |handle| { - macos::macos_window_config( - &handle.window_handle().expect("Unable to get window handle"), - ); - // should work now that we have a window - transform_process_to_ui_element(); + #[cfg(target_os = "macos")] + { + macos::macos_window_config( + &handle.window_handle().expect("Unable to get window handle"), + ); + // should work now that we have a window + transform_process_to_ui_element(); + }; })); let store_icons = config.theme.show_icons; - - let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; - - let paths = vec![ - "/Applications/", - user_local_path.as_str(), - "/System/Applications/", - "/System/Applications/Utilities/", - ]; + let paths; + + #[cfg(target_os = "macos")] + { + let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; + paths = vec![ + "/Applications/", + user_local_path.as_str(), + "/System/Applications/", + "/System/Applications/Utilities/", + ]; + } + #[cfg(target_os = "windows")] + { + paths = vec!["C:\\Program Files\\", "C:\\Program Files (x86)\\"]; + } let mut options: Vec = paths .par_iter() - .map(|path| get_installed_apps(path, store_icons)) + .map(|path| { + #[cfg(target_os = "macos")] + { + get_installed_apps(path, store_icons) + } + #[cfg(target_os = "windows")] + { + get_installed_windows_app(Path::new(path)) + } + }) .flatten() .collect(); @@ -206,7 +241,10 @@ impl Tile { match message { Message::OpenWindow => { self.capture_frontmost(); - focus_this_app(); + #[cfg(target_os = "macos")] + { + focus_this_app(); + } self.focused = true; Task::none() } @@ -272,6 +310,7 @@ impl Tile { Message::ClearSearchQuery => { self.query_lc = String::new(); self.query = String::new(); + self.prev_query_lc = String::new(); Task::none() } @@ -445,18 +484,38 @@ impl Tile { } pub fn capture_frontmost(&mut self) { - use objc2_app_kit::NSWorkspace; + #[cfg(target_os = "macos")] + { + use objc2_app_kit::NSWorkspace; + + let ws = NSWorkspace::sharedWorkspace(); + self.frontmost = ws.frontmostApplication(); + }; - let ws = NSWorkspace::sharedWorkspace(); - self.frontmost = ws.frontmostApplication(); + #[cfg(target_os = "windows")] + { + self.frontmost = Some(unsafe { GetForegroundWindow() }); + } } #[allow(deprecated)] pub fn restore_frontmost(&mut self) { - use objc2_app_kit::NSApplicationActivationOptions; + #[cfg(target_os = "macos")] + { + use objc2_app_kit::NSApplicationActivationOptions; - if let Some(app) = self.frontmost.take() { - app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); + if let Some(app) = self.frontmost.take() { + app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); + } + } + + #[cfg(target_os = "windows")] + { + if let Some(hwnd) = self.frontmost.take() { + unsafe { + let _ = SetForegroundWindow(hwnd); + }; + } } } } @@ -494,3 +553,54 @@ fn handle_hotkeys() -> impl futures::Stream { } }) } + +fn get_installed_windows_app(path: &Path) -> Vec { + use std::ffi::OsString; + + let mut apps = Vec::new(); + + let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + + // where we can find installed applications + // src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848 + let registers = [ + hkey.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall") + .unwrap(), + hkey.open_subkey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall") + .unwrap(), + ]; + + registers.iter().for_each(|reg| { + reg.enum_keys().for_each(|key| { + // https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key + let name = key.unwrap(); + let key = reg.open_subkey(&name).unwrap(); + let display_name = key.get_value("DisplayName").unwrap_or(OsString::new()); + + // they might be useful one day ? + // let publisher = key.get_value("Publisher").unwrap_or(OsString::new()); + // let version = key.get_value("DisplayVersion").unwrap_or(OsString::new()); + + // Trick, I saw on internet to point to the exe location.. + let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new()); + if (exe_path.is_empty()) { + return; + } + // if there is something, it will be in the form of + // "C:\Program Files\Microsoft Office\Office16\WINWORD.EXE",0 + let exe_path = exe_path.to_string_lossy().to_string(); + let exe = exe_path.split(",").next().unwrap().to_string(); + + if !display_name.is_empty() { + apps.push(App { + open_command: Function::OpenApp(exe), + name: display_name.clone().into_string().unwrap(), + name_lc: display_name.clone().into_string().unwrap().to_lowercase(), + icons: None, + }) + } + }); + }); + + apps +} diff --git a/src/commands.rs b/src/commands.rs index 72d614d..8912838 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,10 +1,14 @@ use std::process::Command; use arboard::Clipboard; + +#[cfg(target_os = "macos")] use objc2_app_kit::NSWorkspace; +#[cfg(target_os = "macos")] use objc2_foundation::NSURL; use crate::config::Config; +use crate::utils::{get_config_file_path, open_application}; #[derive(Debug, Clone)] pub enum Function { @@ -20,9 +24,7 @@ impl Function { pub fn execute(&self, config: &Config) { match self { Function::OpenApp(path) => { - NSWorkspace::new().openURL(&NSURL::fileURLWithPath( - &objc2_foundation::NSString::from_str(path), - )); + open_application(path); } Function::RunShellCommand(shell_command) => { Command::new("sh") @@ -40,22 +42,30 @@ impl Function { Function::GoogleSearch(query_string) => { let query_args = query_string.replace(" ", "+"); - let query = config.search_url.replace("%s", &query_args); - NSWorkspace::new().openURL( - &NSURL::URLWithString_relativeToURL( - &objc2_foundation::NSString::from_str(&query), - None, - ) - .unwrap(), - ); + let mut query = config.search_url.replace("%s", &query_args); + query = query[..query.len() - 1].to_string(); + #[cfg(target_os = "windows")] + { + Command::new("powershell") + .args(["-Command", &format!("Start-Process {}", query)]) + .status() + .ok(); + } + #[cfg(target_os = "macos")] + { + NSWorkspace::new().openURL( + &NSURL::URLWithString_relativeToURL( + &objc2_foundation::NSString::from_str(&query), + None, + ) + .unwrap(), + ); + }; } Function::OpenPrefPane => { Command::new("open") - .arg( - std::env::var("HOME").unwrap_or("".to_string()) - + "/.config/rustcast/config.toml", - ) + .arg(get_config_file_path()) .spawn() .ok(); } diff --git a/src/main.rs b/src/main.rs index f9ac01b..30fe0f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ mod app; mod commands; mod config; +#[cfg(target_os = "macos")] mod macos; mod utils; -use crate::{app::Tile, config::Config, utils::to_key_code}; +// import from utils +use crate::utils::{create_config_file, get_config_file_path, read_config_file}; + +use crate::{app::Tile, utils::to_key_code}; use global_hotkey::{ GlobalHotKeyManager, @@ -17,19 +21,10 @@ fn main() -> iced::Result { macos::set_activation_policy_accessory(); } - let home = std::env::var("HOME").unwrap(); - - let file_path = home.clone() + "/.config/rustcast/config.toml"; - let config: Config = match std::fs::read_to_string(&file_path) { - Ok(a) => toml::from_str(&a).unwrap(), - Err(_) => Config::default(), - }; - std::fs::create_dir_all(home + "/.config/rustcast").unwrap(); - std::fs::write( - &file_path, - toml::to_string(&config).unwrap_or_else(|x| x.to_string()), - ) - .unwrap(); + let file_path = get_config_file_path(); + let config = read_config_file(&file_path).unwrap(); + create_config_file(&file_path, &config).unwrap(); + let manager = GlobalHotKeyManager::new().unwrap(); let show_hide = HotKey::new( diff --git a/src/utils.rs b/src/utils.rs index fc8e647..5fec7e8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -287,3 +287,82 @@ pub fn to_key_code(key_str: &str) -> Option { _ => None, } } + +pub fn get_config_installation_dir() -> String { + let path = if cfg!(target_os = "windows") { + std::env::var("LOCALAPPDATA").unwrap() + } else { + std::env::var("HOME").unwrap() + }; + + return path; +} + +pub fn get_config_file_path() -> String { + let home = get_config_installation_dir(); + let file_path = if cfg!(target_os = "windows") { + home + "\\rustcast\\config.toml" + } else { + home + "/.rustcast/config.toml" + }; + + return file_path; +} +use crate::config::Config; + +pub fn read_config_file(file_path: &str) -> Result { + let config: Config = match std::fs::read_to_string(&file_path) { + Ok(a) => toml::from_str(&a).unwrap(), + Err(_) => Config::default(), + }; + + Ok(config) +} + +pub fn create_config_file(file_path: &str, config: &Config) -> Result<(), std::io::Error> { + #[cfg(target_os = "windows")] + { + use std::path::Path; + let path = Path::new(&file_path); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + } + #[cfg(target_os = "macos")] + { + std::fs::create_dir_all(&file_path).unwrap(); + } + std::fs::write( + &file_path, + toml::to_string(&config).unwrap_or_else(|x| x.to_string()), + ) + .unwrap(); + + Ok(()) +} + +pub fn open_application(path: &String) { + #[cfg(target_os = "windows")] + { + use std::process::Command; + + println!("Opening application: {}", path); + + Command::new("powershell") + .arg(format!("Start-Process '{}'", path)) + .status() + .ok(); + } + + #[cfg(target_os = "macos")] + { + NSWorkspace::new().openURL(&NSURL::fileURLWithPath( + &objc2_foundation::NSString::from_str(path), + )); + } + + #[cfg(target_os = "linux")] + { + Command::new("xdg-open").arg(path).status().ok(); + } +} From 189b476206e42076fe5a56589273cc01f4a6f9dd Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:00:29 +0100 Subject: [PATCH 02/65] chore(app.rs): gets config file from new function.. --- src/app.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app.rs b/src/app.rs index 29c338c..dca5cbc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ use crate::commands::Function; use crate::config::Config; +use crate::utils::get_config_file_path; use global_hotkey::{GlobalHotKeyEvent, HotKeyState}; use iced::futures::SinkExt; use iced::{ @@ -316,11 +317,7 @@ impl Tile { Message::ReloadConfig => { self.config = toml::from_str( - &fs::read_to_string( - std::env::var("HOME").unwrap_or("".to_owned()) - + "/.config/rustcast/config.toml", - ) - .unwrap_or("".to_owned()), + &fs::read_to_string(get_config_file_path()).unwrap_or("".to_owned()), ) .unwrap(); From 597deba52e4f4734c1b9678f8e5a5e8bfe2b500b Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:05:56 +0100 Subject: [PATCH 03/65] fix(utils.rs): We do not need to create the file if it already exists... --- src/main.rs | 4 ++-- src/utils.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 30fe0f9..560010b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod macos; mod utils; // import from utils -use crate::utils::{create_config_file, get_config_file_path, read_config_file}; +use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; use crate::{app::Tile, utils::to_key_code}; @@ -23,7 +23,7 @@ fn main() -> iced::Result { let file_path = get_config_file_path(); let config = read_config_file(&file_path).unwrap(); - create_config_file(&file_path, &config).unwrap(); + create_config_file_if_not_exists(&file_path, &config).unwrap(); let manager = GlobalHotKeyManager::new().unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 5fec7e8..b9f326f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -319,7 +319,14 @@ pub fn read_config_file(file_path: &str) -> Result { Ok(config) } -pub fn create_config_file(file_path: &str, config: &Config) -> Result<(), std::io::Error> { +pub fn create_config_file_if_not_exists( + file_path: &str, + config: &Config, +) -> Result<(), std::io::Error> { + if std::path::Path::new(&get_config_file_path()).exists() { + return Ok(()); + } + #[cfg(target_os = "windows")] { use std::path::Path; From 0e6add6434fb0a560608856fae6f671bc903eb39 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:15:25 +0100 Subject: [PATCH 04/65] fix(Windows/app.rs) not sure what I was doing there, but this should be correct --- src/app.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index dca5cbc..f5e8ee8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -246,6 +246,7 @@ impl Tile { { focus_this_app(); } + self.focused = true; Task::none() } @@ -507,13 +508,13 @@ impl Tile { } #[cfg(target_os = "windows")] - { - if let Some(hwnd) = self.frontmost.take() { - unsafe { - let _ = SetForegroundWindow(hwnd); - }; - } - } + { + if let Some(handle) = self.frontmost { + unsafe { + let _ = SetForegroundWindow(handle); + } + } + } } From 5d47c49277754aad070dcea8390f49c1c5da5128 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:22:43 +0100 Subject: [PATCH 05/65] fix(Windows/commands.rs) Brought back windows way to open google.. --- src/commands.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index ff38d4c..b56be1f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -44,13 +44,25 @@ impl Function { let query_args = query_string.replace(" ", "+"); let query = config.search_url.replace("%s", &query_args); let query = query.strip_suffix("?").unwrap_or(&query); - NSWorkspace::new().openURL( - &NSURL::URLWithString_relativeToURL( - &objc2_foundation::NSString::from_str(query), - None, - ) - .unwrap(), - ); + + #[cfg(target_os = "windows")] + { + Command::new("powershell") + .args(["-Command", &format!("Start-Process {}", query)]) + .status() + .ok(); + } + + #[cfg(target_os = "macos")] + { + NSWorkspace::new().openURL( + &NSURL::URLWithString_relativeToURL( + &objc2_foundation::NSString::from_str(query), + None, + ) + .unwrap(), + ); + } } Function::OpenPrefPane => { From 03d683a336545d0d12392e2d703cf81cf94a305a Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:31:22 +0100 Subject: [PATCH 06/65] format --- src/app.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index f5e8ee8..02e7f8a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -508,13 +508,13 @@ impl Tile { } #[cfg(target_os = "windows")] - { - if let Some(handle) = self.frontmost { - unsafe { - let _ = SetForegroundWindow(handle); - } - } - + { + if let Some(handle) = self.frontmost { + unsafe { + let _ = SetForegroundWindow(handle); + } + } + } } } @@ -581,7 +581,7 @@ fn get_installed_windows_app(path: &Path) -> Vec { // Trick, I saw on internet to point to the exe location.. let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new()); - if (exe_path.is_empty()) { + if exe_path.is_empty() { return; } // if there is something, it will be in the form of From d96c44041f34be04a501b4dff18d3a52a0c5c40c Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:33:59 +0100 Subject: [PATCH 07/65] feat(windows): centering Now when we open the app via the shortcut it centers it and also make it appear on the right monitor (where mouse cursor is). --- Cargo.toml | 2 +- src/app.rs | 29 +++++++++++++++++++++++------ src/utils.rs | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06a8cae..ea3abbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [target.'cfg(target_os = "windows")'.dependencies] winreg = "0.52" -windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] } +windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi"] } [target.'cfg(target_os = "macos")'.dependencies] objc2 = "0.6.3" diff --git a/src/app.rs b/src/app.rs index 02e7f8a..4b57451 100644 --- a/src/app.rs +++ b/src/app.rs @@ -329,12 +329,29 @@ impl Tile { if hk_id == self.open_hotkey_id { self.visible = !self.visible; if self.visible { - Task::chain( - window::open(default_settings()) - .1 - .map(|_| Message::OpenWindow), - operation::focus("query"), - ) + #[cfg(target_os = "windows")] + { + // get normal settings and modify position + use crate::utils::open_on_focused_monitor; + use iced::window::Position::Specific; + let pos = open_on_focused_monitor(); + let mut settings = default_settings(); + settings.position = Specific(pos); + Task::chain( + window::open(settings).1.map(|_| Message::OpenWindow), + operation::focus("query"), + ) + } + + #[cfg(target_os = "macos")] + { + Task::chain( + window::open(default_settings()) + .1 + .map(|_| Message::OpenWindow), + operation::focus("query"), + ) + } } else { let to_close = window::latest().map(|x| x.unwrap()); Task::batch([ diff --git a/src/utils.rs b/src/utils.rs index b9f326f..d5693f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,10 +6,14 @@ use std::{ }; use global_hotkey::hotkey::Code; -use iced::widget::image::Handle; +use iced::{futures::io::Window, widget::image::Handle}; use icns::IconFamily; use image::RgbaImage; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use windows::Win32::{ + Graphics::Gdi::MonitorFromPoint, + UI::WindowsAndMessaging::{GetCursor, GetCursorPos}, +}; use crate::{app::App, commands::Function}; @@ -373,3 +377,35 @@ pub fn open_application(path: &String) { Command::new("xdg-open").arg(path).status().ok(); } } + +struct WindowPos { + x: f32, + y: f32, +} + +use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; +pub fn open_on_focused_monitor() -> iced::Point { + use windows::Win32::Foundation::POINT; + use windows::Win32::Graphics::Gdi::{ + GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, + }; + let mut point = POINT { x: 0, y: 0 }; + let mut monitor_info = MONITORINFO { + cbSize: std::mem::size_of::() as u32, + ..Default::default() + }; + + let cursor = unsafe { GetCursorPos(&mut point) }; + let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }; + let monitor_infos = unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; + + let monitor_width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; + let monitor_height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; + let window_width = WINDOW_WIDTH; + let window_height = DEFAULT_WINDOW_HEIGHT; + + let x = monitor_info.rcMonitor.left as f32 + (monitor_width as f32 - window_width) / 2.0; + let y = monitor_info.rcMonitor.top as f32 + (monitor_height as f32 - window_height) / 2.0; + + return iced::Point { x, y }; +} From df924f396248ca4c9ebb9a47ef5c4fad1e1e187d Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:36:27 +0100 Subject: [PATCH 08/65] remove(unused struct) removed an used struct gotta keep things clean --- src/utils.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index d5693f5..466d171 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -378,11 +378,6 @@ pub fn open_application(path: &String) { } } -struct WindowPos { - x: f32, - y: f32, -} - use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; pub fn open_on_focused_monitor() -> iced::Point { use windows::Win32::Foundation::POINT; From 6a06803bd094261c7ac4013c00c38edc9c06da2c Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:46:08 +0100 Subject: [PATCH 09/65] chore(windows/app.rs) on launch of the app, it would not center it nor take it to the right screen, now it does. --- src/app.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index 4b57451..d32efb8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -170,18 +170,30 @@ pub struct Tile { impl Tile { /// A base window pub fn new(keybind_id: u32, config: &Config) -> (Self, Task) { - let (id, open) = window::open(default_settings()); + let mut settings = default_settings(); + #[cfg(target_os = "windows")] + { + // get normal settings and modify position + use crate::utils::open_on_focused_monitor; + use iced::window::Position::Specific; + let pos = open_on_focused_monitor(); + settings.position = Specific(pos); + } - let open = open.discard().chain(window::run(id, |handle| { - #[cfg(target_os = "macos")] - { - macos::macos_window_config( - &handle.window_handle().expect("Unable to get window handle"), - ); - // should work now that we have a window - transform_process_to_ui_element(); - }; - })); + let (id, open) = window::open(settings); + + #[cfg(target_os = "macos")] + { + let open = open.discard().chain(window::run(id, |handle| { + { + macos::macos_window_config( + &handle.window_handle().expect("Unable to get window handle"), + ); + // should work now that we have a window + transform_process_to_ui_element(); + } + })); + } let store_icons = config.theme.show_icons; let paths; From cbf77e4da655f2821bf38ffccff3818efb0a96f4 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:09:17 +0100 Subject: [PATCH 10/65] fix(macos) now compiles again on macos, it was mostly cfg stuff.. --- src/app.rs | 15 ++++++++------- src/commands.rs | 1 - src/utils.rs | 33 ++++++++++++++++++--------------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/app.rs b/src/app.rs index d32efb8..20ba0d1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -182,9 +182,9 @@ impl Tile { let (id, open) = window::open(settings); - #[cfg(target_os = "macos")] - { - let open = open.discard().chain(window::run(id, |handle| { + let open = open.discard().chain(window::run(id, |handle| { + { + #[cfg(target_os = "macos")] { macos::macos_window_config( &handle.window_handle().expect("Unable to get window handle"), @@ -192,15 +192,15 @@ impl Tile { // should work now that we have a window transform_process_to_ui_element(); } - })); - } + } + })); let store_icons = config.theme.show_icons; let paths; - + let user_local_path; #[cfg(target_os = "macos")] { - let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; + user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; paths = vec![ "/Applications/", user_local_path.as_str(), @@ -581,6 +581,7 @@ fn handle_hotkeys() -> impl futures::Stream { }) } +#[cfg(target_os = "windows")] fn get_installed_windows_app(path: &Path) -> Vec { use std::ffi::OsString; diff --git a/src/commands.rs b/src/commands.rs index b56be1f..c29e250 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,7 +1,6 @@ use std::process::Command; use arboard::Clipboard; - #[cfg(target_os = "macos")] use objc2_app_kit::NSWorkspace; #[cfg(target_os = "macos")] diff --git a/src/utils.rs b/src/utils.rs index 466d171..b7cb5bb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,17 +5,24 @@ use std::{ process::exit, }; +use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; use global_hotkey::hotkey::Code; use iced::{futures::io::Window, widget::image::Handle}; use icns::IconFamily; use image::RgbaImage; use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +#[cfg(target_os = "windows")] use windows::Win32::{ Graphics::Gdi::MonitorFromPoint, UI::WindowsAndMessaging::{GetCursor, GetCursorPos}, }; use crate::{app::App, commands::Function}; +#[cfg(target_os = "macos")] +use objc2_app_kit::NSWorkspace; +#[cfg(target_os = "macos")] +use objc2_foundation::NSURL; const ERR_LOG_PATH: &str = "/tmp/rustscan-err.log"; @@ -307,7 +314,7 @@ pub fn get_config_file_path() -> String { let file_path = if cfg!(target_os = "windows") { home + "\\rustcast\\config.toml" } else { - home + "/.rustcast/config.toml" + home + "/.config/rustcast/config.toml" }; return file_path; @@ -327,22 +334,18 @@ pub fn create_config_file_if_not_exists( file_path: &str, config: &Config, ) -> Result<(), std::io::Error> { - if std::path::Path::new(&get_config_file_path()).exists() { - return Ok(()); - } - - #[cfg(target_os = "windows")] - { - use std::path::Path; - let path = Path::new(&file_path); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).unwrap(); + // check if file exists + if let Ok(exists) = std::fs::metadata(&file_path) { + if exists.is_file() { + return Ok(()); } } - #[cfg(target_os = "macos")] - { - std::fs::create_dir_all(&file_path).unwrap(); + + let path = Path::new(&file_path); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap(); } + std::fs::write( &file_path, toml::to_string(&config).unwrap_or_else(|x| x.to_string()), @@ -378,7 +381,7 @@ pub fn open_application(path: &String) { } } -use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; +#[cfg(target_os = "windows")] pub fn open_on_focused_monitor() -> iced::Point { use windows::Win32::Foundation::POINT; use windows::Win32::Graphics::Gdi::{ From c071eb63b3cc5e5bd0407874b14c7a8080688507 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:15:52 +0100 Subject: [PATCH 11/65] fix(windows/app.rs) I fixed type annotation on Windows or it would not compile. --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 20ba0d1..2a8a360 100644 --- a/src/app.rs +++ b/src/app.rs @@ -197,7 +197,7 @@ impl Tile { let store_icons = config.theme.show_icons; let paths; - let user_local_path; + let user_local_path: String; #[cfg(target_os = "macos")] { user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; From fbb7eb3270e3638a980b996e831e8d9a651e562e Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:29:05 +0100 Subject: [PATCH 12/65] fix(windows): focus app focuses the app when opened. --- src/app.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app.rs b/src/app.rs index 2a8a360..d9da9ea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -259,6 +259,15 @@ impl Tile { focus_this_app(); } + #[cfg(target_os = "windows")] + { + if let Some(hwnd) = self.frontmost { + unsafe { + let _ = SetForegroundWindow(hwnd); + } + } + } + self.focused = true; Task::none() } From 16022c78ae333ed3a750bdd26a39ec1e7e8299f6 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Fri, 19 Dec 2025 10:27:52 +0800 Subject: [PATCH 13/65] Fix issues clippy highlighted --- src/app.rs | 6 +----- src/utils.rs | 30 ++++++++++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/app.rs b/src/app.rs index d9da9ea..e7c7db0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,6 @@ use iced::{ }, window::{self, Id, Settings}, }; -use std::path::Path; #[cfg(target_os = "macos")] use crate::macos::{focus_this_app, transform_process_to_ui_element}; @@ -146,9 +145,6 @@ pub fn default_settings() -> Settings { } } -#[derive(Debug, Clone)] -pub struct Temp {} - #[derive(Debug, Clone)] pub struct Tile { theme: iced::Theme, @@ -170,7 +166,7 @@ pub struct Tile { impl Tile { /// A base window pub fn new(keybind_id: u32, config: &Config) -> (Self, Task) { - let mut settings = default_settings(); + let settings = default_settings(); #[cfg(target_os = "windows")] { // get normal settings and modify position diff --git a/src/utils.rs b/src/utils.rs index b7cb5bb..f643248 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,9 +5,8 @@ use std::{ process::exit, }; -use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; use global_hotkey::hotkey::Code; -use iced::{futures::io::Window, widget::image::Handle}; +use iced::widget::image::Handle; use icns::IconFamily; use image::RgbaImage; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -300,29 +299,29 @@ pub fn to_key_code(key_str: &str) -> Option { } pub fn get_config_installation_dir() -> String { - let path = if cfg!(target_os = "windows") { + + + if cfg!(target_os = "windows") { std::env::var("LOCALAPPDATA").unwrap() } else { std::env::var("HOME").unwrap() - }; - - return path; + } } pub fn get_config_file_path() -> String { let home = get_config_installation_dir(); - let file_path = if cfg!(target_os = "windows") { + + + if cfg!(target_os = "windows") { home + "\\rustcast\\config.toml" } else { home + "/.config/rustcast/config.toml" - }; - - return file_path; + } } use crate::config::Config; pub fn read_config_file(file_path: &str) -> Result { - let config: Config = match std::fs::read_to_string(&file_path) { + let config: Config = match std::fs::read_to_string(file_path) { Ok(a) => toml::from_str(&a).unwrap(), Err(_) => Config::default(), }; @@ -335,11 +334,10 @@ pub fn create_config_file_if_not_exists( config: &Config, ) -> Result<(), std::io::Error> { // check if file exists - if let Ok(exists) = std::fs::metadata(&file_path) { - if exists.is_file() { + if let Ok(exists) = std::fs::metadata(file_path) + && exists.is_file() { return Ok(()); } - } let path = Path::new(&file_path); if let Some(parent) = path.parent() { @@ -347,7 +345,7 @@ pub fn create_config_file_if_not_exists( } std::fs::write( - &file_path, + file_path, toml::to_string(&config).unwrap_or_else(|x| x.to_string()), ) .unwrap(); @@ -355,7 +353,7 @@ pub fn create_config_file_if_not_exists( Ok(()) } -pub fn open_application(path: &String) { +pub fn open_application(path: &str) { #[cfg(target_os = "windows")] { use std::process::Command; From 188c908df611a09540e25652f0fb75832084ab46 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Fri, 19 Dec 2025 10:28:46 +0800 Subject: [PATCH 14/65] Formatting --- src/utils.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index f643248..7390751 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -299,8 +299,6 @@ pub fn to_key_code(key_str: &str) -> Option { } pub fn get_config_installation_dir() -> String { - - if cfg!(target_os = "windows") { std::env::var("LOCALAPPDATA").unwrap() } else { @@ -310,7 +308,6 @@ pub fn get_config_installation_dir() -> String { pub fn get_config_file_path() -> String { let home = get_config_installation_dir(); - if cfg!(target_os = "windows") { home + "\\rustcast\\config.toml" @@ -335,9 +332,10 @@ pub fn create_config_file_if_not_exists( ) -> Result<(), std::io::Error> { // check if file exists if let Ok(exists) = std::fs::metadata(file_path) - && exists.is_file() { - return Ok(()); - } + && exists.is_file() + { + return Ok(()); + } let path = Path::new(&file_path); if let Some(parent) = path.parent() { From f03ff73a8e83b99013dddf5922cf93f2210e8d8c Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:35:07 +0100 Subject: [PATCH 15/65] yay --- Cargo.lock | 1 + Cargo.toml | 3 +- src/app.rs | 108 ++---------------------- src/config.rs | 2 + src/macos.rs | 143 ++++++++++++++++++++++++++++++++ src/main.rs | 2 +- src/utils.rs | 221 ++++++++++++++----------------------------------- src/windows.rs | 155 ++++++++++++++++++++++++++++++++++ 8 files changed, 372 insertions(+), 263 deletions(-) create mode 100644 src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 3c8ac70..2e7f21a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3411,6 +3411,7 @@ dependencies = [ "serde", "tokio", "toml", + "walkdir", "windows 0.58.0", "winreg", ] diff --git a/Cargo.toml b/Cargo.toml index ea3abbf..670cd2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [target.'cfg(target_os = "windows")'.dependencies] winreg = "0.52" -windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi"] } +windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_System_Com", "Win32_UI_Shell"] } [target.'cfg(target_os = "macos")'.dependencies] objc2 = "0.6.3" @@ -30,6 +30,7 @@ rayon = "1.11.0" serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.48.0", features = ["full"] } toml = "0.9.8" +walkdir = "2" [package.metadata.bundle] name = "RustCast" diff --git a/src/app.rs b/src/app.rs index d9da9ea..9a83741 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use crate::commands::Function; use crate::config::Config; -use crate::utils::get_config_file_path; +use crate::utils::{get_config_file_path, get_installed_apps, read_config_file}; use global_hotkey::{GlobalHotKeyEvent, HotKeyState}; use iced::futures::SinkExt; use iced::{ @@ -15,7 +15,6 @@ use iced::{ }, window::{self, Id, Settings}, }; -use std::path::Path; #[cfg(target_os = "macos")] use crate::macos::{focus_this_app, transform_process_to_ui_element}; @@ -146,9 +145,6 @@ pub fn default_settings() -> Settings { } } -#[derive(Debug, Clone)] -pub struct Temp {} - #[derive(Debug, Clone)] pub struct Tile { theme: iced::Theme, @@ -174,7 +170,7 @@ impl Tile { #[cfg(target_os = "windows")] { // get normal settings and modify position - use crate::utils::open_on_focused_monitor; + use crate::windows::open_on_focused_monitor; use iced::window::Position::Specific; let pos = open_on_focused_monitor(); settings.position = Specific(pos); @@ -195,38 +191,11 @@ impl Tile { } })); - let store_icons = config.theme.show_icons; - let paths; - let user_local_path: String; - #[cfg(target_os = "macos")] - { - user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; - paths = vec![ - "/Applications/", - user_local_path.as_str(), - "/System/Applications/", - "/System/Applications/Utilities/", - ]; - } - #[cfg(target_os = "windows")] - { - paths = vec!["C:\\Program Files\\", "C:\\Program Files (x86)\\"]; - } + // get config + let path = get_config_file_path(); + let config = read_config_file(&path).unwrap(); - let mut options: Vec = paths - .par_iter() - .map(|path| { - #[cfg(target_os = "macos")] - { - get_installed_apps(path, store_icons) - } - #[cfg(target_os = "windows")] - { - get_installed_windows_app(Path::new(path)) - } - }) - .flatten() - .collect(); + let mut options: Vec = get_installed_apps(&config); options.extend(config.shells.iter().map(|x| x.to_app())); options.extend(App::basic_apps()); @@ -259,17 +228,8 @@ impl Tile { focus_this_app(); } - #[cfg(target_os = "windows")] - { - if let Some(hwnd) = self.frontmost { - unsafe { - let _ = SetForegroundWindow(hwnd); - } - } - } - self.focused = true; - Task::none() + operation::focus("query") } Message::SearchQueryChanged(input, id) => { @@ -353,7 +313,7 @@ impl Tile { #[cfg(target_os = "windows")] { // get normal settings and modify position - use crate::utils::open_on_focused_monitor; + use crate::windows::open_on_focused_monitor; use iced::window::Position::Specific; let pos = open_on_focused_monitor(); let mut settings = default_settings(); @@ -589,55 +549,3 @@ fn handle_hotkeys() -> impl futures::Stream { } }) } - -#[cfg(target_os = "windows")] -fn get_installed_windows_app(path: &Path) -> Vec { - use std::ffi::OsString; - - let mut apps = Vec::new(); - - let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); - - // where we can find installed applications - // src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848 - let registers = [ - hkey.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall") - .unwrap(), - hkey.open_subkey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall") - .unwrap(), - ]; - - registers.iter().for_each(|reg| { - reg.enum_keys().for_each(|key| { - // https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key - let name = key.unwrap(); - let key = reg.open_subkey(&name).unwrap(); - let display_name = key.get_value("DisplayName").unwrap_or(OsString::new()); - - // they might be useful one day ? - // let publisher = key.get_value("Publisher").unwrap_or(OsString::new()); - // let version = key.get_value("DisplayVersion").unwrap_or(OsString::new()); - - // Trick, I saw on internet to point to the exe location.. - let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new()); - if exe_path.is_empty() { - return; - } - // if there is something, it will be in the form of - // "C:\Program Files\Microsoft Office\Office16\WINWORD.EXE",0 - let exe_path = exe_path.to_string_lossy().to_string(); - let exe = exe_path.split(",").next().unwrap().to_string(); - - if !display_name.is_empty() { - apps.push(App { - open_command: Function::OpenApp(exe), - name: display_name.clone().into_string().unwrap(), - name_lc: display_name.clone().into_string().unwrap().to_lowercase(), - icons: None, - }) - } - }); - }); - - apps -} diff --git a/src/config.rs b/src/config.rs index 0f1e9df..9d6ca00 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ pub struct Config { pub placeholder: String, pub search_url: String, pub shells: Vec, + pub index_dirs: Vec, } impl Default for Config { @@ -27,6 +28,7 @@ impl Default for Config { placeholder: String::from("Time to be productive!"), search_url: "https://google.com/search?q=%s".to_string(), shells: vec![], + index_dirs: vec![], } } } diff --git a/src/macos.rs b/src/macos.rs index 4e51ee1..5062950 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -1,7 +1,15 @@ #![allow(deprecated)] +use crate::app::App; +use crate::commands::Function; +use crate::config::Config; +use crate::utils::{handle_from_icns, log_error, log_error_and_exit}; #[cfg(target_os = "macos")] use iced::wgpu::rwh::WindowHandle; +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::exit; #[cfg(target_os = "macos")] pub fn set_activation_policy_accessory() { @@ -63,6 +71,7 @@ struct ProcessSerialNumber { /// returns ApplicationServices OSStatus (u32) /// /// doesn't seem to do anything if you haven't opened a window yet, so wait to call it until after that. +#[cfg(target_os = "macos")] pub fn transform_process_to_ui_element() -> u32 { use std::ptr; @@ -80,3 +89,137 @@ pub fn transform_process_to_ui_element() -> u32 { ) } } + +pub(crate) fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { + let entries: Vec<_> = fs::read_dir(dir.as_ref()) + .unwrap_or_else(|x| { + log_error_and_exit(&x.to_string()); + exit(-1) + }) + .filter_map(|x| x.ok()) + .collect(); + + entries + .into_par_iter() + .filter_map(|x| { + let file_type = x.file_type().unwrap_or_else(|e| { + log_error(&e.to_string()); + exit(-1) + }); + if !file_type.is_dir() { + return None; + } + + let file_name_os = x.file_name(); + let file_name = file_name_os.into_string().unwrap_or_else(|e| { + log_error(e.to_str().unwrap_or("")); + exit(-1) + }); + if !file_name.ends_with(".app") { + return None; + } + + let path = x.path(); + let path_str = path.to_str().map(|x| x.to_string()).unwrap_or_else(|| { + log_error("Unable to get file_name"); + exit(-1) + }); + + let icons = if store_icons { + match fs::read_to_string(format!("{}/Contents/Info.plist", path_str)).map( + |content| { + let icon_line = content + .lines() + .scan(false, |expect_next, line| { + if *expect_next { + *expect_next = false; + // Return this line to the iterator + return Some(Some(line)); + } + + if line.trim() == "CFBundleIconFile" { + *expect_next = true; + } + + // For lines that are not the one after the key, return None to skip + Some(None) + }) + .flatten() // remove the Nones + .next() + .map(|x| { + x.trim() + .strip_prefix("") + .unwrap_or("") + .strip_suffix("") + .unwrap_or("") + }); + + handle_from_icns(Path::new(&format!( + "{}/Contents/Resources/{}", + path_str, + icon_line.unwrap_or("AppIcon.icns") + ))) + }, + ) { + Ok(Some(a)) => Some(a), + _ => { + // Fallback method + let direntry = fs::read_dir(format!("{}/Contents/Resources", path_str)) + .into_iter() + .flatten() + .filter_map(|x| { + let file = x.ok()?; + let name = file.file_name(); + let file_name = name.to_str()?; + if file_name.ends_with(".icns") { + Some(file.path()) + } else { + None + } + }) + .collect::>(); + + if direntry.len() > 1 { + let icns_vec = direntry + .iter() + .filter(|x| x.ends_with("AppIcon.icns")) + .collect::>(); + handle_from_icns(icns_vec.first().unwrap_or(&&PathBuf::new())) + } else if !direntry.is_empty() { + handle_from_icns(direntry.first().unwrap_or(&PathBuf::new())) + } else { + None + } + } + } + } else { + None + }; + + let name = file_name.strip_suffix(".app").unwrap().to_string(); + Some(App { + open_command: Function::OpenApp(path_str), + icons, + name_lc: name.to_lowercase(), + name, + }) + }) + .collect() +} + +pub fn get_installed_macos_apps(config: &Config) -> Vec { + let store_icons = config.theme.show_icons; + let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; + let paths: Vec = vec![ + "/Applications/".to_string(), + user_local_path.to_string(), + "/System/Applications/".to_string(), + "/System/Applications/Utilities/".to_string(), + ]; + + paths + .par_iter() + .map(|path| get_installed_apps(path, store_icons)) + .flatten() + .collect() +} diff --git a/src/main.rs b/src/main.rs index 560010b..c5e9985 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ mod app; mod commands; mod config; -#[cfg(target_os = "macos")] mod macos; mod utils; +mod windows; // import from utils use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; diff --git a/src/utils.rs b/src/utils.rs index b7cb5bb..31f07c9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,22 +1,14 @@ use std::{ fs::{self, File}, io::Write, - path::{Path, PathBuf}, + path::Path, process::exit, }; -use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; use global_hotkey::hotkey::Code; -use iced::{futures::io::Window, widget::image::Handle}; +use iced::widget::image::Handle; use icns::IconFamily; use image::RgbaImage; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; - -#[cfg(target_os = "windows")] -use windows::Win32::{ - Graphics::Gdi::MonitorFromPoint, - UI::WindowsAndMessaging::{GetCursor, GetCursorPos}, -}; use crate::{app::App, commands::Function}; #[cfg(target_os = "macos")] @@ -56,123 +48,6 @@ pub(crate) fn handle_from_icns(path: &Path) -> Option { )) } -pub(crate) fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { - let entries: Vec<_> = fs::read_dir(dir.as_ref()) - .unwrap_or_else(|x| { - log_error_and_exit(&x.to_string()); - exit(-1) - }) - .filter_map(|x| x.ok()) - .collect(); - - entries - .into_par_iter() - .filter_map(|x| { - let file_type = x.file_type().unwrap_or_else(|e| { - log_error(&e.to_string()); - exit(-1) - }); - if !file_type.is_dir() { - return None; - } - - let file_name_os = x.file_name(); - let file_name = file_name_os.into_string().unwrap_or_else(|e| { - log_error(e.to_str().unwrap_or("")); - exit(-1) - }); - if !file_name.ends_with(".app") { - return None; - } - - let path = x.path(); - let path_str = path.to_str().map(|x| x.to_string()).unwrap_or_else(|| { - log_error("Unable to get file_name"); - exit(-1) - }); - - let icons = if store_icons { - match fs::read_to_string(format!("{}/Contents/Info.plist", path_str)).map( - |content| { - let icon_line = content - .lines() - .scan(false, |expect_next, line| { - if *expect_next { - *expect_next = false; - // Return this line to the iterator - return Some(Some(line)); - } - - if line.trim() == "CFBundleIconFile" { - *expect_next = true; - } - - // For lines that are not the one after the key, return None to skip - Some(None) - }) - .flatten() // remove the Nones - .next() - .map(|x| { - x.trim() - .strip_prefix("") - .unwrap_or("") - .strip_suffix("") - .unwrap_or("") - }); - - handle_from_icns(Path::new(&format!( - "{}/Contents/Resources/{}", - path_str, - icon_line.unwrap_or("AppIcon.icns") - ))) - }, - ) { - Ok(Some(a)) => Some(a), - _ => { - // Fallback method - let direntry = fs::read_dir(format!("{}/Contents/Resources", path_str)) - .into_iter() - .flatten() - .filter_map(|x| { - let file = x.ok()?; - let name = file.file_name(); - let file_name = name.to_str()?; - if file_name.ends_with(".icns") { - Some(file.path()) - } else { - None - } - }) - .collect::>(); - - if direntry.len() > 1 { - let icns_vec = direntry - .iter() - .filter(|x| x.ends_with("AppIcon.icns")) - .collect::>(); - handle_from_icns(icns_vec.first().unwrap_or(&&PathBuf::new())) - } else if !direntry.is_empty() { - handle_from_icns(direntry.first().unwrap_or(&PathBuf::new())) - } else { - None - } - } - } - } else { - None - }; - - let name = file_name.strip_suffix(".app").unwrap().to_string(); - Some(App { - open_command: Function::OpenApp(path_str), - icons, - name_lc: name.to_lowercase(), - name, - }) - }) - .collect() -} - pub fn to_key_code(key_str: &str) -> Option { match key_str.to_lowercase().as_str() { // Letters @@ -300,29 +175,25 @@ pub fn to_key_code(key_str: &str) -> Option { } pub fn get_config_installation_dir() -> String { - let path = if cfg!(target_os = "windows") { + if cfg!(target_os = "windows") { std::env::var("LOCALAPPDATA").unwrap() } else { std::env::var("HOME").unwrap() - }; - - return path; + } } pub fn get_config_file_path() -> String { let home = get_config_installation_dir(); - let file_path = if cfg!(target_os = "windows") { + if cfg!(target_os = "windows") { home + "\\rustcast\\config.toml" } else { home + "/.config/rustcast/config.toml" - }; - - return file_path; + } } use crate::config::Config; pub fn read_config_file(file_path: &str) -> Result { - let config: Config = match std::fs::read_to_string(&file_path) { + let config: Config = match std::fs::read_to_string(file_path) { Ok(a) => toml::from_str(&a).unwrap(), Err(_) => Config::default(), }; @@ -335,10 +206,10 @@ pub fn create_config_file_if_not_exists( config: &Config, ) -> Result<(), std::io::Error> { // check if file exists - if let Ok(exists) = std::fs::metadata(&file_path) { - if exists.is_file() { - return Ok(()); - } + if let Ok(exists) = std::fs::metadata(file_path) + && exists.is_file() + { + return Ok(()); } let path = Path::new(&file_path); @@ -347,7 +218,7 @@ pub fn create_config_file_if_not_exists( } std::fs::write( - &file_path, + file_path, toml::to_string(&config).unwrap_or_else(|x| x.to_string()), ) .unwrap(); @@ -381,29 +252,57 @@ pub fn open_application(path: &String) { } } -#[cfg(target_os = "windows")] -pub fn open_on_focused_monitor() -> iced::Point { - use windows::Win32::Foundation::POINT; - use windows::Win32::Graphics::Gdi::{ - GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, - }; - let mut point = POINT { x: 0, y: 0 }; - let mut monitor_info = MONITORINFO { - cbSize: std::mem::size_of::() as u32, - ..Default::default() +pub fn index_dirs_from_config(apps: &mut Vec) -> bool { + let path = get_config_file_path(); + let config = read_config_file(&path); + + // if config is not valid return false otherwise unwrap config so it is usable + let config = match config { + Ok(config) => config, + Err(err) => { + println!("Error reading config file: {}", err); + return false; + } }; - let cursor = unsafe { GetCursorPos(&mut point) }; - let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }; - let monitor_infos = unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; + if config.index_dirs.is_empty() { + return false; + } + + config.index_dirs.clone().iter().for_each(|dir| { + // check if dir exists + if !Path::new(dir).exists() { + println!("Directory {} does not exist", dir); + return; + } + + let paths = fs::read_dir(dir).unwrap(); - let monitor_width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; - let monitor_height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; - let window_width = WINDOW_WIDTH; - let window_height = DEFAULT_WINDOW_HEIGHT; + for path in paths { + let path = path.unwrap().path(); + let metadata = fs::metadata(&path).unwrap(); - let x = monitor_info.rcMonitor.left as f32 + (monitor_width as f32 - window_width) / 2.0; - let y = monitor_info.rcMonitor.top as f32 + (monitor_height as f32 - window_height) / 2.0; + if metadata.is_file() && Path::new(&path).extension().unwrap() == "exe" { + let display_name = path.file_name().unwrap().to_string_lossy().to_string(); + apps.push(App { + open_command: Function::OpenApp(path.to_string_lossy().to_string()), + name: display_name.clone(), + name_lc: display_name.clone().to_lowercase(), + icons: None, + }); + } + } + }); - return iced::Point { x, y }; + true +} + +pub fn get_installed_apps(config: &Config) -> Vec { + if cfg!(target_os = "macos") { + use crate::macos::get_installed_macos_apps; + get_installed_macos_apps(config) + } else { + use crate::windows::get_installed_apps_windows; + get_installed_apps_windows() + } } diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 0000000..6d0a634 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,155 @@ +use crate::utils::index_dirs_from_config; +use crate::{app::App, commands::Function}; +use walkdir::WalkDir; + +use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; +#[cfg(target_os = "windows")] +use windows::Win32::UI::WindowsAndMessaging::GetCursorPos; + +#[cfg(target_os = "windows")] +use windows::Win32::System::Com::CoTaskMemFree; +#[cfg(target_os = "windows")] +use windows::Win32::UI::Shell::{ + FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, + SHGetKnownFolderPath, +}; +use windows::core::GUID; +#[cfg(target_os = "windows")] +fn get_apps_from_registry(apps: &mut Vec) { + use std::ffi::OsString; + let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + + let registers = [ + hkey.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall") + .unwrap(), + hkey.open_subkey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall") + .unwrap(), + ]; + + // where we can find installed applications + // src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848 + registers.iter().for_each(|reg| { + reg.enum_keys().for_each(|key| { + // https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key + let name = key.unwrap(); + let key = reg.open_subkey(&name).unwrap(); + let display_name = key.get_value("DisplayName").unwrap_or(OsString::new()); + + // they might be useful one day ? + // let publisher = key.get_value("Publisher").unwrap_or(OsString::new()); + // let version = key.get_value("DisplayVersion").unwrap_or(OsString::new()); + + // Trick, I saw on internet to point to the exe location.. + let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new()); + if exe_path.is_empty() { + return; + } + // if there is something, it will be in the form of + // "C:\Program Files\Microsoft Office\Office16\WINWORD.EXE",0 + let exe_path = exe_path.to_string_lossy().to_string(); + let exe = exe_path.split(",").next().unwrap().to_string(); + + // make sure it ends with .exe + if !exe.ends_with(".exe") { + return; + } + + if !display_name.is_empty() { + apps.push(App { + open_command: Function::OpenApp(exe), + name: display_name.clone().into_string().unwrap(), + name_lc: display_name.clone().into_string().unwrap().to_lowercase(), + icons: None, + }) + } + }); + }); +} +#[cfg(target_os = "windows")] +fn get_apps_from_known_folder(apps: &mut Vec) { + let paths = get_known_paths(); + + for path in paths { + for entry in WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) + { + apps.push(App { + open_command: Function::OpenApp(entry.path().to_string_lossy().to_string()), + name: entry + .clone() + .file_name() + .to_string_lossy() + .to_string() + .replace(".exe", ""), + name_lc: entry + .clone() + .file_name() + .to_string_lossy() + .to_string() + .to_lowercase() + .replace(".exe", ""), + icons: None, + }); + } + } +} +#[cfg(target_os = "windows")] +fn get_known_paths() -> Vec { + let paths = vec![ + get_windows_path(&FOLDERID_ProgramFiles).unwrap_or_default(), + get_windows_path(&FOLDERID_ProgramFilesX86).unwrap_or_default(), + get_windows_path(&FOLDERID_LocalAppData).unwrap_or_default(), + ]; + paths +} +#[cfg(target_os = "windows")] +fn get_windows_path(folder_id: &GUID) -> Option { + unsafe { + let folder = SHGetKnownFolderPath(folder_id, KF_FLAG_DEFAULT, None); + if let Ok(folder) = folder { + let path = folder.to_string().ok(); + CoTaskMemFree(Some(folder.0 as *mut _)); + path + } else { + None + } + } +} +#[cfg(target_os = "windows")] +pub fn get_installed_apps_windows() -> Vec { + let mut apps = Vec::new(); + get_apps_from_registry(&mut apps); + get_apps_from_known_folder(&mut apps); + index_dirs_from_config(&mut apps); + apps +} + +#[cfg(target_os = "windows")] +pub fn open_on_focused_monitor() -> iced::Point { + use windows::Win32::Foundation::POINT; + use windows::Win32::Graphics::Gdi::{ + GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, + }; + let mut point = POINT { x: 0, y: 0 }; + let mut monitor_info = MONITORINFO { + cbSize: std::mem::size_of::() as u32, + ..Default::default() + }; + + let _cursor = unsafe { GetCursorPos(&mut point) }; + let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }; + let _monitor_infos = unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; + + let monitor_width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; + let monitor_height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; + let window_width = WINDOW_WIDTH; + let window_height = DEFAULT_WINDOW_HEIGHT; + + let x = monitor_info.rcMonitor.left as f32 + (monitor_width as f32 - window_width) / 2.0; + let y = monitor_info.rcMonitor.top as f32 + (monitor_height as f32 - window_height) / 2.0; + + iced::Point { x, y } +} From b6bb6ffeffaf113c52cd25fba91d17cf788b3d26 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:36:52 +0100 Subject: [PATCH 16/65] hmm --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 9a83741..11cc643 100644 --- a/src/app.rs +++ b/src/app.rs @@ -229,7 +229,7 @@ impl Tile { } self.focused = true; - operation::focus("query") + Task::none() } Message::SearchQueryChanged(input, id) => { From 3a8e96ec81228c9d0c6be2cb40fb185923c1a063 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:35:00 +0100 Subject: [PATCH 17/65] fix for mac --- src/macos.rs | 10 ++++++++-- src/utils.rs | 4 ++-- src/windows.rs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/macos.rs b/src/macos.rs index 5062950..e293e68 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -10,6 +10,7 @@ use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterato use std::fs; use std::path::{Path, PathBuf}; use std::process::exit; +use crate::utils::index_dirs_from_config; #[cfg(target_os = "macos")] pub fn set_activation_policy_accessory() { @@ -217,9 +218,14 @@ pub fn get_installed_macos_apps(config: &Config) -> Vec { "/System/Applications/Utilities/".to_string(), ]; - paths + + let mut apps = paths .par_iter() .map(|path| get_installed_apps(path, store_icons)) .flatten() - .collect() + .collect(); + index_dirs_from_config(&mut apps); + + apps + } diff --git a/src/utils.rs b/src/utils.rs index 31f07c9..53db331 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -302,7 +302,7 @@ pub fn get_installed_apps(config: &Config) -> Vec { use crate::macos::get_installed_macos_apps; get_installed_macos_apps(config) } else { - use crate::windows::get_installed_apps_windows; - get_installed_apps_windows() + use crate::windows::get_installed_windows_apps; + get_installed_windows_apps() } } diff --git a/src/windows.rs b/src/windows.rs index 6d0a634..f08d07c 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -119,7 +119,7 @@ fn get_windows_path(folder_id: &GUID) -> Option { } } #[cfg(target_os = "windows")] -pub fn get_installed_apps_windows() -> Vec { +pub fn get_installed_windows_apps() -> Vec { let mut apps = Vec::new(); get_apps_from_registry(&mut apps); get_apps_from_known_folder(&mut apps); From 9f526e2207661795860157d6a0ea1494c0ca2d9c Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:56:13 +0100 Subject: [PATCH 18/65] chore(imports) Cleaned up imports so they are at the top of the file, nothing serious just picky --- src/app.rs | 35 ++++++++++++++++------------------- src/macos.rs | 32 +++++++++++++++----------------- src/utils.rs | 18 +++++++++--------- src/windows.rs | 18 +++++++++--------- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/app.rs b/src/app.rs index 11cc643..5c5a01f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,18 +17,23 @@ use iced::{ }; #[cfg(target_os = "macos")] -use crate::macos::{focus_this_app, transform_process_to_ui_element}; -#[cfg(target_os = "macos")] -use crate::{macos, utils::get_installed_apps}; -#[cfg(target_os = "macos")] -use objc2::rc::Retained; -#[cfg(target_os = "macos")] -use objc2_app_kit::NSRunningApplication; +use { + crate::macos::{focus_this_app, macos_window_config, transform_process_to_ui_element}, + objc2::rc::Retained, + objc2_app_kit::NSApplicationActivationOptions, + objc2_app_kit::NSRunningApplication, + objc2_app_kit::NSWorkspace, +}; #[cfg(target_os = "windows")] -use windows::Win32::Foundation::HWND; -#[cfg(target_os = "windows")] -use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}; +use { + crate::windows::open_on_focused_monitor, + crate::windows::open_on_focused_monitor, + iced::window::Position::Specific, + iced::window::Position::Specific, + windows::Win32::Foundation::HWND, + windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, +}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::slice::ParallelSliceMut; @@ -170,8 +175,6 @@ impl Tile { #[cfg(target_os = "windows")] { // get normal settings and modify position - use crate::windows::open_on_focused_monitor; - use iced::window::Position::Specific; let pos = open_on_focused_monitor(); settings.position = Specific(pos); } @@ -182,7 +185,7 @@ impl Tile { { #[cfg(target_os = "macos")] { - macos::macos_window_config( + macos_window_config( &handle.window_handle().expect("Unable to get window handle"), ); // should work now that we have a window @@ -313,8 +316,6 @@ impl Tile { #[cfg(target_os = "windows")] { // get normal settings and modify position - use crate::windows::open_on_focused_monitor; - use iced::window::Position::Specific; let pos = open_on_focused_monitor(); let mut settings = default_settings(); settings.position = Specific(pos); @@ -482,8 +483,6 @@ impl Tile { pub fn capture_frontmost(&mut self) { #[cfg(target_os = "macos")] { - use objc2_app_kit::NSWorkspace; - let ws = NSWorkspace::sharedWorkspace(); self.frontmost = ws.frontmostApplication(); }; @@ -498,8 +497,6 @@ impl Tile { pub fn restore_frontmost(&mut self) { #[cfg(target_os = "macos")] { - use objc2_app_kit::NSApplicationActivationOptions; - if let Some(app) = self.frontmost.take() { app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); } diff --git a/src/macos.rs b/src/macos.rs index e293e68..3c503c2 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -3,20 +3,30 @@ use crate::app::App; use crate::commands::Function; use crate::config::Config; +use crate::utils::index_dirs_from_config; use crate::utils::{handle_from_icns, log_error, log_error_and_exit}; +use std::ptr; #[cfg(target_os = "macos")] -use iced::wgpu::rwh::WindowHandle; +use { + iced::wgpu::rwh::RawWindowHandle, + iced::wgpu::rwh::WindowHandle, + objc2::MainThreadMarker, + objc2::rc::Retained, + objc2_app_kit::NSView, + objc2_app_kit::{NSApp, NSApplicationActivationPolicy}, + objc2_app_kit::{NSFloatingWindowLevel, NSWindowCollectionBehavior}, + objc2_application_services::{ + TransformProcessType, kCurrentProcess, kProcessTransformToUIElementApplication, + }, +}; + use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use std::fs; use std::path::{Path, PathBuf}; use std::process::exit; -use crate::utils::index_dirs_from_config; #[cfg(target_os = "macos")] pub fn set_activation_policy_accessory() { - use objc2::MainThreadMarker; - use objc2_app_kit::{NSApp, NSApplicationActivationPolicy}; - let mtm = MainThreadMarker::new().expect("must be on main thread"); let app = NSApp(mtm); app.setActivationPolicy(NSApplicationActivationPolicy::Accessory); @@ -24,10 +34,6 @@ pub fn set_activation_policy_accessory() { #[cfg(target_os = "macos")] pub fn macos_window_config(handle: &WindowHandle) { - use iced::wgpu::rwh::RawWindowHandle; - use objc2::rc::Retained; - use objc2_app_kit::NSView; - match handle.as_raw() { RawWindowHandle::AppKit(handle) => { let ns_view = handle.ns_view.as_ptr(); @@ -36,7 +42,6 @@ pub fn macos_window_config(handle: &WindowHandle) { .window() .expect("view was not installed in a window"); - use objc2_app_kit::{NSFloatingWindowLevel, NSWindowCollectionBehavior}; ns_window.setLevel(NSFloatingWindowLevel); ns_window.setCollectionBehavior(NSWindowCollectionBehavior::CanJoinAllSpaces); @@ -74,11 +79,6 @@ struct ProcessSerialNumber { /// doesn't seem to do anything if you haven't opened a window yet, so wait to call it until after that. #[cfg(target_os = "macos")] pub fn transform_process_to_ui_element() -> u32 { - use std::ptr; - - use objc2_application_services::{ - TransformProcessType, kCurrentProcess, kProcessTransformToUIElementApplication, - }; let psn = ProcessSerialNumber { low: 0, hi: kCurrentProcess, @@ -218,7 +218,6 @@ pub fn get_installed_macos_apps(config: &Config) -> Vec { "/System/Applications/Utilities/".to_string(), ]; - let mut apps = paths .par_iter() .map(|path| get_installed_apps(path, store_icons)) @@ -227,5 +226,4 @@ pub fn get_installed_macos_apps(config: &Config) -> Vec { index_dirs_from_config(&mut apps); apps - } diff --git a/src/utils.rs b/src/utils.rs index 53db331..59b8ef8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,9 +12,9 @@ use image::RgbaImage; use crate::{app::App, commands::Function}; #[cfg(target_os = "macos")] -use objc2_app_kit::NSWorkspace; -#[cfg(target_os = "macos")] -use objc2_foundation::NSURL; +use {crate::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, objc2_foundation::NSURL}; +#[cfg(target_os = "windows")] +use {crate::windows::get_installed_windows_apps, std::process::Command}; const ERR_LOG_PATH: &str = "/tmp/rustscan-err.log"; @@ -229,8 +229,6 @@ pub fn create_config_file_if_not_exists( pub fn open_application(path: &String) { #[cfg(target_os = "windows")] { - use std::process::Command; - println!("Opening application: {}", path); Command::new("powershell") @@ -298,11 +296,13 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { } pub fn get_installed_apps(config: &Config) -> Vec { - if cfg!(target_os = "macos") { - use crate::macos::get_installed_macos_apps; + #[cfg(target_os = "macos")] + { get_installed_macos_apps(config) - } else { - use crate::windows::get_installed_windows_apps; + } + + #[cfg(target_os = "windows")] + { get_installed_windows_apps() } } diff --git a/src/windows.rs b/src/windows.rs index f08d07c..e377753 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -4,16 +4,16 @@ use walkdir::WalkDir; use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; #[cfg(target_os = "windows")] -use windows::Win32::UI::WindowsAndMessaging::GetCursorPos; - -#[cfg(target_os = "windows")] -use windows::Win32::System::Com::CoTaskMemFree; -#[cfg(target_os = "windows")] -use windows::Win32::UI::Shell::{ - FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, - SHGetKnownFolderPath, +use { + windows::Win32::System::Com::CoTaskMemFree, + windows::Win32::UI::Shell::{ + FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, + SHGetKnownFolderPath, + }, + windows::Win32::UI::WindowsAndMessaging::GetCursorPos, + windows::core::GUID, }; -use windows::core::GUID; + #[cfg(target_os = "windows")] fn get_apps_from_registry(apps: &mut Vec) { use std::ffi::OsString; From ae4dfa72eb6042362ee5f1ed467e662277e29b41 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:04:46 +0100 Subject: [PATCH 19/65] feat(macos/indexing): we can now have customs paths to index and file exe's from, perhaps in the future we could accept globals in the array.. --- .DS_Store | Bin 0 -> 6148 bytes src/utils.rs | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7df67799341ff078e0fd0c7bf197f11738af5028 GIT binary patch literal 6148 zcmeHKF=_)r43uIM3~5}Z+%Mz@i*a6%4-f!vAh?bBB4{eS+! z-Vpq-jUE0raED9^NC7Dz1*Cu!xKM$rr?ZC(&k z5e~yVdI?}-0N4wML_}boRA5rQS`1G*;;r&};gFbg^SBx3)XiQUipTAUw@5edi5jJV z6gXC3lFK>k|1PpaBFdK_zQgTKO=^8=@09uy2wj)75* gv0yoVjHJwKoMXQi4v9fWJm^6E3{V%D6u7nmzmpgkjsO4v literal 0 HcmV?d00001 diff --git a/src/utils.rs b/src/utils.rs index 59b8ef8..8ead46c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,7 +12,10 @@ use image::RgbaImage; use crate::{app::App, commands::Function}; #[cfg(target_os = "macos")] -use {crate::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, objc2_foundation::NSURL}; +use { + crate::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, objc2_foundation::NSURL, + std::os::unix::fs::PermissionsExt, +}; #[cfg(target_os = "windows")] use {crate::windows::get_installed_windows_apps, std::process::Command}; @@ -280,7 +283,17 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { let path = path.unwrap().path(); let metadata = fs::metadata(&path).unwrap(); - if metadata.is_file() && Path::new(&path).extension().unwrap() == "exe" { + #[cfg(target_os = "windows")] + let is_executable = + metadata.is_file() && path.extension().and_then(|s| s.to_str()) == Some("exe"); + + #[cfg(target_os = "macos")] + let is_executable = { + (metadata.is_file() && (metadata.permissions().mode() & 0o111 != 0)) + || path.extension().and_then(|s| s.to_str()) == Some("app") + }; + + if is_executable { let display_name = path.file_name().unwrap().to_string_lossy().to_string(); apps.push(App { open_command: Function::OpenApp(path.to_string_lossy().to_string()), From 4de0d83f4beece475f434f89bcdec71926b6182a Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:05:57 +0100 Subject: [PATCH 20/65] fix(windows/imports) double imports RIP --- src/app.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5c5a01f..01b1529 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,8 +28,6 @@ use { #[cfg(target_os = "windows")] use { crate::windows::open_on_focused_monitor, - crate::windows::open_on_focused_monitor, - iced::window::Position::Specific, iced::window::Position::Specific, windows::Win32::Foundation::HWND, windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, From 7344853d300611e4fe2f21c07cefefbc2b496371 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Sat, 20 Dec 2025 21:36:59 +0800 Subject: [PATCH 21/65] Remove `.DS_Store` --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 7df67799341ff078e0fd0c7bf197f11738af5028..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKF=_)r43uIM3~5}Z+%Mz@i*a6%4-f!vAh?bBB4{eS+! z-Vpq-jUE0raED9^NC7Dz1*Cu!xKM$rr?ZC(&k z5e~yVdI?}-0N4wML_}boRA5rQS`1G*;;r&};gFbg^SBx3)XiQUipTAUw@5edi5jJV z6gXC3lFK>k|1PpaBFdK_zQgTKO=^8=@09uy2wj)75* gv0yoVjHJwKoMXQi4v9fWJm^6E3{V%D6u7nmzmpgkjsO4v From 5f8a683c037e88b553d842e0be53bd5f125e74ac Mon Sep 17 00:00:00 2001 From: unsecretised Date: Tue, 13 Jan 2026 21:49:54 +0800 Subject: [PATCH 22/65] Fix functionality and a ~~few~~ lot of errors --- src/app.rs | 437 +---------------------------------------- src/app/tile.rs | 49 ++++- src/app/tile/elm.rs | 29 ++- src/app/tile/update.rs | 46 +++-- src/commands.rs | 28 ++- src/macos.rs | 11 +- src/main.rs | 22 +-- src/utils.rs | 139 ++----------- src/windows.rs | 5 - 9 files changed, 127 insertions(+), 639 deletions(-) diff --git a/src/app.rs b/src/app.rs index d48d35b..79f184c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,30 +1,6 @@ //! Main logic for the app use crate::commands::Function; -use crate::config::Config; -use crate::utils::{get_config_file_path, get_installed_apps, read_config_file}; -use global_hotkey::{GlobalHotKeyEvent, HotKeyState}; -use iced::futures::SinkExt; -use iced::{ - Alignment, Element, Fill, Subscription, Task, Theme, - alignment::Vertical, - futures, - keyboard::{self, key::Named}, - stream, - widget::{ - Button, Column, Row, Text, container, image::Viewer, operation, scrollable, space, - text::LineHeight, text_input, - }, - window::{self, Id, Settings}, -}; - -#[cfg(target_os = "macos")] -use { - crate::macos::{focus_this_app, macos_window_config, transform_process_to_ui_element}, - objc2::rc::Retained, - objc2_app_kit::NSApplicationActivationOptions, - objc2_app_kit::NSRunningApplication, - objc2_app_kit::NSWorkspace, -}; +use iced::window::{self, Id, Settings}; #[cfg(target_os = "windows")] use { @@ -34,19 +10,12 @@ use { windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, }; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use rayon::slice::ParallelSliceMut; - -use std::cmp::min; -use std::fs; -use std::time::Duration; use crate::{app::tile::ExtSender, clipboard::ClipBoardContentType}; pub mod apps; pub mod menubar; pub mod tile; -use iced::window::{self, Id, Settings}; /// The default window width pub const WINDOW_WIDTH: f32 = 500.; @@ -98,399 +67,11 @@ pub fn default_settings() -> Settings { } } -#[derive(Debug, Clone)] -pub struct Tile { - theme: iced::Theme, - query: String, - query_lc: String, - prev_query_lc: String, - results: Vec, - options: Vec, - visible: bool, - focused: bool, - #[cfg(target_os = "macos")] - frontmost: Option>, - #[cfg(target_os = "windows")] - frontmost: Option, - config: Config, - open_hotkey_id: u32, -} - -impl Tile { - /// A base window - pub fn new(keybind_id: u32, config: &Config) -> (Self, Task) { - let settings = default_settings(); - #[cfg(target_os = "windows")] - { - // get normal settings and modify position - let pos = open_on_focused_monitor(); - settings.position = Specific(pos); - } - - let (id, open) = window::open(settings); - - let open = open.discard().chain(window::run(id, |handle| { - { - #[cfg(target_os = "macos")] - { - macos_window_config( - &handle.window_handle().expect("Unable to get window handle"), - ); - // should work now that we have a window - transform_process_to_ui_element(); - } - } - })); - - // get config - let path = get_config_file_path(); - let config = read_config_file(&path).unwrap(); - - let mut options: Vec = get_installed_apps(&config); - - options.extend(config.shells.iter().map(|x| x.to_app())); - options.extend(App::basic_apps()); - options.par_sort_by_key(|x| x.name.len()); - - ( - Self { - query: String::new(), - query_lc: String::new(), - prev_query_lc: String::new(), - results: vec![], - options, - visible: true, - frontmost: None, - focused: false, - config: config.clone(), - theme: config.theme.to_owned().to_iced_theme(), - open_hotkey_id: keybind_id, - }, - Task::batch([open.map(|_| Message::OpenWindow)]), - ) - } - - pub fn update(&mut self, message: Message) -> Task { - match message { - Message::OpenWindow => { - self.capture_frontmost(); - #[cfg(target_os = "macos")] - { - focus_this_app(); - } - - self.focused = true; - Task::none() - } - - Message::SearchQueryChanged(input, id) => { - self.query_lc = input.trim().to_lowercase(); - self.query = input; - let prev_size = self.results.len(); - if self.query_lc.is_empty() { - self.results = vec![]; - return window::resize( - id, - iced::Size { - width: WINDOW_WIDTH, - height: DEFAULT_WINDOW_HEIGHT, - }, - ); - } else if self.query_lc == "randomvar" { - let rand_num = rand::random_range(0..100); - self.results = vec![App { - open_command: Function::RandomVar(rand_num), - icons: None, - name: rand_num.to_string(), - name_lc: String::new(), - }]; - return window::resize( - id, - iced::Size { - width: WINDOW_WIDTH, - height: 55. + DEFAULT_WINDOW_HEIGHT, - }, - ); - } else if self.query_lc.ends_with("?") { - self.results = vec![App { - open_command: Function::GoogleSearch(self.query.clone()), - icons: None, - name: format!("Search for: {}", self.query), - name_lc: String::new(), - }]; - return window::resize( - id, - iced::Size::new(WINDOW_WIDTH, 55. + DEFAULT_WINDOW_HEIGHT), - ); - } - - self.handle_search_query_changed(); - let new_length = self.results.len(); - - let max_elem = min(5, new_length); - if prev_size != new_length { - window::resize( - id, - iced::Size { - width: WINDOW_WIDTH, - height: ((max_elem * 55) + DEFAULT_WINDOW_HEIGHT as usize) as f32, - }, - ) - } else { - Task::none() - } - } - - Message::ClearSearchQuery => { - self.query_lc = String::new(); - self.query = String::new(); - self.prev_query_lc = String::new(); - Task::none() - } - - Message::ReloadConfig => { - self.config = toml::from_str( - &fs::read_to_string(get_config_file_path()).unwrap_or("".to_owned()), - ) - .unwrap(); - - Task::none() - } - - Message::KeyPressed(hk_id) => { - if hk_id == self.open_hotkey_id { - self.visible = !self.visible; - if self.visible { - #[cfg(target_os = "windows")] - { - // get normal settings and modify position - let pos = open_on_focused_monitor(); - let mut settings = default_settings(); - settings.position = Specific(pos); - Task::chain( - window::open(settings).1.map(|_| Message::OpenWindow), - operation::focus("query"), - ) - } - - #[cfg(target_os = "macos")] - { - Task::chain( - window::open(default_settings()) - .1 - .map(|_| Message::OpenWindow), - operation::focus("query"), - ) - } - } else { - let to_close = window::latest().map(|x| x.unwrap()); - Task::batch([ - to_close.map(Message::HideWindow), - Task::done(if self.config.buffer_rules.clone().clear_on_hide { - Message::ClearSearchQuery - } else { - Message::_Nothing - }), - ]) - } - } else { - Task::none() - } - } - - Message::RunFunction(command) => { - command.execute(&self.config); - - if self.config.buffer_rules.clear_on_enter { - window::latest() - .map(|x| x.unwrap()) - .map(Message::HideWindow) - .chain(Task::done(Message::ClearSearchQuery)) - } else { - Task::none() - } - } - - Message::HideWindow(a) => { - self.restore_frontmost(); - self.visible = false; - self.focused = false; - Task::batch([window::close(a), Task::done(Message::ClearSearchResults)]) - } - Message::ClearSearchResults => { - self.results = vec![]; - Task::none() - } - Message::WindowFocusChanged(wid, focused) => { - self.focused = focused; - if !focused { - Task::done(Message::HideWindow(wid)) - .chain(Task::done(Message::ClearSearchQuery)) - } else { - Task::none() - } - } - - Message::_Nothing => Task::none(), - } - } - - pub fn view(&self, wid: window::Id) -> Element<'_, Message> { - if self.visible { - let title_input = text_input(self.config.placeholder.as_str(), &self.query) - .on_input(move |a| Message::SearchQueryChanged(a, wid)) - .on_paste(move |a| Message::SearchQueryChanged(a, wid)) - .on_submit({ - if self.results.is_empty() { - Message::_Nothing - } else { - Message::RunFunction(self.results.first().unwrap().to_owned().open_command) - } - }) - .id("query") - .width(Fill) - .padding(20) - .line_height(LineHeight::Relative(1.5)); - - let mut search_results = Column::new(); - for result in &self.results { - search_results = - search_results.push(result.render(self.config.theme.clone().show_icons)); - } - - Column::new() - .push(title_input) - .push(scrollable(search_results)) - .into() - } else { - space().into() - } - } - - pub fn theme(&self, _: window::Id) -> Option { - Some(self.theme.clone()) - } - - pub fn subscription(&self) -> Subscription { - Subscription::batch([ - Subscription::run(handle_hotkeys), - Subscription::run(handle_hot_reloading), - window::close_events().map(Message::HideWindow), - keyboard::listen().filter_map(|event| { - if let keyboard::Event::KeyPressed { key, .. } = event { - match key { - keyboard::Key::Named(Named::Escape) => Some(Message::KeyPressed(65598)), - _ => None, - } - } else { - None - } - }), - window::events() - .with(self.focused) - .filter_map(|(focused, (wid, event))| match event { - window::Event::Unfocused => { - if focused { - Some(Message::WindowFocusChanged(wid, false)) - } else { - None - } - } - window::Event::Focused => Some(Message::WindowFocusChanged(wid, true)), - _ => None, - }), - ]) - } - - pub fn handle_search_query_changed(&mut self) { - let filter_vec: &Vec = if self.query_lc.starts_with(&self.prev_query_lc) { - self.prev_query_lc = self.query_lc.to_owned(); - &self.results - } else { - &self.options - }; - - let query = self.query_lc.clone(); - - let mut exact: Vec = filter_vec - .par_iter() - .filter(|x| x.name_lc == query) - .cloned() - .collect(); - - let mut prefix: Vec = filter_vec - .par_iter() - .filter(|x| x.name_lc != query && x.name_lc.starts_with(&query)) - .cloned() - .collect(); - - exact.append(&mut prefix); - self.results = exact; - } - - pub fn capture_frontmost(&mut self) { - #[cfg(target_os = "macos")] - { - let ws = NSWorkspace::sharedWorkspace(); - self.frontmost = ws.frontmostApplication(); - }; - - #[cfg(target_os = "windows")] - { - self.frontmost = Some(unsafe { GetForegroundWindow() }); - } - } - - #[allow(deprecated)] - pub fn restore_frontmost(&mut self) { - #[cfg(target_os = "macos")] - { - if let Some(app) = self.frontmost.take() { - app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); - } - } - - #[cfg(target_os = "windows")] - { - if let Some(handle) = self.frontmost { - unsafe { - let _ = SetForegroundWindow(handle); - } - } - } - } -} - -fn handle_hot_reloading() -> impl futures::Stream { - stream::channel(100, async |mut output| { - let content = fs::read_to_string( - std::env::var("HOME").unwrap_or("".to_owned()) + "/.config/rustcast/config.toml", - ) - .unwrap_or("".to_string()); - loop { - let current_content = fs::read_to_string( - std::env::var("HOME").unwrap_or("".to_owned()) + "/.config/rustcast/config.toml", - ) - .unwrap_or("".to_string()); - - if current_content != content { - output.send(Message::ReloadConfig).await.unwrap(); - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) -} - -fn handle_hotkeys() -> impl futures::Stream { - stream::channel(100, async |mut output| { - let receiver = GlobalHotKeyEvent::receiver(); - loop { - if let Ok(event) = receiver.recv() - && event.state == HotKeyState::Pressed - { - output.try_send(Message::KeyPressed(event.id)).unwrap(); - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) -} +// Message::ReloadConfig => { +// self.config = toml::from_str( +// &fs::read_to_string(get_config_file_path()).unwrap_or("".to_owned()), +// ) +// .unwrap(); +// +// Task::none() +// } diff --git a/src/app/tile.rs b/src/app/tile.rs index 08bebef..69b29f7 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -1,6 +1,14 @@ //! This module handles the logic for the tile, AKA rustcast's main window -mod elm; -mod update; +pub mod elm; +pub mod update; + +#[cfg(target_os = "windows")] +use { + crate::windows::open_on_focused_monitor, + iced::window::Position::Specific, + windows::Win32::Foundation::HWND, + windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, +}; use crate::app::apps::{App, AppCommand}; use crate::app::tile::elm::default_app_paths; @@ -23,7 +31,9 @@ use iced::{ stream, }; +#[cfg(target_os = "macos")] use objc2::rc::Retained; +#[cfg(target_os = "macos")] use objc2_app_kit::NSRunningApplication; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use tray_icon::TrayIcon; @@ -66,7 +76,10 @@ pub struct Tile { options: Vec, visible: bool, focused: bool, + #[cfg(target_os = "macos")] frontmost: Option>, + #[cfg(target_os = "windows")] + frontmost: Option, config: Config, open_hotkey_id: u32, hotkey: (Option, Code), @@ -197,19 +210,39 @@ impl Tile { /// Gets the frontmost application to focus later. pub fn capture_frontmost(&mut self) { - use objc2_app_kit::NSWorkspace; + #[cfg(target_os = "macos")] + { + use objc2_app_kit::NSWorkspace; + + let ws = NSWorkspace::sharedWorkspace(); + self.frontmost = ws.frontmostApplication(); + }; - let ws = NSWorkspace::sharedWorkspace(); - self.frontmost = ws.frontmostApplication(); + #[cfg(target_os = "windows")] + { + self.frontmost = Some(unsafe { GetForegroundWindow() }); + } } /// Restores the frontmost application. #[allow(deprecated)] pub fn restore_frontmost(&mut self) { - use objc2_app_kit::NSApplicationActivationOptions; + #[cfg(target_os = "macos")] + { + if let Some(app) = self.frontmost.take() { + use objc2_app_kit::NSApplicationActivationOptions; - if let Some(app) = self.frontmost.take() { - app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); + app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); + } + } + + #[cfg(target_os = "windows")] + { + if let Some(handle) = self.frontmost { + unsafe { + let _ = SetForegroundWindow(handle); + } + } } } } diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 60e25b1..c53894c 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -10,18 +10,15 @@ use iced::{Color, window}; use iced::{Element, Task}; use iced::{Length::Fill, widget::text_input}; -use rayon::{ - iter::{IntoParallelRefIterator, ParallelIterator}, - slice::ParallelSliceMut, -}; +use rayon::slice::ParallelSliceMut; use crate::app::apps::AppCommand; use crate::config::Theme; +use crate::utils::get_installed_apps; use crate::{ app::{Message, Page, apps::App, default_settings, tile::Tile}, config::Config, macos::{self, transform_process_to_ui_element}, - utils::get_installed_apps, }; pub fn default_app_paths() -> Vec { @@ -43,22 +40,24 @@ pub fn new( keybind_id: u32, config: &Config, ) -> (Tile, Task) { - let (id, open) = window::open(default_settings()); + #[allow(unused_mut)] + let mut settings = default_settings(); + + #[cfg(target_os = "windows")] + { + // get normal settings and modify position + let pos = open_on_focused_monitor(); + settings.position = Specific(pos); + } + + let (id, open) = window::open(settings); let open = open.discard().chain(window::run(id, |handle| { macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); transform_process_to_ui_element(); })); - let store_icons = config.theme.show_icons; - - let paths = default_app_paths(); - - let mut options: Vec = paths - .par_iter() - .map(|path| get_installed_apps(path, store_icons)) - .flatten() - .collect(); + let mut options: Vec = get_installed_apps(&config); options.extend(config.shells.iter().map(|x| x.to_app())); options.extend(App::basic_apps()); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index ccd06d8..6bcecb9 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -9,8 +9,6 @@ use iced::Task; use iced::widget::image::Handle; use iced::widget::operation; use iced::window; -use rayon::iter::IntoParallelRefIterator; -use rayon::iter::ParallelIterator; use rayon::slice::ParallelSliceMut; use crate::app::DEFAULT_WINDOW_HEIGHT; @@ -20,7 +18,6 @@ use crate::app::apps::App; use crate::app::apps::AppCommand; use crate::app::default_settings; use crate::app::menubar::menu_icon; -use crate::app::tile::elm::default_app_paths; use crate::calculator::Expression; use crate::commands::Function; use crate::config::Config; @@ -36,8 +33,11 @@ use crate::{ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { match message { Message::OpenWindow => { - tile.capture_frontmost(); - focus_this_app(); + #[cfg(target_os = "macos")] + { + tile.capture_frontmost(); + focus_this_app(); + } tile.focused = true; tile.visible = true; Task::none() @@ -132,6 +132,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name_lc: "".to_string(), }); } else if tile.results.is_empty() && is_valid_url(&tile.query) { + #[cfg(target_os = "macos")] tile.results.push(App { open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), desc: "Web Browsing".to_string(), @@ -140,6 +141,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name_lc: "".to_string(), }); } else if tile.results.is_empty() && tile.query_lc == "lemon" { + #[cfg(target_os = "macos")] tile.results.push(App { open_command: AppCommand::Display, desc: "Easter Egg".to_string(), @@ -199,6 +201,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { } Message::ReloadConfig => { + #[cfg(target_os = "macos")] let new_config: Config = toml::from_str( &fs::read_to_string( std::env::var("HOME").unwrap_or("".to_owned()) @@ -208,11 +211,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { ) .unwrap(); - let mut new_options: Vec = default_app_paths() - .par_iter() - .map(|path| get_installed_apps(path, new_config.theme.show_icons)) - .flatten() - .collect(); + let mut new_options: Vec = get_installed_apps(&new_config); new_options.extend(new_config.shells.iter().map(|x| x.to_app())); new_options.extend(App::basic_apps()); @@ -229,12 +228,27 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { if hk_id == tile.open_hotkey_id { tile.visible = !tile.visible; if tile.visible { - Task::chain( - window::open(default_settings()) - .1 - .map(|_| Message::OpenWindow), - operation::focus("query"), - ) + #[cfg(target_os = "windows")] + { + // get normal settings and modify position + let pos = open_on_focused_monitor(); + let mut settings = default_settings(); + settings.position = Specific(pos); + Task::chain( + window::open(settings).1.map(|_| Message::OpenWindow), + operation::focus("query"), + ) + } + + #[cfg(target_os = "macos")] + { + Task::chain( + window::open(default_settings()) + .1 + .map(|_| Message::OpenWindow), + operation::focus("query"), + ) + } } else { let clear_search_query = if tile.config.buffer_rules.clear_on_hide { Task::done(Message::ClearSearchQuery) diff --git a/src/commands.rs b/src/commands.rs index 609f4f9..8444337 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,6 +1,8 @@ //! This handles all the different commands that rustcast can perform, such as opening apps, //! copying to clipboard, etc. -use std::{process::Command, thread}; +use std::process::Command; +#[cfg(target_os = "macos")] +use std::thread; use arboard::Clipboard; #[cfg(target_os = "macos")] @@ -8,7 +10,7 @@ use objc2_app_kit::NSWorkspace; #[cfg(target_os = "macos")] use objc2_foundation::NSURL; -use crate::utils::{get_config_file_path, open_application}; +use crate::utils::open_application; use crate::{calculator::Expression, clipboard::ClipBoardContentType, config::Config}; /// The different functions that rustcast can perform @@ -63,17 +65,13 @@ impl Function { } #[cfg(target_os = "macos")] - { - NSWorkspace::new().openURL( - &NSURL::URLWithString_relativeToURL( - &objc2_foundation::NSString::from_str(query), - &NSURL::URLWithString_relativeToURL( - &objc2_foundation::NSString::from_str(&query), - None, - ) - .unwrap(), - ); - }); + NSWorkspace::new().openURL( + &NSURL::URLWithString_relativeToURL( + &objc2_foundation::NSString::from_str(query), + None, + ) + .unwrap(), + ); } Function::OpenWebsite(url) => { @@ -82,7 +80,7 @@ impl Function { } else { format!("https://{}", url) }; - #[cfg(os= "macos")] + #[cfg(target_os = "macos")] thread::spawn(move || { NSWorkspace::new().openURL( &NSURL::URLWithString_relativeToURL( @@ -111,7 +109,7 @@ impl Function { }, Function::OpenPrefPane => { - #[cfg(os="macos")] + #[cfg(target_os = "macos")] thread::spawn(move || { NSWorkspace::new().openURL(&NSURL::fileURLWithPath( &objc2_foundation::NSString::from_str( diff --git a/src/macos.rs b/src/macos.rs index 213626a..5e50e58 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -1,11 +1,10 @@ //! Macos specific logic, such as window settings, etc. #![allow(deprecated)] -use crate::app::App; +use crate::app::apps::{App, AppCommand}; use crate::commands::Function; use crate::config::Config; use crate::utils::index_dirs_from_config; use crate::utils::{handle_from_icns, log_error, log_error_and_exit}; -use std::ptr; #[cfg(target_os = "macos")] use { iced::wgpu::rwh::RawWindowHandle, @@ -15,9 +14,6 @@ use { objc2_app_kit::NSView, objc2_app_kit::{NSApp, NSApplicationActivationPolicy}, objc2_app_kit::{NSFloatingWindowLevel, NSWindowCollectionBehavior}, - objc2_application_services::{ - TransformProcessType, kCurrentProcess, kProcessTransformToUIElementApplication, - }, }; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; @@ -104,7 +100,7 @@ pub fn transform_process_to_ui_element() -> u32 { } } -pub(crate) fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { +fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let entries: Vec<_> = fs::read_dir(dir.as_ref()) .unwrap_or_else(|x| { log_error_and_exit(&x.to_string()); @@ -212,7 +208,8 @@ pub(crate) fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Ve let name = file_name.strip_suffix(".app").unwrap().to_string(); Some(App { - open_command: Function::OpenApp(path_str), + open_command: AppCommand::Function(Function::OpenApp(path_str)), + desc: "Application".to_string(), icons, name_lc: name.to_lowercase(), name, diff --git a/src/main.rs b/src/main.rs index 51e6d30..dae10ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,10 @@ mod calculator; mod clipboard; mod commands; mod config; + +#[cfg(target_os = "macos")] mod haptics; + mod macos; mod utils; mod windows; @@ -11,10 +14,7 @@ mod windows; // import from utils use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; -use crate::{app::Tile, utils::to_key_code}; -use std::path::Path; - -use crate::{app::tile::Tile, config::Config}; +use crate::app::tile::Tile; use global_hotkey::{ GlobalHotKeyManager, @@ -31,20 +31,6 @@ fn main() -> iced::Result { let config = read_config_file(&file_path).unwrap(); create_config_file_if_not_exists(&file_path, &config).unwrap(); - let file_path = home.clone() + "/.config/rustcast/config.toml"; - if !Path::new(&file_path).exists() { - std::fs::create_dir_all(home + "/.config/rustcast").unwrap(); - std::fs::write( - &file_path, - toml::to_string(&Config::default()).unwrap_or_else(|x| x.to_string()), - ) - .unwrap(); - } - let config: Config = match std::fs::read_to_string(&file_path) { - Ok(a) => toml::from_str(&a).unwrap_or(Config::default()), - Err(_) => Config::default(), - }; - let manager = GlobalHotKeyManager::new().unwrap(); let modifier = Modifiers::from_name(&config.toggle_mod); diff --git a/src/utils.rs b/src/utils.rs index de6ff73..adda156 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,17 +11,14 @@ use iced::widget::image::Handle; use icns::IconFamily; use image::RgbaImage; -use crate::{app::App, commands::Function}; #[cfg(target_os = "macos")] use { crate::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, objc2_foundation::NSURL, std::os::unix::fs::PermissionsExt, }; + #[cfg(target_os = "windows")] use {crate::windows::get_installed_windows_apps, std::process::Command}; -use objc2_app_kit::NSWorkspace; -use objc2_foundation::NSURL; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; use crate::{ app::apps::{App, AppCommand}, @@ -65,128 +62,8 @@ pub(crate) fn handle_from_icns(path: &Path) -> Option { )) } -/// This gets all the installed apps in the given directory -/// -/// the directories are defined in [`crate::app::tile::Tile::new`] -pub(crate) fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { - let entries: Vec<_> = fs::read_dir(dir.as_ref()) - .unwrap_or_else(|x| { - log_error_and_exit(&x.to_string()); - exit(-1) - }) - .filter_map(|x| x.ok()) - .collect(); - - entries - .into_par_iter() - .filter_map(|x| { - let file_type = x.file_type().unwrap_or_else(|e| { - log_error(&e.to_string()); - exit(-1) - }); - if !file_type.is_dir() { - return None; - } - - let file_name_os = x.file_name(); - let file_name = file_name_os.into_string().unwrap_or_else(|e| { - log_error(e.to_str().unwrap_or("")); - exit(-1) - }); - if !file_name.ends_with(".app") { - return None; - } - - let path = x.path(); - let path_str = path.to_str().map(|x| x.to_string()).unwrap_or_else(|| { - log_error("Unable to get file_name"); - exit(-1) - }); - - let icons = if store_icons { - match fs::read_to_string(format!("{}/Contents/Info.plist", path_str)).map( - |content| { - let icon_line = content - .lines() - .scan(false, |expect_next, line| { - if *expect_next { - *expect_next = false; - // Return this line to the iterator - return Some(Some(line)); - } - - if line.trim() == "CFBundleIconFile" { - *expect_next = true; - } - - // For lines that are not the one after the key, return None to skip - Some(None) - }) - .flatten() // remove the Nones - .next() - .map(|x| { - x.trim() - .strip_prefix("") - .unwrap_or("") - .strip_suffix("") - .unwrap_or("") - }); - - handle_from_icns(Path::new(&format!( - "{}/Contents/Resources/{}", - path_str, - icon_line.unwrap_or("AppIcon.icns") - ))) - }, - ) { - Ok(Some(a)) => Some(a), - _ => { - // Fallback method - let direntry = fs::read_dir(format!("{}/Contents/Resources", path_str)) - .into_iter() - .flatten() - .filter_map(|x| { - let file = x.ok()?; - let name = file.file_name(); - let file_name = name.to_str()?; - if file_name.ends_with(".icns") { - Some(file.path()) - } else { - None - } - }) - .collect::>(); - - if direntry.len() > 1 { - let icns_vec = direntry - .iter() - .filter(|x| x.ends_with("AppIcon.icns")) - .collect::>(); - handle_from_icns(icns_vec.first().unwrap_or(&&PathBuf::new())) - } else if !direntry.is_empty() { - handle_from_icns(direntry.first().unwrap_or(&PathBuf::new())) - } else { - None - } - } - } - } else { - None - }; - - let name = file_name.strip_suffix(".app").unwrap().to_string(); - Some(App { - open_command: AppCommand::Function(Function::OpenApp(path_str)), - desc: "Application".to_string(), - icons, - name_lc: name.to_lowercase(), - name, - }) - }) - .collect() -} - pub fn open_settings() { + #[cfg(target_os = "macos")] thread::spawn(move || { NSWorkspace::new().openURL(&NSURL::fileURLWithPath( &objc2_foundation::NSString::from_str( @@ -199,6 +76,7 @@ pub fn open_settings() { pub fn open_url(url: &str) { let url = url.to_owned(); + #[cfg(target_os = "macos")] thread::spawn(move || { NSWorkspace::new().openURL( &NSURL::URLWithString_relativeToURL(&objc2_foundation::NSString::from_str(&url), None) @@ -274,7 +152,9 @@ pub fn create_config_file_if_not_exists( } pub fn open_application(path: &str) { + let path_string = path.to_string(); thread::spawn(move || { + let path = &path_string; #[cfg(target_os = "windows")] { println!("Opening application: {}", path); @@ -296,9 +176,10 @@ pub fn open_application(path: &str) { { Command::new("xdg-open").arg(path).status().ok(); } - }; + }); } +#[allow(unused)] pub fn index_dirs_from_config(apps: &mut Vec) -> bool { let path = get_config_file_path(); let config = read_config_file(&path); @@ -342,8 +223,11 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { if is_executable { let display_name = path.file_name().unwrap().to_string_lossy().to_string(); apps.push(App { - open_command: Function::OpenApp(path.to_string_lossy().to_string()), + open_command: AppCommand::Function(Function::OpenApp( + path.to_string_lossy().to_string(), + )), name: display_name.clone(), + desc: "Application".to_string(), name_lc: display_name.clone().to_lowercase(), icons: None, }); @@ -354,6 +238,7 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { true } +/// Use this to get installed apps pub fn get_installed_apps(config: &Config) -> Vec { #[cfg(target_os = "macos")] { diff --git a/src/windows.rs b/src/windows.rs index e377753..7a4171f 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,8 +1,3 @@ -use crate::utils::index_dirs_from_config; -use crate::{app::App, commands::Function}; -use walkdir::WalkDir; - -use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; #[cfg(target_os = "windows")] use { windows::Win32::System::Com::CoTaskMemFree, From 9b3e8f14bc9f6cdd20e33483e2c730dd30218d75 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 19:01:09 +0000 Subject: [PATCH 23/65] fix: make it compile --- build.rs | 9 ++++++--- src/app/tile/elm.rs | 40 +++++++++++++++++++++++++++++----------- src/app/tile/update.rs | 25 ++++++++++++++++++++++--- src/main.rs | 2 ++ src/windows.rs | 23 ++++++++++++++++------- 5 files changed, 75 insertions(+), 24 deletions(-) diff --git a/build.rs b/build.rs index a4b0b05..e2dfc45 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,8 @@ fn main() { - println!("cargo:rustc-link-search=framework=/System/Library/PrivateFrameworks"); - println!("cargo:rustc-link-lib=framework=IOKit"); - println!("cargo:rustc-link-lib=framework=MultitouchSupport"); + #[cfg(target_os = "macos")] + { + println!("cargo:rustc-link-search=framework=/System/Library/PrivateFrameworks"); + println!("cargo:rustc-link-lib=framework=IOKit"); + println!("cargo:rustc-link-lib=framework=MultitouchSupport"); + } } diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index c53894c..e7cc2f3 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -18,20 +18,33 @@ use crate::utils::get_installed_apps; use crate::{ app::{Message, Page, apps::App, default_settings, tile::Tile}, config::Config, - macos::{self, transform_process_to_ui_element}, }; -pub fn default_app_paths() -> Vec { - let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; +#[cfg(target_os = "macos")] +use crate::macos::{self, transform_process_to_ui_element}; - let paths = vec![ - "/Applications/".to_string(), - user_local_path, - "/System/Applications/".to_string(), - "/System/Applications/Utilities/".to_string(), - ]; +pub fn default_app_paths() -> Vec { + #[cfg(target_os = "macos")] + { + let user_local_path = std::env::var("HOME").unwrap() + "/Applications/"; + + let paths = vec![ + "/Applications/".to_string(), + user_local_path, + "/System/Applications/".to_string(), + "/System/Applications/Utilities/".to_string(), + ]; + paths + } - paths + #[cfg(target_os = "windows")] + { + let paths = vec![ + "C:/Program Files".to_string(), + "C:/Program Files (x86)".to_string() + ]; + paths + } } /// Initialise the base window @@ -46,12 +59,17 @@ pub fn new( #[cfg(target_os = "windows")] { // get normal settings and modify position + + use iced::window::Position; + + use crate::windows::open_on_focused_monitor; let pos = open_on_focused_monitor(); - settings.position = Specific(pos); + settings.position = Position::Specific(pos); } let (id, open) = window::open(settings); + #[cfg(target_os = "macos")] let open = open.discard().chain(window::run(id, |handle| { macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); transform_process_to_ui_element(); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 6bcecb9..9a767b0 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -21,13 +21,18 @@ use crate::app::menubar::menu_icon; use crate::calculator::Expression; use crate::commands::Function; use crate::config::Config; -use crate::haptics::HapticPattern; -use crate::haptics::perform_haptic; + use crate::utils::get_installed_apps; use crate::utils::is_valid_url; use crate::{ app::{Message, Page, tile::Tile}, +}; + +#[cfg(target_os = "macos")] +use crate::{ macos::focus_this_app, + haptics::HapticPattern, + haptics::perform_haptic }; pub fn handle_update(tile: &mut Tile, message: Message) -> Task { @@ -211,6 +216,16 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { ) .unwrap(); + #[cfg(target_os = "windows")] + let new_config: Config = toml::from_str( + &fs::read_to_string( + std::env::var("LOCALAPPDATA").unwrap_or("".to_owned()) + + "/rustcast/config.toml", + ) + .unwrap_or("".to_owned()), + ) + .unwrap(); + let mut new_options: Vec = get_installed_apps(&new_config); new_options.extend(new_config.shells.iter().map(|x| x.to_app())); @@ -231,9 +246,13 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] { // get normal settings and modify position + + use iced::window::Position; + + use crate::windows::open_on_focused_monitor; let pos = open_on_focused_monitor(); let mut settings = default_settings(); - settings.position = Specific(pos); + settings.position = Position::Specific(pos); Task::chain( window::open(settings).1.map(|_| Message::OpenWindow), operation::focus("query"), diff --git a/src/main.rs b/src/main.rs index dae10ef..368d00c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,8 @@ fn main() -> iced::Result { .register_all(&hotkeys) .expect("Unable to register hotkey"); + println!("Starting"); + iced::daemon( move || Tile::new((modifier, key), show_hide.id(), &config), Tile::update, diff --git a/src/windows.rs b/src/windows.rs index 7a4171f..3602c02 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,12 +1,9 @@ #[cfg(target_os = "windows")] use { - windows::Win32::System::Com::CoTaskMemFree, - windows::Win32::UI::Shell::{ + crate::app::apps::App, windows::{Win32::{System::Com::CoTaskMemFree, UI::{Shell::{ FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, SHGetKnownFolderPath, - }, - windows::Win32::UI::WindowsAndMessaging::GetCursorPos, - windows::core::GUID, + }, WindowsAndMessaging::GetCursorPos}}, core::GUID} }; #[cfg(target_os = "windows")] @@ -50,11 +47,14 @@ fn get_apps_from_registry(apps: &mut Vec) { } if !display_name.is_empty() { + use crate::{app::apps::AppCommand, commands::Function}; + apps.push(App { - open_command: Function::OpenApp(exe), + open_command: AppCommand::Function(Function::OpenApp(exe)), name: display_name.clone().into_string().unwrap(), name_lc: display_name.clone().into_string().unwrap().to_lowercase(), icons: None, + desc: "TODO: Implement".to_string() }) } }); @@ -65,14 +65,18 @@ fn get_apps_from_known_folder(apps: &mut Vec) { let paths = get_known_paths(); for path in paths { + use walkdir::WalkDir; + for entry in WalkDir::new(path) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) { + use crate::{app::apps::AppCommand, commands::Function}; + apps.push(App { - open_command: Function::OpenApp(entry.path().to_string_lossy().to_string()), + open_command: AppCommand::Function(Function::OpenApp(entry.path().to_string_lossy().to_string())), name: entry .clone() .file_name() @@ -87,6 +91,7 @@ fn get_apps_from_known_folder(apps: &mut Vec) { .to_lowercase() .replace(".exe", ""), icons: None, + desc: "TODO: Implement".to_string() }); } } @@ -115,6 +120,8 @@ fn get_windows_path(folder_id: &GUID) -> Option { } #[cfg(target_os = "windows")] pub fn get_installed_windows_apps() -> Vec { + use crate::utils::index_dirs_from_config; + let mut apps = Vec::new(); get_apps_from_registry(&mut apps); get_apps_from_known_folder(&mut apps); @@ -128,6 +135,8 @@ pub fn open_on_focused_monitor() -> iced::Point { use windows::Win32::Graphics::Gdi::{ GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, }; + + use crate::app::{DEFAULT_WINDOW_HEIGHT, WINDOW_WIDTH}; let mut point = POINT { x: 0, y: 0 }; let mut monitor_info = MONITORINFO { cbSize: std::mem::size_of::() as u32, From 33d5f55e1a6cf3b44f38e27c26067bf25ea64137 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 19:12:20 +0000 Subject: [PATCH 24/65] refractor: general refractors --- src/app.rs | 7 ------- src/app/tile.rs | 2 -- src/app/tile/elm.rs | 21 +++++++++++---------- src/app/tile/update.rs | 2 -- src/macos.rs | 5 ----- src/main.rs | 8 +++++--- 6 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/app.rs b/src/app.rs index 79f184c..29ccd32 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,13 +2,6 @@ use crate::commands::Function; use iced::window::{self, Id, Settings}; -#[cfg(target_os = "windows")] -use { - crate::windows::open_on_focused_monitor, - iced::window::Position::Specific, - windows::Win32::Foundation::HWND, - windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, -}; use crate::{app::tile::ExtSender, clipboard::ClipBoardContentType}; diff --git a/src/app/tile.rs b/src/app/tile.rs index 69b29f7..edc2f69 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -4,8 +4,6 @@ pub mod update; #[cfg(target_os = "windows")] use { - crate::windows::open_on_focused_monitor, - iced::window::Position::Specific, windows::Win32::Foundation::HWND, windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, }; diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index e7cc2f3..11cd89d 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -39,11 +39,7 @@ pub fn default_app_paths() -> Vec { #[cfg(target_os = "windows")] { - let paths = vec![ - "C:/Program Files".to_string(), - "C:/Program Files (x86)".to_string() - ]; - paths + Vec::new() } } @@ -67,13 +63,18 @@ pub fn new( settings.position = Position::Specific(pos); } - let (id, open) = window::open(settings); + #[cfg(target_os = "macos")] - let open = open.discard().chain(window::run(id, |handle| { - macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); - transform_process_to_ui_element(); - })); + { + let open = open.discard().chain(window::run(id, |handle| { + macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); + transform_process_to_ui_element(); + })); + } + + #[cfg(target_os = "windows")] + let (id, open) = window::open(settings); let mut options: Vec = get_installed_apps(&config); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 9a767b0..1f000e4 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -1,12 +1,10 @@ //! This handles the update logic for the tile (AKA rustcast's main window) use std::cmp::min; use std::fs; -use std::path::Path; use std::time::Duration; use global_hotkey::hotkey::HotKey; use iced::Task; -use iced::widget::image::Handle; use iced::widget::operation; use iced::window; use rayon::slice::ParallelSliceMut; diff --git a/src/macos.rs b/src/macos.rs index 5e50e58..830025c 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -5,7 +5,6 @@ use crate::commands::Function; use crate::config::Config; use crate::utils::index_dirs_from_config; use crate::utils::{handle_from_icns, log_error, log_error_and_exit}; -#[cfg(target_os = "macos")] use { iced::wgpu::rwh::RawWindowHandle, iced::wgpu::rwh::WindowHandle, @@ -23,7 +22,6 @@ use std::process::exit; /// This sets the activation policy of the app to Accessory, allowing rustcast to be visible ontop /// of fullscreen apps -#[cfg(target_os = "macos")] pub fn set_activation_policy_accessory() { let mtm = MainThreadMarker::new().expect("must be on main thread"); let app = NSApp(mtm); @@ -31,7 +29,6 @@ pub fn set_activation_policy_accessory() { } /// This carries out the window configuration for the macos window (only things that are macos specific) -#[cfg(target_os = "macos")] pub fn macos_window_config(handle: &WindowHandle) { match handle.as_raw() { RawWindowHandle::AppKit(handle) => { @@ -55,7 +52,6 @@ pub fn macos_window_config(handle: &WindowHandle) { /// This is the function that forces focus onto rustcast #[allow(deprecated)] -#[cfg(target_os = "macos")] pub fn focus_this_app() { use objc2::MainThreadMarker; use objc2_app_kit::NSApp; @@ -80,7 +76,6 @@ struct ProcessSerialNumber { /// returns ApplicationServices OSStatus (u32) /// /// doesn't seem to do anything if you haven't opened a window yet, so wait to call it until after that. -#[cfg(target_os = "macos")] pub fn transform_process_to_ui_element() -> u32 { use objc2_application_services::{ TransformProcessType, kCurrentProcess, kProcessTransformToUIElementApplication, diff --git a/src/main.rs b/src/main.rs index 368d00c..c4d7ae1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,15 @@ mod calculator; mod clipboard; mod commands; mod config; +mod utils; + +#[cfg(target_os = "windows")] +mod windows; #[cfg(target_os = "macos")] mod haptics; - +#[cfg(target_os = "macos")] mod macos; -mod utils; -mod windows; // import from utils use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; From d56dd9844ffb2bba82c868b3fecb578a04631fff Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 19:37:54 +0000 Subject: [PATCH 25/65] refractor: shove cross platform into its own module Hopefully doesn't break macos too much by accident --- src/app/tile/elm.rs | 4 ++-- src/app/tile/update.rs | 4 ++-- src/{ => cross_platform/macos}/haptics.rs | 0 src/{macos.rs => cross_platform/macos/mod.rs} | 3 +++ src/cross_platform/mod.rs | 5 +++++ src/{ => cross_platform}/windows.rs | 0 src/main.rs | 8 +------- src/utils.rs | 9 +++++++-- 8 files changed, 20 insertions(+), 13 deletions(-) rename src/{ => cross_platform/macos}/haptics.rs (100%) rename src/{macos.rs => cross_platform/macos/mod.rs} (99%) create mode 100644 src/cross_platform/mod.rs rename src/{ => cross_platform}/windows.rs (100%) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 11cd89d..ca5533a 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -21,7 +21,7 @@ use crate::{ }; #[cfg(target_os = "macos")] -use crate::macos::{self, transform_process_to_ui_element}; +use crate::cross_platform::macos::{self, transform_process_to_ui_element}; pub fn default_app_paths() -> Vec { #[cfg(target_os = "macos")] @@ -58,7 +58,7 @@ pub fn new( use iced::window::Position; - use crate::windows::open_on_focused_monitor; + use crate::cross_platform::windows::open_on_focused_monitor; let pos = open_on_focused_monitor(); settings.position = Position::Specific(pos); } diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 1f000e4..900e873 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -246,8 +246,8 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { // get normal settings and modify position use iced::window::Position; - - use crate::windows::open_on_focused_monitor; + use crate::cross_platform::windows::open_on_focused_monitor; + let pos = open_on_focused_monitor(); let mut settings = default_settings(); settings.position = Position::Specific(pos); diff --git a/src/haptics.rs b/src/cross_platform/macos/haptics.rs similarity index 100% rename from src/haptics.rs rename to src/cross_platform/macos/haptics.rs diff --git a/src/macos.rs b/src/cross_platform/macos/mod.rs similarity index 99% rename from src/macos.rs rename to src/cross_platform/macos/mod.rs index 830025c..5ea1ac7 100644 --- a/src/macos.rs +++ b/src/cross_platform/macos/mod.rs @@ -1,5 +1,8 @@ //! Macos specific logic, such as window settings, etc. #![allow(deprecated)] + +pub mod haptics; + use crate::app::apps::{App, AppCommand}; use crate::commands::Function; use crate::config::Config; diff --git a/src/cross_platform/mod.rs b/src/cross_platform/mod.rs new file mode 100644 index 0000000..2bf19bd --- /dev/null +++ b/src/cross_platform/mod.rs @@ -0,0 +1,5 @@ +#[cfg(target_os = "macos")] +pub mod macos; + +#[cfg(target_os = "windows")] +pub mod windows; \ No newline at end of file diff --git a/src/windows.rs b/src/cross_platform/windows.rs similarity index 100% rename from src/windows.rs rename to src/cross_platform/windows.rs diff --git a/src/main.rs b/src/main.rs index c4d7ae1..47bd6e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,7 @@ mod commands; mod config; mod utils; -#[cfg(target_os = "windows")] -mod windows; - -#[cfg(target_os = "macos")] -mod haptics; -#[cfg(target_os = "macos")] -mod macos; +mod cross_platform; // import from utils use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; diff --git a/src/utils.rs b/src/utils.rs index adda156..8f1d48f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,12 +13,17 @@ use image::RgbaImage; #[cfg(target_os = "macos")] use { - crate::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, objc2_foundation::NSURL, + crate::cross_platform::macos::get_installed_macos_apps, + objc2_app_kit::NSWorkspace, + objc2_foundation::NSURL, std::os::unix::fs::PermissionsExt, }; #[cfg(target_os = "windows")] -use {crate::windows::get_installed_windows_apps, std::process::Command}; +use { + crate::cross_platform::windows::get_installed_windows_apps, + std::process::Command +}; use crate::{ app::apps::{App, AppCommand}, From 035ad27e2201d81b579fa4969ea3e5465a1694b2 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 21:33:09 +0000 Subject: [PATCH 26/65] refractor: use url crate --- Cargo.lock | 260 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/app/tile/update.rs | 4 +- src/commands.rs | 2 +- src/utils.rs | 13 --- 5 files changed, 264 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2abd18a..fa576a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,6 +941,17 @@ dependencies = [ "objc2 0.6.3", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1233,6 +1244,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -1966,6 +1986,108 @@ dependencies = [ "png 0.16.8", ] +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.9" @@ -2254,6 +2376,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "litrs" version = "1.0.0" @@ -3208,6 +3336,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3579,6 +3716,7 @@ dependencies = [ "tokio", "toml 0.9.11+spec-1.1.0", "tray-icon", + "url", "walkdir", "windows 0.58.0", "winreg", @@ -3961,6 +4099,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -4103,6 +4252,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.10.0" @@ -4351,6 +4510,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.19.0" @@ -5396,6 +5573,12 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "x11" version = "2.21.0" @@ -5481,6 +5664,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + [[package]] name = "zbus" version = "5.13.1" @@ -5568,6 +5774,60 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 72b4534..58a65bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ tokio = { version = "1.48.0", features = ["full"] } toml = "0.9.8" walkdir = "2" tray-icon = "0.21.3" +url = "2.5.8" [package.metadata.bundle] name = "RustCast" diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 900e873..da2fc71 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -8,6 +8,7 @@ use iced::Task; use iced::widget::operation; use iced::window; use rayon::slice::ParallelSliceMut; +use url::Url; use crate::app::DEFAULT_WINDOW_HEIGHT; use crate::app::RUSTCAST_DESC_NAME; @@ -21,7 +22,6 @@ use crate::commands::Function; use crate::config::Config; use crate::utils::get_installed_apps; -use crate::utils::is_valid_url; use crate::{ app::{Message, Page, tile::Tile}, }; @@ -134,7 +134,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name: res.eval().to_string(), name_lc: "".to_string(), }); - } else if tile.results.is_empty() && is_valid_url(&tile.query) { + } else if let Err(_) = Url::parse(&tile.query) && tile.results.is_empty() { #[cfg(target_os = "macos")] tile.results.push(App { open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), diff --git a/src/commands.rs b/src/commands.rs index 8444337..71c536c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -75,7 +75,7 @@ impl Function { } Function::OpenWebsite(url) => { - let open = if url.starts_with("http") { + let _ = if url.starts_with("http") { url.to_owned() } else { format!("https://{}", url) diff --git a/src/utils.rs b/src/utils.rs index 8f1d48f..9da6349 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -90,19 +90,6 @@ pub fn open_url(url: &str) { }); } -pub fn is_valid_url(s: &str) -> bool { - s.ends_with(".com") - || s.ends_with(".net") - || s.ends_with(".org") - || s.ends_with(".edu") - || s.ends_with(".gov") - || s.ends_with(".io") - || s.ends_with(".co") - || s.ends_with(".me") - || s.ends_with(".app") - || s.ends_with(".dev") -} - pub fn get_config_installation_dir() -> String { if cfg!(target_os = "windows") { std::env::var("LOCALAPPDATA").unwrap() From d98040e8434469425738988775a8bea576b77fb4 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 21:45:06 +0000 Subject: [PATCH 27/65] clean: remove pointless #[cfg] These are useless since the entire module is now gated with #[cfg(target_os = "windows")] --- src/cross_platform/windows.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 3602c02..68e8071 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,4 +1,3 @@ -#[cfg(target_os = "windows")] use { crate::app::apps::App, windows::{Win32::{System::Com::CoTaskMemFree, UI::{Shell::{ FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, @@ -6,7 +5,6 @@ use { }, WindowsAndMessaging::GetCursorPos}}, core::GUID} }; -#[cfg(target_os = "windows")] fn get_apps_from_registry(apps: &mut Vec) { use std::ffi::OsString; let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); @@ -60,7 +58,6 @@ fn get_apps_from_registry(apps: &mut Vec) { }); }); } -#[cfg(target_os = "windows")] fn get_apps_from_known_folder(apps: &mut Vec) { let paths = get_known_paths(); @@ -96,7 +93,6 @@ fn get_apps_from_known_folder(apps: &mut Vec) { } } } -#[cfg(target_os = "windows")] fn get_known_paths() -> Vec { let paths = vec![ get_windows_path(&FOLDERID_ProgramFiles).unwrap_or_default(), @@ -105,7 +101,6 @@ fn get_known_paths() -> Vec { ]; paths } -#[cfg(target_os = "windows")] fn get_windows_path(folder_id: &GUID) -> Option { unsafe { let folder = SHGetKnownFolderPath(folder_id, KF_FLAG_DEFAULT, None); @@ -118,7 +113,6 @@ fn get_windows_path(folder_id: &GUID) -> Option { } } } -#[cfg(target_os = "windows")] pub fn get_installed_windows_apps() -> Vec { use crate::utils::index_dirs_from_config; @@ -129,7 +123,6 @@ pub fn get_installed_windows_apps() -> Vec { apps } -#[cfg(target_os = "windows")] pub fn open_on_focused_monitor() -> iced::Point { use windows::Win32::Foundation::POINT; use windows::Win32::Graphics::Gdi::{ From 2d14724ae48b5f0d8ebfe6cf4dc467669ed014b1 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 21:51:59 +0000 Subject: [PATCH 28/65] refractor: use get_config_installation_dir instead of hardcoded env var --- src/app/tile/update.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index da2fc71..253841d 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -21,6 +21,8 @@ use crate::calculator::Expression; use crate::commands::Function; use crate::config::Config; +#[cfg(target_os = "windows")] +use crate::utils::get_config_installation_dir; use crate::utils::get_installed_apps; use crate::{ app::{Message, Page, tile::Tile}, @@ -217,8 +219,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] let new_config: Config = toml::from_str( &fs::read_to_string( - std::env::var("LOCALAPPDATA").unwrap_or("".to_owned()) - + "/rustcast/config.toml", + get_config_installation_dir() + "/rustcast/config.toml", ) .unwrap_or("".to_owned()), ) From 910d25efa29391ee92e5a5a058a06f1f3e1d9b3e Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 13 Jan 2026 21:55:14 +0000 Subject: [PATCH 29/65] clean: run cargo fmt --- src/app.rs | 1 - src/app/tile/elm.rs | 6 +++--- src/app/tile/update.rs | 24 +++++++++--------------- src/cross_platform/mod.rs | 2 +- src/cross_platform/windows.rs | 26 +++++++++++++++++++------- src/utils.rs | 11 +++-------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/app.rs b/src/app.rs index 29ccd32..57d6bfb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,6 @@ use crate::commands::Function; use iced::window::{self, Id, Settings}; - use crate::{app::tile::ExtSender, clipboard::ClipBoardContentType}; pub mod apps; diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index ca5533a..3e9913b 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -63,12 +63,12 @@ pub fn new( settings.position = Position::Specific(pos); } - - #[cfg(target_os = "macos")] { let open = open.discard().chain(window::run(id, |handle| { - macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); + macos::macos_window_config( + &handle.window_handle().expect("Unable to get window handle"), + ); transform_process_to_ui_element(); })); } diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 253841d..76d292f 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -21,19 +21,13 @@ use crate::calculator::Expression; use crate::commands::Function; use crate::config::Config; +use crate::app::{Message, Page, tile::Tile}; #[cfg(target_os = "windows")] use crate::utils::get_config_installation_dir; use crate::utils::get_installed_apps; -use crate::{ - app::{Message, Page, tile::Tile}, -}; #[cfg(target_os = "macos")] -use crate::{ - macos::focus_this_app, - haptics::HapticPattern, - haptics::perform_haptic -}; +use crate::{haptics::HapticPattern, haptics::perform_haptic, macos::focus_this_app}; pub fn handle_update(tile: &mut Tile, message: Message) -> Task { match message { @@ -136,7 +130,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name: res.eval().to_string(), name_lc: "".to_string(), }); - } else if let Err(_) = Url::parse(&tile.query) && tile.results.is_empty() { + } else if let Err(_) = Url::parse(&tile.query) + && tile.results.is_empty() + { #[cfg(target_os = "macos")] tile.results.push(App { open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), @@ -218,10 +214,8 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] let new_config: Config = toml::from_str( - &fs::read_to_string( - get_config_installation_dir() + "/rustcast/config.toml", - ) - .unwrap_or("".to_owned()), + &fs::read_to_string(get_config_installation_dir() + "/rustcast/config.toml") + .unwrap_or("".to_owned()), ) .unwrap(); @@ -246,9 +240,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { { // get normal settings and modify position - use iced::window::Position; use crate::cross_platform::windows::open_on_focused_monitor; - + use iced::window::Position; + let pos = open_on_focused_monitor(); let mut settings = default_settings(); settings.position = Position::Specific(pos); diff --git a/src/cross_platform/mod.rs b/src/cross_platform/mod.rs index 2bf19bd..cdc0dc5 100644 --- a/src/cross_platform/mod.rs +++ b/src/cross_platform/mod.rs @@ -2,4 +2,4 @@ pub mod macos; #[cfg(target_os = "windows")] -pub mod windows; \ No newline at end of file +pub mod windows; diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 68e8071..da79aee 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,8 +1,18 @@ use { - crate::app::apps::App, windows::{Win32::{System::Com::CoTaskMemFree, UI::{Shell::{ - FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, - SHGetKnownFolderPath, - }, WindowsAndMessaging::GetCursorPos}}, core::GUID} + crate::app::apps::App, + windows::{ + Win32::{ + System::Com::CoTaskMemFree, + UI::{ + Shell::{ + FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, + KF_FLAG_DEFAULT, SHGetKnownFolderPath, + }, + WindowsAndMessaging::GetCursorPos, + }, + }, + core::GUID, + }, }; fn get_apps_from_registry(apps: &mut Vec) { @@ -52,7 +62,7 @@ fn get_apps_from_registry(apps: &mut Vec) { name: display_name.clone().into_string().unwrap(), name_lc: display_name.clone().into_string().unwrap().to_lowercase(), icons: None, - desc: "TODO: Implement".to_string() + desc: "TODO: Implement".to_string(), }) } }); @@ -73,7 +83,9 @@ fn get_apps_from_known_folder(apps: &mut Vec) { use crate::{app::apps::AppCommand, commands::Function}; apps.push(App { - open_command: AppCommand::Function(Function::OpenApp(entry.path().to_string_lossy().to_string())), + open_command: AppCommand::Function(Function::OpenApp( + entry.path().to_string_lossy().to_string(), + )), name: entry .clone() .file_name() @@ -88,7 +100,7 @@ fn get_apps_from_known_folder(apps: &mut Vec) { .to_lowercase() .replace(".exe", ""), icons: None, - desc: "TODO: Implement".to_string() + desc: "TODO: Implement".to_string(), }); } } diff --git a/src/utils.rs b/src/utils.rs index 9da6349..d215059 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,17 +13,12 @@ use image::RgbaImage; #[cfg(target_os = "macos")] use { - crate::cross_platform::macos::get_installed_macos_apps, - objc2_app_kit::NSWorkspace, - objc2_foundation::NSURL, - std::os::unix::fs::PermissionsExt, + crate::cross_platform::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, + objc2_foundation::NSURL, std::os::unix::fs::PermissionsExt, }; #[cfg(target_os = "windows")] -use { - crate::cross_platform::windows::get_installed_windows_apps, - std::process::Command -}; +use {crate::cross_platform::windows::get_installed_windows_apps, std::process::Command}; use crate::{ app::apps::{App, AppCommand}, From a077788813864a948c832b950e2dbfa4cf9ba6df Mon Sep 17 00:00:00 2001 From: unsecretised Date: Wed, 14 Jan 2026 09:14:30 +0800 Subject: [PATCH 30/65] fix up macos related stuff that broke --- src/app.rs | 1 - src/app/tile/elm.rs | 15 +++++---------- src/app/tile/update.rs | 27 ++++++++++++++------------- src/commands.rs | 4 ++-- src/cross_platform/mod.rs | 2 +- src/cross_platform/windows.rs | 26 +++++++++++++++++++------- src/main.rs | 6 +----- src/utils.rs | 11 +++-------- 8 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/app.rs b/src/app.rs index 29ccd32..57d6bfb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,6 @@ use crate::commands::Function; use iced::window::{self, Id, Settings}; - use crate::{app::tile::ExtSender, clipboard::ClipBoardContentType}; pub mod apps; diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index ca5533a..cdb99fe 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -63,18 +63,13 @@ pub fn new( settings.position = Position::Specific(pos); } - + let (id, open) = window::open(settings); #[cfg(target_os = "macos")] - { - let open = open.discard().chain(window::run(id, |handle| { - macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); - transform_process_to_ui_element(); - })); - } - - #[cfg(target_os = "windows")] - let (id, open) = window::open(settings); + let open = open.discard().chain(window::run(id, |handle| { + macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); + transform_process_to_ui_element(); + })); let mut options: Vec = get_installed_apps(&config); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 253841d..9aafbf9 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -1,10 +1,14 @@ //! This handles the update logic for the tile (AKA rustcast's main window) use std::cmp::min; use std::fs; +#[cfg(target_os = "macos")] +use std::path::Path; use std::time::Duration; use global_hotkey::hotkey::HotKey; use iced::Task; +#[cfg(target_os = "macos")] +use iced::widget::image::Handle; use iced::widget::operation; use iced::window; use rayon::slice::ParallelSliceMut; @@ -21,18 +25,15 @@ use crate::calculator::Expression; use crate::commands::Function; use crate::config::Config; +use crate::app::{Message, Page, tile::Tile}; #[cfg(target_os = "windows")] use crate::utils::get_config_installation_dir; use crate::utils::get_installed_apps; -use crate::{ - app::{Message, Page, tile::Tile}, -}; #[cfg(target_os = "macos")] use crate::{ - macos::focus_this_app, - haptics::HapticPattern, - haptics::perform_haptic + cross_platform::macos::focus_this_app, + cross_platform::macos::haptics::{HapticPattern, perform_haptic}, }; pub fn handle_update(tile: &mut Tile, message: Message) -> Task { @@ -136,7 +137,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name: res.eval().to_string(), name_lc: "".to_string(), }); - } else if let Err(_) = Url::parse(&tile.query) && tile.results.is_empty() { + } else if let Err(_) = Url::parse(&tile.query) + && tile.results.is_empty() + { #[cfg(target_os = "macos")] tile.results.push(App { open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), @@ -218,10 +221,8 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] let new_config: Config = toml::from_str( - &fs::read_to_string( - get_config_installation_dir() + "/rustcast/config.toml", - ) - .unwrap_or("".to_owned()), + &fs::read_to_string(get_config_installation_dir() + "/rustcast/config.toml") + .unwrap_or("".to_owned()), ) .unwrap(); @@ -246,9 +247,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { { // get normal settings and modify position - use iced::window::Position; use crate::cross_platform::windows::open_on_focused_monitor; - + use iced::window::Position; + let pos = open_on_focused_monitor(); let mut settings = default_settings(); settings.position = Position::Specific(pos); diff --git a/src/commands.rs b/src/commands.rs index 71c536c..1a6b20f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -75,7 +75,7 @@ impl Function { } Function::OpenWebsite(url) => { - let _ = if url.starts_with("http") { + let open_url = if url.starts_with("http") { url.to_owned() } else { format!("https://{}", url) @@ -84,7 +84,7 @@ impl Function { thread::spawn(move || { NSWorkspace::new().openURL( &NSURL::URLWithString_relativeToURL( - &objc2_foundation::NSString::from_str(&open), + &objc2_foundation::NSString::from_str(&open_url), None, ) .unwrap(), diff --git a/src/cross_platform/mod.rs b/src/cross_platform/mod.rs index 2bf19bd..cdc0dc5 100644 --- a/src/cross_platform/mod.rs +++ b/src/cross_platform/mod.rs @@ -2,4 +2,4 @@ pub mod macos; #[cfg(target_os = "windows")] -pub mod windows; \ No newline at end of file +pub mod windows; diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 68e8071..da79aee 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,8 +1,18 @@ use { - crate::app::apps::App, windows::{Win32::{System::Com::CoTaskMemFree, UI::{Shell::{ - FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT, - SHGetKnownFolderPath, - }, WindowsAndMessaging::GetCursorPos}}, core::GUID} + crate::app::apps::App, + windows::{ + Win32::{ + System::Com::CoTaskMemFree, + UI::{ + Shell::{ + FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, + KF_FLAG_DEFAULT, SHGetKnownFolderPath, + }, + WindowsAndMessaging::GetCursorPos, + }, + }, + core::GUID, + }, }; fn get_apps_from_registry(apps: &mut Vec) { @@ -52,7 +62,7 @@ fn get_apps_from_registry(apps: &mut Vec) { name: display_name.clone().into_string().unwrap(), name_lc: display_name.clone().into_string().unwrap().to_lowercase(), icons: None, - desc: "TODO: Implement".to_string() + desc: "TODO: Implement".to_string(), }) } }); @@ -73,7 +83,9 @@ fn get_apps_from_known_folder(apps: &mut Vec) { use crate::{app::apps::AppCommand, commands::Function}; apps.push(App { - open_command: AppCommand::Function(Function::OpenApp(entry.path().to_string_lossy().to_string())), + open_command: AppCommand::Function(Function::OpenApp( + entry.path().to_string_lossy().to_string(), + )), name: entry .clone() .file_name() @@ -88,7 +100,7 @@ fn get_apps_from_known_folder(apps: &mut Vec) { .to_lowercase() .replace(".exe", ""), icons: None, - desc: "TODO: Implement".to_string() + desc: "TODO: Implement".to_string(), }); } } diff --git a/src/main.rs b/src/main.rs index 47bd6e8..cbbb2db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,7 @@ use global_hotkey::{ fn main() -> iced::Result { #[cfg(target_os = "macos")] - { - macos::set_activation_policy_accessory(); - } + cross_platform::macos::set_activation_policy_accessory(); let file_path = get_config_file_path(); let config = read_config_file(&file_path).unwrap(); @@ -42,8 +40,6 @@ fn main() -> iced::Result { .register_all(&hotkeys) .expect("Unable to register hotkey"); - println!("Starting"); - iced::daemon( move || Tile::new((modifier, key), show_hide.id(), &config), Tile::update, diff --git a/src/utils.rs b/src/utils.rs index 9da6349..d215059 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,17 +13,12 @@ use image::RgbaImage; #[cfg(target_os = "macos")] use { - crate::cross_platform::macos::get_installed_macos_apps, - objc2_app_kit::NSWorkspace, - objc2_foundation::NSURL, - std::os::unix::fs::PermissionsExt, + crate::cross_platform::macos::get_installed_macos_apps, objc2_app_kit::NSWorkspace, + objc2_foundation::NSURL, std::os::unix::fs::PermissionsExt, }; #[cfg(target_os = "windows")] -use { - crate::cross_platform::windows::get_installed_windows_apps, - std::process::Command -}; +use {crate::cross_platform::windows::get_installed_windows_apps, std::process::Command}; use crate::{ app::apps::{App, AppCommand}, From d901d8bb074c0270078f4e627d414f941ff583fd Mon Sep 17 00:00:00 2001 From: unsecretised Date: Wed, 14 Jan 2026 11:37:37 +0800 Subject: [PATCH 31/65] A few fixes --- src/app/menubar.rs | 2 +- src/app/tile/update.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index bd19bf6..7a1a15e 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -50,7 +50,7 @@ pub fn menu_icon(hotkey: (Option, Code), hotkey_id: u32, sender: ExtS } fn get_image() -> DynamicImage { - let image_path = if cfg!(debug_assertions) { + let image_path = if cfg!(debug_assertions) && cfg!(target_os = "macos") { "docs/icon.png" } else { "/Applications/Rustcast.app/Contents/Resources/icon.png" diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 9aafbf9..348d65d 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -137,7 +137,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { name: res.eval().to_string(), name_lc: "".to_string(), }); - } else if let Err(_) = Url::parse(&tile.query) + } else if let Ok(_) = Url::parse(&tile.query) && tile.results.is_empty() { #[cfg(target_os = "macos")] From 067def7f04fddc2955da462b7caebe03a2b49f2b Mon Sep 17 00:00:00 2001 From: unsecretised Date: Wed, 14 Jan 2026 11:38:30 +0800 Subject: [PATCH 32/65] Switch condition --- src/app/menubar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 7a1a15e..a740e3f 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -50,7 +50,7 @@ pub fn menu_icon(hotkey: (Option, Code), hotkey_id: u32, sender: ExtS } fn get_image() -> DynamicImage { - let image_path = if cfg!(debug_assertions) && cfg!(target_os = "macos") { + let image_path = if cfg!(debug_assertions) && !cfg!(target_os = "macos") { "docs/icon.png" } else { "/Applications/Rustcast.app/Contents/Resources/icon.png" From eba8e68fa8d299d482a93ceb263f8a85dcf46591 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Wed, 14 Jan 2026 11:51:09 +0800 Subject: [PATCH 33/65] remove warning --- src/app/tile/elm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 4a340f7..f2999dc 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -3,7 +3,7 @@ use global_hotkey::hotkey::{Code, Modifiers}; use iced::border::Radius; -use iced::widget::scrollable::{Anchor, Direction, Scrollbar}; +use iced::widget::scrollable::{Direction, Scrollbar}; use iced::widget::text::LineHeight; use iced::widget::{Column, Scrollable, container, space}; use iced::{Color, window}; From 4e34b2325e33e46e72c61bb10d0b19b685bb4ab0 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Fri, 16 Jan 2026 08:49:45 +0800 Subject: [PATCH 34/65] Format -_- --- src/app/tile/elm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 5bb717d..f068121 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -47,7 +47,7 @@ pub fn default_app_paths() -> Vec { pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { #[allow(unused_mut)] let mut settings = default_settings(); - + // get normal settings and modify position #[cfg(target_os = "windows")] { From c220d733183326eb9191891891fa29d365fafe01 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Fri, 16 Jan 2026 08:53:16 +0800 Subject: [PATCH 35/65] chore: fix import error --- src/app/tile/elm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index f068121..da70005 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -3,7 +3,7 @@ use global_hotkey::hotkey::HotKey; use iced::border::Radius; -use iced::widget::scrollable::{Direction, Scrollbar}; +use iced::widget::scrollable::{Anchor, Direction, Scrollbar}; use iced::widget::text::LineHeight; use iced::widget::{Column, Scrollable, container, space}; use iced::{Color, window}; From 9b5d2c96017ea241b10289086db8b40de34d9961 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Mon, 19 Jan 2026 23:05:33 +0800 Subject: [PATCH 36/65] Fix up merge stuff --- src/app/tile/elm.rs | 1 - src/app/tile/update.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 32ae486..b1803c9 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -13,7 +13,6 @@ use iced::{Length::Fill, widget::text_input}; use rayon::slice::ParallelSliceMut; use crate::app::tile::AppIndex; -use crate::config::Theme; use crate::utils::get_installed_apps; use crate::styles::{contents_style, rustcast_text_input_style}; use crate::{ diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 3fd83a2..bd697d6 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -12,7 +12,6 @@ use iced::widget::operation; use iced::widget::operation::AbsoluteOffset; use iced::window; use rayon::slice::ParallelSliceMut; -use url::Url; use crate::app::ArrowKey; use crate::app::DEFAULT_WINDOW_HEIGHT; @@ -24,6 +23,7 @@ use crate::app::apps::AppCommand; use crate::app::default_settings; use crate::app::menubar::menu_icon; use crate::app::tile::AppIndex; +#[cfg(target_os = "windows")] use crate::app::tile::elm::default_app_paths; use crate::app::{Message, Page, tile::Tile}; @@ -34,11 +34,10 @@ use crate::calculator::Expr; use crate::clipboard::ClipBoardContentType; use crate::commands::Function; use crate::config::Config; -use crate::haptics::HapticPattern; -use crate::haptics::perform_haptic; use crate::unit_conversion; use crate::utils::get_installed_apps; +use crate::utils::is_valid_url; #[cfg(target_os = "macos")] use crate::{ cross_platform::macos::focus_this_app, From d53ce1c694fd900b1081a97efaeaf6f8dba38231 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Mon, 19 Jan 2026 20:38:23 +0000 Subject: [PATCH 37/65] feat: a bit of tracing support --- Cargo.lock | 67 +++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++ src/app/menubar.rs | 1 + src/app/tile/update.rs | 1 + src/commands.rs | 1 + src/cross_platform/macos/mod.rs | 6 +-- src/main.rs | 42 ++++++++++++++++++--- src/utils.rs | 25 ++++-------- 8 files changed, 122 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa576a5..a1980d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2247,6 +2247,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lebe" version = "0.5.3" @@ -2645,6 +2651,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3715,6 +3730,8 @@ dependencies = [ "serde", "tokio", "toml 0.9.11+spec-1.1.0", + "tracing", + "tracing-subscriber", "tray-icon", "url", "walkdir", @@ -3859,6 +3876,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -4200,6 +4226,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.10.3" @@ -4431,6 +4466,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -4550,6 +4611,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 58a65bf..56a580f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ toml = "0.9.8" walkdir = "2" tray-icon = "0.21.3" url = "2.5.8" +tracing = "0.1.44" +tracing-subscriber = "0.3.22" [package.metadata.bundle] name = "RustCast" @@ -52,3 +54,5 @@ info_plist_path = "bundling/Info.plist" [package.metadata.bundle.osx.info] LSUIElement = true NSHighResolutionCapable = true + +[profile.release] diff --git a/src/app/menubar.rs b/src/app/menubar.rs index bd19bf6..294be95 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -60,6 +60,7 @@ fn get_image() -> DynamicImage { } fn init_event_handler(sender: ExtSender, hotkey_id: u32) { + tracing::debug!("Initing event handler"); let runtime = Runtime::new().unwrap(); MenuEvent::set_event_handler(Some(move |x: MenuEvent| { diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 76d292f..5c3f06d 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -30,6 +30,7 @@ use crate::utils::get_installed_apps; use crate::{haptics::HapticPattern, haptics::perform_haptic, macos::focus_this_app}; pub fn handle_update(tile: &mut Tile, message: Message) -> Task { + tracing::debug!("Handling update (message: {:?})", message); match message { Message::OpenWindow => { #[cfg(target_os = "macos")] diff --git a/src/commands.rs b/src/commands.rs index 71c536c..12bcdde 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -30,6 +30,7 @@ pub enum Function { impl Function { /// Run the command pub fn execute(&self, config: &Config, query: &str) { + tracing::debug!("Executing command: {:?}", self); match self { Function::OpenApp(path) => { open_application(path); diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index 5ea1ac7..e0fc438 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -111,7 +111,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { .into_par_iter() .filter_map(|x| { let file_type = x.file_type().unwrap_or_else(|e| { - log_error(&e.to_string()); + tracing::error!("{}", e.to_string()); exit(-1) }); if !file_type.is_dir() { @@ -120,7 +120,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let file_name_os = x.file_name(); let file_name = file_name_os.into_string().unwrap_or_else(|e| { - log_error(e.to_str().unwrap_or("")); + tracing::error!("{}",e.to_string()); exit(-1) }); if !file_name.ends_with(".app") { @@ -129,7 +129,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let path = x.path(); let path_str = path.to_str().map(|x| x.to_string()).unwrap_or_else(|| { - log_error("Unable to get file_name"); + tracing::error!("Unable to get file_name"); exit(-1) }); diff --git a/src/main.rs b/src/main.rs index 47bd6e8..53c1644 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,10 @@ mod utils; mod cross_platform; +use std::fs::File; + // import from utils -use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; +use crate::utils::{create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, get_log_dir, read_config_file}; use crate::app::tile::Tile; @@ -16,6 +18,9 @@ use global_hotkey::{ GlobalHotKeyManager, hotkey::{HotKey, Modifiers}, }; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::Layer; +use tracing_subscriber::layer::SubscriberExt; fn main() -> iced::Result { #[cfg(target_os = "macos")] @@ -27,6 +32,23 @@ fn main() -> iced::Result { let config = read_config_file(&file_path).unwrap(); create_config_file_if_not_exists(&file_path, &config).unwrap(); + { + let log_path = get_log_dir() + "/log.log"; + + create_config_file_if_not_exists(&log_path, &config).unwrap(); + + let file = File::create(&log_path) + .expect("Failed to create logfile"); + + let log_file = tracing_subscriber::fmt::layer().with_ansi(false).with_writer(file); + let console_out = tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO); + + let subscriber = tracing_subscriber::registry().with(log_file).with(console_out); + + tracing::subscriber::set_global_default(subscriber).expect("Error initing tracing"); + tracing::info!("Log file at: {}", &log_path); + } + let manager = GlobalHotKeyManager::new().unwrap(); let modifier = Modifiers::from_name(&config.toggle_mod); @@ -38,11 +60,21 @@ fn main() -> iced::Result { // Hotkeys are stored as a vec so that hyperkey support can be added later let hotkeys = vec![show_hide]; - manager - .register_all(&hotkeys) - .expect("Unable to register hotkey"); + let result = manager + .register_all(&hotkeys); + + if let Err(global_hotkey::Error::AlreadyRegistered(key)) = result { + if key == show_hide { + // It probably should give up here. + panic!("Couldn't register the key to open ({})", key) + } + else { tracing::warn!("Couldn't register hotkey {}", key) } + } + else if let Err(e) = result { + tracing::error!("{}", e.to_string()) + } - println!("Starting"); + tracing::info!("Starting."); iced::daemon( move || Tile::new((modifier, key), show_hide.id(), &config), diff --git a/src/utils.rs b/src/utils.rs index d215059..3c4bed8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -25,23 +25,6 @@ use crate::{ commands::Function, }; -/// The default error log path (works only on unix systems, and must be changed for windows -/// support) -const ERR_LOG_PATH: &str = "/tmp/rustscan-err.log"; - -/// This logs an error to the error log file -pub(crate) fn log_error(msg: &str) { - if let Ok(mut file) = File::options().create(true).append(true).open(ERR_LOG_PATH) { - let _ = file.write_all(msg.as_bytes()).ok(); - } -} - -/// This logs an error to the error log file, and exits the program -pub(crate) fn log_error_and_exit(msg: &str) { - log_error(msg); - exit(-1) -} - /// This converts an icns file to an iced image handle pub(crate) fn handle_from_icns(path: &Path) -> Option { let data = std::fs::read(path).ok()?; @@ -93,6 +76,14 @@ pub fn get_config_installation_dir() -> String { } } +pub fn get_log_dir() -> String { + if cfg!(target_os = "windows") { + std::env::var("TEMP").unwrap() + "/rustcast" + } else { + todo!("problem for secretised") + } +} + pub fn get_config_file_path() -> String { let home = get_config_installation_dir(); From c626864679f4e0581ac5c64d0c67c308fd92582a Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Mon, 19 Jan 2026 21:20:54 +0000 Subject: [PATCH 38/65] clean: use_unwrap_or_default() Blindly following the warning, as it should be --- src/app/tile.rs | 4 +++- src/cross_platform/windows.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/tile.rs b/src/app/tile.rs index edc2f69..75a8c32 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -343,8 +343,10 @@ fn handle_clipboard_history() -> impl futures::Stream { fn handle_recipient() -> impl futures::Stream { stream::channel(100, async |mut output| { let (sender, mut recipient) = channel(100); + let msg = Message::SetSender(ExtSender(sender)); + tracing::debug!("Sending ExtSender ({msg:?})"); output - .send(Message::SetSender(ExtSender(sender))) + .send(msg) .await .expect("Sender not sent"); loop { diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index da79aee..b9dd2df 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -33,14 +33,14 @@ fn get_apps_from_registry(apps: &mut Vec) { // https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key let name = key.unwrap(); let key = reg.open_subkey(&name).unwrap(); - let display_name = key.get_value("DisplayName").unwrap_or(OsString::new()); + let display_name: OsString = key.get_value("DisplayName").unwrap_or_default(); // they might be useful one day ? // let publisher = key.get_value("Publisher").unwrap_or(OsString::new()); // let version = key.get_value("DisplayVersion").unwrap_or(OsString::new()); // Trick, I saw on internet to point to the exe location.. - let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new()); + let exe_path: OsString = key.get_value("DisplayIcon").unwrap_or_default(); if exe_path.is_empty() { return; } From 5fb7cd8cfea75669291ca631dd559b66bc25d612 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Mon, 19 Jan 2026 21:23:25 +0000 Subject: [PATCH 39/65] clean: smol rename --- src/main.rs | 4 ++-- src/utils.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 53c1644..198d7c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ mod cross_platform; use std::fs::File; // import from utils -use crate::utils::{create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, get_log_dir, read_config_file}; +use crate::utils::{create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, get_temp_dir, read_config_file}; use crate::app::tile::Tile; @@ -33,7 +33,7 @@ fn main() -> iced::Result { create_config_file_if_not_exists(&file_path, &config).unwrap(); { - let log_path = get_log_dir() + "/log.log"; + let log_path = get_temp_dir() + "/log.log"; create_config_file_if_not_exists(&log_path, &config).unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 3c4bed8..256379f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -76,7 +76,7 @@ pub fn get_config_installation_dir() -> String { } } -pub fn get_log_dir() -> String { +pub fn get_temp_dir() -> String { if cfg!(target_os = "windows") { std::env::var("TEMP").unwrap() + "/rustcast" } else { @@ -225,6 +225,7 @@ pub fn get_installed_apps(config: &Config) -> Vec { #[cfg(target_os = "windows")] { + get_installed_windows_apps() } } From 10ad72502e4ca931ee4c9ccbe79397944e53a862 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Mon, 19 Jan 2026 22:15:35 +0000 Subject: [PATCH 40/65] feat: more tracing --- src/cross_platform/windows.rs | 21 ++++++++++++++++++--- src/main.rs | 1 + src/utils.rs | 3 ++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index b9dd2df..549cd7f 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,6 +1,5 @@ use { - crate::app::apps::App, - windows::{ + crate::app::apps::App, tracing::Level, windows::{ Win32::{ System::Com::CoTaskMemFree, UI::{ @@ -12,7 +11,7 @@ use { }, }, core::GUID, - }, + } }; fn get_apps_from_registry(apps: &mut Vec) { @@ -30,6 +29,9 @@ fn get_apps_from_registry(apps: &mut Vec) { // src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848 registers.iter().for_each(|reg| { reg.enum_keys().for_each(|key| { + // Not debug only just because it doesn't run too often + tracing::trace!("App added [reg]: {:?}", key); + // https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key let name = key.unwrap(); let key = reg.open_subkey(&name).unwrap(); @@ -82,6 +84,10 @@ fn get_apps_from_known_folder(apps: &mut Vec) { { use crate::{app::apps::AppCommand, commands::Function}; + // only in debug builds since it's chonky and this is run a *lot* + #[cfg(debug_assertions)] + tracing::trace!("App added [kfolder]: {}", entry.path().display()); + apps.push(App { open_command: AppCommand::Function(Function::OpenApp( entry.path().to_string_lossy().to_string(), @@ -129,9 +135,18 @@ pub fn get_installed_windows_apps() -> Vec { use crate::utils::index_dirs_from_config; let mut apps = Vec::new(); + + tracing::debug!("Getting apps from registry"); get_apps_from_registry(&mut apps); + + tracing::debug!("Getting apps from known folder"); get_apps_from_known_folder(&mut apps); + + tracing::debug!("Getting apps from config"); index_dirs_from_config(&mut apps); + + tracing::debug!("Apps loaded ({} total count)", apps.len()); + apps } diff --git a/src/main.rs b/src/main.rs index 198d7c2..14d1c4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use global_hotkey::{ }; use tracing::level_filters::LevelFilter; use tracing_subscriber::Layer; +use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; fn main() -> iced::Result { diff --git a/src/utils.rs b/src/utils.rs index 256379f..8b462ca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -218,6 +218,8 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { /// Use this to get installed apps pub fn get_installed_apps(config: &Config) -> Vec { + tracing::debug!("Indexing installed apps"); + #[cfg(target_os = "macos")] { get_installed_macos_apps(config) @@ -225,7 +227,6 @@ pub fn get_installed_apps(config: &Config) -> Vec { #[cfg(target_os = "windows")] { - get_installed_windows_apps() } } From e01d657d1e12f2cfc68c64f3c7cb67366886e49c Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Mon, 19 Jan 2026 22:16:02 +0000 Subject: [PATCH 41/65] clean: run cargo fmt --- src/app/tile.rs | 5 +---- src/cross_platform/macos/mod.rs | 2 +- src/cross_platform/windows.rs | 6 ++++-- src/main.rs | 27 ++++++++++++++++----------- src/utils.rs | 2 +- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/app/tile.rs b/src/app/tile.rs index 75a8c32..8daebe6 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -345,10 +345,7 @@ fn handle_recipient() -> impl futures::Stream { let (sender, mut recipient) = channel(100); let msg = Message::SetSender(ExtSender(sender)); tracing::debug!("Sending ExtSender ({msg:?})"); - output - .send(msg) - .await - .expect("Sender not sent"); + output.send(msg).await.expect("Sender not sent"); loop { let abcd = recipient .try_next() diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index e0fc438..66442b6 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -120,7 +120,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let file_name_os = x.file_name(); let file_name = file_name_os.into_string().unwrap_or_else(|e| { - tracing::error!("{}",e.to_string()); + tracing::error!("{}", e.to_string()); exit(-1) }); if !file_name.ends_with(".app") { diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 549cd7f..5f515f5 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,5 +1,7 @@ use { - crate::app::apps::App, tracing::Level, windows::{ + crate::app::apps::App, + tracing::Level, + windows::{ Win32::{ System::Com::CoTaskMemFree, UI::{ @@ -11,7 +13,7 @@ use { }, }, core::GUID, - } + }, }; fn get_apps_from_registry(apps: &mut Vec) { diff --git a/src/main.rs b/src/main.rs index 14d1c4b..ecf573d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,10 @@ mod cross_platform; use std::fs::File; // import from utils -use crate::utils::{create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, get_temp_dir, read_config_file}; +use crate::utils::{ + create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, + get_temp_dir, read_config_file, +}; use crate::app::tile::Tile; @@ -37,14 +40,17 @@ fn main() -> iced::Result { let log_path = get_temp_dir() + "/log.log"; create_config_file_if_not_exists(&log_path, &config).unwrap(); - - let file = File::create(&log_path) - .expect("Failed to create logfile"); - let log_file = tracing_subscriber::fmt::layer().with_ansi(false).with_writer(file); + let file = File::create(&log_path).expect("Failed to create logfile"); + + let log_file = tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_writer(file); let console_out = tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO); - let subscriber = tracing_subscriber::registry().with(log_file).with(console_out); + let subscriber = tracing_subscriber::registry() + .with(log_file) + .with(console_out); tracing::subscriber::set_global_default(subscriber).expect("Error initing tracing"); tracing::info!("Log file at: {}", &log_path); @@ -61,17 +67,16 @@ fn main() -> iced::Result { // Hotkeys are stored as a vec so that hyperkey support can be added later let hotkeys = vec![show_hide]; - let result = manager - .register_all(&hotkeys); + let result = manager.register_all(&hotkeys); if let Err(global_hotkey::Error::AlreadyRegistered(key)) = result { if key == show_hide { // It probably should give up here. panic!("Couldn't register the key to open ({})", key) + } else { + tracing::warn!("Couldn't register hotkey {}", key) } - else { tracing::warn!("Couldn't register hotkey {}", key) } - } - else if let Err(e) = result { + } else if let Err(e) = result { tracing::error!("{}", e.to_string()) } diff --git a/src/utils.rs b/src/utils.rs index 8b462ca..b87ac76 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -218,7 +218,7 @@ pub fn index_dirs_from_config(apps: &mut Vec) -> bool { /// Use this to get installed apps pub fn get_installed_apps(config: &Config) -> Vec { - tracing::debug!("Indexing installed apps"); + tracing::debug!("Indexing installed apps"); #[cfg(target_os = "macos")] { From 6739d3e70ab516bfe36bb7fa52e57bbe63894f01 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Tue, 20 Jan 2026 02:16:39 +0100 Subject: [PATCH 42/65] fix(cfg/macos) styles was hidden behind a cfg macos for now reasons at all it seems handle_from_icns was returning errors on Windows (lol) for now it only returns None --- src/main.rs | 1 - src/utils.rs | 36 ++++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index a2cd767..55362b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ mod calculator; mod clipboard; mod commands; mod config; -#[cfg(target_os = "macos")] mod styles; mod unit_conversion; mod utils; diff --git a/src/utils.rs b/src/utils.rs index df181f4..ca084b6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,6 +8,7 @@ use std::{ }; use iced::widget::image::Handle; +#[cfg(target_os = "macos")] use icns::IconFamily; use image::RgbaImage; @@ -44,22 +45,25 @@ pub(crate) fn log_error_and_exit(msg: &str) { /// This converts an icns file to an iced image handle pub(crate) fn handle_from_icns(path: &Path) -> Option { - let data = std::fs::read(path).ok()?; - let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?; - - let icon_type = family.available_icons(); - - let icon = family.get_icon_with_type(*icon_type.first()?).ok()?; - let image = RgbaImage::from_raw( - icon.width() as u32, - icon.height() as u32, - icon.data().to_vec(), - )?; - Some(Handle::from_rgba( - image.width(), - image.height(), - image.into_raw(), - )) + #[cfg(target_os = "macos")] { + let data = std::fs::read(path).ok()?; + let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?; + + let icon_type = family.available_icons(); + + let icon = family.get_icon_with_type(*icon_type.first()?).ok()?; + let image = RgbaImage::from_raw( + icon.width() as u32, + icon.height() as u32, + icon.data().to_vec(), + )?; + Some(Handle::from_rgba( + image.width(), + image.height(), + image.into_raw(), + )) + } + None } From 08af1ecf6f0f2ccdd3e9020ba3519a91ffedba5e Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Tue, 20 Jan 2026 02:41:33 +0100 Subject: [PATCH 43/65] fix(windows/indexing) Fixed LocalAppData searching everywhere, now looks in the Programs folder of LocalAppData also a little log because why not --- src/cross_platform/windows.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index da79aee..8b363db 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -82,6 +82,8 @@ fn get_apps_from_known_folder(apps: &mut Vec) { { use crate::{app::apps::AppCommand, commands::Function}; + + apps.push(App { open_command: AppCommand::Function(Function::OpenApp( entry.path().to_string_lossy().to_string(), @@ -109,7 +111,7 @@ fn get_known_paths() -> Vec { let paths = vec![ get_windows_path(&FOLDERID_ProgramFiles).unwrap_or_default(), get_windows_path(&FOLDERID_ProgramFilesX86).unwrap_or_default(), - get_windows_path(&FOLDERID_LocalAppData).unwrap_or_default(), + String::from(get_windows_path(&FOLDERID_LocalAppData).unwrap_or_default() + "\\Programs\\"), ]; paths } @@ -128,10 +130,18 @@ fn get_windows_path(folder_id: &GUID) -> Option { pub fn get_installed_windows_apps() -> Vec { use crate::utils::index_dirs_from_config; + use std::time::Instant; + let start = Instant::now(); + let mut apps = Vec::new(); get_apps_from_registry(&mut apps); get_apps_from_known_folder(&mut apps); index_dirs_from_config(&mut apps); + + let elapsed = start.elapsed(); + + println!("Took {:?}", elapsed); + apps } From 4634d60f7afd23dc6e964059ed46c3e1683fb764 Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Tue, 20 Jan 2026 02:42:38 +0100 Subject: [PATCH 44/65] fix(menubar) it was once again only for macos,. it is now a black square on windows. because why not, and yeah. --- src/app/menubar.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 575dd50..9dbc257 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -51,13 +51,21 @@ pub fn menu_icon(hotkey: HotKey, sender: ExtSender) -> TrayIcon { } fn get_image() -> DynamicImage { - let image_path = if cfg!(debug_assertions) && !cfg!(target_os = "macos") { - "docs/icon.png" - } else { - "/Applications/Rustcast.app/Contents/Resources/icon.png" - }; - - ImageReader::open(image_path).unwrap().decode().unwrap() + #[cfg(target_os = "macos")] + { + let image_path = if cfg!(debug_assertions) && !cfg!(target_os = "macos") { + "docs/icon.png" + } else { + "/Applications/Rustcast.app/Contents/Resources/icon.png" + }; + + ImageReader::open(image_path).unwrap().decode().unwrap() + } + + #[cfg(target_os = "windows")] + { + DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(64, 64, image::Rgba([0, 0, 0, 255]))) + } } fn init_event_handler(sender: ExtSender, hotkey_id: u32) { From cf52bd68fb41181a902bd5b4678f0b1368a8d8eb Mon Sep 17 00:00:00 2001 From: Alexander <96749659+Nazeofel@users.noreply.github.com> Date: Tue, 20 Jan 2026 03:06:33 +0100 Subject: [PATCH 45/65] fastser --- src/cross_platform/windows.rs | 61 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 8b363db..c738042 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,6 +1,5 @@ use { - crate::app::apps::App, - windows::{ + crate::app::apps::App, rayon::prelude::*, std::path::PathBuf, windows::{ Win32::{ System::Com::CoTaskMemFree, UI::{ @@ -12,7 +11,8 @@ use { }, }, core::GUID, - }, + } + }; fn get_apps_from_registry(apps: &mut Vec) { @@ -70,42 +70,37 @@ fn get_apps_from_registry(apps: &mut Vec) { } fn get_apps_from_known_folder(apps: &mut Vec) { let paths = get_known_paths(); + use walkdir::WalkDir; + use crate::{app::apps::AppCommand, commands::Function}; - for path in paths { - use walkdir::WalkDir; - for entry in WalkDir::new(path) + let found_apps: Vec = paths.par_iter().flat_map(|path| { + WalkDir::new(path) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) - { - use crate::{app::apps::AppCommand, commands::Function}; - - - - apps.push(App { - open_command: AppCommand::Function(Function::OpenApp( - entry.path().to_string_lossy().to_string(), - )), - name: entry - .clone() - .file_name() - .to_string_lossy() - .to_string() - .replace(".exe", ""), - name_lc: entry - .clone() - .file_name() - .to_string_lossy() - .to_string() - .to_lowercase() - .replace(".exe", ""), - icons: None, - desc: "TODO: Implement".to_string(), - }); - } - } + .map(|entry| { + let path = entry.path(); + let file_name = path.file_name().unwrap().to_string_lossy(); + let name = file_name.replace(".exe", ""); + + App { + open_command: AppCommand::Function(Function::OpenApp( + path.to_string_lossy().to_string(), + )), + name: name.clone(), + name_lc: name.to_lowercase(), + icons: None, + desc: "TODO: Implement".to_string(), + } + + + }).collect::>() + }).collect(); + + apps.extend(found_apps); + } fn get_known_paths() -> Vec { let paths = vec![ From e30904133b24204f70210b6d18a91839a14902e4 Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:40:05 +0000 Subject: [PATCH 46/65] feat: put very verbose (i.e. trace) logs in another file Makes the file vv_log.log that also contains TRACE, while log.log just contains DEBUG and above. Done on mobile (and thus untested), so likely to be wrong --- src/main.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 06eaaae..58fcf92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,18 +39,25 @@ fn main() -> iced::Result { { let log_path = get_temp_dir() + "/log.log"; + let vv_log_path = get_temp_dir() + "/vv_log.log"; create_config_file_if_not_exists(&log_path, &config).unwrap(); let file = File::create(&log_path).expect("Failed to create logfile"); - + let vv_file = File::create(&vv_log_path).expect("Failed to create logfile"); + let log_file = tracing_subscriber::fmt::layer() .with_ansi(false) + .with_filter(LevelFilter::DEBUG) .with_writer(file); + let vv_log_file = tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_writer(vv_file); let console_out = tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO); let subscriber = tracing_subscriber::registry() .with(log_file) + .with(vv_file) .with(console_out); tracing::subscriber::set_global_default(subscriber).expect("Error initing tracing"); @@ -87,4 +94,4 @@ fn main() -> iced::Result { .subscription(Tile::subscription) .theme(Tile::theme) .run() -} + } From 498fddea0d317878cc9319c23c8c838ff29f7b74 Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:05:29 +0000 Subject: [PATCH 47/65] stuff --- src/app/tile/elm.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index caaf221..2cbebef 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -57,19 +57,16 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { let pos = open_on_focused_monitor(); settings.position = Position::Specific(pos); } - - #[cfg(target_os = "macos")] - { - let open = open.discard().chain(window::run(id, |handle| { + + let open = open + .discard() + .chain(window::run(id, |handle| { + #[cfg(target_os = "macos")] macos::macos_window_config( &handle.window_handle().expect("Unable to get window handle"), - ); - transform_process_to_ui_element(); - })); - } - - #[cfg(target_os = "windows")] - let (id, open) = window::open(settings); + ); + transform_process_to_ui_element(); + })); let mut options: Vec = get_installed_apps(&config); From 057c8966b63b9afdb551b67737637b493cdd039d Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:09:30 +0000 Subject: [PATCH 48/65] clean: fix formatting --- src/app/tile/elm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 2cbebef..edec5f6 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -64,8 +64,8 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { #[cfg(target_os = "macos")] macos::macos_window_config( &handle.window_handle().expect("Unable to get window handle"), - ); - transform_process_to_ui_element(); + ); + transform_process_to_ui_element(); })); let mut options: Vec = get_installed_apps(&config); From 72af67be3da686408ccd39981e3cccf765cd8b57 Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:26:06 +0000 Subject: [PATCH 49/65] fix: better error message --- src/cross_platform/macos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index 66442b6..69ed09f 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -120,7 +120,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let file_name_os = x.file_name(); let file_name = file_name_os.into_string().unwrap_or_else(|e| { - tracing::error!("{}", e.to_string()); + tracing::error!("Failed to to get file_name_os: {}", e.to_string()); exit(-1) }); if !file_name.ends_with(".app") { From bf9aa591cf8e2c728bfc38c636c62db4db82644b Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:59:47 +0000 Subject: [PATCH 50/65] fix: remove random URL sitting in the code how tf did this happen --- src/app/tile/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index b588e58..b2b9df3 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -36,7 +36,7 @@ use crate::clipboard::ClipBoardContentType; use crate::commands::Function; use crate::config::Config; use crate::unit_conversion; -https://github.com/unsecretised/rustcast/pull/113/conflict?name=src%252Fcross_platform%252Fwindows.rs&ancestor_oid=68e807100a8921fa1fc6c5f6a77737eb2839a64e&base_oid=5f515f5bc8a6ecbef17e85c334b3fa678b6c5bc8&head_oid=da79aee567002566c8056ced3edcb4d9cd022828use crate::utils::get_installed_apps; +use crate::utils::get_installed_apps; use crate::utils::is_valid_url; #[cfg(target_os = "macos")] From 3e1067a32127c09406d83f9665cb1cd2b5e7d434 Mon Sep 17 00:00:00 2001 From: Mnem42 <177770058+Mnem42@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:02:04 +0000 Subject: [PATCH 51/65] clean: better error message --- src/cross_platform/macos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index 69ed09f..f034c61 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -111,7 +111,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { .into_par_iter() .filter_map(|x| { let file_type = x.file_type().unwrap_or_else(|e| { - tracing::error!("{}", e.to_string()); + tracing::error!("Failed to get file type: {}", e.to_string()); exit(-1) }); if !file_type.is_dir() { From a41bcf337cd2647aaaee4da7b7a580ed0254510b Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 18:59:41 +0000 Subject: [PATCH 52/65] fix: replace todo with some proper text --- src/cross_platform/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 9fde726..efb89e8 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -67,7 +67,7 @@ fn get_apps_from_registry(apps: &mut Vec) { name: display_name.clone().into_string().unwrap(), name_lc: display_name.clone().into_string().unwrap().to_lowercase(), icons: None, - desc: "TODO: Implement".to_string(), + desc: "Application".to_string(), }) } }); From d56d9d41e44337678345a968661891705bf12284 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 18:59:57 +0000 Subject: [PATCH 53/65] clean: remove unused header from Cargo.toml --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29d72b3..ca9c4cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,5 +54,3 @@ info_plist_path = "bundling/Info.plist" [package.metadata.bundle.osx.info] LSUIElement = true NSHighResolutionCapable = true - -[profile.release] From 425b78efb0246faedc229fb14ccf7566014e2a7f Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 19:00:39 +0000 Subject: [PATCH 54/65] fix: unbreak use --- src/cross_platform/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index efb89e8..3fb5471 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,7 +1,7 @@ use { tracing::Level, + crate::app::apps::App, rayon::prelude::*, std::path::PathBuf, windows::{ - crate::app::apps::App, rayon::prelude::*, std::path::PathBuf, windows::{ Win32::{ System::Com::CoTaskMemFree, UI::{ From 565528faf33ccfbd91cb512116027ea08e5e1eb3 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 19:48:50 +0000 Subject: [PATCH 55/65] =?UTF-8?q?=E0=B6=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/tile/elm.rs | 21 ++++++++------- src/app/tile/update.rs | 3 --- src/cross_platform/windows.rs | 50 +++++++++++++++-------------------- src/main.rs | 18 ++++++++----- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index edec5f6..43c5f3c 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -58,15 +58,18 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { settings.position = Position::Specific(pos); } - let open = open - .discard() - .chain(window::run(id, |handle| { - #[cfg(target_os = "macos")] - macos::macos_window_config( - &handle.window_handle().expect("Unable to get window handle"), - ); - transform_process_to_ui_element(); - })); + + let (id, open) = window::open(settings); + + let open: Task = open.discard(); + + #[cfg(target_os = "macos")] + open.chain(window::run(id, |handle| { + macos::macos_window_config( + &handle.window_handle().expect("Unable to get window handle"), + ); + transform_process_to_ui_element(); + })); let mut options: Vec = get_installed_apps(&config); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index b2b9df3..06dfd19 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -23,11 +23,8 @@ use crate::app::apps::AppCommand; use crate::app::default_settings; use crate::app::menubar::menu_icon; use crate::app::tile::AppIndex; -#[cfg(target_os = "windows")] -use crate::app::tile::elm::default_app_paths; use crate::app::{Message, Page, tile::Tile}; -use crate::app::{Message, Page, tile::Tile}; #[cfg(target_os = "windows")] use crate::utils::get_config_installation_dir; diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 3fb5471..d9fadab 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -85,35 +85,27 @@ fn get_apps_from_known_folder(apps: &mut Vec) { .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) - { - use crate::{app::apps::AppCommand, commands::Function}; - - // only in debug builds since it's chonky and this is run a *lot* - #[cfg(debug_assertions)] - tracing::trace!("App added [kfolder]: {}", entry.path().display()); - - apps.push(App { - open_command: AppCommand::Function(Function::OpenApp( - entry.path().to_string_lossy().to_string(), - )), - name: entry - .clone() - .file_name() - .to_string_lossy() - .to_string() - .replace(".exe", ""), - name_lc: entry - .clone() - .file_name() - .to_string_lossy() - .to_string() - .to_lowercase() - .replace(".exe", ""), - icons: None, - desc: "TODO: Implement".to_string(), - }); - } - } + .map(|entry| { + let path = entry.path(); + let file_name = path.file_name().unwrap().to_string_lossy(); + let name = file_name.replace(".exe", ""); + + #[cfg(debug_assertions)] + tracing::trace!("Executable loaded [kfolder]: {:?}", path.to_str()); + + App { + open_command: AppCommand::Function(Function::OpenApp( + path.to_string_lossy().to_string(), + )), + name: name.clone(), + name_lc: name.to_lowercase(), + icons: None, + desc: "TODO: Implement".to_string(), + } + }).collect::>() + }).collect(); + + apps.extend(found_apps); } fn get_known_paths() -> Vec { let paths = vec![ diff --git a/src/main.rs b/src/main.rs index 5f58cff..162280e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,22 +47,28 @@ fn main() -> iced::Result { let log_file = tracing_subscriber::fmt::layer() .with_ansi(false) - .with_filter(LevelFilter::DEBUG) - .with_writer(file); + .with_writer(file) + .with_filter(LevelFilter::DEBUG); let vv_log_file = tracing_subscriber::fmt::layer() .with_ansi(false) .with_writer(vv_file); - let console_out = tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO); + let console_out = tracing_subscriber::fmt::layer() + .with_filter(LevelFilter::INFO); let subscriber = tracing_subscriber::registry() .with(log_file) - .with(vv_file) + .with(vv_log_file) .with(console_out); tracing::subscriber::set_global_default(subscriber).expect("Error initing tracing"); - tracing::info!("Log file at: {}", &log_path); + + tracing::info!("Main log file at : {}", &vv_log_path); + tracing::info!("Verbose log file at : {}", &log_path); + tracing::info!("Config file at : {}", &file_path); } + tracing::debug!("Loaded config data: {:#?}", &config); + let manager = GlobalHotKeyManager::new().unwrap(); let show_hide = config.toggle_hotkey.parse().unwrap(); @@ -80,7 +86,7 @@ fn main() -> iced::Result { tracing::warn!("Couldn't register hotkey {}", key) } } else if let Err(e) = result { - tracing::error!("{}", e.to_string()) + tracing::error!("{}", e.to_string()); } tracing::info!("Starting."); From 8c78b79b31f32d6483736358f7e5fdb97dee0d7e Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 19:49:34 +0000 Subject: [PATCH 56/65] clean: run cargo fmt --- src/app/menubar.rs | 6 +++- src/app/tile/elm.rs | 9 ++--- src/cross_platform/windows.rs | 66 +++++++++++++++++++---------------- src/main.rs | 7 ++-- src/utils.rs | 4 +-- 5 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index af7e712..6492558 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -64,7 +64,11 @@ fn get_image() -> DynamicImage { #[cfg(target_os = "windows")] { - DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(64, 64, image::Rgba([0, 0, 0, 255]))) + DynamicImage::ImageRgba8(image::RgbaImage::from_pixel( + 64, + 64, + image::Rgba([0, 0, 0, 255]), + )) } } diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 43c5f3c..dd9ff7e 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -13,8 +13,8 @@ use iced::{Length::Fill, widget::text_input}; use rayon::slice::ParallelSliceMut; use crate::app::tile::AppIndex; -use crate::utils::get_installed_apps; use crate::styles::{contents_style, rustcast_text_input_style}; +use crate::utils::get_installed_apps; use crate::{ app::{Message, Page, apps::App, default_settings, tile::Tile}, config::Config, @@ -57,17 +57,14 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { let pos = open_on_focused_monitor(); settings.position = Position::Specific(pos); } - let (id, open) = window::open(settings); let open: Task = open.discard(); - + #[cfg(target_os = "macos")] open.chain(window::run(id, |handle| { - macos::macos_window_config( - &handle.window_handle().expect("Unable to get window handle"), - ); + macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); transform_process_to_ui_element(); })); diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index d9fadab..9750bac 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,6 +1,8 @@ use { + crate::app::apps::App, + rayon::prelude::*, + std::path::PathBuf, tracing::Level, - crate::app::apps::App, rayon::prelude::*, std::path::PathBuf, windows::{ Win32::{ System::Com::CoTaskMemFree, @@ -13,8 +15,7 @@ use { }, }, core::GUID, - } - + }, }; fn get_apps_from_registry(apps: &mut Vec) { @@ -75,37 +76,40 @@ fn get_apps_from_registry(apps: &mut Vec) { } fn get_apps_from_known_folder(apps: &mut Vec) { let paths = get_known_paths(); - use walkdir::WalkDir; use crate::{app::apps::AppCommand, commands::Function}; + use walkdir::WalkDir; + let found_apps: Vec = paths + .par_iter() + .flat_map(|path| { + WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) + .map(|entry| { + let path = entry.path(); + let file_name = path.file_name().unwrap().to_string_lossy(); + let name = file_name.replace(".exe", ""); + + #[cfg(debug_assertions)] + tracing::trace!("Executable loaded [kfolder]: {:?}", path.to_str()); + + App { + open_command: AppCommand::Function(Function::OpenApp( + path.to_string_lossy().to_string(), + )), + name: name.clone(), + name_lc: name.to_lowercase(), + icons: None, + desc: "TODO: Implement".to_string(), + } + }) + .collect::>() + }) + .collect(); - let found_apps: Vec = paths.par_iter().flat_map(|path| { - WalkDir::new(path) - .follow_links(false) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) - .map(|entry| { - let path = entry.path(); - let file_name = path.file_name().unwrap().to_string_lossy(); - let name = file_name.replace(".exe", ""); - - #[cfg(debug_assertions)] - tracing::trace!("Executable loaded [kfolder]: {:?}", path.to_str()); - - App { - open_command: AppCommand::Function(Function::OpenApp( - path.to_string_lossy().to_string(), - )), - name: name.clone(), - name_lc: name.to_lowercase(), - icons: None, - desc: "TODO: Implement".to_string(), - } - }).collect::>() - }).collect(); - - apps.extend(found_apps); + apps.extend(found_apps); } fn get_known_paths() -> Vec { let paths = vec![ diff --git a/src/main.rs b/src/main.rs index 162280e..5eba7e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ fn main() -> iced::Result { let file = File::create(&log_path).expect("Failed to create logfile"); let vv_file = File::create(&vv_log_path).expect("Failed to create logfile"); - + let log_file = tracing_subscriber::fmt::layer() .with_ansi(false) .with_writer(file) @@ -52,8 +52,7 @@ fn main() -> iced::Result { let vv_log_file = tracing_subscriber::fmt::layer() .with_ansi(false) .with_writer(vv_file); - let console_out = tracing_subscriber::fmt::layer() - .with_filter(LevelFilter::INFO); + let console_out = tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO); let subscriber = tracing_subscriber::registry() .with(log_file) @@ -99,4 +98,4 @@ fn main() -> iced::Result { .subscription(Tile::subscription) .theme(Tile::theme) .run() - } +} diff --git a/src/utils.rs b/src/utils.rs index 86b6ea2..8652900 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -28,7 +28,8 @@ use crate::{ /// This converts an icns file to an iced image handle pub(crate) fn handle_from_icns(path: &Path) -> Option { - #[cfg(target_os = "macos")] { + #[cfg(target_os = "macos")] + { let data = std::fs::read(path).ok()?; let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?; @@ -49,7 +50,6 @@ pub(crate) fn handle_from_icns(path: &Path) -> Option { None } - /// Open the settings file with the system default editor pub fn open_settings() { #[cfg(target_os = "macos")] From 6356081a0a4c45698dd8b93ac353d74542ba7618 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 19:52:36 +0000 Subject: [PATCH 57/65] clean: run cargo clippy --fix --- src/app/menubar.rs | 2 +- src/app/tile/elm.rs | 2 +- src/cross_platform/windows.rs | 4 +--- src/main.rs | 8 ++------ src/unit_conversion.rs | 7 +++---- src/utils.rs | 5 +---- 6 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 6492558..3622eff 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -1,7 +1,7 @@ //! This has the menubar icon logic for the app use global_hotkey::hotkey::{Code, HotKey, Modifiers}; -use image::{DynamicImage, ImageReader}; +use image::DynamicImage; use tray_icon::{ Icon, TrayIcon, TrayIconBuilder, menu::{ diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index dd9ff7e..6328243 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -68,7 +68,7 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { transform_process_to_ui_element(); })); - let mut options: Vec = get_installed_apps(&config); + let mut options: Vec = get_installed_apps(config); options.extend(config.shells.iter().map(|x| x.to_app())); options.extend(App::basic_apps()); diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 9750bac..fc45b13 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -1,8 +1,6 @@ use { crate::app::apps::App, rayon::prelude::*, - std::path::PathBuf, - tracing::Level, windows::{ Win32::{ System::Com::CoTaskMemFree, @@ -115,7 +113,7 @@ fn get_known_paths() -> Vec { let paths = vec![ get_windows_path(&FOLDERID_ProgramFiles).unwrap_or_default(), get_windows_path(&FOLDERID_ProgramFilesX86).unwrap_or_default(), - String::from(get_windows_path(&FOLDERID_LocalAppData).unwrap_or_default() + "\\Programs\\"), + (get_windows_path(&FOLDERID_LocalAppData).unwrap_or_default() + "\\Programs\\"), ]; paths } diff --git a/src/main.rs b/src/main.rs index 5eba7e2..dd2183e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,19 +13,15 @@ use std::fs::File; // import from utils use crate::utils::{ - create_config_file_if_not_exists, get_config_file_path, get_config_installation_dir, + create_config_file_if_not_exists, get_config_file_path, get_temp_dir, read_config_file, }; use crate::app::tile::Tile; -use global_hotkey::{ - GlobalHotKeyManager, - hotkey::{HotKey, Modifiers}, -}; +use global_hotkey::GlobalHotKeyManager; use tracing::level_filters::LevelFilter; use tracing_subscriber::Layer; -use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; fn main() -> iced::Result { diff --git a/src/unit_conversion.rs b/src/unit_conversion.rs index 5fa3379..f74304e 100644 --- a/src/unit_conversion.rs +++ b/src/unit_conversion.rs @@ -333,11 +333,10 @@ fn parse_number_prefix(s: &str) -> Option<(&str, &str)> { } let mut chars = s.char_indices().peekable(); - if let Some((_, c)) = chars.peek() { - if *c == '+' || *c == '-' { + if let Some((_, c)) = chars.peek() + && (*c == '+' || *c == '-') { chars.next(); } - } let mut end = 0; let mut has_digit = false; @@ -370,7 +369,7 @@ fn find_unit(token: &str) -> Option<&'static UnitDef> { UNITS .iter() - .find(|unit| unit.name == token || unit.aliases.iter().any(|alias| *alias == token)) + .find(|unit| unit.name == token || unit.aliases.contains(&token)) } fn to_base(value: f64, unit: &UnitDef) -> f64 { diff --git a/src/utils.rs b/src/utils.rs index 8652900..464e371 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,16 +1,13 @@ //! This has all the utility functions that rustcast uses use std::{ - fs::{self, File}, - io::Write, + fs::{self}, path::Path, - process::exit, thread, }; use iced::widget::image::Handle; #[cfg(target_os = "macos")] use icns::IconFamily; -use image::RgbaImage; #[cfg(target_os = "macos")] use { From 3639e3c99b743653ed350ea0484d5cd0641509a2 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 20:19:17 +0000 Subject: [PATCH 58/65] stuff --- src/app/tile.rs | 2 +- src/app/tile/update.rs | 2 +- src/main.rs | 14 +++++++------- src/utils.rs | 33 ++++++++++++--------------------- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/app/tile.rs b/src/app/tile.rs index e3f4316..a257b84 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -373,7 +373,7 @@ fn handle_recipient() -> impl futures::Stream { stream::channel(100, async |mut output| { let (sender, mut recipient) = channel(100); let msg = Message::SetSender(ExtSender(sender)); - tracing::debug!("Sending ExtSender ({msg:?})"); + tracing::debug!("Sending ExtSender"); output.send(msg).await.expect("Sender not sent"); loop { let abcd = recipient diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 06dfd19..1eb7ceb 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -331,7 +331,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] let new_config: Config = toml::from_str( - &fs::read_to_string(get_config_installation_dir() + "/rustcast/config.toml") + &fs::read_to_string(get_config_installation_dir().join("/rustcast/config.toml")) .unwrap_or("".to_owned()), ) .unwrap(); diff --git a/src/main.rs b/src/main.rs index dd2183e..60b74e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,12 +9,12 @@ mod utils; mod cross_platform; +use std::env::temp_dir; use std::fs::File; // import from utils use crate::utils::{ - create_config_file_if_not_exists, get_config_file_path, - get_temp_dir, read_config_file, + create_config_file_if_not_exists, get_config_file_path, read_config_file, }; use crate::app::tile::Tile; @@ -33,8 +33,8 @@ fn main() -> iced::Result { create_config_file_if_not_exists(&file_path, &config).unwrap(); { - let log_path = get_temp_dir() + "/log.log"; - let vv_log_path = get_temp_dir() + "/vv_log.log"; + let log_path = temp_dir().join("rustcast/log.log"); + let vv_log_path = temp_dir().join("rustcast/vv_log.log"); create_config_file_if_not_exists(&log_path, &config).unwrap(); @@ -57,9 +57,9 @@ fn main() -> iced::Result { tracing::subscriber::set_global_default(subscriber).expect("Error initing tracing"); - tracing::info!("Main log file at : {}", &vv_log_path); - tracing::info!("Verbose log file at : {}", &log_path); - tracing::info!("Config file at : {}", &file_path); + tracing::info!("Main log file at : {}", &vv_log_path.display()); + tracing::info!("Verbose log file at : {}", &log_path.display()); + tracing::info!("Config file at : {}", &file_path.display()); } tracing::debug!("Loaded config data: {:#?}", &config); diff --git a/src/utils.rs b/src/utils.rs index 464e371..0b0a232 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ //! This has all the utility functions that rustcast uses use std::{ fs::{self}, - path::Path, + path::{Path, PathBuf}, thread, }; @@ -72,34 +72,26 @@ pub fn open_url(url: &str) { }); } -pub fn get_config_installation_dir() -> String { +pub fn get_config_installation_dir() -> PathBuf { if cfg!(target_os = "windows") { - std::env::var("LOCALAPPDATA").unwrap() + std::env::var("LOCALAPPDATA").unwrap().into() } else { - std::env::var("HOME").unwrap() + std::env::var("HOME").unwrap().into() } } -pub fn get_temp_dir() -> String { - if cfg!(target_os = "windows") { - std::env::var("TEMP").unwrap() + "/rustcast" - } else { - todo!("problem for secretised") - } -} - -pub fn get_config_file_path() -> String { +pub fn get_config_file_path() -> PathBuf { let home = get_config_installation_dir(); if cfg!(target_os = "windows") { - home + "\\rustcast\\config.toml" + home.join("rustcast/config.toml") } else { - home + "/.config/rustcast/config.toml" + home.join(".config/rustcast/config.toml") } } use crate::config::Config; -pub fn read_config_file(file_path: &str) -> Result { +pub fn read_config_file(file_path: &Path) -> Result { let config: Config = match std::fs::read_to_string(file_path) { Ok(a) => toml::from_str(&a).unwrap(), Err(_) => Config::default(), @@ -109,7 +101,7 @@ pub fn read_config_file(file_path: &str) -> Result { } pub fn create_config_file_if_not_exists( - file_path: &str, + file_path: &Path, config: &Config, ) -> Result<(), std::io::Error> { // check if file exists @@ -119,13 +111,12 @@ pub fn create_config_file_if_not_exists( return Ok(()); } - let path = Path::new(&file_path); - if let Some(parent) = path.parent() { + if let Some(parent) = file_path.parent() { std::fs::create_dir_all(parent).unwrap(); } std::fs::write( - file_path, + &file_path, toml::to_string(&config).unwrap_or_else(|x| x.to_string()), ) .unwrap(); @@ -164,7 +155,7 @@ pub fn open_application(path: &str) { #[allow(unused)] pub fn index_dirs_from_config(apps: &mut Vec) -> bool { let path = get_config_file_path(); - let config = read_config_file(&path); + let config = read_config_file(path.as_path()); // if config is not valid return false otherwise unwrap config so it is usable let config = match config { From 21404e114acf1d30ee82c283f7f32d992f482c39 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 20:20:12 +0000 Subject: [PATCH 59/65] clean: run cargo fmt and clippy --fix --- src/main.rs | 6 ++---- src/unit_conversion.rs | 7 ++++--- src/utils.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 60b74e7..3594a37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,9 +13,7 @@ use std::env::temp_dir; use std::fs::File; // import from utils -use crate::utils::{ - create_config_file_if_not_exists, get_config_file_path, read_config_file, -}; +use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; use crate::app::tile::Tile; @@ -33,7 +31,7 @@ fn main() -> iced::Result { create_config_file_if_not_exists(&file_path, &config).unwrap(); { - let log_path = temp_dir().join("rustcast/log.log"); + let log_path = temp_dir().join("rustcast/log.log"); let vv_log_path = temp_dir().join("rustcast/vv_log.log"); create_config_file_if_not_exists(&log_path, &config).unwrap(); diff --git a/src/unit_conversion.rs b/src/unit_conversion.rs index f74304e..3bc6f7f 100644 --- a/src/unit_conversion.rs +++ b/src/unit_conversion.rs @@ -334,9 +334,10 @@ fn parse_number_prefix(s: &str) -> Option<(&str, &str)> { let mut chars = s.char_indices().peekable(); if let Some((_, c)) = chars.peek() - && (*c == '+' || *c == '-') { - chars.next(); - } + && (*c == '+' || *c == '-') + { + chars.next(); + } let mut end = 0; let mut has_digit = false; diff --git a/src/utils.rs b/src/utils.rs index 0b0a232..b948d12 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -116,7 +116,7 @@ pub fn create_config_file_if_not_exists( } std::fs::write( - &file_path, + file_path, toml::to_string(&config).unwrap_or_else(|x| x.to_string()), ) .unwrap(); From cadf527f8fc01d307a7fa14ac06d7469964a97ff Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 21:24:40 +0000 Subject: [PATCH 60/65] general stuff --- src/app/menubar.rs | 9 ++++----- src/app/tile.rs | 34 ++++++++++++++++----------------- src/app/tile/elm.rs | 2 ++ src/app/tile/update.rs | 2 +- src/commands.rs | 5 ++++- src/cross_platform/macos/mod.rs | 23 ++++++++++++++++++++++ src/cross_platform/mod.rs | 10 ++++++++++ src/cross_platform/windows.rs | 5 +---- src/utils.rs | 25 ------------------------ 9 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 3622eff..841bad5 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -9,15 +9,14 @@ use tray_icon::{ accelerator::Accelerator, }, }; +use tokio::runtime::Runtime; use crate::{ - app::{Message, tile::ExtSender}, - utils::{open_settings, open_url}, + app::{Message, tile::ExtSender}, + cross_platform::{open_url, open_settings} }; -use tokio::runtime::Runtime; - -/// This create a new menubar icon for the app +/// This creates a new menubar icon for the app pub fn menu_icon(hotkey: HotKey, sender: ExtSender) -> TrayIcon { let builder = TrayIconBuilder::new(); diff --git a/src/app/tile.rs b/src/app/tile.rs index a257b84..a1b4776 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -5,7 +5,7 @@ pub mod update; #[cfg(target_os = "windows")] use { windows::Win32::Foundation::HWND, - windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow}, + windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow, }; use crate::app::apps::App; @@ -13,7 +13,7 @@ use crate::app::tile::elm::default_app_paths; use crate::app::{ArrowKey, Message, Move, Page}; use crate::clipboard::ClipBoardContentType; use crate::config::Config; -use crate::utils::open_settings; +use crate::cross_platform::open_settings; use arboard::Clipboard; use global_hotkey::hotkey::HotKey; @@ -235,21 +235,21 @@ impl Tile { self.results = results; } - /// Gets the frontmost application to focus later. - pub fn capture_frontmost(&mut self) { - #[cfg(target_os = "macos")] - { - use objc2_app_kit::NSWorkspace; - - let ws = NSWorkspace::sharedWorkspace(); - self.frontmost = ws.frontmostApplication(); - }; - - #[cfg(target_os = "windows")] - { - self.frontmost = Some(unsafe { GetForegroundWindow() }); - } - } + // Unused, keeping it for now + // pub fn capture_frontmost(&mut self) { + // #[cfg(target_os = "macos")] + // { + // use objc2_app_kit::NSWorkspace; + + // let ws = NSWorkspace::sharedWorkspace(); + // self.frontmost = ws.frontmostApplication(); + // }; + + // #[cfg(target_os = "windows")] + // { + // self.frontmost = Some(unsafe { GetForegroundWindow() }); + // } + // } /// Restores the frontmost application. #[allow(deprecated)] diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 6328243..73008df 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -58,6 +58,8 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { settings.position = Position::Specific(pos); } + // id unused on windows, but not macos + #[cfg_attr(target_os = "windows", allow(unused))] let (id, open) = window::open(settings); let open: Task = open.discard(); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 1eb7ceb..e8aa98b 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -331,7 +331,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { #[cfg(target_os = "windows")] let new_config: Config = toml::from_str( - &fs::read_to_string(get_config_installation_dir().join("/rustcast/config.toml")) + &fs::read_to_string(get_config_installation_dir().join("rustcast/config.toml")) .unwrap_or("".to_owned()), ) .unwrap(); diff --git a/src/commands.rs b/src/commands.rs index 845dcac..6238400 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -75,13 +75,14 @@ impl Function { ); } + #[cfg(target_os = "macos")] Function::OpenWebsite(url) => { let open_url = if url.starts_with("http") { url.to_owned() } else { format!("https://{}", url) }; - #[cfg(target_os = "macos")] + thread::spawn(move || { NSWorkspace::new().openURL( &NSURL::URLWithString_relativeToURL( @@ -120,7 +121,9 @@ impl Function { )); }); } + Function::Quit => std::process::exit(0), + _ => todo!("Actual handling for this situation") } } } diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index f034c61..bd1bf04 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -235,3 +235,26 @@ pub fn get_installed_macos_apps(config: &Config) -> Vec { apps } + +/// Opens a provided URL +pub fn open_url(url: &str) { + let url = url.to_owned(); + thread::spawn(move || { + NSWorkspace::new().openURL( + &NSURL::URLWithString_relativeToURL(&objc2_foundation::NSString::from_str(&url), None) + .unwrap(), + ); + }); +} + +/// Open the settings file with the system default editor +pub fn open_settings() { + thread::spawn(move || { + NSWorkspace::new().openURL(&NSURL::fileURLWithPath( + &objc2_foundation::NSString::from_str( + &(std::env::var("HOME").unwrap_or("".to_string()) + + "/.config/rustcast/config.toml"), + ), + )); + }); +} \ No newline at end of file diff --git a/src/cross_platform/mod.rs b/src/cross_platform/mod.rs index cdc0dc5..e53d0e2 100644 --- a/src/cross_platform/mod.rs +++ b/src/cross_platform/mod.rs @@ -3,3 +3,13 @@ pub mod macos; #[cfg(target_os = "windows")] pub mod windows; + +pub fn open_url(url: &str){ + #[cfg(target_os = "macos")] + macos::open_url(url) +} + +pub fn open_settings() { + #[cfg(target_os = "macos")] + macos::open_settings() +} \ No newline at end of file diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index fc45b13..4264451 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -131,10 +131,7 @@ fn get_windows_path(folder_id: &GUID) -> Option { } pub fn get_installed_windows_apps() -> Vec { use crate::utils::index_dirs_from_config; - - use std::time::Instant; - let start = Instant::now(); - + let mut apps = Vec::new(); tracing::debug!("Getting apps from registry"); diff --git a/src/utils.rs b/src/utils.rs index b948d12..8d1fc05 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -47,31 +47,6 @@ pub(crate) fn handle_from_icns(path: &Path) -> Option { None } -/// Open the settings file with the system default editor -pub fn open_settings() { - #[cfg(target_os = "macos")] - thread::spawn(move || { - NSWorkspace::new().openURL(&NSURL::fileURLWithPath( - &objc2_foundation::NSString::from_str( - &(std::env::var("HOME").unwrap_or("".to_string()) - + "/.config/rustcast/config.toml"), - ), - )); - }); -} - -/// Open a provided URL (Platform specific) -pub fn open_url(url: &str) { - let url = url.to_owned(); - #[cfg(target_os = "macos")] - thread::spawn(move || { - NSWorkspace::new().openURL( - &NSURL::URLWithString_relativeToURL(&objc2_foundation::NSString::from_str(&url), None) - .unwrap(), - ); - }); -} - pub fn get_config_installation_dir() -> PathBuf { if cfg!(target_os = "windows") { std::env::var("LOCALAPPDATA").unwrap().into() From 2996e8cb98db3aa080a08832188e1d142118212b Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 21:26:03 +0000 Subject: [PATCH 61/65] clean: run cargo fmt --- src/app/menubar.rs | 6 +++--- src/app/tile.rs | 3 +-- src/commands.rs | 4 ++-- src/cross_platform/macos/mod.rs | 2 +- src/cross_platform/mod.rs | 4 ++-- src/cross_platform/windows.rs | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 841bad5..80f9edc 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -2,6 +2,7 @@ use global_hotkey::hotkey::{Code, HotKey, Modifiers}; use image::DynamicImage; +use tokio::runtime::Runtime; use tray_icon::{ Icon, TrayIcon, TrayIconBuilder, menu::{ @@ -9,11 +10,10 @@ use tray_icon::{ accelerator::Accelerator, }, }; -use tokio::runtime::Runtime; use crate::{ - app::{Message, tile::ExtSender}, - cross_platform::{open_url, open_settings} + app::{Message, tile::ExtSender}, + cross_platform::{open_settings, open_url}, }; /// This creates a new menubar icon for the app diff --git a/src/app/tile.rs b/src/app/tile.rs index a1b4776..51f8a6c 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -4,8 +4,7 @@ pub mod update; #[cfg(target_os = "windows")] use { - windows::Win32::Foundation::HWND, - windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow, + windows::Win32::Foundation::HWND, windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow, }; use crate::app::apps::App; diff --git a/src/commands.rs b/src/commands.rs index 6238400..6ebef5a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -82,7 +82,7 @@ impl Function { } else { format!("https://{}", url) }; - + thread::spawn(move || { NSWorkspace::new().openURL( &NSURL::URLWithString_relativeToURL( @@ -123,7 +123,7 @@ impl Function { } Function::Quit => std::process::exit(0), - _ => todo!("Actual handling for this situation") + _ => todo!("Actual handling for this situation"), } } } diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index bd1bf04..def84ad 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -257,4 +257,4 @@ pub fn open_settings() { ), )); }); -} \ No newline at end of file +} diff --git a/src/cross_platform/mod.rs b/src/cross_platform/mod.rs index e53d0e2..e84b658 100644 --- a/src/cross_platform/mod.rs +++ b/src/cross_platform/mod.rs @@ -4,7 +4,7 @@ pub mod macos; #[cfg(target_os = "windows")] pub mod windows; -pub fn open_url(url: &str){ +pub fn open_url(url: &str) { #[cfg(target_os = "macos")] macos::open_url(url) } @@ -12,4 +12,4 @@ pub fn open_url(url: &str){ pub fn open_settings() { #[cfg(target_os = "macos")] macos::open_settings() -} \ No newline at end of file +} diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 4264451..2251401 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -131,7 +131,7 @@ fn get_windows_path(folder_id: &GUID) -> Option { } pub fn get_installed_windows_apps() -> Vec { use crate::utils::index_dirs_from_config; - + let mut apps = Vec::new(); tracing::debug!("Getting apps from registry"); From bb229851e767008baf11233bd4b85218ad061341 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 21:28:18 +0000 Subject: [PATCH 62/65] shhh --- src/cross_platform/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 2251401..cefd369 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -100,7 +100,7 @@ fn get_apps_from_known_folder(apps: &mut Vec) { name: name.clone(), name_lc: name.to_lowercase(), icons: None, - desc: "TODO: Implement".to_string(), + desc: "Application".to_string(), } }) .collect::>() From 96711656a2411e72c9727bdc844e1e154c3f1d11 Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 22:06:23 +0000 Subject: [PATCH 63/65] refactor: make get_apps_from_known_folder an iter This is totally not an excercise in pointlessness --- src/cross_platform/windows.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index cefd369..846b0cf 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -72,17 +72,18 @@ fn get_apps_from_registry(apps: &mut Vec) { }); }); } -fn get_apps_from_known_folder(apps: &mut Vec) { +fn get_apps_from_known_folder() -> impl ParallelIterator { let paths = get_known_paths(); use crate::{app::apps::AppCommand, commands::Function}; use walkdir::WalkDir; - let found_apps: Vec = paths - .par_iter() + paths + .into_par_iter() .flat_map(|path| { WalkDir::new(path) .follow_links(false) .into_iter() + .par_bridge() .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) .map(|entry| { @@ -103,11 +104,7 @@ fn get_apps_from_known_folder(apps: &mut Vec) { desc: "Application".to_string(), } }) - .collect::>() }) - .collect(); - - apps.extend(found_apps); } fn get_known_paths() -> Vec { let paths = vec![ @@ -138,7 +135,7 @@ pub fn get_installed_windows_apps() -> Vec { get_apps_from_registry(&mut apps); tracing::debug!("Getting apps from known folder"); - get_apps_from_known_folder(&mut apps); + apps.par_extend(get_apps_from_known_folder()); tracing::debug!("Getting apps from config"); index_dirs_from_config(&mut apps); From f630b765c50e71d6dbb39e55f3b8eb8eae0fb99f Mon Sep 17 00:00:00 2001 From: Mnem42 Date: Tue, 20 Jan 2026 22:09:45 +0000 Subject: [PATCH 64/65] Revert "refactor: make get_apps_from_known_folder an iter" This reverts commit 96711656a2411e72c9727bdc844e1e154c3f1d11. --- src/cross_platform/windows.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/cross_platform/windows.rs b/src/cross_platform/windows.rs index 846b0cf..cefd369 100644 --- a/src/cross_platform/windows.rs +++ b/src/cross_platform/windows.rs @@ -72,18 +72,17 @@ fn get_apps_from_registry(apps: &mut Vec) { }); }); } -fn get_apps_from_known_folder() -> impl ParallelIterator { +fn get_apps_from_known_folder(apps: &mut Vec) { let paths = get_known_paths(); use crate::{app::apps::AppCommand, commands::Function}; use walkdir::WalkDir; - paths - .into_par_iter() + let found_apps: Vec = paths + .par_iter() .flat_map(|path| { WalkDir::new(path) .follow_links(false) .into_iter() - .par_bridge() .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "exe")) .map(|entry| { @@ -104,7 +103,11 @@ fn get_apps_from_known_folder() -> impl ParallelIterator { desc: "Application".to_string(), } }) + .collect::>() }) + .collect(); + + apps.extend(found_apps); } fn get_known_paths() -> Vec { let paths = vec![ @@ -135,7 +138,7 @@ pub fn get_installed_windows_apps() -> Vec { get_apps_from_registry(&mut apps); tracing::debug!("Getting apps from known folder"); - apps.par_extend(get_apps_from_known_folder()); + get_apps_from_known_folder(&mut apps); tracing::debug!("Getting apps from config"); index_dirs_from_config(&mut apps); From 2e381a2253689984ad9d55e804a67cbc3c244b31 Mon Sep 17 00:00:00 2001 From: unsecretised Date: Wed, 21 Jan 2026 12:18:04 +0800 Subject: [PATCH 65/65] Fixes for macos --- src/app/menubar.rs | 2 ++ src/app/tile.rs | 28 +++++++++++------------ src/app/tile/elm.rs | 6 +++-- src/commands.rs | 1 - src/cross_platform/macos/mod.rs | 14 ++++++++---- src/main.rs | 5 +---- src/utils.rs | 39 ++++++++++++++++----------------- 7 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 80f9edc..5b724d3 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -52,6 +52,8 @@ pub fn menu_icon(hotkey: HotKey, sender: ExtSender) -> TrayIcon { fn get_image() -> DynamicImage { #[cfg(target_os = "macos")] { + use image::ImageReader; + let image_path = if cfg!(debug_assertions) && !cfg!(target_os = "macos") { "docs/icon.png" } else { diff --git a/src/app/tile.rs b/src/app/tile.rs index a8375a1..c40c587 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -217,20 +217,20 @@ impl Tile { } // Unused, keeping it for now - // pub fn capture_frontmost(&mut self) { - // #[cfg(target_os = "macos")] - // { - // use objc2_app_kit::NSWorkspace; - - // let ws = NSWorkspace::sharedWorkspace(); - // self.frontmost = ws.frontmostApplication(); - // }; - - // #[cfg(target_os = "windows")] - // { - // self.frontmost = Some(unsafe { GetForegroundWindow() }); - // } - // } + pub fn capture_frontmost(&mut self) { + #[cfg(target_os = "macos")] + { + use objc2_app_kit::NSWorkspace; + + let ws = NSWorkspace::sharedWorkspace(); + self.frontmost = ws.frontmostApplication(); + }; + + #[cfg(target_os = "windows")] + { + self.frontmost = Some(unsafe { GetForegroundWindow() }); + } + } /// Restores the frontmost application. #[allow(deprecated)] diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 73008df..e11b6ab 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -62,12 +62,14 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { #[cfg_attr(target_os = "windows", allow(unused))] let (id, open) = window::open(settings); + #[cfg(target_os = "windows")] let open: Task = open.discard(); #[cfg(target_os = "macos")] - open.chain(window::run(id, |handle| { + let open = open.discard().chain(window::run(id, |handle| { macos::macos_window_config(&handle.window_handle().expect("Unable to get window handle")); transform_process_to_ui_element(); + Message::OpenWindow })); let mut options: Vec = get_installed_apps(config); @@ -96,7 +98,7 @@ pub fn new(hotkey: HotKey, config: &Config) -> (Tile, Task) { sender: None, page: Page::Main, }, - Task::batch([open.map(|_| Message::OpenWindow)]), + open, ) } diff --git a/src/commands.rs b/src/commands.rs index 6ebef5a..3b5fc7b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -123,7 +123,6 @@ impl Function { } Function::Quit => std::process::exit(0), - _ => todo!("Actual handling for this situation"), } } } diff --git a/src/cross_platform/macos/mod.rs b/src/cross_platform/macos/mod.rs index def84ad..7b29f44 100644 --- a/src/cross_platform/macos/mod.rs +++ b/src/cross_platform/macos/mod.rs @@ -6,8 +6,8 @@ pub mod haptics; use crate::app::apps::{App, AppCommand}; use crate::commands::Function; use crate::config::Config; +use crate::utils::handle_from_icns; use crate::utils::index_dirs_from_config; -use crate::utils::{handle_from_icns, log_error, log_error_and_exit}; use { iced::wgpu::rwh::RawWindowHandle, iced::wgpu::rwh::WindowHandle, @@ -16,12 +16,14 @@ use { objc2_app_kit::NSView, objc2_app_kit::{NSApp, NSApplicationActivationPolicy}, objc2_app_kit::{NSFloatingWindowLevel, NSWindowCollectionBehavior}, + objc2_foundation::NSURL, }; +use objc2_app_kit::NSWorkspace; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use std::fs; use std::path::{Path, PathBuf}; use std::process::exit; +use std::{fs, thread}; /// This sets the activation policy of the app to Accessory, allowing rustcast to be visible ontop /// of fullscreen apps @@ -101,7 +103,11 @@ pub fn transform_process_to_ui_element() -> u32 { fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let entries: Vec<_> = fs::read_dir(dir.as_ref()) .unwrap_or_else(|x| { - log_error_and_exit(&x.to_string()); + tracing::error!( + "An error occurred while reading dir ({}) {}", + dir.as_ref().to_str().unwrap_or(""), + x + ); exit(-1) }) .filter_map(|x| x.ok()) @@ -120,7 +126,7 @@ fn get_installed_apps(dir: impl AsRef, store_icons: bool) -> Vec { let file_name_os = x.file_name(); let file_name = file_name_os.into_string().unwrap_or_else(|e| { - tracing::error!("Failed to to get file_name_os: {}", e.to_string()); + tracing::error!("Failed to to get file_name_os: {}", e.to_string_lossy()); exit(-1) }); if !file_name.ends_with(".app") { diff --git a/src/main.rs b/src/main.rs index 7ad7a09..73fe500 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,10 +15,7 @@ use std::fs::File; // import from utils use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file}; -use crate::{ - app::tile::{self, Tile}, - config::Config, -}; +use crate::app::tile::{self, Tile}; use global_hotkey::GlobalHotKeyManager; use tracing::level_filters::LevelFilter; diff --git a/src/utils.rs b/src/utils.rs index 8d1fc05..ed1f5c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -24,27 +24,26 @@ use crate::{ }; /// This converts an icns file to an iced image handle +#[cfg(target_os = "macos")] pub(crate) fn handle_from_icns(path: &Path) -> Option { - #[cfg(target_os = "macos")] - { - let data = std::fs::read(path).ok()?; - let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?; - - let icon_type = family.available_icons(); - - let icon = family.get_icon_with_type(*icon_type.first()?).ok()?; - let image = RgbaImage::from_raw( - icon.width() as u32, - icon.height() as u32, - icon.data().to_vec(), - )?; - Some(Handle::from_rgba( - image.width(), - image.height(), - image.into_raw(), - )) - } - None + use image::RgbaImage; + + let data = std::fs::read(path).ok()?; + let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?; + + let icon_type = family.available_icons(); + + let icon = family.get_icon_with_type(*icon_type.first()?).ok()?; + let image = RgbaImage::from_raw( + icon.width() as u32, + icon.height() as u32, + icon.data().to_vec(), + )?; + return Some(Handle::from_rgba( + image.width(), + image.height(), + image.into_raw(), + )); } pub fn get_config_installation_dir() -> PathBuf {