Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add MessageFilter to selectively block window message #183

Merged
merged 7 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
}

Expand Down
1 change: 1 addition & 0 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod backend;
mod input;
mod keys;
pub(crate) mod msg_filter;
mod pipeline;

use imgui::{Context, DrawData};
Expand Down
91 changes: 91 additions & 0 deletions src/renderer/msg_filter.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
17 changes: 9 additions & 8 deletions src/renderer/pipeline.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<dyn ImguiRenderLoop + Send + Sync>;

Expand All @@ -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<PipelineMessage>,
}
Expand Down Expand Up @@ -79,7 +79,7 @@ impl<T: RenderEngine> Pipeline<T> {

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,
});
Expand Down Expand Up @@ -108,9 +108,9 @@ impl<T: RenderEngine> Pipeline<T> {
});
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();

Expand Down Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion tests/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
}
}
Loading