Skip to content

Commit f03ff73

Browse files
committed
yay
1 parent fbb7eb3 commit f03ff73

8 files changed

Lines changed: 372 additions & 263 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2024"
66

77
[target.'cfg(target_os = "windows")'.dependencies]
88
winreg = "0.52"
9-
windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi"] }
9+
windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_System_Com", "Win32_UI_Shell"] }
1010

1111
[target.'cfg(target_os = "macos")'.dependencies]
1212
objc2 = "0.6.3"
@@ -30,6 +30,7 @@ rayon = "1.11.0"
3030
serde = { version = "1.0.228", features = ["derive"] }
3131
tokio = { version = "1.48.0", features = ["full"] }
3232
toml = "0.9.8"
33+
walkdir = "2"
3334

3435
[package.metadata.bundle]
3536
name = "RustCast"

src/app.rs

Lines changed: 8 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::commands::Function;
22
use crate::config::Config;
3-
use crate::utils::get_config_file_path;
3+
use crate::utils::{get_config_file_path, get_installed_apps, read_config_file};
44
use global_hotkey::{GlobalHotKeyEvent, HotKeyState};
55
use iced::futures::SinkExt;
66
use iced::{
@@ -15,7 +15,6 @@ use iced::{
1515
},
1616
window::{self, Id, Settings},
1717
};
18-
use std::path::Path;
1918

2019
#[cfg(target_os = "macos")]
2120
use crate::macos::{focus_this_app, transform_process_to_ui_element};
@@ -146,9 +145,6 @@ pub fn default_settings() -> Settings {
146145
}
147146
}
148147

149-
#[derive(Debug, Clone)]
150-
pub struct Temp {}
151-
152148
#[derive(Debug, Clone)]
153149
pub struct Tile {
154150
theme: iced::Theme,
@@ -174,7 +170,7 @@ impl Tile {
174170
#[cfg(target_os = "windows")]
175171
{
176172
// get normal settings and modify position
177-
use crate::utils::open_on_focused_monitor;
173+
use crate::windows::open_on_focused_monitor;
178174
use iced::window::Position::Specific;
179175
let pos = open_on_focused_monitor();
180176
settings.position = Specific(pos);
@@ -195,38 +191,11 @@ impl Tile {
195191
}
196192
}));
197193

198-
let store_icons = config.theme.show_icons;
199-
let paths;
200-
let user_local_path: String;
201-
#[cfg(target_os = "macos")]
202-
{
203-
user_local_path = std::env::var("HOME").unwrap() + "/Applications/";
204-
paths = vec![
205-
"/Applications/",
206-
user_local_path.as_str(),
207-
"/System/Applications/",
208-
"/System/Applications/Utilities/",
209-
];
210-
}
211-
#[cfg(target_os = "windows")]
212-
{
213-
paths = vec!["C:\\Program Files\\", "C:\\Program Files (x86)\\"];
214-
}
194+
// get config
195+
let path = get_config_file_path();
196+
let config = read_config_file(&path).unwrap();
215197

216-
let mut options: Vec<App> = paths
217-
.par_iter()
218-
.map(|path| {
219-
#[cfg(target_os = "macos")]
220-
{
221-
get_installed_apps(path, store_icons)
222-
}
223-
#[cfg(target_os = "windows")]
224-
{
225-
get_installed_windows_app(Path::new(path))
226-
}
227-
})
228-
.flatten()
229-
.collect();
198+
let mut options: Vec<App> = get_installed_apps(&config);
230199

231200
options.extend(config.shells.iter().map(|x| x.to_app()));
232201
options.extend(App::basic_apps());
@@ -259,17 +228,8 @@ impl Tile {
259228
focus_this_app();
260229
}
261230

262-
#[cfg(target_os = "windows")]
263-
{
264-
if let Some(hwnd) = self.frontmost {
265-
unsafe {
266-
let _ = SetForegroundWindow(hwnd);
267-
}
268-
}
269-
}
270-
271231
self.focused = true;
272-
Task::none()
232+
operation::focus("query")
273233
}
274234

275235
Message::SearchQueryChanged(input, id) => {
@@ -353,7 +313,7 @@ impl Tile {
353313
#[cfg(target_os = "windows")]
354314
{
355315
// get normal settings and modify position
356-
use crate::utils::open_on_focused_monitor;
316+
use crate::windows::open_on_focused_monitor;
357317
use iced::window::Position::Specific;
358318
let pos = open_on_focused_monitor();
359319
let mut settings = default_settings();
@@ -589,55 +549,3 @@ fn handle_hotkeys() -> impl futures::Stream<Item = Message> {
589549
}
590550
})
591551
}
592-
593-
#[cfg(target_os = "windows")]
594-
fn get_installed_windows_app(path: &Path) -> Vec<App> {
595-
use std::ffi::OsString;
596-
597-
let mut apps = Vec::new();
598-
599-
let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
600-
601-
// where we can find installed applications
602-
// src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848
603-
let registers = [
604-
hkey.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
605-
.unwrap(),
606-
hkey.open_subkey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
607-
.unwrap(),
608-
];
609-
610-
registers.iter().for_each(|reg| {
611-
reg.enum_keys().for_each(|key| {
612-
// https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key
613-
let name = key.unwrap();
614-
let key = reg.open_subkey(&name).unwrap();
615-
let display_name = key.get_value("DisplayName").unwrap_or(OsString::new());
616-
617-
// they might be useful one day ?
618-
// let publisher = key.get_value("Publisher").unwrap_or(OsString::new());
619-
// let version = key.get_value("DisplayVersion").unwrap_or(OsString::new());
620-
621-
// Trick, I saw on internet to point to the exe location..
622-
let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new());
623-
if exe_path.is_empty() {
624-
return;
625-
}
626-
// if there is something, it will be in the form of
627-
// "C:\Program Files\Microsoft Office\Office16\WINWORD.EXE",0
628-
let exe_path = exe_path.to_string_lossy().to_string();
629-
let exe = exe_path.split(",").next().unwrap().to_string();
630-
631-
if !display_name.is_empty() {
632-
apps.push(App {
633-
open_command: Function::OpenApp(exe),
634-
name: display_name.clone().into_string().unwrap(),
635-
name_lc: display_name.clone().into_string().unwrap().to_lowercase(),
636-
icons: None,
637-
})
638-
}
639-
});
640-
});
641-
642-
apps
643-
}

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Config {
1515
pub placeholder: String,
1616
pub search_url: String,
1717
pub shells: Vec<Shelly>,
18+
pub index_dirs: Vec<String>,
1819
}
1920

2021
impl Default for Config {
@@ -27,6 +28,7 @@ impl Default for Config {
2728
placeholder: String::from("Time to be productive!"),
2829
search_url: "https://google.com/search?q=%s".to_string(),
2930
shells: vec![],
31+
index_dirs: vec![],
3032
}
3133
}
3234
}

src/macos.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
#![allow(deprecated)]
22

3+
use crate::app::App;
4+
use crate::commands::Function;
5+
use crate::config::Config;
6+
use crate::utils::{handle_from_icns, log_error, log_error_and_exit};
37
#[cfg(target_os = "macos")]
48
use iced::wgpu::rwh::WindowHandle;
9+
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
10+
use std::fs;
11+
use std::path::{Path, PathBuf};
12+
use std::process::exit;
513

614
#[cfg(target_os = "macos")]
715
pub fn set_activation_policy_accessory() {
@@ -63,6 +71,7 @@ struct ProcessSerialNumber {
6371
/// returns ApplicationServices OSStatus (u32)
6472
///
6573
/// doesn't seem to do anything if you haven't opened a window yet, so wait to call it until after that.
74+
#[cfg(target_os = "macos")]
6675
pub fn transform_process_to_ui_element() -> u32 {
6776
use std::ptr;
6877

@@ -80,3 +89,137 @@ pub fn transform_process_to_ui_element() -> u32 {
8089
)
8190
}
8291
}
92+
93+
pub(crate) fn get_installed_apps(dir: impl AsRef<Path>, store_icons: bool) -> Vec<App> {
94+
let entries: Vec<_> = fs::read_dir(dir.as_ref())
95+
.unwrap_or_else(|x| {
96+
log_error_and_exit(&x.to_string());
97+
exit(-1)
98+
})
99+
.filter_map(|x| x.ok())
100+
.collect();
101+
102+
entries
103+
.into_par_iter()
104+
.filter_map(|x| {
105+
let file_type = x.file_type().unwrap_or_else(|e| {
106+
log_error(&e.to_string());
107+
exit(-1)
108+
});
109+
if !file_type.is_dir() {
110+
return None;
111+
}
112+
113+
let file_name_os = x.file_name();
114+
let file_name = file_name_os.into_string().unwrap_or_else(|e| {
115+
log_error(e.to_str().unwrap_or(""));
116+
exit(-1)
117+
});
118+
if !file_name.ends_with(".app") {
119+
return None;
120+
}
121+
122+
let path = x.path();
123+
let path_str = path.to_str().map(|x| x.to_string()).unwrap_or_else(|| {
124+
log_error("Unable to get file_name");
125+
exit(-1)
126+
});
127+
128+
let icons = if store_icons {
129+
match fs::read_to_string(format!("{}/Contents/Info.plist", path_str)).map(
130+
|content| {
131+
let icon_line = content
132+
.lines()
133+
.scan(false, |expect_next, line| {
134+
if *expect_next {
135+
*expect_next = false;
136+
// Return this line to the iterator
137+
return Some(Some(line));
138+
}
139+
140+
if line.trim() == "<key>CFBundleIconFile</key>" {
141+
*expect_next = true;
142+
}
143+
144+
// For lines that are not the one after the key, return None to skip
145+
Some(None)
146+
})
147+
.flatten() // remove the Nones
148+
.next()
149+
.map(|x| {
150+
x.trim()
151+
.strip_prefix("<string>")
152+
.unwrap_or("")
153+
.strip_suffix("</string>")
154+
.unwrap_or("")
155+
});
156+
157+
handle_from_icns(Path::new(&format!(
158+
"{}/Contents/Resources/{}",
159+
path_str,
160+
icon_line.unwrap_or("AppIcon.icns")
161+
)))
162+
},
163+
) {
164+
Ok(Some(a)) => Some(a),
165+
_ => {
166+
// Fallback method
167+
let direntry = fs::read_dir(format!("{}/Contents/Resources", path_str))
168+
.into_iter()
169+
.flatten()
170+
.filter_map(|x| {
171+
let file = x.ok()?;
172+
let name = file.file_name();
173+
let file_name = name.to_str()?;
174+
if file_name.ends_with(".icns") {
175+
Some(file.path())
176+
} else {
177+
None
178+
}
179+
})
180+
.collect::<Vec<PathBuf>>();
181+
182+
if direntry.len() > 1 {
183+
let icns_vec = direntry
184+
.iter()
185+
.filter(|x| x.ends_with("AppIcon.icns"))
186+
.collect::<Vec<&PathBuf>>();
187+
handle_from_icns(icns_vec.first().unwrap_or(&&PathBuf::new()))
188+
} else if !direntry.is_empty() {
189+
handle_from_icns(direntry.first().unwrap_or(&PathBuf::new()))
190+
} else {
191+
None
192+
}
193+
}
194+
}
195+
} else {
196+
None
197+
};
198+
199+
let name = file_name.strip_suffix(".app").unwrap().to_string();
200+
Some(App {
201+
open_command: Function::OpenApp(path_str),
202+
icons,
203+
name_lc: name.to_lowercase(),
204+
name,
205+
})
206+
})
207+
.collect()
208+
}
209+
210+
pub fn get_installed_macos_apps(config: &Config) -> Vec<App> {
211+
let store_icons = config.theme.show_icons;
212+
let user_local_path = std::env::var("HOME").unwrap() + "/Applications/";
213+
let paths: Vec<String> = vec![
214+
"/Applications/".to_string(),
215+
user_local_path.to_string(),
216+
"/System/Applications/".to_string(),
217+
"/System/Applications/Utilities/".to_string(),
218+
];
219+
220+
paths
221+
.par_iter()
222+
.map(|path| get_installed_apps(path, store_icons))
223+
.flatten()
224+
.collect()
225+
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
mod app;
22
mod commands;
33
mod config;
4-
#[cfg(target_os = "macos")]
54
mod macos;
65
mod utils;
6+
mod windows;
77

88
// import from utils
99
use crate::utils::{create_config_file_if_not_exists, get_config_file_path, read_config_file};

0 commit comments

Comments
 (0)