Skip to main content

native_windows_gui2/win32/
window.rs

1/*!
2Native Windows GUI windowing base. Includes events dispatching and window creation.
3
4Warning. Not for the faint of heart.
5*/
6use super::base_helper::{CUSTOM_ID_BEGIN, cwstr_to_str, to_utf16};
7use super::high_dpi;
8use super::window_helper::{NOTICE_MESSAGE, NWG_INIT, NWG_TIMER_STOP, NWG_TIMER_TICK, NWG_TRAY};
9use crate::controls::ControlHandle;
10use crate::{Event, EventData, NwgError};
11use std::ffi::OsString;
12use std::os::windows::prelude::OsStringExt;
13use std::rc::Rc;
14use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
15use std::{mem, ptr};
16use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
17use winapi::shared::minwindef::{BOOL, DWORD, HMODULE, LPARAM, LRESULT, UINT, WPARAM};
18use winapi::shared::windef::{HBRUSH, HMENU, HWND};
19use winapi::um::commctrl::{NMTTDISPINFOW, SUBCLASSPROC};
20use winapi::um::winuser::{
21    HCF_HIGHCONTRASTON, HIGHCONTRASTW, IDCANCEL, IDOK, MAKEINTRESOURCEA, NMHDR,
22    SPI_GETHIGHCONTRAST, SystemParametersInfoW, WM_SETTINGCHANGE, WNDPROC,
23};
24
25static TIMER_ID: AtomicU32 = AtomicU32::new(1);
26static NOTICE_ID: AtomicU32 = AtomicU32::new(1);
27static EVENT_HANDLER_ID: AtomicUsize = AtomicUsize::new(1);
28
29const NO_DATA: EventData = EventData::NoData;
30
31type RawCallback = dyn Fn(HWND, UINT, WPARAM, LPARAM) -> Option<LRESULT>;
32type Callback = dyn Fn(Event, EventData, ControlHandle) -> ();
33
34/**
35    An opaque structure that represent a window subclass hook.
36*/
37pub struct EventHandler {
38    handles: Vec<HWND>,
39    id: SUBCLASSPROC,
40    subclass_id: UINT_PTR,
41}
42
43/**
44    An opaque structure that represent a window subclass hook.
45*/
46pub struct RawEventHandler {
47    handle: HWND,
48    subclass_proc: SUBCLASSPROC,
49    handler_id: UINT_PTR,
50}
51
52/**
53    Note. While there might be a race condition here, it does not matter because
54    All controls are thread local and the true id is (HANDLE + NOTICE_ID)
55    The same apply to timers
56*/
57pub fn build_notice(parent: HWND) -> ControlHandle {
58    let id = NOTICE_ID.fetch_add(1, Ordering::SeqCst);
59    ControlHandle::Notice(parent, id)
60}
61
62pub unsafe fn build_timer(parent: HWND, interval: u32, stopped: bool) -> ControlHandle {
63    use winapi::um::winuser::SetTimer;
64
65    let id = TIMER_ID.fetch_add(1, Ordering::SeqCst);
66
67    if !stopped {
68        unsafe { SetTimer(parent, id as UINT_PTR, interval as UINT, None) };
69    }
70
71    ControlHandle::Timer(parent, id)
72}
73
74/**
75    Hook the window subclass with the default event dispatcher.
76    The hook is applied to the window and all it's children (recursively).
77
78    Returns a `EventHandler` that can be passed to `unbind_event_handler` to remove the callbacks.
79
80    This function will panic if `handle` is not a window handle.
81*/
82pub fn full_bind_event_handler<F>(handle: &ControlHandle, f: F) -> EventHandler
83where
84    F: Fn(Event, EventData, ControlHandle) -> () + 'static,
85{
86    use winapi::um::winuser::EnumChildWindows;
87
88    struct SetSubclassParam {
89        callback_ptr: *mut *const Callback,
90        subclass_id: UINT_PTR,
91    }
92
93    /**
94        Function that iters over a top level window and bind the events dispatch callback
95    */
96    unsafe extern "system" fn set_children_subclass(h: HWND, p: LPARAM) -> i32 {
97        let params_ptr = p as *mut SetSubclassParam;
98        let params = unsafe { &*params_ptr };
99
100        let cb: Rc<Callback> = unsafe { Rc::from_raw(*params.callback_ptr) };
101
102        // Simply increase the rc count because the callback
103        // will also be stored into the current children window.
104        mem::forget(cb.clone());
105        SetWindowSubclass(
106            h,
107            Some(process_events),
108            params.subclass_id,
109            params.callback_ptr as UINT_PTR,
110        );
111
112        // Do not decrease the refcount
113        mem::forget(cb);
114
115        1
116    }
117
118    /**
119        Push the children window handle into the EventHandler
120    */
121    unsafe extern "system" fn handler_children(h: HWND, p: LPARAM) -> i32 {
122        let handles_ptr: *mut Vec<HWND> = p as *mut Vec<HWND>;
123        let handles = unsafe { &mut *handles_ptr };
124        handles.push(h);
125        1
126    }
127
128    let hwnd = handle
129        .hwnd()
130        .expect("Cannot bind control with an handle of type");
131
132    // The callback function must be passed to each children of the control
133    // To do so, we must RC the callback
134    let callback: Rc<Callback> = Rc::new(f);
135    let callback_box: Box<*const Callback> = Box::new(Rc::into_raw(callback));
136    let callback_ptr: *mut *const Callback = Box::into_raw(callback_box);
137
138    let callback_fn: SUBCLASSPROC = Some(process_events);
139    let subclass_id = EVENT_HANDLER_ID.fetch_add(1, Ordering::SeqCst);
140    let mut handler = EventHandler {
141        handles: vec![hwnd],
142        id: callback_fn,
143        subclass_id,
144    };
145
146    let params = Box::new(SetSubclassParam {
147        callback_ptr,
148        subclass_id,
149    });
150    let params_ptr: *mut SetSubclassParam = Box::into_raw(params);
151
152    unsafe {
153        EnumChildWindows(
154            hwnd,
155            Some(handler_children),
156            (&mut handler.handles as *mut Vec<HWND>) as LPARAM,
157        );
158        EnumChildWindows(hwnd, Some(set_children_subclass), params_ptr as LPARAM);
159        SetWindowSubclass(hwnd, callback_fn, subclass_id, callback_ptr as UINT_PTR);
160        let _ = Box::from_raw(params_ptr);
161    }
162
163    handler
164}
165
166/**
167Hook the window subclass with the default event dispatcher.
168The hook is applied to the control and its parent. All common controls send their events to their parent.
169
170Arguments:
171    - handle: Handle to the main control to hook
172    - parent_handle: Parent to the main control.
173    - f: User event callback
174
175Returns a `EventHandler` that can be passed to `unbind_event_handler` to remove the callbacks.
176
177*/
178pub fn bind_event_handler<F>(
179    handle: &ControlHandle,
180    parent_handle: &ControlHandle,
181    f: F,
182) -> EventHandler
183where
184    F: Fn(Event, EventData, ControlHandle) -> () + 'static,
185{
186    let hwnd = handle
187        .hwnd()
188        .expect("Cannot bind control with an handle of type");
189    let parent_hwnd = parent_handle
190        .hwnd()
191        .expect("Cannot bind control with an handle of type");
192
193    let callback: Rc<Callback> = Rc::new(f);
194    let parent_callback = callback.clone();
195
196    let callback_box: Box<*const Callback> = Box::new(Rc::into_raw(callback));
197    let callback_box_parent: Box<*const Callback> = Box::new(Rc::into_raw(parent_callback));
198
199    let callback_ptr: *mut *const Callback = Box::into_raw(callback_box);
200    let callback_ptr_parent: *mut *const Callback = Box::into_raw(callback_box_parent);
201
202    let callback_fn: SUBCLASSPROC = Some(process_events);
203    let subclass_id = EVENT_HANDLER_ID.fetch_add(1, Ordering::SeqCst);
204    let handler = EventHandler {
205        handles: vec![hwnd, parent_hwnd],
206        id: callback_fn,
207        subclass_id,
208    };
209
210    SetWindowSubclass(hwnd, callback_fn, subclass_id, callback_ptr as UINT_PTR);
211    SetWindowSubclass(
212        parent_hwnd,
213        callback_fn,
214        subclass_id,
215        callback_ptr_parent as UINT_PTR,
216    );
217
218    handler
219}
220
221/**
222    Free all associated callbacks with the event handler.
223
224    This function will panic if the handler was already freed.
225*/
226pub fn unbind_event_handler(handler: &EventHandler) {
227    let id = handler.id;
228    let subclass_id = handler.subclass_id;
229    let mut callback_ptr: *mut *const Callback = ptr::null_mut();
230
231    for &handle in handler.handles.iter() {
232        unsafe {
233            let mut callback_value: UINT_PTR = 0;
234            let result = GetWindowSubclass(handle, id, subclass_id, &mut callback_value);
235            if result == 0 {
236                panic!("Parent of hander was either freed or is already unbound");
237            }
238
239            callback_ptr = callback_value as *mut *const Callback;
240            let callback: Rc<Callback> = Rc::from_raw(*Box::from_raw(callback_ptr));
241
242            // Remove the window subclass before dropping the callback to prevent the
243            // subclass window procedure from being called during the drop.
244            RemoveWindowSubclass(handle, id, subclass_id);
245
246            mem::drop(callback);
247        };
248    }
249
250    // Finally free the pointer to the pointer to the callback
251    unsafe {
252        let _ = Box::from_raw(callback_ptr);
253    }
254}
255
256pub(crate) fn bind_raw_event_handler_inner<F>(
257    handle: &ControlHandle,
258    handler_id: UINT_PTR,
259    f: F,
260) -> Result<RawEventHandler, NwgError>
261where
262    F: Fn(HWND, UINT, WPARAM, LPARAM) -> Option<LRESULT> + 'static,
263{
264    let handler_id = handler_id;
265    let subclass_proc: SUBCLASSPROC = Some(process_raw_events);
266
267    let handle = match handle {
268        &ControlHandle::Hwnd(h) => {
269            // Check if the handler is already bound to the control
270            let mut tmp_value = 0;
271            let result = GetWindowSubclass(h, subclass_proc, handler_id, &mut tmp_value);
272            if result != 0 {
273                return Err(NwgError::events_binding(format!(
274                    "Events id {} is already present on this",
275                    handler_id
276                )));
277            }
278
279            // Bind the callback
280            let boxed_proc: Box<RawCallback> = Box::new(f);
281            let boxed_proc_wrapper: Box<*mut RawCallback> = Box::new(Box::into_raw(boxed_proc));
282            let proc_data: *mut *mut RawCallback = Box::into_raw(boxed_proc_wrapper);
283            SetWindowSubclass(h, subclass_proc, handler_id, proc_data as UINT_PTR);
284
285            h
286        }
287        htype => panic!("Cannot bind control with an handle of type {:?}.", htype),
288    };
289
290    Ok(RawEventHandler {
291        handle,
292        subclass_proc,
293        handler_id,
294    })
295}
296
297/**
298
299Set a window subclass the uses the `process_raw_events` function of NWG.
300The subclass is only applied to the control itself and NOT the children.
301
302When assigning multiple callback to the same control, a different `id` must be specified for each call
303or otherwise, the old callback will be replaced by the new one. See `Label::hook_background_color` for example.
304
305Error:
306- If the event handler with the same ID is already bound, this function will return an Error. The `has_raw_handler` method can be used to check this.
307
308Panic:
309- If the `handle` parameter is not a window-like control
310- If the `handler_id` parameter is <= 0xFFFF
311
312
313```rust
314use native_windows_gui2 as nwg;
315
316fn bind_raw_handler(window: &nwg::Window) -> nwg::RawEventHandler {
317    const WM_MOVE: u32 = 3287542; // Not the actual value, but who cares?
318    let handler_id = 0x10000;     // handler ids equal or smaller than 0xFFFF are reserved by NWG
319
320    nwg::bind_raw_event_handler(&window.handle, handler_id, move |_hwnd, msg, _w, _l| {
321        if msg == WM_MOVE {
322            println!("MOVING!");
323        }
324        None
325    }).unwrap()
326}
327
328```
329*/
330pub fn bind_raw_event_handler<F>(
331    handle: &ControlHandle,
332    handler_id: UINT_PTR,
333    f: F,
334) -> Result<RawEventHandler, NwgError>
335where
336    F: Fn(HWND, UINT, WPARAM, LPARAM) -> Option<LRESULT> + 'static,
337{
338    if handler_id <= 0xFFFF {
339        panic!("handler_id <= 0xFFFF are reserved by NWG");
340    }
341
342    bind_raw_event_handler_inner(handle, handler_id, f)
343}
344
345/**
346    Check if a raw handler with the specified handler_id is currently bound on the control.
347    This function will panic if the handle parameter is not a window control.
348*/
349pub fn has_raw_handler(handle: &ControlHandle, handler_id: UINT_PTR) -> bool {
350    let handle = handle
351        .hwnd()
352        .expect("This type of control cannot have a raw handler.");
353    let subclass_proc: SUBCLASSPROC = Some(process_raw_events);
354    let mut tmp_value = 0;
355    GetWindowSubclass(handle, subclass_proc, handler_id, &mut tmp_value) != 0
356}
357
358/**
359    Remove the raw event handler from the associated window.
360    Calling unbind twice or trying to unbind an handler after destroying its parent will cause the function to panic.
361*/
362pub fn unbind_raw_event_handler(handler: &RawEventHandler) -> Result<(), NwgError> {
363    let subclass_proc = handler.subclass_proc;
364    let handler_id = handler.handler_id;
365    let handle = handler.handle;
366
367    unsafe {
368        let mut callback_value: UINT_PTR = 0;
369        let result = GetWindowSubclass(handle, subclass_proc, handler_id, &mut callback_value);
370        if result == 0 {
371            let err = format!(
372                concat!(
373                    "Could not fetch raw event handler #{:?}.",
374                    "This can happen if the control ({:?}) was freed or",
375                    "if this raw event handler was already unbound"
376                ),
377                handler_id, handle
378            );
379            return Err(NwgError::EventsBinding(err));
380        }
381
382        let callback_wrapper_ptr = callback_value as *mut *mut RawCallback;
383        let callback_wrapper: Box<*mut RawCallback> = Box::from_raw(callback_wrapper_ptr);
384        let callback: Box<RawCallback> = Box::from_raw(*callback_wrapper);
385
386        // Remove the window subclass before dropping the callback to prevent the
387        // subclass window procedure from being called during the drop.
388        RemoveWindowSubclass(handle, subclass_proc, handler_id);
389
390        mem::drop(callback);
391
392        Ok(())
393    }
394}
395
396unsafe fn is_dark_mode_active() -> bool {
397    // type FnIsDarkModeAllowedForWindow = extern "system" fn(hwnd: HWND) -> bool;
398    type FnShouldAppsUseDarkMode = extern "system" fn() -> bool;
399    // type FnIsHighContrast = extern "system" fn() -> bool;
400
401    let h_uxtheme = unsafe {
402        LoadLibraryExW(
403            to_utf16("uxtheme.dll").as_ptr(),
404            0 as HANDLE,
405            LOAD_LIBRARY_SEARCH_SYSTEM32,
406        )
407    };
408
409    if h_uxtheme.is_null() {
410        println!("Failed to load uxtheme.dll");
411        return false;
412    }
413
414    let should_apps_use_dark_mode: FnShouldAppsUseDarkMode =
415        unsafe { mem::transmute(GetProcAddress(h_uxtheme, MAKEINTRESOURCEA(132))) };
416
417    let mut is_high_contrast = false;
418    let mut high_contrast = HIGHCONTRASTW::default();
419    high_contrast.cbSize = size_of::<HIGHCONTRASTW>() as UINT;
420
421    if unsafe {
422        SystemParametersInfoW(
423            SPI_GETHIGHCONTRAST,
424            high_contrast.cbSize,
425            &mut high_contrast as *mut _ as *mut winapi::ctypes::c_void,
426            0,
427        )
428    } != 0
429    {
430        is_high_contrast = (high_contrast.dwFlags & HCF_HIGHCONTRASTON) != 0;
431    }
432
433    let result = should_apps_use_dark_mode() && !is_high_contrast;
434    unsafe { FreeLibrary(h_uxtheme) };
435    result
436}
437
438/// Updates dark mode status for a top-level window.
439/// This function only affects the titlebar's color.
440///
441/// # Arguments
442///
443/// * `hwnd`: Handle to a top-level window.
444///
445/// returns: Result<(), String>
446fn update_dark_mode_for_window(hwnd: HWND) -> Result<(), String> {
447    let value: BOOL = unsafe { is_dark_mode_active() as BOOL };
448    let result: HRESULT = unsafe {
449        winapi::um::dwmapi::DwmSetWindowAttribute(
450            hwnd,
451            20,
452            &value as *const BOOL as *const _,
453            size_of::<BOOL>() as _,
454        )
455    };
456
457    if result != 0 {
458        return Err("DwmSetWindowAttribute failed".to_string());
459    }
460
461    return Ok(());
462}
463
464/**
465    High level function that handle the creation of custom window control or built in window control
466*/
467pub(crate) fn build_hwnd_control<'a>(
468    class_name: &'a str,
469    window_title: Option<&'a str>,
470    size: Option<(i32, i32)>,
471    pos: Option<(i32, i32)>,
472    flags: Option<DWORD>,
473    ex_flags: Option<DWORD>,
474    forced_flags: DWORD,
475    parent: Option<HWND>,
476) -> Result<ControlHandle, NwgError> {
477    use winapi::shared::windef::RECT;
478    use winapi::um::libloaderapi::GetModuleHandleW;
479    use winapi::um::winuser::{AdjustWindowRectEx, CreateWindowExW};
480    use winapi::um::winuser::{
481        WS_CLIPCHILDREN, /*WS_EX_LAYERED*/
482        WS_OVERLAPPEDWINDOW, WS_VISIBLE,
483    };
484
485    let hmod = unsafe { GetModuleHandleW(ptr::null_mut()) };
486    if hmod.is_null() {
487        return Err(NwgError::initialization("GetModuleHandleW failed"));
488    }
489
490    let class_name = to_utf16(class_name);
491    let window_title = to_utf16(window_title.unwrap_or("New Window"));
492    let ex_flags = ex_flags.unwrap_or(0);
493    let flags = flags.unwrap_or(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_VISIBLE) | forced_flags;
494
495    let pos = pos.unwrap_or((0, 0));
496    let size = size.unwrap_or((500, 500));
497    let (px, py) = high_dpi::logical_to_physical(pos.0, pos.1);
498    let (mut sx, mut sy) = high_dpi::logical_to_physical(size.0, size.1);
499    let parent_handle = parent.unwrap_or(ptr::null_mut());
500    let menu = ptr::null_mut();
501    let lp_params = ptr::null_mut();
502
503    if parent.is_none() {
504        let mut rect = RECT {
505            left: 0,
506            top: 0,
507            right: sx,
508            bottom: sy,
509        };
510        unsafe { AdjustWindowRectEx(&mut rect, flags, 0, ex_flags) };
511
512        sx = rect.right - rect.left;
513        sy = rect.bottom - rect.top;
514    }
515
516    let handle = unsafe {
517        CreateWindowExW(
518            ex_flags,
519            class_name.as_ptr(),
520            window_title.as_ptr(),
521            flags,
522            px,
523            py,
524            sx,
525            sy,
526            parent_handle,
527            menu,
528            hmod,
529            lp_params,
530        )
531    };
532
533    if handle.is_null() {
534        Err(NwgError::initialization("Window creation failed"))
535    } else {
536        Ok(ControlHandle::Hwnd(handle))
537    }
538}
539
540pub(crate) fn build_sysclass<'a>(
541    hmod: HMODULE,
542    class_name: &'a str,
543    clsproc: WNDPROC,
544    background: Option<HBRUSH>,
545    style: Option<UINT>,
546) -> Result<(), NwgError> {
547    use winapi::shared::winerror::ERROR_CLASS_ALREADY_EXISTS;
548    use winapi::um::errhandlingapi::GetLastError;
549    use winapi::um::winuser::{COLOR_WINDOW, CS_HREDRAW, CS_VREDRAW, IDC_ARROW, WNDCLASSEXW};
550    use winapi::um::winuser::{LoadCursorW, RegisterClassExW};
551
552    let class_name = to_utf16(class_name);
553    let background: HBRUSH = background.unwrap_or(COLOR_WINDOW as usize as HBRUSH);
554    let style: UINT = style.unwrap_or(CS_HREDRAW | CS_VREDRAW);
555
556    let class = unsafe {
557        WNDCLASSEXW {
558            cbSize: mem::size_of::<WNDCLASSEXW>() as UINT,
559            style,
560            lpfnWndProc: clsproc,
561            cbClsExtra: 0,
562            cbWndExtra: 0,
563            hInstance: hmod,
564            hIcon: ptr::null_mut(),
565            hCursor: LoadCursorW(ptr::null_mut(), IDC_ARROW),
566            hbrBackground: background,
567            lpszMenuName: ptr::null(),
568            lpszClassName: class_name.as_ptr(),
569            hIconSm: ptr::null_mut(),
570        }
571    };
572
573    let class_token = unsafe { RegisterClassExW(&class) };
574    if class_token == 0 && unsafe { GetLastError() } != ERROR_CLASS_ALREADY_EXISTS {
575        Err(NwgError::initialization("System class creation failed"))
576    } else {
577        Ok(())
578    }
579}
580
581/// Create the window class for the base nwg window
582pub(crate) fn init_window_class() -> Result<(), NwgError> {
583    use winapi::um::libloaderapi::GetModuleHandleW;
584
585    unsafe {
586        let hmod = GetModuleHandleW(ptr::null_mut());
587        if hmod.is_null() {
588            return Err(NwgError::initialization("GetModuleHandleW failed"));
589        }
590
591        build_sysclass(
592            hmod,
593            "NativeWindowsGuiWindow",
594            Some(blank_window_proc),
595            None,
596            None,
597        )?;
598    }
599
600    Ok(())
601}
602
603#[cfg(feature = "frame")]
604/// Create the window class for the frame control
605pub(crate) fn create_frame_classes() -> Result<(), NwgError> {
606    use winapi::um::libloaderapi::GetModuleHandleW;
607
608    unsafe {
609        let hmod = GetModuleHandleW(ptr::null_mut());
610        if hmod.is_null() {
611            return Err(NwgError::initialization("GetModuleHandleW failed"));
612        }
613
614        build_sysclass(hmod, "NWG_FRAME", Some(blank_window_proc), None, None)?;
615    }
616
617    Ok(())
618}
619
620#[cfg(feature = "message-window")]
621/// Create a message only window. Used with the `MessageWindow` control
622pub(crate) fn create_message_window() -> Result<ControlHandle, NwgError> {
623    use winapi::um::libloaderapi::GetModuleHandleW;
624    use winapi::um::winuser::CreateWindowExW;
625    use winapi::um::winuser::HWND_MESSAGE;
626
627    let class_name = to_utf16("NativeWindowsGuiWindow");
628    let window_title = vec![0];
629
630    unsafe {
631        let hmod = GetModuleHandleW(ptr::null_mut());
632        if hmod.is_null() {
633            return Err(NwgError::initialization("GetModuleHandleW failed"));
634        }
635
636        let handle = CreateWindowExW(
637            0,
638            class_name.as_ptr(),
639            window_title.as_ptr(),
640            0,
641            0,
642            0,
643            0,
644            0,
645            HWND_MESSAGE,
646            ptr::null_mut(),
647            hmod,
648            ptr::null_mut(),
649        );
650
651        if handle.is_null() {
652            Err(NwgError::initialization(
653                "Message only window creation failed",
654            ))
655        } else {
656            Ok(ControlHandle::Hwnd(handle))
657        }
658    }
659}
660
661/**
662    A blank system procedure used when creating new window class. Actual system event handling is done in the subclass procedure `process_events`.
663*/
664extern "system" fn blank_window_proc(hwnd: HWND, msg: UINT, w: WPARAM, l: LPARAM) -> LRESULT {
665    use winapi::um::winuser::{DefWindowProcW, PostMessageW, ShowWindow};
666    use winapi::um::winuser::{SW_HIDE, WM_CLOSE, WM_CREATE};
667
668    let handled = match msg {
669        WM_CREATE => {
670            unsafe { PostMessageW(hwnd, NWG_INIT, 0, 0) };
671            true
672        }
673        WM_CLOSE => {
674            unsafe { ShowWindow(hwnd, SW_HIDE) };
675            true
676        }
677        _ => false,
678    };
679
680    if handled {
681        0
682    } else {
683        unsafe { DefWindowProcW(hwnd, msg, w, l) }
684    }
685}
686
687/**
688    A window subclass procedure that dispatch the windows control events to the associated application control
689*/
690#[allow(unused_variables)]
691extern "system" fn process_events(
692    hwnd: HWND,
693    msg: UINT,
694    w: WPARAM,
695    l: LPARAM,
696    id: UINT_PTR,
697    data: DWORD_PTR,
698) -> LRESULT {
699    use crate::events::*;
700    use std::char;
701
702    use winapi::shared::minwindef::{HIWORD, LOWORD};
703    use winapi::um::commctrl::{DefSubclassProc, TTN_GETDISPINFOW};
704    use winapi::um::shellapi::{
705        NIN_BALLOONHIDE, NIN_BALLOONSHOW, NIN_BALLOONTIMEOUT, NIN_BALLOONUSERCLICK,
706    };
707    use winapi::um::winnt::WCHAR;
708    use winapi::um::winuser::{
709        GET_WHEEL_DELTA_WPARAM, SIZE_MAXIMIZED, SIZE_MINIMIZED, WM_CHAR, WM_CLOSE, WM_COMMAND,
710        WM_CONTEXTMENU, WM_DROPFILES, WM_ENTERMENULOOP, WM_ENTERSIZEMOVE, WM_EXITMENULOOP,
711        WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_HSCROLL, WM_INITMENUPOPUP, WM_KEYDOWN, WM_KEYUP,
712        WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MENUCOMMAND, WM_MENUSELECT, WM_MOUSEMOVE, WM_MOUSEWHEEL,
713        WM_MOVE, WM_NOTIFY, WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN,
714        WM_SYSKEYUP, WM_TIMER, WM_VSCROLL,
715    };
716    use winapi::um::winuser::{GetClassNameW, GetMenuItemID, GetSubMenu};
717
718    let callback_ptr = data as *mut *const Callback;
719    unsafe { Rc::increment_strong_count(*callback_ptr) };
720    let callback = unsafe { Rc::from_raw(*callback_ptr) };
721    let callback = &*callback;
722
723    let base_handle = ControlHandle::Hwnd(hwnd);
724
725    match msg {
726        WM_KEYDOWN | WM_KEYUP | WM_SYSKEYDOWN | WM_SYSKEYUP => {
727            let evt = match msg {
728                WM_SYSKEYDOWN => Event::OnSysKeyPress,
729                WM_SYSKEYUP=> Event::OnSysKeyRelease,
730                WM_KEYDOWN => Event::OnKeyPress,
731                _ /* WM_KEYUP */ => Event::OnKeyRelease,
732            };
733
734            // Block the textbox ESC key from closing the whole application
735            if w == 27 {
736                if is_textbox_control(hwnd) {
737                    return 0;
738                }
739            }
740
741            let keycode = w as u32;
742            let data = EventData::OnKey(keycode);
743            callback(evt, data, base_handle);
744        }
745        WM_NOTIFY => {
746            let code = {
747                let notif_ptr = l as *mut NMHDR;
748                unsafe { (&*notif_ptr).code }
749            };
750
751            match code {
752                TTN_GETDISPINFOW => handle_tooltip_callback(l as *mut NMTTDISPINFOW, callback),
753                _ => handle_default_notify_callback(l as *const NMHDR, callback),
754            }
755        }
756        WM_MENUCOMMAND => {
757            let parent_handle = l as HMENU;
758            let item_id = unsafe { GetMenuItemID(parent_handle, w as i32) };
759            let handle = ControlHandle::MenuItem(parent_handle, item_id);
760            callback(Event::OnMenuItemSelected, NO_DATA, handle);
761        }
762        WM_INITMENUPOPUP => {
763            callback(
764                Event::OnMenuOpen,
765                NO_DATA,
766                ControlHandle::Menu(ptr::null_mut(), w as HMENU),
767            );
768        }
769        WM_ENTERMENULOOP => {
770            callback(
771                Event::OnMenuEnter,
772                NO_DATA,
773                ControlHandle::Menu(ptr::null_mut(), w as HMENU),
774            );
775        }
776        WM_EXITMENULOOP => {
777            callback(
778                Event::OnMenuExit,
779                NO_DATA,
780                ControlHandle::Menu(ptr::null_mut(), w as HMENU),
781            );
782        }
783        WM_MOUSEWHEEL => {
784            callback(
785                Event::OnMouseWheel,
786                EventData::OnMouseWheel(GET_WHEEL_DELTA_WPARAM(w) as i32),
787                base_handle,
788            );
789        }
790        WM_MENUSELECT => {
791            let index = LOWORD(w as u32) as u32;
792            let parent = l as HMENU;
793            if index < CUSTOM_ID_BEGIN {
794                // Item is a sub menu
795                callback(
796                    Event::OnMenuHover,
797                    NO_DATA,
798                    ControlHandle::Menu(parent, unsafe { GetSubMenu(parent, index as i32) }),
799                );
800            } else {
801                // Item is a menu item
802                callback(
803                    Event::OnMenuHover,
804                    NO_DATA,
805                    ControlHandle::MenuItem(parent, index),
806                );
807            }
808        }
809        WM_COMMAND => {
810            let child_handle: HWND = l as HWND;
811            let message = HIWORD(w as u32) as u16;
812            let handle = ControlHandle::Hwnd(child_handle);
813
814            // Converting the class name into rust string might not be the most efficient way to do this
815            // It might be a good idea to just compare the class_name_raw
816            let mut class_name_raw: [WCHAR; 100] = [0; 100];
817            let count =
818                unsafe { GetClassNameW(child_handle, class_name_raw.as_mut_ptr(), 100) as usize };
819            let class_name = OsString::from_wide(&class_name_raw[..count])
820                .into_string()
821                .unwrap_or("".to_string());
822
823            match &class_name as &str {
824                "Button" => callback(button_commands(message), NO_DATA, handle),
825                "Edit" => callback(edit_commands(message), NO_DATA, handle),
826                "ComboBox" => callback(combo_commands(message), NO_DATA, handle),
827                "Static" => callback(static_commands(child_handle, message), NO_DATA, handle),
828                "ListBox" => callback(listbox_commands(message), NO_DATA, handle),
829                _ => match w as i32 {
830                    IDOK | IDCANCEL => callback(no_class_name_commands(w), NO_DATA, base_handle),
831                    _ => {}
832                },
833            }
834        }
835        WM_CONTEXTMENU => {
836            let target_handle = w as HWND;
837            let handle = ControlHandle::Hwnd(target_handle);
838            callback(Event::OnContextMenu, NO_DATA, handle);
839        }
840        NWG_TRAY => {
841            let msg = LOWORD(l as u32) as u32;
842            let handle = ControlHandle::SystemTray(hwnd);
843
844            match msg {
845                NIN_BALLOONSHOW => callback(Event::OnTrayNotificationShow, NO_DATA, handle),
846                NIN_BALLOONHIDE => callback(Event::OnTrayNotificationHide, NO_DATA, handle),
847                NIN_BALLOONTIMEOUT => callback(Event::OnTrayNotificationTimeout, NO_DATA, handle),
848                NIN_BALLOONUSERCLICK => {
849                    callback(Event::OnTrayNotificationUserClose, NO_DATA, handle)
850                }
851                WM_LBUTTONUP => callback(
852                    Event::OnMousePress(MousePressEvent::MousePressLeftUp),
853                    NO_DATA,
854                    handle,
855                ),
856                WM_LBUTTONDOWN => callback(
857                    Event::OnMousePress(MousePressEvent::MousePressLeftDown),
858                    NO_DATA,
859                    handle,
860                ),
861                WM_RBUTTONUP => {
862                    callback(
863                        Event::OnMousePress(MousePressEvent::MousePressRightUp),
864                        NO_DATA,
865                        handle,
866                    );
867                    callback(Event::OnContextMenu, NO_DATA, handle);
868                }
869                WM_RBUTTONDOWN => callback(
870                    Event::OnMousePress(MousePressEvent::MousePressRightDown),
871                    NO_DATA,
872                    handle,
873                ),
874                WM_MOUSEMOVE => callback(Event::OnMouseMove, NO_DATA, handle),
875                _ => {}
876            }
877        }
878        WM_SIZE => match w {
879            SIZE_MAXIMIZED => callback(Event::OnWindowMaximize, NO_DATA, base_handle),
880            SIZE_MINIMIZED => callback(Event::OnWindowMinimize, NO_DATA, base_handle),
881            _ => callback(Event::OnResize, NO_DATA, base_handle),
882        },
883        WM_PAINT => {
884            let data = EventData::OnPaint(PaintData { hwnd });
885            callback(Event::OnPaint, data, base_handle)
886        }
887        WM_DROPFILES => {
888            let data = EventData::OnFileDrop(DropFiles { drop: w as _ });
889            callback(Event::OnFileDrop, data, base_handle)
890        }
891        WM_GETMINMAXINFO => {
892            let data = EventData::OnMinMaxInfo(MinMaxInfo { inner: l as _ });
893            callback(Event::OnMinMaxInfo, data, base_handle)
894        }
895        WM_CHAR => callback(
896            Event::OnChar,
897            EventData::OnChar(char::from_u32(w as u32).unwrap_or('?')),
898            base_handle,
899        ),
900        WM_EXITSIZEMOVE => callback(Event::OnResizeEnd, NO_DATA, base_handle),
901        WM_ENTERSIZEMOVE => callback(Event::OnResizeBegin, NO_DATA, base_handle),
902        WM_TIMER => callback(
903            Event::OnTimerTick,
904            NO_DATA,
905            ControlHandle::Timer(hwnd, w as u32),
906        ),
907        WM_MOVE => callback(Event::OnMove, NO_DATA, base_handle),
908        WM_HSCROLL => callback(
909            Event::OnHorizontalScroll,
910            NO_DATA,
911            ControlHandle::Hwnd(l as HWND),
912        ),
913        WM_VSCROLL => callback(
914            Event::OnVerticalScroll,
915            NO_DATA,
916            ControlHandle::Hwnd(l as HWND),
917        ),
918        WM_MOUSEMOVE => callback(Event::OnMouseMove, NO_DATA, base_handle),
919        WM_LBUTTONUP => callback(
920            Event::OnMousePress(MousePressEvent::MousePressLeftUp),
921            NO_DATA,
922            base_handle,
923        ),
924        WM_LBUTTONDOWN => callback(
925            Event::OnMousePress(MousePressEvent::MousePressLeftDown),
926            NO_DATA,
927            base_handle,
928        ),
929        WM_RBUTTONUP => callback(
930            Event::OnMousePress(MousePressEvent::MousePressRightUp),
931            NO_DATA,
932            base_handle,
933        ),
934        WM_RBUTTONDOWN => callback(
935            Event::OnMousePress(MousePressEvent::MousePressRightDown),
936            NO_DATA,
937            base_handle,
938        ),
939        WM_SETTINGCHANGE => {
940            let str = cwstr_to_str(l);
941            match str.as_str() {
942                "ImmersiveColorSet" => {
943                    if update_dark_mode_for_window(hwnd).is_err() {
944                        println!(
945                            "enable_dark_mode_for_window failed, is the Windows version too old?"
946                        );
947                    }
948                }
949                _ => {}
950            }
951        }
952        NOTICE_MESSAGE => callback(
953            Event::OnNotice,
954            NO_DATA,
955            ControlHandle::Notice(hwnd, w as u32),
956        ),
957        NWG_TIMER_STOP => callback(
958            Event::OnTimerStop,
959            NO_DATA,
960            ControlHandle::Timer(hwnd, w as u32),
961        ),
962        NWG_TIMER_TICK => callback(
963            Event::OnTimerTick,
964            NO_DATA,
965            ControlHandle::Timer(hwnd, w as u32),
966        ),
967        NWG_INIT => {
968            if update_dark_mode_for_window(hwnd).is_err() {
969                println!("enable_dark_mode_for_window failed, is the Windows version too old?");
970            }
971            callback(Event::OnInit, NO_DATA, base_handle)
972        }
973        WM_CLOSE => {
974            let mut should_exit = true;
975            let data = EventData::OnWindowClose(WindowCloseData {
976                data: &mut should_exit as *mut bool,
977            });
978            callback(Event::OnWindowClose, data, base_handle);
979
980            if !should_exit {
981                return 0;
982            }
983        }
984        _ => {}
985    }
986
987    unsafe { DefSubclassProc(hwnd, msg, w, l) }
988}
989
990/**
991    A window subclass procedure that dispatch the windows control events to the associated application control
992*/
993#[allow(unused_variables)]
994extern "system" fn process_raw_events(
995    hwnd: HWND,
996    msg: UINT,
997    w: WPARAM,
998    l: LPARAM,
999    id: UINT_PTR,
1000    data: DWORD_PTR,
1001) -> LRESULT {
1002    let callback_wrapper_ptr = data as *mut *mut RawCallback;
1003    let callback: Box<RawCallback> = unsafe { Box::from_raw(*callback_wrapper_ptr) };
1004
1005    let result = callback(hwnd, msg, w, l);
1006    let _ = Box::into_raw(callback);
1007
1008    match result {
1009        Some(r) => r,
1010        None => unsafe { ::winapi::um::commctrl::DefSubclassProc(hwnd, msg, w, l) },
1011    }
1012}
1013
1014fn button_commands(m: u16) -> Event {
1015    use winapi::um::winuser::{BN_CLICKED, BN_DBLCLK};
1016    match m {
1017        BN_CLICKED => Event::OnButtonClick,
1018        BN_DBLCLK => Event::OnButtonDoubleClick,
1019        _ => Event::Unknown,
1020    }
1021}
1022
1023fn edit_commands(m: u16) -> Event {
1024    use winapi::um::winuser::EN_CHANGE;
1025
1026    match m {
1027        EN_CHANGE => Event::OnTextInput,
1028        _ => Event::Unknown,
1029    }
1030}
1031
1032fn combo_commands(m: u16) -> Event {
1033    use winapi::um::winuser::{CBN_CLOSEUP, CBN_DROPDOWN, CBN_SELCHANGE};
1034    match m {
1035        CBN_CLOSEUP => Event::OnComboBoxClosed,
1036        CBN_DROPDOWN => Event::OnComboBoxDropdown,
1037        CBN_SELCHANGE => Event::OnComboxBoxSelection,
1038        _ => Event::Unknown,
1039    }
1040}
1041
1042fn datetimepick_commands(m: u32) -> Event {
1043    use winapi::um::commctrl::{DTN_CLOSEUP, DTN_DATETIMECHANGE, DTN_DROPDOWN};
1044    match m {
1045        DTN_CLOSEUP => Event::OnDatePickerClosed,
1046        DTN_DROPDOWN => Event::OnDatePickerDropdown,
1047        DTN_DATETIMECHANGE => Event::OnDatePickerChanged,
1048        _ => Event::Unknown,
1049    }
1050}
1051
1052fn tabs_commands(m: u32) -> Event {
1053    use winapi::um::commctrl::{TCN_SELCHANGE, TCN_SELCHANGING};
1054    match m {
1055        TCN_SELCHANGE => Event::TabsContainerChanged,
1056        TCN_SELCHANGING => Event::TabsContainerChanging,
1057        _ => Event::Unknown,
1058    }
1059}
1060
1061fn track_commands(m: u32) -> Event {
1062    use winapi::um::commctrl::NM_RELEASEDCAPTURE;
1063
1064    match m {
1065        NM_RELEASEDCAPTURE => Event::TrackBarUpdated,
1066        _ => Event::Unknown,
1067    }
1068}
1069
1070fn tree_commands(m: u32) -> Event {
1071    use winapi::um::commctrl::{
1072        NM_CLICK, NM_DBLCLK, NM_KILLFOCUS, NM_RCLICK, NM_SETFOCUS, TVN_BEGINLABELEDITW,
1073        TVN_DELETEITEMW, TVN_ENDLABELEDITW, TVN_ITEMCHANGEDW, TVN_ITEMEXPANDEDW, TVN_SELCHANGEDW,
1074    };
1075
1076    match m {
1077        NM_CLICK => Event::OnTreeViewClick,
1078        NM_DBLCLK => Event::OnTreeViewDoubleClick,
1079        NM_KILLFOCUS => Event::OnTreeFocusLost,
1080        NM_SETFOCUS => Event::OnTreeFocus,
1081        NM_RCLICK => Event::OnTreeViewRightClick,
1082        TVN_DELETEITEMW => Event::OnTreeItemDelete,
1083        TVN_ITEMEXPANDEDW => Event::OnTreeItemExpanded,
1084        TVN_SELCHANGEDW => Event::OnTreeItemSelectionChanged,
1085        TVN_ITEMCHANGEDW => Event::OnTreeItemChanged,
1086        TVN_BEGINLABELEDITW => Event::OnTreeViewBeginItemEdit,
1087        TVN_ENDLABELEDITW => Event::OnTreeViewEndItemEdit,
1088        _ => Event::Unknown,
1089    }
1090}
1091
1092fn list_view_commands(m: u32) -> Event {
1093    use winapi::um::commctrl::{
1094        LVN_COLUMNCLICK, LVN_DELETEALLITEMS, LVN_DELETEITEM, LVN_INSERTITEM, LVN_ITEMACTIVATE,
1095        LVN_ITEMCHANGED, NM_CLICK, NM_DBLCLK, NM_KILLFOCUS, NM_RCLICK, NM_SETFOCUS,
1096    };
1097
1098    match m {
1099        NM_CLICK => Event::OnListViewClick,
1100        NM_DBLCLK => Event::OnListViewDoubleClick,
1101        NM_RCLICK => Event::OnListViewRightClick,
1102        LVN_COLUMNCLICK => Event::OnListViewColumnClick,
1103        LVN_DELETEALLITEMS => Event::OnListViewClear,
1104        LVN_DELETEITEM => Event::OnListViewItemRemoved,
1105        LVN_INSERTITEM => Event::OnListViewItemInsert,
1106        LVN_ITEMACTIVATE => Event::OnListViewItemActivated,
1107        LVN_ITEMCHANGED => Event::OnListViewItemChanged,
1108        NM_KILLFOCUS => Event::OnListViewFocusLost,
1109        NM_SETFOCUS => Event::OnListViewFocus,
1110        _ => Event::Unknown,
1111    }
1112}
1113
1114fn no_class_name_commands(m: usize) -> Event {
1115    match m as i32 {
1116        IDOK => Event::OnKeyEnter,
1117        IDCANCEL => Event::OnKeyEsc,
1118        _ => Event::Unknown,
1119    }
1120}
1121
1122#[cfg(feature = "tree-view")]
1123fn tree_data(m: u32, notif_raw: *const NMHDR) -> EventData {
1124    use crate::{ExpandState, TreeItem, TreeItemAction, TreeItemState};
1125    use winapi::um::commctrl::{
1126        NMTREEVIEWW, NMTVDISPINFOW, NMTVITEMCHANGE, TVE_COLLAPSE, TVE_EXPAND, TVN_DELETEITEMW,
1127        TVN_ENDLABELEDITW, TVN_ITEMCHANGEDW, TVN_ITEMEXPANDEDW, TVN_SELCHANGEDW,
1128    };
1129
1130    match m {
1131        TVN_DELETEITEMW => {
1132            let data = unsafe { &*(notif_raw as *const NMTREEVIEWW) };
1133            let item = TreeItem {
1134                handle: data.itemOld.hItem,
1135            };
1136            EventData::OnTreeItemDelete(item)
1137        }
1138        TVN_ITEMEXPANDEDW => {
1139            let data = unsafe { &*(notif_raw as *const NMTREEVIEWW) };
1140            let item = TreeItem {
1141                handle: data.itemNew.hItem,
1142            };
1143
1144            let action = match data.action as usize {
1145                TVE_COLLAPSE => TreeItemAction::Expand(ExpandState::Collapse),
1146                TVE_EXPAND => TreeItemAction::Expand(ExpandState::Expand),
1147                _ => TreeItemAction::Unknown, // Other values shoudn't be raised by this event
1148            };
1149
1150            EventData::OnTreeItemUpdate { item, action }
1151        }
1152        TVN_SELCHANGEDW => {
1153            let data = unsafe { &*(notif_raw as *const NMTREEVIEWW) };
1154            let new = TreeItem {
1155                handle: data.itemNew.hItem,
1156            };
1157            let old = TreeItem {
1158                handle: data.itemOld.hItem,
1159            };
1160            EventData::OnTreeItemSelectionChanged { old, new }
1161        }
1162        TVN_ITEMCHANGEDW => {
1163            let data = unsafe { &*(notif_raw as *const NMTVITEMCHANGE) };
1164            let item = TreeItem { handle: data.hItem };
1165            let action = TreeItemAction::State {
1166                new: TreeItemState::from_bits_truncate(data.uStateNew),
1167                old: TreeItemState::from_bits_truncate(data.uStateOld),
1168            };
1169            EventData::OnTreeItemUpdate { item, action }
1170        }
1171        TVN_ENDLABELEDITW => {
1172            let data = unsafe { &*(notif_raw as *const NMTVDISPINFOW) };
1173            let new_psztext = data.item.pszText;
1174            if !new_psztext.is_null() {
1175                let new_text_osstr = u16_ptr_to_string(new_psztext);
1176                if let Ok(new_text) = new_text_osstr.into_string() {
1177                    EventData::OnTreeViewEndItemEdit {
1178                        f_cancel: false,
1179                        new_text,
1180                    }
1181                } else {
1182                    EventData::OnTreeViewEndItemEdit {
1183                        f_cancel: false,
1184                        new_text: String::from(""),
1185                    }
1186                }
1187            } else {
1188                EventData::OnTreeViewEndItemEdit {
1189                    f_cancel: true,
1190                    new_text: String::from(""),
1191                }
1192            }
1193        }
1194        _ => NO_DATA,
1195    }
1196}
1197
1198fn u16_ptr_to_string(ptr: *const u16) -> OsString {
1199    let len = (0..)
1200        .take_while(|&i| unsafe { *ptr.offset(i) } != 0)
1201        .count();
1202    let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
1203
1204    OsString::from_wide(slice)
1205}
1206
1207#[cfg(not(feature = "tree-view"))]
1208fn tree_data(_m: u32, _notif_raw: *const NMHDR) -> EventData {
1209    // If tree-view is not enabled, the data type won't be available so we return NO_DATA
1210    NO_DATA
1211}
1212
1213#[cfg(feature = "list-view")]
1214fn list_view_data(m: u32, notif_raw: *const NMHDR) -> EventData {
1215    use winapi::um::commctrl::{
1216        LVIS_SELECTED, LVN_COLUMNCLICK, LVN_DELETEITEM, LVN_INSERTITEM, LVN_ITEMACTIVATE,
1217        LVN_ITEMCHANGED, NM_CLICK, NM_DBLCLK, NM_RCLICK, NMITEMACTIVATE, NMLISTVIEW,
1218    };
1219
1220    match m {
1221        LVN_DELETEITEM | LVN_INSERTITEM | LVN_COLUMNCLICK => {
1222            let data: &NMLISTVIEW = unsafe { &*(notif_raw as *const NMLISTVIEW) };
1223            EventData::OnListViewItemIndex {
1224                row_index: data.iItem as _,
1225                column_index: data.iSubItem as _,
1226            }
1227        }
1228        LVN_ITEMACTIVATE | NM_CLICK | NM_DBLCLK | NM_RCLICK => {
1229            let data: &NMITEMACTIVATE = unsafe { &*(notif_raw as *const NMITEMACTIVATE) };
1230            EventData::OnListViewItemIndex {
1231                row_index: data.iItem as _,
1232                column_index: data.iSubItem as _,
1233            }
1234        }
1235        LVN_ITEMCHANGED => {
1236            let data: &NMLISTVIEW = unsafe { &*(notif_raw as *const NMLISTVIEW) };
1237            EventData::OnListViewItemChanged {
1238                row_index: data.iItem as _,
1239                column_index: data.iSubItem as _,
1240                selected: data.uNewState & LVIS_SELECTED == LVIS_SELECTED,
1241            }
1242        }
1243        _ => NO_DATA,
1244    }
1245}
1246
1247#[cfg(not(feature = "list-view"))]
1248fn list_view_data(_m: u32, _notif_raw: *const NMHDR) -> EventData {
1249    // If list-view is not enabled, the data type won't be available so we return NO_DATA
1250    NO_DATA
1251}
1252
1253fn static_commands(handle: HWND, m: u16) -> Event {
1254    use winapi::um::winuser::SendMessageW;
1255    use winapi::um::winuser::{
1256        IMAGE_BITMAP, IMAGE_CURSOR, IMAGE_ICON, STM_GETIMAGE, STN_CLICKED, STN_DBLCLK,
1257    };
1258
1259    let has_image = unsafe { SendMessageW(handle, STM_GETIMAGE, IMAGE_BITMAP as usize, 0) } != 0;
1260    let has_icon = unsafe { SendMessageW(handle, STM_GETIMAGE, IMAGE_ICON as usize, 0) } != 0;
1261    let has_cursor = unsafe { SendMessageW(handle, STM_GETIMAGE, IMAGE_CURSOR as usize, 0) } != 0;
1262
1263    if has_image | has_icon | has_cursor {
1264        match m {
1265            STN_CLICKED => Event::OnImageFrameClick,
1266            STN_DBLCLK => Event::OnImageFrameDoubleClick,
1267            _ => Event::Unknown,
1268        }
1269    } else {
1270        match m {
1271            STN_CLICKED => Event::OnLabelClick,
1272            STN_DBLCLK => Event::OnLabelDoubleClick,
1273            _ => Event::Unknown,
1274        }
1275    }
1276}
1277
1278fn listbox_commands(m: u16) -> Event {
1279    use winapi::um::winuser::{LBN_DBLCLK, LBN_SELCHANGE};
1280
1281    match m {
1282        LBN_SELCHANGE => Event::OnListBoxSelect,
1283        LBN_DBLCLK => Event::OnListBoxDoubleClick,
1284        _ => Event::Unknown,
1285    }
1286}
1287
1288fn handle_tooltip_callback<'a>(notif: *mut NMTTDISPINFOW, callback: &Callback) {
1289    use crate::events::ToolTipTextData;
1290
1291    let notif = unsafe { &mut *notif };
1292    let handle = ControlHandle::Hwnd(notif.hdr.idFrom as HWND);
1293    let data = EventData::OnTooltipText(ToolTipTextData { data: notif });
1294    callback(Event::OnTooltipText, data, handle);
1295}
1296
1297fn handle_default_notify_callback<'a>(notif_raw: *const NMHDR, callback: &Callback) {
1298    use winapi::um::winnt::WCHAR;
1299    use winapi::um::winuser::GetClassNameW;
1300
1301    let notif = unsafe { &*notif_raw };
1302    let handle = ControlHandle::Hwnd(notif.hwndFrom);
1303
1304    let mut class_name_raw: [WCHAR; 100] = unsafe { mem::zeroed() };
1305    let count = unsafe { GetClassNameW(notif.hwndFrom, class_name_raw.as_mut_ptr(), 100) as usize };
1306    let class_name = OsString::from_wide(&class_name_raw[..count])
1307        .into_string()
1308        .unwrap_or("".to_string());
1309
1310    let code = notif.code;
1311
1312    match &class_name as &str {
1313        "SysDateTimePick32" => callback(datetimepick_commands(code), NO_DATA, handle),
1314        "SysTabControl32" => callback(tabs_commands(code), NO_DATA, handle),
1315        "msctls_trackbar32" => callback(track_commands(code), NO_DATA, handle),
1316        winapi::um::commctrl::WC_TREEVIEW => {
1317            callback(tree_commands(code), tree_data(code, notif_raw), handle)
1318        }
1319        winapi::um::commctrl::WC_LISTVIEW => callback(
1320            list_view_commands(code),
1321            list_view_data(code, notif_raw),
1322            handle,
1323        ),
1324        _ => {}
1325    }
1326}
1327
1328fn is_textbox_control(hwnd: HWND) -> bool {
1329    use winapi::um::winnt::WCHAR;
1330    use winapi::um::winuser::GetClassNameW;
1331
1332    let mut class_name_raw: [WCHAR; 100] = [0; 100];
1333    let count = unsafe { GetClassNameW(hwnd, class_name_raw.as_mut_ptr(), 100) as usize };
1334    let class_name = OsString::from_wide(&class_name_raw[..count])
1335        .into_string()
1336        .unwrap_or("".to_string());
1337
1338    class_name == "Edit" || class_name == "RICHEDIT50W"
1339}
1340
1341//
1342// Hack to make `GetWindowSubclass` work on GNU
1343//
1344
1345#[cfg(target_env = "gnu")]
1346use std::{collections::HashMap, sync::Mutex};
1347use winapi::shared::winerror::HRESULT;
1348use winapi::um::libloaderapi::{
1349    FreeLibrary, GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW,
1350};
1351use winapi::um::winnt::HANDLE;
1352
1353#[cfg(target_env = "gnu")]
1354type SubclassId = (usize, usize, UINT_PTR);
1355
1356#[cfg(target_env = "gnu")]
1357static mut SUBCLASS_COLLECTION: Option<Mutex<HashMap<SubclassId, DWORD_PTR>>> = None;
1358
1359#[cfg(target_env = "gnu")]
1360#[allow(non_snake_case)]
1361unsafe fn GetWindowSubclass(
1362    hwnd: HWND,
1363    proc: SUBCLASSPROC,
1364    uid: UINT_PTR,
1365    data: *mut DWORD_PTR,
1366) -> BOOL {
1367    if SUBCLASS_COLLECTION.is_none() {
1368        SUBCLASS_COLLECTION = Some(Mutex::new(HashMap::new()));
1369    }
1370
1371    let proc_id = match proc {
1372        Some(p) => p as usize,
1373        None => 0,
1374    };
1375
1376    let id = (hwnd as usize, proc_id, uid);
1377    match SUBCLASS_COLLECTION.as_ref() {
1378        Some(collection_mutex) => {
1379            let collection = collection_mutex.lock().unwrap();
1380            match collection.get(&id) {
1381                Some(v) => {
1382                    *data = *v;
1383                    1
1384                }
1385                None => 0,
1386            }
1387        }
1388        None => unreachable!(),
1389    }
1390}
1391
1392#[cfg(target_env = "gnu")]
1393#[allow(non_snake_case)]
1394unsafe fn SetWindowSubclass(
1395    hwnd: HWND,
1396    proc: SUBCLASSPROC,
1397    uid: UINT_PTR,
1398    data: DWORD_PTR,
1399) -> BOOL {
1400    use winapi::um::commctrl::SetWindowSubclass;
1401
1402    if SUBCLASS_COLLECTION.is_none() {
1403        SUBCLASS_COLLECTION = Some(Mutex::new(HashMap::new()));
1404    }
1405
1406    let proc_id = match proc {
1407        Some(p) => p as usize,
1408        None => 0,
1409    };
1410
1411    let id = (hwnd as usize, proc_id, uid);
1412    match SUBCLASS_COLLECTION.as_ref() {
1413        Some(collection_mutex) => {
1414            let mut collection = collection_mutex.lock().unwrap();
1415            collection.insert(id, data);
1416        }
1417        None => unreachable!(),
1418    }
1419
1420    SetWindowSubclass(hwnd, proc, uid, data)
1421}
1422
1423#[cfg(target_env = "gnu")]
1424#[allow(non_snake_case)]
1425unsafe fn RemoveWindowSubclass(hwnd: HWND, proc: SUBCLASSPROC, uid: UINT_PTR) -> BOOL {
1426    use winapi::um::commctrl::RemoveWindowSubclass;
1427
1428    if SUBCLASS_COLLECTION.is_none() {
1429        SUBCLASS_COLLECTION = Some(Mutex::new(HashMap::new()));
1430    }
1431
1432    let proc_id = match proc {
1433        Some(p) => p as usize,
1434        None => 0,
1435    };
1436
1437    let id = (hwnd as usize, proc_id, uid);
1438    match SUBCLASS_COLLECTION.as_ref() {
1439        Some(collection_mutex) => {
1440            let mut collection = collection_mutex.lock().unwrap();
1441            collection.remove(&id);
1442        }
1443        None => unreachable!(),
1444    }
1445
1446    RemoveWindowSubclass(hwnd, proc, uid)
1447}
1448
1449#[cfg(not(target_env = "gnu"))]
1450#[allow(non_snake_case)]
1451fn GetWindowSubclass(hwnd: HWND, proc: SUBCLASSPROC, uid: UINT_PTR, data: *mut DWORD_PTR) -> BOOL {
1452    unsafe {
1453        use winapi::um::commctrl::GetWindowSubclass;
1454
1455        GetWindowSubclass(hwnd, proc, uid, data)
1456    }
1457}
1458
1459#[cfg(not(target_env = "gnu"))]
1460#[allow(non_snake_case)]
1461fn SetWindowSubclass(hwnd: HWND, proc: SUBCLASSPROC, uid: UINT_PTR, data: DWORD_PTR) -> BOOL {
1462    unsafe {
1463        use winapi::um::commctrl::SetWindowSubclass;
1464        SetWindowSubclass(hwnd, proc, uid, data)
1465    }
1466}
1467
1468#[cfg(not(target_env = "gnu"))]
1469#[allow(non_snake_case)]
1470fn RemoveWindowSubclass(hwnd: HWND, proc: SUBCLASSPROC, uid: UINT_PTR) -> BOOL {
1471    unsafe {
1472        use winapi::um::commctrl::RemoveWindowSubclass;
1473        RemoveWindowSubclass(hwnd, proc, uid)
1474    }
1475}