diff --git a/Cargo.toml b/Cargo.toml index 37c3226..b7291fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ name = "demo_hook_opengl3" crate-type = ["cdylib"] [dependencies] +bitflags = "2.5.0" imgui = "0.11" memoffset = "0.9.0" once_cell = { version = "1.18.0", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index fbdcdd5..5ac885b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,8 @@ pub mod inject; pub mod mh; pub(crate) mod renderer; +pub use renderer::msg_filter::MessageFilter; + pub mod util; // Global state objects. @@ -259,10 +261,10 @@ pub trait ImguiRenderLoop { /// Called during the window procedure. fn on_wnd_proc(&self, _hwnd: HWND, _umsg: u32, _wparam: WPARAM, _lparam: LPARAM) {} - /// If this method returns `true`, the WndProc function will not call the - /// procedure of the parent window. - fn should_block_messages(&self, _io: &Io) -> bool { - false + /// Returns the types of window message that + /// you do not want to propagate to the main window + fn message_filter(&self, _io: &Io) -> MessageFilter { + MessageFilter::empty() } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 3297529..ed4cf2f 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,6 +2,7 @@ mod backend; mod input; mod keys; +pub(crate) mod msg_filter; mod pipeline; use imgui::{Context, DrawData}; diff --git a/src/renderer/msg_filter.rs b/src/renderer/msg_filter.rs new file mode 100644 index 0000000..fce2199 --- /dev/null +++ b/src/renderer/msg_filter.rs @@ -0,0 +1,91 @@ +//! This module contains logic for filtering windows messages. + +use bitflags::bitflags; +use windows::Win32::UI::WindowsAndMessaging::*; + +bitflags! { + /// Bitflag for specifying types of window message to be filtered. + /// + /// Return this on [`ImguiRenderLoop::message_filter`](crate::ImguiRenderLoop::message_filter) + /// to filter certain types of window message. + /// + /// You can use bitwise-or to combine multiple flags. + /// + /// Example usage: + /// ```no_run + /// // impl ImguiRenderLoop for ... + /// fn message_filter(&self, _io: &Io) -> MessageFilter { + /// if self.visible { + /// MessageFilter::InputAll | MessageFilter::WindowClose + /// } else { + /// MessageFilter::empty() + /// } + /// } + /// ``` + #[repr(transparent)] + pub struct MessageFilter: u32 { + /// Blocks keyboard input event messages. + const InputKeyboard = 1u32 << 0; + /// Blocks mouse input event message. + const InputMouse = 1u32 << 1; + /// Blocks raw input event messages. + const InputRaw = 1u32 << 2; + + /// Blocks window gain/lose focus event messages. + const WindowFocus = 1u32 << 8; + /// Blocks window control event messages + /// like move, resize, minimize, etc. + const WindowControl = 1u32 << 9; + /// Blocks window close messages. + const WindowClose = 1u32 << 10; + + /// Blocks messages ID from 0 to `WM_USER` - 1 + /// (the range for system-defined messages). + const RangeSystemDefined = 1u32 << 28; + /// Blocks messages ID from `WM_USER` to `WM_APP` - 1 + /// (the range for private window classes like form button). + const RangePrivateReserved = 1u32 << 29; + /// Blocks messages ID from `WM_APP` to 0xBFFF + /// (the range for internal use of user application). + const RangeAppPrivate = 1u32 << 30; + /// Blocks messages ID from 0xC000 to 0xFFFF + /// (the range for registered use between user applications). + const RangeAppRegistered = 1u32 << 31; + + /// Blocks keyboard, mouse, raw input messages. + const InputAll = Self::InputKeyboard.bits() | Self::InputMouse.bits() | Self::InputRaw.bits(); + /// Blocks window focus, control, close messages. + const WindowAll = Self::WindowFocus.bits() | Self::WindowControl.bits() | Self::WindowClose.bits(); + } +} + +impl MessageFilter { + /// Check whether the message ID is blocked by this filter + pub(crate) fn is_blocking(&self, message_id: u32) -> bool { + if match message_id { + 0x0000..=0x03FF => self.contains(Self::RangeSystemDefined), + WM_USER..=0x7FFF => self.contains(Self::RangePrivateReserved), + WM_APP..=0xBFFF => self.contains(Self::RangeAppPrivate), + 0xC000..=0xFFFF => self.contains(Self::RangeAppRegistered), + 0x10000.. => return false, + } { + return true; + } + + match message_id { + WM_KEYFIRST..=WM_KEYLAST => self.contains(Self::InputKeyboard), + WM_MOUSEFIRST..=WM_MOUSELAST => self.contains(Self::InputMouse), + WM_INPUT => self.contains(Self::InputRaw), + + WM_MOUSEACTIVATE | WM_ACTIVATEAPP | WM_ACTIVATE | WM_SETFOCUS | WM_KILLFOCUS + | WM_ENABLE => self.contains(Self::WindowFocus), + + WM_SYSCOMMAND | WM_GETMINMAXINFO | WM_ENTERSIZEMOVE | WM_EXITSIZEMOVE + | WM_WINDOWPOSCHANGING | WM_WINDOWPOSCHANGED | WM_SHOWWINDOW | WM_MOVING | WM_MOVE + | WM_SIZING | WM_SIZE => self.contains(Self::WindowControl), + + WM_CLOSE => self.contains(Self::WindowClose), + _ => false, + } + } +} diff --git a/src/renderer/pipeline.rs b/src/renderer/pipeline.rs index c45bf17..020f90b 100644 --- a/src/renderer/pipeline.rs +++ b/src/renderer/pipeline.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::mem; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::Arc; @@ -16,7 +16,7 @@ use windows::Win32::UI::WindowsAndMessaging::{ use crate::renderer::input::{imgui_wnd_proc_impl, WndProcType}; use crate::renderer::RenderEngine; -use crate::{util, ImguiRenderLoop}; +use crate::{util, ImguiRenderLoop, MessageFilter}; type RenderLoop = Box; @@ -32,7 +32,7 @@ pub(crate) struct PipelineMessage( ); pub(crate) struct PipelineSharedState { - pub(crate) should_block_events: AtomicBool, + pub(crate) message_filter: AtomicU32, pub(crate) wnd_proc: WndProcType, pub(crate) tx: Sender, } @@ -79,7 +79,7 @@ impl Pipeline { let (tx, rx) = mpsc::channel(); let shared_state = Arc::new(PipelineSharedState { - should_block_events: AtomicBool::new(false), + message_filter: AtomicU32::new(MessageFilter::empty().bits()), wnd_proc, tx, }); @@ -108,9 +108,9 @@ impl Pipeline { }); self.queue_buffer.set(queue_buffer).expect("OnceCell should be empty"); - let should_block_events = self.render_loop.should_block_messages(self.ctx.io_mut()); + let message_filter = self.render_loop.message_filter(self.ctx.io()); - self.shared_state.should_block_events.store(should_block_events, Ordering::SeqCst); + self.shared_state.message_filter.store(message_filter.bits(), Ordering::SeqCst); let io = self.ctx.io_mut(); @@ -190,9 +190,10 @@ unsafe extern "system" fn pipeline_wnd_proc( // CONCURRENCY: as the message interpretation now happens out of band, this // expresses the intent as of *before* the current message was received. - let should_block_messages = shared_state.should_block_events.load(Ordering::SeqCst); + let message_filter = + MessageFilter::from_bits_retain(shared_state.message_filter.load(Ordering::SeqCst)); - if should_block_messages { + if message_filter.is_blocking(msg) { LRESULT(1) } else { CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam) diff --git a/tests/hook.rs b/tests/hook.rs index b3130c7..9f3f85c 100644 --- a/tests/hook.rs +++ b/tests/hook.rs @@ -3,7 +3,7 @@ use std::io::Cursor; use std::sync::Mutex; use std::time::{Duration, Instant}; -use hudhook::{ImguiRenderLoop, RenderContext}; +use hudhook::{ImguiRenderLoop, MessageFilter, RenderContext}; use image::imageops::FilterType; use image::io::Reader as ImageReader; use image::{DynamicImage, EncodableLayout, RgbaImage}; @@ -59,6 +59,7 @@ pub struct HookExample { image_pos: [[f32; 2]; IMAGE_COUNT], image_vel: [[f32; 2]; IMAGE_COUNT], last_upload_time: u64, + main_window_movable: bool, } impl HookExample { @@ -87,6 +88,7 @@ impl HookExample { image_pos: [[16.0, 16.0], [16.0, 16.0], [16.0, 16.0]], image_vel: [[200.0, 200.0], [100.0, 200.0], [200.0, 100.0]], last_upload_time: 0, + main_window_movable: true, } } } @@ -184,6 +186,11 @@ impl ImguiRenderLoop for HookExample { ui.text(format!("Avg: {:8.2}", avg.as_secs_f64() * 1000.)); ui.text(format!("FPS: {:8.2}", 1. / last.as_secs_f64())); ui.text(format!("Avg: {:8.2}", 1. / avg.as_secs_f64())); + ui.separator(); + ui.text(format!("Main window movable: {}", self.main_window_movable)); + if ui.button("Toggle movability") { + self.main_window_movable ^= true; + } }); ui.window("Image") @@ -218,4 +225,12 @@ impl ImguiRenderLoop for HookExample { } }); } + + fn message_filter(&self, _io: &imgui::Io) -> MessageFilter { + if self.main_window_movable { + MessageFilter::empty() + } else { + MessageFilter::WindowControl + } + } }