From 165ac339ec5a54132e6906ee816599904fa9268e Mon Sep 17 00:00:00 2001 From: lynnux Date: Fri, 19 Aug 2022 18:04:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0emacs=2029=E4=BB=A5=E4=B8=8Bw?= =?UTF-8?q?in10=20dark=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/gui.rs | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 9 ++ 3 files changed, 250 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10ec097..454e12b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ emacs = "0.11.0" native-windows-gui = "1.0.12" winapi = {version="0.3", features=["minwindef", "windef", "wingdi", "winuser", "libloaderapi", "processthreadsapi", "basetsd", "debugapi", "errhandlingapi"]} lazy_static = "1.4" +detours-sys = {version="0.1.2"} [lib] crate-type = ["cdylib"] diff --git a/src/gui.rs b/src/gui.rs index fad309b..3731bcf 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,10 +1,24 @@ -extern crate native_windows_gui as nwg; +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] + +extern crate native_windows_gui as nwg; +use crate::to_wstring; +use crate::transparent::debug_output; +use std::ffi; +use std::os::raw::c_int; use std::rc::Rc; +use winapi::shared::basetsd::LONG_PTR; +use winapi::shared::minwindef::BOOL; use winapi::shared::minwindef::*; +use winapi::shared::ntdef::LPCSTR; +use winapi::shared::windef::*; +use winapi::um::libloaderapi::*; use winapi::um::processthreadsapi::*; +use winapi::um::sysinfoapi::GetVersionExW; +use winapi::um::winbase::lstrcmpiA; +use winapi::um::winnt::LPCWSTR; use winapi::um::winuser::*; -use crate::transparent::debug_output; -use crate::to_wstring; +use winapi::{shared::ntdef::HRESULT, shared::windef::HWND}; // hotkey参考 https://blog.csdn.net/x356982611/article/details/16341797 static mut HACCEL: usize = 0; @@ -192,3 +206,226 @@ pub fn gui_init() { } } +///////////// 实现win10 dark mode +type DwmSetWindowAttribute = unsafe extern "system" fn( + h_wnd: HWND, + dw_attribute: DWORD, + pv_attribute: LPCVOID, + cb_attribute: DWORD, +) -> HRESULT; +type SetWindowTheme = + unsafe extern "system" fn(hwnd: HWND, pszSubAppName: LPCWSTR, pszSubIdList: LPCWSTR) -> HRESULT; + +static mut DwmSetWindowAttribute_fn: usize = 0; +static mut SetWindowTheme_fn: usize = 0; +static mut w32_darkmode: Option = None; +static mut w32_build_number: DWORD = 0; +fn w32_applytheme(hwnd: HWND) { + unsafe { + if let Some(wd) = &w32_darkmode { + if SetWindowTheme_fn != 0 { + let pfnSetWindowTheme: SetWindowTheme = std::mem::transmute(SetWindowTheme_fn); + let DARK_MODE_APP_NAME = to_wstring("DarkMode_Explorer"); + pfnSetWindowTheme(hwnd, DARK_MODE_APP_NAME.as_ptr(), std::ptr::null_mut()); + } + if DwmSetWindowAttribute_fn != 0 { + let pfnDwmSetWindowAttribute: DwmSetWindowAttribute = + std::mem::transmute(DwmSetWindowAttribute_fn); + let mut attr = 20; // DWMWA_USE_IMMERSIVE_DARK_MODE; + if w32_build_number < 19041 { + attr = 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_OLD + } + pfnDwmSetWindowAttribute(hwnd, attr, &wd as *const _ as *const _, 4 as u32); + debug_output!(format!("hwnd:{:#X}", hwnd as usize)); + } + } + } +} + +struct EnumData { + pid: DWORD, + ret: Vec, +} +fn enum_windows_item(data: &mut EnumData, hwnd: HWND) -> BOOL { + let mut p: DWORD = 0; + if unsafe { GetWindowThreadProcessId(hwnd, &mut p as *mut _) != 0 } && p == data.pid { + unsafe { + let mut buf: [u8; MAX_PATH] = std::mem::zeroed(); + let len = GetClassNameA(hwnd, &mut buf as *mut _ as *mut _, MAX_PATH as i32); + if (len == 5 && &buf[0..5] == b"Emacs") || (len == 9 && &buf[0..9] == b"ScrollBar") + // 实际也就这两种需要,https://github.com/emacs-mirror/emacs/blob/master/src/w32fns.c 搜索 w32_applytheme + { + data.ret.push(hwnd); + } + } + } + 1 +} +unsafe extern "system" fn enum_callback(win_hwnd: HWND, arg: LONG_PTR) -> BOOL { + let pthis = arg as *mut EnumData; + enum_windows_item(&mut *pthis, win_hwnd) +} +fn get_current_process_wnd() -> Option> { + let mut data = EnumData { + pid: unsafe { winapi::um::processthreadsapi::GetCurrentProcessId() }, + ret: Vec::new(), + }; + unsafe { + EnumWindows(Some(enum_callback), &mut data as *mut _ as LONG_PTR); + } + + // scrollbar属于子窗口,还要枚举字窗口 + let wd = data.ret.clone(); + for w in wd { + unsafe { + EnumChildWindows(w, Some(enum_callback), &mut data as *mut _ as LONG_PTR); + } + } + if !data.ret.is_empty() { + Some(data.ret) + } else { + None + } +} + +pub fn ensure_all_window_dark_mode() { + unsafe { + if w32_darkmode.is_none() { + use winapi::um::winnt::OSVERSIONINFOW; + let mut osi: OSVERSIONINFOW = std::mem::zeroed(); + osi.dwOSVersionInfoSize = std::mem::size_of::() as u32; + GetVersionExW(&mut osi as *mut _ as *mut _); + w32_build_number = osi.dwBuildNumber; // maj获取win10可能有问题,但buildnumber一般不会错 + + let dwmapi_lib = LoadLibraryA(b"dwmapi.dll\0".as_ptr() as *const _); + if !dwmapi_lib.is_null() { + DwmSetWindowAttribute_fn = + GetProcAddress(dwmapi_lib, b"DwmSetWindowAttribute\0".as_ptr() as *const _) + as usize; + } + let uxtheme_lib = LoadLibraryA(b"uxtheme.dll\0".as_ptr() as *const _); + if !uxtheme_lib.is_null() { + SetWindowTheme_fn = + GetProcAddress(uxtheme_lib, b"SetWindowTheme\0".as_ptr() as *const _) as usize; + } + + if SetWindowTheme_fn == 0 || DwmSetWindowAttribute_fn == 0 { + w32_darkmode = Some(0); + } else { + w32_darkmode = Some(1); // TODO; 参考emacs源码可以读注册表判断是否开启dark mode + } + debug_output!(format!("current maj:{}, build:{}, DwmSetWindowAttribute_fn: {:#X}, SetWindowTheme_fn: {:#X}", osi.dwMajorVersion, osi.dwBuildNumber, DwmSetWindowAttribute_fn, SetWindowTheme_fn)); + + // hook emacs,让后面创建的窗口也有效果 + hook_CreateWindowExA(); + } + if *w32_darkmode.as_ref().unwrap_or(&0) == 0 { + return; + } + } + + if let Some(windows) = get_current_process_wnd() { + for w in windows { + w32_applytheme(w); + // TODO: 目前主窗口不会即时生效,试了很多方法不能让它重绘,只能elisp里放大重绘了 + } + } +} + +// emacs调用的是CreateWindow,但x64dbg里没找到?CreateWindowExA是调用了的。rust的hook库有个跨平台的detour只支持nightly +// 定义在这里找https://github.com/microsoft/Detours/blob/24357c6a5a6bb9025a71050e50b38dbe9c02713a/src/detours.h +// demo https://github.com/DianaNites/detours/blob/master/detours-sys/src/lib.rs +type FnCreateWindowExA = unsafe extern "system" fn( + dwExStyle: DWORD, + lpClassName: LPCSTR, + lpWindowName: LPCSTR, + dwStyle: DWORD, + x: c_int, + y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, +) -> HWND; + +unsafe extern "system" fn CreateWindowExA_detour( + dwExStyle: DWORD, + lpClassName: LPCSTR, + lpWindowName: LPCSTR, + dwStyle: DWORD, + x: c_int, + y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, +) -> HWND { + let org: FnCreateWindowExA = std::mem::transmute(CreateWindowExAOrg); + let ret = org( + dwExStyle, + lpClassName, + lpWindowName, + dwStyle, + x, + y, + nWidth, + nHeight, + hWndParent, + hMenu, + hInstance, + lpParam, + ); + if !ret.is_null() { + if 0 == lstrcmpiA(lpClassName, b"emacs\0".as_ptr() as _) + || 0 == lstrcmpiA(lpClassName, b"ScrollBar\0".as_ptr() as _) + { + //debug_output!(format!("hook {:#x}", ret as usize)); + w32_applytheme(ret); + } + } + + ret +} + +fn get_module_symbol_address(module: &str, symbol: &str) -> Option { + let module = module + .encode_utf16() + .chain(std::iter::once(0)) + .collect::>(); + let symbol = std::ffi::CString::new(symbol).unwrap(); + unsafe { + let handle = GetModuleHandleW(module.as_ptr()); + match GetProcAddress(handle, symbol.as_ptr()) as usize { + 0 => None, + n => Some(n), + } + } +} + +static mut CreateWindowExAOrg: usize = 0; + +fn hook_CreateWindowExA() { + if let Some(address) = get_module_symbol_address("user32.dll", "CreateWindowExA") { + //let target: FnCreateWindowExA = ; + unsafe { + CreateWindowExAOrg = std::mem::transmute(address); + let tru = &mut CreateWindowExAOrg as *mut _ as *mut *mut ffi::c_void; + let new = CreateWindowExA_detour as *mut ffi::c_void; + + use detours_sys::{ + DetourAttach, DetourTransactionBegin, DetourTransactionCommit, DetourUpdateThread, + }; + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread() as _); + DetourAttach(tru, new); + DetourTransactionCommit(); + // Initialize AND enable the detour (the 2nd parameter can also be a closure) + } + } else { + debug_output!(format!("can't get CreateWindowExA!")); + } +} diff --git a/src/lib.rs b/src/lib.rs index d2d9676..bc0f56d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,3 +67,12 @@ fn to_wstring(s: &str) -> Vec { .chain(std::iter::once(0)) .collect() } + +// 让标题和滚动条支持in10的dark mode,emacs28.1版本不支持,最新版本支持,但最新版本有点卡 +// https://github.com/emacs-mirror/emacs/blob/1a9175a0de98676ac9aa1dec8f3c5c585ce2a9cd/src/w32fns.c#L11209-L11238 +// https://github.com/godotengine/godot-proposals/issues/1868 +#[defun] +fn ensure_all_window_dark_mode() -> Result { + crate::gui::ensure_all_window_dark_mode(); + Ok(0) +}