use crate::*;
use winapi::shared::windef::*;
use winapi::um::winuser::*;
use std::any::*;
use std::cell::*;
use std::collections::*;
use std::marker::*;
use std::ptr::*;
use std::sync::{*, atomic::{*, Ordering::*}};
pub struct Slot<T: 'static> {
ty: SlotType,
dense_slot_no: AtomicUsize, pd: PhantomData<std::thread::LocalKey<&'static T>>,
}
impl<T: 'static> Slot<T> {
pub const fn new_drop_early() -> Self { Self::new_impl(SlotType::DenseDropEarly) }
pub const fn new_drop_late() -> Self { Self::new_impl(SlotType::DenseDropLate) }
pub fn get_copy(&'static self, hwnd: HWnd) -> Result<Option<T>, Error> where T : Copy { self.get_clone(hwnd) }
pub fn get_clone(&'static self, hwnd: HWnd) -> Result<Option<T>, Error> where T : Clone {
check_window_thread_local(hwnd)?;
let slot_idx = self.slot_idx();
ThreadLocal::with(move |tl| {
let forbid_destroying = tl.global.any_early.load(Acquire) && tl.global.dense_slots.read().unwrap()[slot_idx].drop_early;
let pw = tl.per_window.borrow();
let pw = match pw.get(&hwnd) {
None => return Ok(None),
Some(pw) => pw,
};
if forbid_destroying && pw.destroying.get() { return fn_err!(ERROR::INVALID_WINDOW_HANDLE) }
let pw_dense_slots = pw.dense_slots.borrow();
match pw_dense_slots.get(slot_idx) {
None => Ok(None), Some(None) => Ok(None), Some(Some(slot)) => {
if let Some(slot_t) = slot.downcast_ref::<T>() {
Ok(Some((*slot_t).clone()))
} else {
fn_err!(ERROR::DATATYPE_MISMATCH)
}
}
}
})
}
pub fn set(&'static self, hwnd: HWnd, value: T) -> Result<Option<T>, Error> {
check_window_thread_local(hwnd)?;
let value = Box::new(value);
let slot_idx = self.slot_idx();
let prev = ThreadLocal::with(move |tl| {
let forbid_destroying = tl.global.any_early.load(Acquire) && tl.global.dense_slots.read().unwrap()[slot_idx].drop_early;
let mut pw = tl.per_window.borrow_mut();
let pw = pw.entry(hwnd).or_default();
if forbid_destroying && pw.destroying.get() { return fn_err!(ERROR::INVALID_WINDOW_HANDLE) }
let mut pw_dense_slots = pw.dense_slots.borrow_mut();
if slot_idx >= pw_dense_slots.len() { pw_dense_slots.resize_with(slot_idx+1, || None) }
let pw_dense_slot = &mut pw_dense_slots[slot_idx];
Ok(pw_dense_slot.replace(value))
})?;
match prev.map(|p| p.downcast::<T>()) {
Some(Ok(prev)) => Ok(Some(*prev)),
Some(Err(_)) => fn_err!(ERROR::DATATYPE_MISMATCH),
None => Ok(None),
}
}
const fn new_impl(ty: SlotType) -> Self {
Self {
ty,
dense_slot_no: AtomicUsize::new(0),
pd: PhantomData,
}
}
fn slot_idx(&'static self) -> usize { self.slot_no() - 1 }
fn slot_no(&'static self) -> usize {
let s = self.dense_slot_no.load(Acquire);
if s != 0 { return s }
let g = Global::get();
let mut g_dense_slots = g.dense_slots.write().unwrap(); let s = self.dense_slot_no.load(Relaxed);
if s != 0 { return s }
let drop_early = match self.ty {
SlotType::DenseDropEarly => true,
SlotType::DenseDropLate => false,
};
if drop_early { g.any_early.store(true, Release); }
else { g.any_late .store(true, Release); }
g_dense_slots.push(DenseSlotMeta { drop_early });
let slot_no = g_dense_slots.len();
self.dense_slot_no.store(slot_no, Release);
slot_no
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] enum SlotType {
DenseDropEarly,
DenseDropLate,
}
struct ThreadLocal {
global: &'static Global,
hooks: Hooks,
per_window: RefCell<HashMap<HWnd, PerWindow>>,
}
impl Default for ThreadLocal {
fn default() -> Self {
Self {
global: Global::get(),
hooks: Default::default(),
per_window: Default::default(),
}
}
}
impl ThreadLocal {
fn with<R>(f: impl FnOnce(&ThreadLocal) -> R) -> R {
thread_local! { static TL : ThreadLocal = ThreadLocal::default(); }
TL.with(f)
}
}
#[derive(Default)]
struct Global {
any_early: AtomicBool,
any_late: AtomicBool,
dense_slots: RwLock<Vec<DenseSlotMeta>>,
}
impl Global {
fn get() -> &'static Self {
lazy_static::lazy_static! { static ref G : Global = Global::default(); }
&*G
}
}
struct DenseSlotMeta {
drop_early: bool,
}
#[derive(Default)]
struct PerWindow {
destroying: Cell<bool>,
dense_slots: RefCell<Vec<Option<Box<dyn Any + 'static>>>>,
}
impl PerWindow {
fn before_wm_get_min_max_info(&self) {
self.destroying.set(false);
}
fn before_wm_nc_create(&self) {
self.destroying.set(false);
}
fn after_wm_create(&self) {
}
fn before_wm_destroy(&mut self, global: &Global) {
self.destroying.set(true);
if !global.any_early.load(Acquire) { return }
let g_dense_slots = global.dense_slots.read().unwrap();
let mut s_dense_slots = self.dense_slots.borrow_mut();
for (g_dense, s_dense) in g_dense_slots.iter().zip(s_dense_slots.iter_mut()) {
if g_dense.drop_early {
let _ = s_dense.take();
}
}
}
fn after_wm_nc_destroy(mut self) {
self.dense_slots.get_mut().clear();
}
}
struct Hooks {
wh_cbt: HHOOK,
wh_callwndproc: HHOOK,
wh_callwndprocret: HHOOK,
}
impl Default for Hooks {
fn default() -> Self {
let thread = get_current_thread_id();
let hooks = Self {
wh_cbt: unsafe { SetWindowsHookExW(WH_CBT, Some(wh_cbt), null_mut(), thread) },
wh_callwndproc: unsafe { SetWindowsHookExW(WH_CALLWNDPROC, Some(wh_callwndproc), null_mut(), thread) },
wh_callwndprocret: unsafe { SetWindowsHookExW(WH_CALLWNDPROCRET, Some(wh_callwndprocret), null_mut(), thread) },
};
debug_assert!(!hooks.wh_cbt .is_null());
debug_assert!(!hooks.wh_callwndproc .is_null());
debug_assert!(!hooks.wh_callwndprocret .is_null());
hooks
}
}
impl Drop for Hooks {
fn drop(&mut self) {
let unhook_wh_cbt = self.wh_cbt.is_null() || unsafe { UnhookWindowsHookEx(self.wh_cbt ) != 0 };
let unhook_wh_callwndproc = self.wh_callwndproc.is_null() || unsafe { UnhookWindowsHookEx(self.wh_callwndproc ) != 0 };
let unhook_wh_callwndprocret = self.wh_callwndprocret.is_null() || unsafe { UnhookWindowsHookEx(self.wh_callwndprocret ) != 0 };
debug_assert!(unhook_wh_cbt);
debug_assert!(unhook_wh_callwndproc);
debug_assert!(unhook_wh_callwndprocret);
}
}
fn check_window_thread_local(hwnd: HWnd) -> Result<(), Error> {
fn_context!(assoc::local::check_window_thread_local => GetWindowThreadProcessId);
let tid = get_window_thread_id(hwnd)?;
if get_current_thread_id() != tid { return fn_err!(ERROR::WINDOW_OF_OTHER_THREAD) }
Ok(())
}
unsafe extern "system" fn wh_cbt(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let hook = ThreadLocal::with(|tl| tl.hooks.wh_cbt);
if code >= 0 {
}
let lr = unsafe { CallNextHookEx(hook, code, wparam, lparam) };
lr
}
unsafe extern "system" fn wh_callwndproc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let hook = ThreadLocal::with(|tl| tl.hooks.wh_callwndproc);
if code == HC_ACTION {
let _from_current_proc_or_thread = wparam != 0;
let call = unsafe { &*(lparam as *const CWPSTRUCT) };
let hwnd = HWnd::from(call.hwnd);
let msg = WM32::from(call.message);
if !call.hwnd.is_null() {
match msg {
WM::GETMINMAXINFO => { ThreadLocal::with(|tl| tl.per_window.borrow ().get (&hwnd).map(|pw| pw.before_wm_get_min_max_info() )); },
WM::NCCREATE => { ThreadLocal::with(|tl| tl.per_window.borrow ().get (&hwnd).map(|pw| pw.before_wm_nc_create() )); },
WM::DESTROY => { ThreadLocal::with(|tl| tl.per_window.borrow_mut().get_mut(&hwnd).map(|pw| pw.before_wm_destroy(tl.global) )); },
_ => {}
}
}
}
let lr = unsafe { CallNextHookEx(hook, code, wparam, lparam) };
lr
}
unsafe extern "system" fn wh_callwndprocret(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let hook = ThreadLocal::with(|tl| tl.hooks.wh_callwndprocret);
if code >= 0 {
let _from_current_proc_or_thread = wparam != 0;
let ret = unsafe { &*(lparam as *const CWPRETSTRUCT) };
let hwnd = HWnd::from(ret.hwnd);
let msg = WM32::from(ret.message);
if !hwnd.is_null() {
match msg {
WM::CREATE => { ThreadLocal::with(|tl| tl.per_window.borrow ().get (&hwnd).map(|pw| pw.after_wm_create() )); }
WM::NCDESTROY => { ThreadLocal::with(|tl| tl.per_window.borrow_mut().remove(&hwnd)).map(|pw| pw.after_wm_nc_destroy()); }
_ => {}
}
}
}
let lr = unsafe { CallNextHookEx(hook, code, wparam, lparam) };
lr
}