diff --git a/Cargo.lock b/Cargo.lock index 8286120..e6c7f83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "check_elevation" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f7310b71e7b968cdadd13480b9e4f2def9f173f67fd7317e8eddb8d7a4ba00" +dependencies = [ + "windows 0.51.1", +] + [[package]] name = "cocoa" version = "0.25.0" @@ -197,8 +206,10 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" name = "cute-borders" version = "1.1.0" dependencies = [ + "check_elevation", "lazy_static", "open", + "planif", "serde", "serde_yaml", "tray-icon", @@ -849,6 +860,15 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "planif" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7502ee5af341c06aa00593d655d09e4b7126f0e886b80745c218d6dc674e8b21" +dependencies = [ + "windows 0.48.0", +] + [[package]] name = "png" version = "0.17.13" @@ -1210,6 +1230,34 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 0337b97..4cc179e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,10 @@ version = "1.1.0" edition = "2021" [dependencies] +check_elevation = "0.2.4" lazy_static = "1.4.0" open = "5.1.4" +planif = "1.0.0" serde = "1.0.203" serde_yaml = "0.9.34+deprecated" tray-icon = "0.14.3" diff --git a/README.md b/README.md index 1f5a898..d809f74 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,11 @@ Windows 11 only. ## Installing -Download it from [GitHub Releases](https://github.com/keifufu/cute-borders/releases/latest) +- Download `cute-borders.exe` from [GitHub Releases](https://github.com/keifufu/cute-borders/releases/latest) +- Start the executable +- Select "install" in the tray menu + +You can then delete the downloaded file ## Configuration @@ -37,8 +41,3 @@ window_rules: active_border_color: "#c6a0f6" inactive_border_color: "#ffffff" ``` - -# TODO - -- square window option -- draw thick borders with direct2d (could be used as a fallback for 1px borders on windows 10) diff --git a/src/config.rs b/src/config.rs index a032f8a..5ab1b43 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,6 @@ use std::{io::Read, sync::Mutex}; -use crate::{ - logger::Logger, - util::{disable_startup, enable_startup, get_file}, -}; +use crate::{logger::Logger, util::get_file}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -31,10 +28,11 @@ pub struct WindowRule { pub inactive_border_color: String, } +// Some are Options because i cant be bothered handling config upgrades +// if they are not defined we just use the default #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Config { - pub run_at_startup: bool, - pub hide_tray_icon: Option, // option because i cant be bothered handling config upgrades + pub hide_tray_icon: Option, pub window_rules: Vec, } @@ -59,12 +57,6 @@ impl Config { } }; - if config.run_at_startup { - enable_startup(); - } else { - disable_startup(); - } - config } pub fn reload() { diff --git a/src/data/config.yaml b/src/data/config.yaml index b664d54..b28c09a 100644 --- a/src/data/config.yaml +++ b/src/data/config.yaml @@ -1,4 +1,3 @@ -run_at_startup: false hide_tray_icon: false window_rules: - match: "Global" diff --git a/src/main.rs b/src/main.rs index 0d9a74b..3396944 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ #![windows_subsystem = "windows"] #![allow(unused_assignments)] +use check_elevation::is_elevated; use config::Config; use config::RuleMatch; use logger::Logger; -use util::get_file_path; use std::ffi::c_ulong; +use std::ffi::OsStr; use std::ffi::OsString; +use std::os::windows::ffi::OsStrExt; use std::os::windows::prelude::OsStringExt; use tray_icon::menu::Menu; use tray_icon::menu::MenuEvent; @@ -14,13 +16,20 @@ use tray_icon::menu::MenuId; use tray_icon::menu::MenuItemBuilder; use tray_icon::Icon; use tray_icon::TrayIconBuilder; +use util::get_exe_path; +use util::get_file_path; use util::hex_to_colorref; +use util::set_startup; use winapi::ctypes::c_int; use winapi::ctypes::c_void; use winapi::shared::minwindef::{BOOL, DWORD, LPARAM}; use winapi::shared::windef::{HWINEVENTHOOK__, HWND}; use winapi::um::dwmapi::DwmSetWindowAttribute; use winapi::um::errhandlingapi::GetLastError; +use winapi::um::shellapi::ShellExecuteExW; +use winapi::um::shellapi::SEE_MASK_NOASYNC; +use winapi::um::shellapi::SEE_MASK_NOCLOSEPROCESS; +use winapi::um::shellapi::SHELLEXECUTEINFOW; use winapi::um::winuser::EnumWindows; use winapi::um::winuser::GetClassNameW; use winapi::um::winuser::GetWindowTextLengthW; @@ -41,6 +50,12 @@ mod logger; mod util; fn main() { + if let Err(err) = set_startup(true) { + Logger::log("[ERROR] Failed to create or update startup task"); + Logger::log(&format!("[DEBUG] {:?}", err)); + } + + let is_elevated = is_elevated().unwrap_or(false); unsafe { let create_hook = SetWinEventHook( EVENT_OBJECT_CREATE, @@ -83,12 +98,17 @@ fn main() { .id(MenuId::new("1")) .build(), &MenuItemBuilder::new() - .text("Exit") + .text(if is_elevated { "Uninstall" } else { "Install" }) .enabled(true) .id(MenuId::new("2")) .build(), + &MenuItemBuilder::new() + .text("Exit") + .enabled(true) + .id(MenuId::new("3")) + .build(), ]); - + let tray_menu = match tray_menu_builder { Ok(tray_menu) => tray_menu, Err(err) => { @@ -97,7 +117,7 @@ fn main() { std::process::exit(1); } }; - + let icon = match Icon::from_resource(1, Some((64, 64))) { Ok(icon) => icon, Err(err) => { @@ -106,13 +126,13 @@ fn main() { std::process::exit(1); } }; - + let tray_icon_builder = TrayIconBuilder::new() .with_menu(Box::new(tray_menu)) .with_menu_on_left_click(true) .with_icon(icon) .with_tooltip(format!("cute-borders v{}", env!("CARGO_PKG_VERSION"))); - + tray_icon = match tray_icon_builder.build() { Ok(tray_icon) => tray_icon, Err(err) => { @@ -121,14 +141,60 @@ fn main() { std::process::exit(1); } }; - - MenuEvent::set_event_handler(Some(|event: MenuEvent| { + + MenuEvent::set_event_handler(Some(move |event: MenuEvent| { if event.id == MenuId::new("0") { let _ = open::that(get_file_path("config.yaml")); } else if event.id == MenuId::new("1") { Config::reload(); apply_colors(false); } else if event.id == MenuId::new("2") { + if is_elevated { + if let Err(err) = set_startup(false) { + Logger::log("[ERROR] Failed to create or update startup task"); + Logger::log(&format!("[DEBUG] {:?}", err)); + } + apply_colors(true); + std::process::exit(0); + } else { + let lp_verb: Vec = OsStr::new("runas") + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let d = get_exe_path(); + let v = d.to_str().unwrap_or_default(); + let lp_file: Vec = OsStr::new(&v) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let lp_par: Vec = OsStr::new("") + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + let mut sei = SHELLEXECUTEINFOW { + cbSize: std::mem::size_of::() as u32, + fMask: SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS, + lpVerb: lp_verb.as_ptr(), + lpFile: lp_file.as_ptr(), + lpParameters: lp_par.as_ptr(), + nShow: 1, + dwHotKey: 0, + hInstApp: std::ptr::null_mut(), + hMonitor: std::ptr::null_mut(), + hProcess: std::ptr::null_mut(), + hkeyClass: std::ptr::null_mut(), + hwnd: std::ptr::null_mut(), + lpClass: std::ptr::null_mut(), + lpDirectory: std::ptr::null_mut(), + lpIDList: std::ptr::null_mut(), + }; + + ShellExecuteExW(&mut sei); + apply_colors(true); + std::process::exit(0); + } + } else if event.id == MenuId::new("3") { apply_colors(true); std::process::exit(0); } diff --git a/src/util.rs b/src/util.rs index 588000f..d3aad38 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,10 +1,19 @@ +use check_elevation::is_elevated; +use planif::enums::TaskCreationFlags; +use planif::schedule::TaskScheduler; +use planif::schedule_builder::Action; +use planif::schedule_builder::ScheduleBuilder; +use planif::settings::Duration; +use planif::settings::LogonType; +use planif::settings::PrincipalSettings; +use planif::settings::RunLevel; +use planif::settings::Settings; use std::{ env, fs::{self, File, OpenOptions}, io::Write, path::{Path, PathBuf}, }; - use winapi::um::winnt::{KEY_READ, KEY_WRITE}; use winreg::{enums::HKEY_CURRENT_USER, RegKey}; @@ -92,38 +101,27 @@ pub fn hex_to_colorref(hex: &str) -> u32 { } } -fn get_registry_key() -> Option { - match RegKey::predef(HKEY_CURRENT_USER).open_subkey_with_flags( +fn clean_old_registry_key() { + let key = match RegKey::predef(HKEY_CURRENT_USER).open_subkey_with_flags( "Software\\Microsoft\\Windows\\CurrentVersion\\Run", KEY_READ | KEY_WRITE, ) { Ok(key) => Some(key), - Err(err) => { - Logger::log("[ERROR] Failed to open registry key"); - Logger::log(&format!("[DEBUG] {:?}", err)); - None - } - } -} + Err(_) => None, + }; -fn key_exists(app_name: &str) -> bool { - match get_registry_key() { - Some(key) => key.get_raw_value(app_name).is_ok(), - None => false, + if let Some(key) = key { + let _ = key.delete_value("cute-borders"); } } -pub fn enable_startup() { - if key_exists("cute-borders") { - return; - } - - let exe_path = match env::current_exe() { +pub fn get_exe_path() -> PathBuf { + let exe_path: PathBuf = match env::current_exe() { Ok(path) => path, Err(err) => { Logger::log("[ERROR] Failed to find own executable path"); Logger::log(&format!("[DEBUG] {:?}", err)); - return; + std::process::exit(1); } }; @@ -150,7 +148,7 @@ pub fn enable_startup() { &new_exe_path.to_string_lossy() )); Logger::log(&format!("[DEBUG] {:?}", err)); - return; + std::process::exit(1); } } } @@ -164,31 +162,59 @@ pub fn enable_startup() { &new_exe_path.to_string_lossy() )); Logger::log(&format!("[DEBUG] {:?}", err)); - return; + std::process::exit(1); } } } - if let Some(key) = get_registry_key() { - if let Err(err) = key.set_value("cute-borders", &new_exe_path.to_string_lossy().to_string()) { - Logger::log("[ERROR] Failed to set registry key"); - Logger::log(&format!("[DEBUG] {:?}", err)); - } - } + return new_exe_path; } -pub fn disable_startup() { - if !key_exists("cute-borders") { - return; - } +pub fn set_startup(enabled: bool) -> Result<(), Box> { + clean_old_registry_key(); + let exe_path = get_exe_path(); + let is_elevated = is_elevated().unwrap_or(false); - if let Some(key) = get_registry_key() { - match key.delete_value("cute-borders") { - Ok(_) => {} - Err(err) => { - Logger::log("[ERROR] Failed to delete registry key"); - Logger::log(&format!("[DEBUG] {:?}", err)); - } - } + if !is_elevated { + return Ok(()); } + + let ts = TaskScheduler::new()?; + let com = ts.get_com(); + let sb = ScheduleBuilder::new(&com).unwrap(); + + let mut settings = Settings::new(); + settings.stop_if_going_on_batteries = Some(false); + settings.disallow_start_if_on_batteries = Some(false); + settings.enabled = Some(true); + + let action = Action::new("cute-borders-action", &exe_path.to_string_lossy(), "", ""); + + let delay = Duration { + seconds: Some(5), + // see https://github.com/mattrobineau/planif/commit/ac2e7f79ec8de8935c6292d64533a6c7ce37212e + // github has 1.0.1 but crates.io doesnt + hours: Some(0), + ..Default::default() + }; + + sb.create_logon() + .settings(settings)? + .author("keifufu")? + .description("cute-borders startup")? + .principal(PrincipalSettings { + display_name: "".to_string(), + group_id: None, + id: "".to_string(), + logon_type: LogonType::Password, + run_level: RunLevel::Highest, + user_id: None, + })? + .trigger("cute-borders-trigger", enabled)? + .delay(delay)? + .action(action)? + .build()? + .register("cute-borders", TaskCreationFlags::CreateOrUpdate as i32)?; + + Ok(()) }