diff --git a/.gitignore b/.gitignore index ea8c4bf..5225d67 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ /target +/.idea/.gitignore +/.idea/ihateborders.iml +/.idea/modules.xml +/.idea/dictionaries/project.xml +/.idea/vcs.xml diff --git a/Cargo.lock b/Cargo.lock index 8de2607..287a9a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -821,13 +821,15 @@ dependencies = [ [[package]] name = "ihateborders" -version = "1.1.1" +version = "1.2.0" dependencies = [ "anyhow", "eframe", "egui", "embed-resource", "image", + "serde", + "serde_json", "windows", ] @@ -854,6 +856,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jni" version = "0.21.1" @@ -1581,6 +1589,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1628,6 +1642,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index aed14d7..77a4adf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ihateborders" -version = "1.1.1" +version = "1.2.0" edition = "2024" [dependencies] @@ -20,6 +20,8 @@ windows = { version = "0.61", features = [ ] } anyhow = { version = "1.0", default-features = false } image = { version = "0.25", default-features = false, features = ["ico", "png"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [build-dependencies] embed-resource = { version = "3.0", default-features = false } diff --git a/src/app.rs b/src/app.rs index ea37c81..f59dca6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,6 @@ use crate::{ + config::Config, + startup::{is_elevated, relaunch_elevated, remove_scheduled_task, task_exists}, ui::{self, IconCacheInterface}, window_manager::{DisplayInfo, WindowManager}, }; @@ -87,34 +89,44 @@ pub struct BorderlessApp { window_manager: WindowManager, selected_window: Option, - last_refresh: std::time::Instant, + last_refresh: Instant, icon_cache: IconCache, resize_to_screen: bool, selected_display: Option, displays: Vec, needs_repaint: bool, refresh_receiver: Option>>, + config: Config, + auto_borderless_enabled: bool, + show_settings: bool, + applied_auto_borderless: std::collections::HashSet, } impl BorderlessApp { - pub fn new(cc: &eframe::CreationContext<'_>) -> Self + pub fn new(cc: &eframe::CreationContext<'_>, open_settings: bool) -> Self { ui::setup_dark_theme(&cc.egui_ctx); let window_manager = WindowManager::new(); let displays = window_manager.get_displays(); + let config = Config::load().unwrap_or_default(); + let mut app = Self { window_manager, selected_window: None, - last_refresh: std::time::Instant::now(), + last_refresh: Instant::now(), icon_cache: IconCache::new(), resize_to_screen: true, selected_display: if !displays.is_empty() { Some(0) } else { None }, displays, needs_repaint: false, refresh_receiver: None, + config, + auto_borderless_enabled: false, + show_settings: open_settings, + applied_auto_borderless: std::collections::HashSet::new(), }; app.start_async_refresh(); @@ -136,7 +148,7 @@ impl BorderlessApp if let Ok(windows) = receiver.try_recv() { if !windows.is_empty() { self.window_manager.set_windows(windows); - self.last_refresh = std::time::Instant::now(); + self.last_refresh = Instant::now(); self.needs_repaint = true; if let Some(selected) = self.selected_window { @@ -144,6 +156,7 @@ impl BorderlessApp self.selected_window = None; } } + self.apply_auto_borderless(); } self.refresh_receiver = None; } @@ -155,6 +168,32 @@ impl BorderlessApp } } + fn apply_auto_borderless(&mut self) + { + let windows = self.window_manager.get_windows().to_vec(); + + for window in windows.iter() { + if self.config.is_auto_borderless(&window.process_name) + && !window.is_borderless + && !self.applied_auto_borderless.contains(&window.hwnd) + { + let selected_display = if self.resize_to_screen { + self.selected_display.and_then(|idx| self.displays.get(idx)) + } else { + None + }; + + if let Ok(_) = self.window_manager.toggle_borderless( + window.hwnd, + self.resize_to_screen, + selected_display, + ) { + self.applied_auto_borderless.insert(window.hwnd); + } + } + } + } + fn handle_keyboard_input(&mut self, ctx: &egui::Context) { if ctx.input(|i| i.key_pressed(egui::Key::F5)) { @@ -164,53 +203,184 @@ impl BorderlessApp } if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.selected_window = None; + if self.show_settings { + self.show_settings = false; + } else { + self.selected_window = None; + } self.needs_repaint = true; } } fn handle_window_action(&mut self, window_index: usize) { - let windows = self.window_manager.get_windows(); - if let Some(window) = windows.get(window_index) { - let selected_display = if self.resize_to_screen { - self.selected_display.and_then(|idx| self.displays.get(idx)) - } else { - None - }; - - if let Err(e) = self.window_manager.toggle_borderless( - window.hwnd, - self.resize_to_screen, - selected_display, - ) { - eprintln!("Failed to toggle borderless for window '{}': {}", window.title, e); - } else { - self.refresh_receiver = None; - self.start_async_refresh(); - self.needs_repaint = true; + let hwnd = self.window_manager.get_windows()[window_index].hwnd; + let selected_display = if self.resize_to_screen { + self.selected_display.and_then(|idx| self.displays.get(idx)) + } else { + None + }; + + if let Err(e) = + self.window_manager.toggle_borderless(hwnd, self.resize_to_screen, selected_display) + { + eprintln!("Failed to toggle borderless for window: {}", e); + } else { + if let Some(window) = self.window_manager.get_window_mut(window_index) { + window.is_borderless = !window.is_borderless; } + self.refresh_receiver = None; + self.start_async_refresh(); + self.needs_repaint = true; + } + } + + fn save_config(&self) + { + if let Err(e) = self.config.save() { + eprintln!("Failed to save config: {}", e); } } + + fn handle_settings_window(&mut self, ctx: &egui::Context) + { + if !self.show_settings { + return; + } + + egui::Window::new("Settings") + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + ui.set_min_width(300.0); + let mut run_on_startup = self.config.run_on_startup; + if ui + .checkbox(&mut run_on_startup, "Run ihateborders when my computer starts") + .changed() + { + if run_on_startup { + if !is_elevated() { + self.config.run_on_startup = true; + self.save_config(); + if let Err(e) = relaunch_elevated(&["--create-startup-task"]) { + eprintln!("Failed to relaunch elevated: {}", e); + self.config.run_on_startup = false; + self.save_config(); + } + } else { + match crate::startup::create_scheduled_task(false) { + Ok(_) => { + self.config.run_on_startup = true; + self.save_config(); + }, + Err(e) => { + eprintln!("Failed to create startup task: {}", e); + }, + } + } + } else { + self.config.run_on_startup = false; + self.config.startup_admin = false; + if task_exists() { + let _ = remove_scheduled_task(); + } + self.save_config(); + } + } + ui.add_enabled_ui(self.config.run_on_startup, |ui| { + let mut startup_admin = self.config.startup_admin; + + if ui + .checkbox( + &mut startup_admin, + "Enable startup with administrator privileges", + ) + .changed() + { + if startup_admin { + if !is_elevated() { + self.config.startup_admin = true; + self.save_config(); + if let Err(e) = relaunch_elevated(&["--install-admin-task"]) { + eprintln!("Failed to relaunch elevated: {}", e); + self.config.startup_admin = false; + self.save_config(); + } + } else { + match crate::startup::create_scheduled_task(true) { + Ok(_) => { + self.config.startup_admin = true; + self.save_config(); + }, + Err(e) => { + eprintln!("Failed to create scheduled task: {}", e); + }, + } + } + } else { + if !is_elevated() { + self.config.startup_admin = false; + self.save_config(); + if let Err(e) = relaunch_elevated(&["--create-startup-task"]) { + eprintln!("Failed to relaunch elevated: {}", e); + } + } else { + if task_exists() { + let _ = remove_scheduled_task(); + } + + match crate::startup::create_scheduled_task(false) { + Ok(_) => { + self.config.startup_admin = false; + self.save_config(); + }, + Err(e) => { + eprintln!("Failed to recreate startup task: {}", e); + }, + } + } + } + } + }); + + ui.add_space(10.0); + + if ui.button("Close").clicked() { + self.show_settings = false; + } + }); + } } impl eframe::App for BorderlessApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if let Some(pos) = ctx.input(|i| i.viewport().outer_rect).map(|r| r.min) { + let should_update = self.last_refresh.elapsed().as_secs() >= 1; + + if should_update + && self.config.window_position.as_ref().map_or(true, |saved_pos| { + (saved_pos.x - pos.x).abs() > 1.0 || (saved_pos.y - pos.y).abs() > 1.0 + }) + { + self.config.window_position = + Some(crate::config::WindowPosition { x: pos.x, y: pos.y }); + } + } + self.handle_refresh(); self.handle_keyboard_input(ctx); self.icon_cache.cleanup_expired(); egui::CentralPanel::default().show(ctx, |ui| { - let windows = self.window_manager.get_windows(); - - ui::render_header(ui, windows.len()); + ui::render_header(ui, self.window_manager.get_windows().len()); ui::render_window_selector( ui, - windows, + self.window_manager.get_windows(), &mut self.selected_window, &mut self.icon_cache, ); @@ -221,12 +391,112 @@ impl eframe::App for BorderlessApp ui::render_display_selector(ui, &self.displays, &mut self.selected_display); } - if let Some(window_index) = ui::render_action_button(ui, windows, self.selected_window) - { + let mut auto_changed = false; + let checkbox_enabled = self.selected_window.is_some(); + + ui::render_auto_borderless_checkbox( + ui, + &mut self.auto_borderless_enabled, + &mut auto_changed, + checkbox_enabled, + ); + + if auto_changed { + if let Some(index) = self.selected_window { + let windows = self.window_manager.get_windows(); + if let Some(window) = windows.get(index) { + let process_name = window.process_name.clone(); + let hwnd = window.hwnd; + let is_borderless = window.is_borderless; + + if self.auto_borderless_enabled { + self.config.add_auto_borderless(process_name); + if !is_borderless { + let selected_display = if self.resize_to_screen { + self.selected_display.and_then(|idx| self.displays.get(idx)) + } else { + None + }; + + if self + .window_manager + .toggle_borderless( + hwnd, + self.resize_to_screen, + selected_display, + ) + .is_ok() + { + if let Some(w) = self.window_manager.get_window_mut(index) { + w.is_borderless = true; + } + self.applied_auto_borderless.insert(hwnd); + self.needs_repaint = true; + } + } + } else { + self.config.remove_auto_borderless(&process_name); + if is_borderless { + let selected_display = if self.resize_to_screen { + self.selected_display.and_then(|idx| self.displays.get(idx)) + } else { + None + }; + + if self + .window_manager + .toggle_borderless( + hwnd, + self.resize_to_screen, + selected_display, + ) + .is_ok() + { + if let Some(w) = self.window_manager.get_window_mut(index) { + w.is_borderless = false; + } + self.applied_auto_borderless.remove(&hwnd); + self.needs_repaint = true; + } + } + } + self.save_config(); + } + } + } + + if let Some(index) = self.selected_window { + let windows = self.window_manager.get_windows(); + if let Some(window) = windows.get(index) { + self.auto_borderless_enabled = + self.config.is_auto_borderless(&window.process_name); + } + } + + let action_button_enabled = + self.selected_window.is_some() && !self.auto_borderless_enabled; + + let clicked_index = ui::render_action_button( + ui, + self.window_manager.get_windows(), + self.selected_window, + action_button_enabled, + ); + + if let Some(window_index) = clicked_index { self.handle_window_action(window_index); } + + ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { + ui.add_space(5.0); + if ui.button("⚙ Settings").clicked() { + self.show_settings = !self.show_settings; + } + }); }); + self.handle_settings_window(ctx); + if self.needs_repaint { self.needs_repaint = false; ctx.request_repaint_after(Duration::from_millis(16)); @@ -234,20 +504,28 @@ impl eframe::App for BorderlessApp ctx.request_repaint_after(Duration::from_secs(5)); } } + + fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) + { + let _ = self.config.save(); + } } pub fn create_app_options() -> eframe::NativeOptions { let icon_data = load_icon(); + let config = Config::load().unwrap_or_default(); + let initial_position = config.window_position.map(|pos| egui::Pos2::new(pos.x, pos.y)); eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("ihateborders") - .with_inner_size([350.0, 320.0]) - .with_min_inner_size([350.0, 320.0]) - .with_max_inner_size([350.0, 320.0]) + .with_inner_size([350.0, 360.0]) + .with_min_inner_size([350.0, 360.0]) + .with_max_inner_size([350.0, 360.0]) .with_resizable(false) .with_maximize_button(false) + .with_position(initial_position.unwrap_or(egui::Pos2::new(100.0, 100.0))) .with_icon(icon_data), ..Default::default() } @@ -262,5 +540,5 @@ fn load_icon() -> egui::IconData let (width, height) = image.dimensions(); let rgba = image.into_raw(); - egui::IconData { rgba, width: width as u32, height: height as u32 } + egui::IconData { rgba, width, height } } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..09d8686 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,96 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::{fs, path::PathBuf}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WindowPosition +{ + pub x: f32, + pub y: f32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config +{ + #[serde(default)] + pub auto_borderless_apps: Vec, + + #[serde(default)] + pub run_on_startup: bool, + + #[serde(default)] + pub startup_admin: bool, + + #[serde(default)] + pub window_position: Option, +} + +impl Default for Config +{ + fn default() -> Self + { + Self { + auto_borderless_apps: Vec::new(), + run_on_startup: false, + startup_admin: false, + window_position: None, + } + } +} + +impl Config +{ + pub fn load() -> Result + { + let config_path = Self::get_config_path()?; + + if !config_path.exists() { + let config = Self::default(); + config.save()?; + return Ok(config); + } + + let content = fs::read_to_string(&config_path)?; + let config: Self = serde_json::from_str(&content)?; + Ok(config) + } + + pub fn save(&self) -> Result<()> + { + let config_path = Self::get_config_path()?; + + if let Some(parent) = config_path.parent() { + fs::create_dir_all(parent)?; + } + + let content = serde_json::to_string_pretty(self)?; + fs::write(&config_path, content)?; + Ok(()) + } + + pub fn get_config_path() -> Result + { + let exe_path = std::env::current_exe()?; + let exe_dir = exe_path + .parent() + .ok_or_else(|| anyhow::anyhow!("Failed to get executable directory"))?; + Ok(exe_dir.join("ihateborders_config.json")) + } + + pub fn add_auto_borderless(&mut self, process_name: String) + { + if !self.auto_borderless_apps.contains(&process_name) { + self.auto_borderless_apps.push(process_name); + } + } + + pub fn remove_auto_borderless(&mut self, process_name: &str) + { + self.auto_borderless_apps.retain(|app| app != process_name); + } + + pub fn is_auto_borderless(&self, process_name: &str) -> bool + { + self.auto_borderless_apps.contains(&process_name.to_string()) + } +} diff --git a/src/main.rs b/src/main.rs index 9565ffa..78b8477 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,58 @@ #![windows_subsystem = "windows"] mod app; +mod config; +mod startup; mod ui; mod window_manager; use app::{BorderlessApp, create_app_options}; +use startup::{create_scheduled_task, is_elevated}; + +fn handle_elevated_task_creation(with_admin: bool) -> ! +{ + if !is_elevated() { + eprintln!("Error: Task creation requires administrator privileges"); + std::process::exit(1); + } + + match create_scheduled_task(with_admin) { + Ok(_) => { + let exe_path = std::env::current_exe().expect("Failed to get exe path"); + std::process::Command::new(exe_path) + .arg("--open-settings") + .spawn() + .expect("Failed to relaunch app"); + std::process::exit(0); + }, + Err(e) => { + eprintln!("Failed to create scheduled task: {}", e); + std::process::exit(1); + }, + } +} fn main() -> Result<(), eframe::Error> { + let args: Vec = std::env::args().collect(); + + let mut open_settings = false; + + if args.len() > 1 && args[1] == "--install-admin-task" { + handle_elevated_task_creation(true); + } + + if args.len() > 1 && args[1] == "--create-startup-task" { + handle_elevated_task_creation(false); + } + + if args.len() > 1 && args[1] == "--open-settings" { + open_settings = true; + } + eframe::run_native( "ihateborders", create_app_options(), - Box::new(|cc| Ok(Box::new(BorderlessApp::new(cc)))), + Box::new(move |cc| Ok(Box::new(BorderlessApp::new(cc, open_settings)))), ) } diff --git a/src/startup.rs b/src/startup.rs new file mode 100644 index 0000000..0a57945 --- /dev/null +++ b/src/startup.rs @@ -0,0 +1,98 @@ +use anyhow::Result; +use std::{os::windows::process::CommandExt, process::Command}; +use windows::Win32::{ + Foundation::HWND, + UI::{ + Shell::{IsUserAnAdmin, ShellExecuteW}, + WindowsAndMessaging::SW_SHOWNORMAL, + }, +}; + +const CREATE_NO_WINDOW: u32 = 0x08000000; + +pub fn is_elevated() -> bool +{ + unsafe { IsUserAnAdmin().as_bool() } +} + +pub fn relaunch_elevated(args: &[&str]) -> Result<()> +{ + let exe_path = std::env::current_exe()?; + let exe_path_str = exe_path.to_string_lossy(); + + let args_string = args.join(" "); + + unsafe { + let operation = windows::core::w!("runas"); + let file = windows::core::HSTRING::from(exe_path_str.as_ref()); + let parameters = windows::core::HSTRING::from(&args_string); + + let result = ShellExecuteW( + Some(HWND::default()), + operation, + &file, + ¶meters, + None, + SW_SHOWNORMAL, + ); + + if result.0 as i32 <= 32 { + anyhow::bail!("Failed to relaunch with elevation"); + } + } + + std::process::exit(0); +} + +pub fn create_scheduled_task(with_admin: bool) -> Result<()> +{ + let exe_path = std::env::current_exe()?; + let exe_path_str = exe_path.to_string_lossy(); + let tr_arg = format!("\"{}\"", exe_path_str); + + let mut args = + vec!["/Create", "/TN", "ihateborders_startup", "/TR", &tr_arg, "/SC", "ONLOGON", "/F"]; + let rl_highest = "HIGHEST"; + if with_admin { + args.push("/RL"); + args.push(rl_highest); + } + let output = Command::new("schtasks").args(&args).creation_flags(CREATE_NO_WINDOW).output()?; + + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("Failed to create scheduled task: {}", error); + } + + Ok(()) +} + +pub fn remove_scheduled_task() -> Result<()> +{ + let output = Command::new("schtasks") + .args(&["/Delete", "/TN", "ihateborders_startup", "/F"]) + .creation_flags(CREATE_NO_WINDOW) + .output()?; + + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + if !error.contains("cannot find the file") { + anyhow::bail!("Failed to remove scheduled task: {}", error); + } + } + + Ok(()) +} + +pub fn task_exists() -> bool +{ + let output = Command::new("schtasks") + .args(&["/Query", "/TN", "ihateborders_startup"]) + .creation_flags(CREATE_NO_WINDOW) + .output(); + + match output { + Ok(output) => output.status.success(), + Err(_) => false, + } +} diff --git a/src/ui.rs b/src/ui.rs index 9614914..9086744 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -224,6 +224,33 @@ pub fn render_position_checkbox(ui: &mut egui::Ui, resize_to_screen: &mut bool) }); } +pub fn render_auto_borderless_checkbox( + ui: &mut egui::Ui, + auto_borderless: &mut bool, + changed: &mut bool, + enabled: bool, +) +{ + ui.add_space(5.0); + + ui.horizontal(|ui| { + ui.add_space(5.0); + + ui.add_enabled_ui(enabled, |ui| { + let response = ui.checkbox(auto_borderless, ""); + if response.changed() { + *changed = true; + } + + ui.label( + RichText::new("Automatically make borderless") + .font(FontId::proportional(12.0)) + .color(if enabled { Color32::from_gray(180) } else { Color32::from_gray(120) }), + ); + }); + }); +} + pub fn render_display_selector( ui: &mut egui::Ui, displays: &[DisplayInfo], @@ -276,13 +303,13 @@ pub fn render_action_button( ui: &mut egui::Ui, windows: &[WindowInfo], selected_window: Option, + enabled: bool, ) -> Option { ui.add_space(15.0); let mut clicked_window = None; - let button_enabled = selected_window.is_some(); let button_text = if let Some(index) = selected_window { if let Some(window) = windows.get(index) { if window.is_borderless { "Restore Borders" } else { "Make Borderless" } @@ -294,15 +321,17 @@ pub fn render_action_button( }; ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.add_enabled_ui(button_enabled, |ui| { + ui.add_enabled_ui(enabled, |ui| { let button = egui::Button::new( - RichText::new(button_text).font(FontId::proportional(14.0)).color( - if button_enabled { Color32::from_gray(255) } else { Color32::from_gray(120) }, - ), + RichText::new(button_text).font(FontId::proportional(14.0)).color(if enabled { + Color32::from_gray(255) + } else { + Color32::from_gray(120) + }), ) .min_size(egui::vec2(180.0, 35.0)); - if ui.add(button).clicked() && button_enabled { + if ui.add(button).clicked() && enabled { clicked_window = selected_window; } }); diff --git a/src/window_manager.rs b/src/window_manager.rs index 14a4c63..68eefe4 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -13,9 +13,9 @@ use windows::Win32::{ UI::WindowsAndMessaging::{ DrawIconEx, EnumWindows, GCLP_HICON, GWL_STYLE, GetClassLongPtrW, GetSystemMetrics, GetWindowLongW, GetWindowTextW, GetWindowThreadProcessId, HWND_TOP, ICON_SMALL, - IsWindowVisible, SM_CXSCREEN, SM_CYSCREEN, SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, - SWP_NOZORDER, SendMessageW, SetWindowLongW, SetWindowPos, WM_GETICON, WS_BORDER, - WS_CAPTION, WS_DLGFRAME, WS_THICKFRAME, + IsWindowVisible, SM_CXSCREEN, SM_CYSCREEN, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, + SWP_NOSIZE, SWP_NOZORDER, SendMessageW, SetWindowLongW, SetWindowPos, WM_GETICON, + WS_BORDER, WS_CAPTION, WS_DLGFRAME, WS_THICKFRAME, }, }; @@ -197,7 +197,7 @@ impl WindowManager y, width, height, - SWP_FRAMECHANGED | SWP_NOZORDER, + SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE, )?; } else { SetWindowPos( @@ -207,7 +207,7 @@ impl WindowManager 0, 0, 0, - SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE, )?; } } @@ -215,6 +215,11 @@ impl WindowManager Ok(()) } + pub fn get_window_mut(&mut self, index: usize) -> Option<&mut WindowInfo> + { + self.windows.get_mut(index) + } + pub fn is_refresh_in_progress(&self) -> bool { *self.refresh_in_progress.lock().unwrap()