Skip to content

Commit 58cbb2d

Browse files
authored
Merge pull request #32 from Nazeofel/master
Start adding windows support
2 parents a6dd02c + 6a06803 commit 58cbb2d

6 files changed

Lines changed: 344 additions & 75 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ name = "rustcast"
33
version = "0.1.0"
44
edition = "2024"
55

6+
7+
[target.'cfg(target_os = "windows")'.dependencies]
8+
winreg = "0.52"
9+
windows = { version = "0.58", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi"] }
10+
11+
[target.'cfg(target_os = "macos")'.dependencies]
12+
objc2 = "0.6.3"
13+
objc2-app-kit = "0.3.2"
14+
objc2-application-services = { version = "0.3.2", default-features = false, features = [
15+
"HIServices",
16+
"Processes",
17+
] }
18+
objc2-foundation = { version = "0.3.2", features = ["NSString"] }
19+
620
[dependencies]
721
anyhow = "1.0.100"
822
applications = "0.3.1"
@@ -11,13 +25,6 @@ global-hotkey = "0.7.0"
1125
iced = { version = "0.14.0", features = ["image", "smol", "tokio"] }
1226
icns = "0.3.1"
1327
image = "0.25.9"
14-
objc2 = "0.6.3"
15-
objc2-app-kit = "0.3.2"
16-
objc2-application-services = { version = "0.3.2", default-features = false, features = [
17-
"HIServices",
18-
"Processes",
19-
] }
20-
objc2-foundation = { version = "0.3.2", features = ["NSString"] }
2128
rand = "0.9.2"
2229
rayon = "1.11.0"
2330
serde = { version = "1.0.228", features = ["derive"] }

src/app.rs

Lines changed: 176 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use crate::commands::Function;
22
use crate::config::Config;
3-
use crate::macos::{focus_this_app, transform_process_to_ui_element};
4-
use crate::{macos, utils::get_installed_apps};
5-
3+
use crate::utils::get_config_file_path;
64
use global_hotkey::{GlobalHotKeyEvent, HotKeyState};
75
use iced::futures::SinkExt;
86
use iced::{
@@ -17,9 +15,22 @@ use iced::{
1715
},
1816
window::{self, Id, Settings},
1917
};
18+
use std::path::Path;
2019

20+
#[cfg(target_os = "macos")]
21+
use crate::macos::{focus_this_app, transform_process_to_ui_element};
22+
#[cfg(target_os = "macos")]
23+
use crate::{macos, utils::get_installed_apps};
24+
#[cfg(target_os = "macos")]
2125
use objc2::rc::Retained;
26+
#[cfg(target_os = "macos")]
2227
use objc2_app_kit::NSRunningApplication;
28+
29+
#[cfg(target_os = "windows")]
30+
use windows::Win32::Foundation::HWND;
31+
#[cfg(target_os = "windows")]
32+
use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, SetForegroundWindow};
33+
2334
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
2435
use rayon::slice::ParallelSliceMut;
2536

@@ -135,6 +146,9 @@ pub fn default_settings() -> Settings {
135146
}
136147
}
137148

149+
#[derive(Debug, Clone)]
150+
pub struct Temp {}
151+
138152
#[derive(Debug, Clone)]
139153
pub struct Tile {
140154
theme: iced::Theme,
@@ -145,38 +159,72 @@ pub struct Tile {
145159
options: Vec<App>,
146160
visible: bool,
147161
focused: bool,
162+
#[cfg(target_os = "macos")]
148163
frontmost: Option<Retained<NSRunningApplication>>,
164+
#[cfg(target_os = "windows")]
165+
frontmost: Option<HWND>,
149166
config: Config,
150167
open_hotkey_id: u32,
151168
}
152169

153170
impl Tile {
154171
/// A base window
155172
pub fn new(keybind_id: u32, config: &Config) -> (Self, Task<Message>) {
156-
let (id, open) = window::open(default_settings());
173+
let mut settings = default_settings();
174+
#[cfg(target_os = "windows")]
175+
{
176+
// get normal settings and modify position
177+
use crate::utils::open_on_focused_monitor;
178+
use iced::window::Position::Specific;
179+
let pos = open_on_focused_monitor();
180+
settings.position = Specific(pos);
181+
}
157182

158-
let open = open.discard().chain(window::run(id, |handle| {
159-
macos::macos_window_config(
160-
&handle.window_handle().expect("Unable to get window handle"),
161-
);
162-
// should work now that we have a window
163-
transform_process_to_ui_element();
164-
}));
183+
let (id, open) = window::open(settings);
165184

166-
let store_icons = config.theme.show_icons;
167-
168-
let user_local_path = std::env::var("HOME").unwrap() + "/Applications/";
185+
#[cfg(target_os = "macos")]
186+
{
187+
let open = open.discard().chain(window::run(id, |handle| {
188+
{
189+
macos::macos_window_config(
190+
&handle.window_handle().expect("Unable to get window handle"),
191+
);
192+
// should work now that we have a window
193+
transform_process_to_ui_element();
194+
}
195+
}));
196+
}
169197

170-
let paths = vec![
171-
"/Applications/",
172-
user_local_path.as_str(),
173-
"/System/Applications/",
174-
"/System/Applications/Utilities/",
175-
];
198+
let store_icons = config.theme.show_icons;
199+
let paths;
200+
201+
#[cfg(target_os = "macos")]
202+
{
203+
let 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+
}
176215

177216
let mut options: Vec<App> = paths
178217
.par_iter()
179-
.map(|path| get_installed_apps(path, store_icons))
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+
})
180228
.flatten()
181229
.collect();
182230

@@ -206,7 +254,11 @@ impl Tile {
206254
match message {
207255
Message::OpenWindow => {
208256
self.capture_frontmost();
209-
focus_this_app();
257+
#[cfg(target_os = "macos")]
258+
{
259+
focus_this_app();
260+
}
261+
210262
self.focused = true;
211263
Task::none()
212264
}
@@ -272,16 +324,13 @@ impl Tile {
272324
Message::ClearSearchQuery => {
273325
self.query_lc = String::new();
274326
self.query = String::new();
327+
self.prev_query_lc = String::new();
275328
Task::none()
276329
}
277330

278331
Message::ReloadConfig => {
279332
self.config = toml::from_str(
280-
&fs::read_to_string(
281-
std::env::var("HOME").unwrap_or("".to_owned())
282-
+ "/.config/rustcast/config.toml",
283-
)
284-
.unwrap_or("".to_owned()),
333+
&fs::read_to_string(get_config_file_path()).unwrap_or("".to_owned()),
285334
)
286335
.unwrap();
287336

@@ -292,12 +341,29 @@ impl Tile {
292341
if hk_id == self.open_hotkey_id {
293342
self.visible = !self.visible;
294343
if self.visible {
295-
Task::chain(
296-
window::open(default_settings())
297-
.1
298-
.map(|_| Message::OpenWindow),
299-
operation::focus("query"),
300-
)
344+
#[cfg(target_os = "windows")]
345+
{
346+
// get normal settings and modify position
347+
use crate::utils::open_on_focused_monitor;
348+
use iced::window::Position::Specific;
349+
let pos = open_on_focused_monitor();
350+
let mut settings = default_settings();
351+
settings.position = Specific(pos);
352+
Task::chain(
353+
window::open(settings).1.map(|_| Message::OpenWindow),
354+
operation::focus("query"),
355+
)
356+
}
357+
358+
#[cfg(target_os = "macos")]
359+
{
360+
Task::chain(
361+
window::open(default_settings())
362+
.1
363+
.map(|_| Message::OpenWindow),
364+
operation::focus("query"),
365+
)
366+
}
301367
} else {
302368
let to_close = window::latest().map(|x| x.unwrap());
303369
Task::batch([
@@ -445,18 +511,38 @@ impl Tile {
445511
}
446512

447513
pub fn capture_frontmost(&mut self) {
448-
use objc2_app_kit::NSWorkspace;
514+
#[cfg(target_os = "macos")]
515+
{
516+
use objc2_app_kit::NSWorkspace;
517+
518+
let ws = NSWorkspace::sharedWorkspace();
519+
self.frontmost = ws.frontmostApplication();
520+
};
449521

450-
let ws = NSWorkspace::sharedWorkspace();
451-
self.frontmost = ws.frontmostApplication();
522+
#[cfg(target_os = "windows")]
523+
{
524+
self.frontmost = Some(unsafe { GetForegroundWindow() });
525+
}
452526
}
453527

454528
#[allow(deprecated)]
455529
pub fn restore_frontmost(&mut self) {
456-
use objc2_app_kit::NSApplicationActivationOptions;
530+
#[cfg(target_os = "macos")]
531+
{
532+
use objc2_app_kit::NSApplicationActivationOptions;
533+
534+
if let Some(app) = self.frontmost.take() {
535+
app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps);
536+
}
537+
}
457538

458-
if let Some(app) = self.frontmost.take() {
459-
app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps);
539+
#[cfg(target_os = "windows")]
540+
{
541+
if let Some(handle) = self.frontmost {
542+
unsafe {
543+
let _ = SetForegroundWindow(handle);
544+
}
545+
}
460546
}
461547
}
462548
}
@@ -494,3 +580,54 @@ fn handle_hotkeys() -> impl futures::Stream<Item = Message> {
494580
}
495581
})
496582
}
583+
584+
fn get_installed_windows_app(path: &Path) -> Vec<App> {
585+
use std::ffi::OsString;
586+
587+
let mut apps = Vec::new();
588+
589+
let hkey = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
590+
591+
// where we can find installed applications
592+
// src: https://stackoverflow.com/questions/2864984/how-to-programatically-get-the-list-of-installed-programs/2892848#2892848
593+
let registers = [
594+
hkey.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
595+
.unwrap(),
596+
hkey.open_subkey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
597+
.unwrap(),
598+
];
599+
600+
registers.iter().for_each(|reg| {
601+
reg.enum_keys().for_each(|key| {
602+
// https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key
603+
let name = key.unwrap();
604+
let key = reg.open_subkey(&name).unwrap();
605+
let display_name = key.get_value("DisplayName").unwrap_or(OsString::new());
606+
607+
// they might be useful one day ?
608+
// let publisher = key.get_value("Publisher").unwrap_or(OsString::new());
609+
// let version = key.get_value("DisplayVersion").unwrap_or(OsString::new());
610+
611+
// Trick, I saw on internet to point to the exe location..
612+
let exe_path = key.get_value("DisplayIcon").unwrap_or(OsString::new());
613+
if exe_path.is_empty() {
614+
return;
615+
}
616+
// if there is something, it will be in the form of
617+
// "C:\Program Files\Microsoft Office\Office16\WINWORD.EXE",0
618+
let exe_path = exe_path.to_string_lossy().to_string();
619+
let exe = exe_path.split(",").next().unwrap().to_string();
620+
621+
if !display_name.is_empty() {
622+
apps.push(App {
623+
open_command: Function::OpenApp(exe),
624+
name: display_name.clone().into_string().unwrap(),
625+
name_lc: display_name.clone().into_string().unwrap().to_lowercase(),
626+
icons: None,
627+
})
628+
}
629+
});
630+
});
631+
632+
apps
633+
}

0 commit comments

Comments
 (0)