use std::collections::HashMap;
use std::thread::sleep;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use windows::core::Result;
use windows::core::PWSTR;
use windows::Win32::Foundation::{HWND, LPARAM, POINT, WPARAM};
use windows::Win32::UI::Controls::{
LVIF_TEXT, LVITEMW, LVM_GETITEMCOUNT, LVM_GETITEMPOSITION, LVM_GETITEMTEXTW,
};
const LVM_SETITEMPOSITION32: u32 = 0x1000 + 49;
use windows::Win32::UI::WindowsAndMessaging::SendMessageW;
use crate::remote_memory::{ProcessHandle, RemoteBuffer};
use crate::shell_locator;
use crate::types::{DesktopProfile, IconEntry, Resolution};
const TEXT_BUF_CHARS: usize = 512;
pub fn save_layout(profile_name: &str, resolution: Resolution) -> Result<DesktopProfile> {
let listview = shell_locator::find_desktop_listview()?;
let process = shell_locator::open_explorer_process(listview)?;
let count = unsafe { SendMessageW(listview, LVM_GETITEMCOUNT, None, LPARAM(0)).0 } as usize;
let point_buf = RemoteBuffer::new(&process, std::mem::size_of::<POINT>())?;
let item_buf = RemoteBuffer::new(&process, std::mem::size_of::<LVITEMW>())?;
let text_buf = RemoteBuffer::new(&process, TEXT_BUF_CHARS * 2)?;
let mut icons = Vec::with_capacity(count);
for index in 0..count {
let (name, point) = read_item(listview, &process, &point_buf, &item_buf, &text_buf, index)?;
icons.push(IconEntry {
name,
x: point.x,
y: point.y,
});
}
let timestamp_utc = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or_default();
Ok(DesktopProfile {
profile_name: profile_name.to_string(),
resolution,
timestamp_utc,
icons,
resolutions: HashMap::new(),
})
}
const RESTORE_ATTEMPTS: usize = 10;
const RETRY_DELAY: Duration = Duration::from_millis(750);
const DESKTOP_WAIT_TIMEOUT: Duration = Duration::from_secs(60);
const DESKTOP_POLL_INTERVAL: Duration = Duration::from_millis(500);
pub fn restore_layout(profile: &DesktopProfile) -> Result<usize> {
let listview = wait_for_desktop()?;
let process = shell_locator::open_explorer_process(listview)?;
let count = unsafe { SendMessageW(listview, LVM_GETITEMCOUNT, None, LPARAM(0)).0 } as usize;
let point_buf = RemoteBuffer::new(&process, std::mem::size_of::<POINT>())?;
let item_buf = RemoteBuffer::new(&process, std::mem::size_of::<LVITEMW>())?;
let text_buf = RemoteBuffer::new(&process, TEXT_BUF_CHARS * 2)?;
let mut name_to_index: HashMap<String, usize> = HashMap::with_capacity(count);
for index in 0..count {
let (name, _) = read_item(listview, &process, &point_buf, &item_buf, &text_buf, index)?;
name_to_index.insert(name, index);
}
let current = shell_locator::current_resolution();
let res_key = format!("{}x{}", current.width, current.height);
let icons = profile.resolutions.get(&res_key).unwrap_or(&profile.icons);
let mut moved = 0usize;
for attempt in 0..RESTORE_ATTEMPTS {
if attempt > 0 {
sleep(RETRY_DELAY);
}
let mut divergent = 0usize;
for icon in icons {
if let Some(&index) = name_to_index.get(&icon.name) {
if attempt > 0 {
let (_, current_pos) =
read_item(listview, &process, &point_buf, &item_buf, &text_buf, index)?;
if current_pos.x == icon.x && current_pos.y == icon.y {
continue;
}
}
let point = POINT { x: icon.x, y: icon.y };
point_buf.write(&point)?;
unsafe {
SendMessageW(
listview,
LVM_SETITEMPOSITION32,
WPARAM(index),
LPARAM(point_buf.as_ptr() as isize),
);
}
divergent += 1;
}
}
shell_locator::refresh(listview);
moved = moved.max(divergent);
if divergent == 0 {
break;
}
}
Ok(moved)
}
fn wait_for_desktop() -> Result<HWND> {
let start = Instant::now();
loop {
if let Ok(listview) = shell_locator::find_desktop_listview() {
let count =
unsafe { SendMessageW(listview, LVM_GETITEMCOUNT, None, LPARAM(0)).0 } as usize;
if count > 0 {
return Ok(listview);
}
}
if start.elapsed() >= DESKTOP_WAIT_TIMEOUT {
return shell_locator::find_desktop_listview();
}
sleep(DESKTOP_POLL_INTERVAL);
}
}
fn read_item(
listview: HWND,
_process: &ProcessHandle,
point_buf: &RemoteBuffer,
item_buf: &RemoteBuffer,
text_buf: &RemoteBuffer,
index: usize,
) -> Result<(String, POINT)> {
unsafe {
SendMessageW(
listview,
LVM_GETITEMPOSITION,
WPARAM(index),
LPARAM(point_buf.as_ptr() as isize),
);
}
let point: POINT = point_buf.read()?;
let item = LVITEMW {
mask: LVIF_TEXT,
iItem: index as i32,
pszText: PWSTR(text_buf.as_ptr() as *mut u16),
cchTextMax: TEXT_BUF_CHARS as i32,
..Default::default()
};
item_buf.write(&item)?;
unsafe {
SendMessageW(
listview,
LVM_GETITEMTEXTW,
WPARAM(index),
LPARAM(item_buf.as_ptr() as isize),
);
}
let name = text_buf.read_wide_string()?;
Ok((name, point))
}