use winit::raw_window_handle::HasWindowHandle;
#[cfg(target_os = "windows")]
fn handle_to_hwnd(
handle: winit::raw_window_handle::Win32WindowHandle,
) -> windows::Win32::Foundation::HWND {
let hwnd_isize: isize = handle.hwnd.into();
let hwnd = hwnd_isize as *mut core::ffi::c_void;
windows::Win32::Foundation::HWND(hwnd)
}
extern crate e_window;
use e_window::position_grid::PositionGrid;
use e_window::position_grid_manager::PositionGridManager;
use eframe::egui;
use std::io::BufRead;
#[cfg(target_os = "windows")]
use std::process::Command;
#[cfg(target_os = "windows")]
use winapi::shared::windef::HWND;
use std::sync::mpsc::{self, Receiver};
use std::sync::{Arc, Mutex};
use std::thread;
#[cfg(target_os = "windows")]
#[derive(Debug, Default, Clone)]
struct ChromeWindowInfo {
hwnd: Option<u32>,
pid: Option<u32>,
title: Option<String>,
target: Option<String>,
page_url: Option<String>,
}
#[cfg(target_os = "windows")]
pub struct HydraDemoApp {
script_tempfile: Option<tempfile::NamedTempFile>,
chrome_launch_request_tx: Option<std::sync::mpsc::Sender<(i32, i32, i32, i32)>>,
window_info: Arc<Mutex<ChromeWindowInfo>>,
chrome_pid: Option<u32>,
last_pinned_rect: Option<(i32, i32, i32, i32)>,
eframe_hwnd: Option<u32>,
chrome_spawned: Arc<std::sync::atomic::AtomicBool>,
initial_chrome_rect: Option<(i32, i32, i32, i32)>,
fill_grid: PositionGrid,
grid_manager: PositionGridManager,
chrome_output_rx: Option<Receiver<String>>,
pin_request_rx: Option<Receiver<(u32, (i32, i32, i32, i32))>>,
click_request_rx: Option<Receiver<(usize, usize)>>,
}
#[cfg(target_os = "windows")]
impl HydraDemoApp {
pub fn with_hwnd(hwnd: Option<u32>) -> Self {
let dummy_grid = PositionGrid::default();
let window_info = Arc::new(Mutex::new(ChromeWindowInfo::default()));
let (chrome_output_tx, chrome_output_rx) = mpsc::channel();
let (pin_request_tx, pin_request_rx) = mpsc::channel();
let (chrome_launch_request_tx, chrome_launch_request_rx) = mpsc::channel();
let (click_request_tx, click_request_rx) = mpsc::channel();
const INJECT_SCRIPT: &str = r#"
document.getElementById('close-icon').click();
//document.getElementById('modal').remove();
// Always attempt to click to close at the end
setInterval(() => {
const el = document.getElementById('close-icon');
if (el) {
el.click();
// Optionally remove the modal as well:
const modal = document.getElementById('modal');
if (modal) modal.remove();
}
}, 16);
"#;
let script_tempfile = {
use std::io::Write;
use tempfile::NamedTempFile;
let mut file = NamedTempFile::new().expect("Failed to create temp script file");
file.write_all(INJECT_SCRIPT.as_bytes())
.expect("Failed to write script file");
file
};
{
let window_info_clone = window_info.clone();
let chrome_output_tx_clone = chrome_output_tx.clone();
let script_file_path = script_tempfile.path().to_path_buf();
thread::spawn(move || {
let click_request_tx_clone = click_request_tx.clone();
let (x, y, w, h) = chrome_launch_request_rx.recv().unwrap_or((0, 0, 800, 600));
let hydra_url = format!("debugchrome:https://hydra.ojack.xyz/?sketch_id=example&!id=&!openwindow&!x={}&!y={}&!w={}&!h={}", x, y, w, h);
println!("[HydraDemo] Spawning debugchrome with URL: {}", hydra_url);
let chrome = Command::new("debugchrome")
.arg(&hydra_url)
.arg("--script-file")
.arg(script_file_path.display().to_string())
.stdout(std::process::Stdio::piped())
.spawn();
match chrome {
Ok(mut child) => {
let pid = child.id();
window_info_clone.lock().unwrap().pid = Some(pid);
let tx = chrome_output_tx_clone;
let window_info_inner = window_info_clone.clone();
thread::spawn(move || {
let mut info = ChromeWindowInfo::default();
let click_tx = click_request_tx_clone;
if let Some(stdout) = child.stdout.take() {
use std::io::BufReader;
let reader = BufReader::new(stdout);
for line in reader.lines().flatten() {
let _ = tx.send(format!("[debugchrome stdout] {}", line));
if let Some(hwnd_hex) = line.strip_prefix("HWND: 0x") {
if let Ok(hwnd_val) =
u32::from_str_radix(hwnd_hex.trim(), 16)
{
info.hwnd = Some(hwnd_val);
let _ = click_tx.send((15, 10));
}
}
if let Some(pid_str) = line.strip_prefix("PID: ") {
if let Ok(pid_val) = pid_str.trim().parse::<u32>() {
info.pid = Some(pid_val);
}
}
if let Some(title) = line.strip_prefix("TITLE: ") {
info.title = Some(title.trim().to_string());
}
if let Some(target) = line.strip_prefix("TARGET: ") {
info.target = Some(target.trim().to_string());
}
if let Some(page_url) = line.strip_prefix("PAGE_URL: ") {
info.page_url = Some(page_url.trim().to_string());
}
if info.hwnd.is_some() && info.pid.is_some() {
let mut win_lock = window_info_inner.lock().unwrap();
*win_lock = info.clone();
}
}
} else {
let _ = tx.send(
"[HydraDemo] No stdout from debugchrome child process"
.to_string(),
);
}
});
}
Err(e) => {
let _ = chrome_output_tx_clone
.send(format!("[HydraDemo] Failed to launch debugchrome: {}", e));
}
}
});
}
let mut grid_manager = PositionGridManager::new();
if let Some(hwnd) = hwnd {
grid_manager.host_hwnd = Some(hwnd);
}
Self {
script_tempfile: Some(script_tempfile),
window_info,
chrome_pid: None,
last_pinned_rect: None,
eframe_hwnd: hwnd,
chrome_spawned: Arc::new(std::sync::atomic::AtomicBool::new(false)),
initial_chrome_rect: None,
fill_grid: dummy_grid,
grid_manager,
chrome_output_rx: Some(chrome_output_rx),
pin_request_rx: Some(pin_request_rx),
chrome_launch_request_tx: Some(chrome_launch_request_tx),
click_request_rx: Some(click_request_rx),
}
}
}
#[cfg(target_os = "windows")]
impl Default for HydraDemoApp {
fn default() -> Self {
HydraDemoApp::with_hwnd(None)
}
}
#[cfg(target_os = "windows")]
impl Drop for HydraDemoApp {
fn drop(&mut self) {
let hwnd_opt = self.window_info.lock().unwrap().hwnd.clone();
if let Some(hwnd_val) = hwnd_opt {
let hwnd = hwnd_val as HWND;
unsafe {
use winapi::um::winuser::PostMessageW;
use winapi::um::winuser::WM_CLOSE;
let result = PostMessageW(hwnd, WM_CLOSE, 0, 0);
println!(
"[HydraDemo] Sent WM_CLOSE to pinned HWND: 0x{:X}, result={}",
hwnd_val, result
);
}
}
if let Some(pid) = self.chrome_pid.take() {
println!("[HydraDemo] Killing Chrome PID: {}", pid);
let _ = Command::new("taskkill")
.args(&["/PID", &pid.to_string(), "/F"])
.status();
}
}
}
#[cfg(target_os = "windows")]
impl eframe::App for HydraDemoApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.request_repaint();
egui::CentralPanel::default().show(ctx, |ui| {
let label_height = 32.0;
let available = ui.available_size();
let grid_area = egui::vec2(available.x, (available.y - label_height).max(32.0));
let (mut new_grid, _char_size) = PositionGrid::from_text_style(self.eframe_hwnd,ui, egui::TextStyle::Heading, egui::Color32::LIGHT_GREEN, None);
new_grid.rect = ui.allocate_exact_size(grid_area, egui::Sense::hover()).0;
self.fill_grid = new_grid;
{
self.grid_manager.grid = Some(&self.fill_grid as *const PositionGrid);
if let Some(tx) = &self.chrome_launch_request_tx {
if self.chrome_spawned.load(std::sync::atomic::Ordering::SeqCst) == false {
if let Some((grid_x, grid_y, grid_w, grid_h)) = unsafe { self.grid_manager.get_grid_screen_rect() } {
let x = grid_x;
let y = grid_y;
let w = grid_w;
let h = grid_h;
let _ = tx.send((x, y, w, h));
self.chrome_spawned.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
}
if let Some(rx) = &self.click_request_rx {
for (cell_x, cell_y) in rx.try_iter() {
std::thread::sleep(std::time::Duration::from_secs(5));
let result = self.fill_grid.send_mouse_click_to_cell(cell_x, cell_y);
println!(
"[HydraDemo] Mouse click requested at cell ({}, {}) for HWND=0x{:?}, result={:?}",
cell_x, cell_y, self.eframe_hwnd, result
);
unsafe {
use winapi::shared::windef::POINT;
use winapi::um::winuser::GetCursorPos;
let mut pt: POINT = std::mem::zeroed();
if GetCursorPos(&mut pt) != 0 {
println!("[HydraDemo] Mouse pointer is now at: x={}, y={}", pt.x, pt.y);
} else {
println!("[HydraDemo] Failed to get mouse pointer position");
}
}
}
}
}
let grid_rect = unsafe { self.grid_manager.get_grid_screen_rect().unwrap_or((0, 0, 0, 0)) };
let host_rect = unsafe { self.grid_manager.get_host_screen_rect().unwrap_or((0, 0, 0, 0)) };
ui.label(format!(
"Grid: cells={} | grid_rect=({}, {}, {}, {}) | host_rect=({}, {}, {}, {}) | host_hwnd={:?}",
self.fill_grid.cell_count(),
grid_rect.0, grid_rect.1, grid_rect.2, grid_rect.3,
host_rect.0, host_rect.1, host_rect.2, host_rect.3,
self.grid_manager.host_hwnd
));
self.fill_grid.draw(ui);
{
let hwnd_val_opt = self.window_info.lock().unwrap().hwnd.clone();
if let Some(hwnd_val) = hwnd_val_opt {
ui.label(format!("Pinned HWND: 0x{:X}", hwnd_val));
} else {
ui.label("Waiting for Chrome HWND...");
}
if let Some(pid) = self.chrome_pid {
ui.label(format!("Chrome PID: {}", pid));
}
if let Some(rx) = &self.chrome_output_rx {
for msg in rx.try_iter() {
println!("{}", msg);
}
}
if let Some(rx) = &self.pin_request_rx {
for (hwnd, rect) in rx.try_iter() {
ui.label(format!("Pin request: HWND=0x{:X}, rect={:?}", hwnd, rect));
self.window_info.lock().unwrap().hwnd = Some(hwnd);
self.last_pinned_rect = Some(rect);
}
}
if let Some(hwnd_val) = self.window_info.lock().unwrap().hwnd {
let hwnd = hwnd_val as HWND;
if unsafe { PositionGridManager::is_window(hwnd) } {
let _ = unsafe { self.grid_manager.move_and_resize(hwnd) };
} else {
println!("[HydraDemo] Target HWND is invalid or closed. Exiting app.");
std::process::exit(0);
}
}
}
}); } }
fn main() {
#[cfg(target_os = "windows")]
{
use std::process::Command;
use std::sync::{Arc, Mutex};
let window_info = Arc::new(Mutex::new(ChromeWindowInfo::default()));
let window_info_ctrlc = Arc::clone(&window_info);
ctrlc::set_handler(move || {
let win = window_info_ctrlc.lock().unwrap();
if let Some(pid) = win.pid {
println!("[HydraDemo] Ctrl-C pressed, killing Chrome PID: {}", pid);
let _ = Command::new("taskkill")
.args(&["/PID", &pid.to_string(), "/F"])
.status();
}
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");
let options = eframe::NativeOptions::default();
let _ = eframe::run_native(
"e_window_hydra",
options,
Box::new(|cc| {
let mut hwnd_opt = None;
#[cfg(target_os = "windows")]
{
use winit::raw_window_handle::RawWindowHandle;
let raw = cc.window_handle().unwrap().as_raw();
if let RawWindowHandle::Win32(handle) = raw {
let hwnd = handle_to_hwnd(handle);
hwnd_opt = Some(hwnd.0 as u32);
}
}
Ok::<Box<dyn eframe::App>, Box<dyn std::error::Error + Send + Sync>>(Box::new(
HydraDemoApp::with_hwnd(hwnd_opt),
))
}),
);
}
#[cfg(not(target_os = "windows"))]
{
println!("Sorry, e_window_hydra is only supported on Windows targets at this time.");
println!("This example will compile, but does not run on non-Windows platforms.");
}
}