Skip to content

Commit

Permalink
添加emacs 29以下win10 dark mode
Browse files Browse the repository at this point in the history
  • Loading branch information
lynnux committed Aug 19, 2022
1 parent 14cc9ef commit 165ac33
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
243 changes: 240 additions & 3 deletions src/gui.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<BOOL> = 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<HWND>,
}
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<Vec<HWND>> {
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::<OSVERSIONINFOW>() 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<usize> {
let module = module
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<u16>>();
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!"));
}
}
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,12 @@ fn to_wstring(s: &str) -> Vec<u16> {
.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<usize> {
crate::gui::ensure_all_window_dark_mode();
Ok(0)
}

0 comments on commit 165ac33

Please sign in to comment.