Sunrise
September 18, 2024, 8:04pm
1
I read the win32 api and tried to write the following code but sadly it didn t work. Maybe someone can come in to help me.
@[Link("User32")]
lib LibUser32
alias WORD = UInt16
alias UINT = UInt32
alias DWORD = UInt32
alias ULONG_PTR = LibC::ULong
union LPINPUT
ki : KEYBDINPUT
end
struct INPUT
type : DWORD
input : LPINPUT
end
struct KEYBDINPUT
wVk : WORD
wScan : WORD
dwFlags : DWORD
time : DWORD
dwExtraInfo : ULONG_PTR
end
fun SendInput = "SendInput"(cInputs : UINT, pInputs : INPUT*, cbSize : Int32) : UINT
end
def press_key(key : UInt16)
inputs = [] of LibUser32::INPUT
# Key down
inputs << LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 0,
time: 0,
dwExtraInfo: 0
)
)
)
# Key up
inputs << LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 2, # KEYEVENTF_KEYUP
time: 0,
dwExtraInfo: 0
)
)
)
# Send inputs
LibUser32.SendInput(inputs.size.to_u32, inputs.to_unsafe, sizeof(LibUser32::INPUT))
end
keys = [87_u16, 83_u16, 65_u16, 68_u16] # Virtual key codes for 'w', 's', 'a', 'd'
10.times do
keys.each do |key|
press_key(key)
sleep 0.1
end
end
aiac
September 19, 2024, 7:20am
2
You should add MOUSEINPUT
and HAREWAREINPUT
definitions to get correct size of LPINPUT
@[Link("User32")]
lib LibUser32
alias WORD = UInt16
alias UINT = UInt32
alias DWORD = UInt32
alias ULONG_PTR = LibC::ULONG_PTR
alias LONG = LibC::LONG
@[Extern]
union LPINPUT
ki : KEYBDINPUT
mi : MOUSEINPUT
hi : HARDWAREINPUT
end
@[Extern]
struct INPUT
type : DWORD
input : LPINPUT
end
@[Extern]
struct KEYBDINPUT
wVk : WORD
wScan : WORD
dwFlags : DWORD
time : DWORD
dwExtraInfo : ULONG_PTR
end
@[Extern]
struct MOUSEINPUT
dx : LONG
dy : LONG
mouseData : DWORD
dwFlags : DWORD
time : DWORD
dwExtraInfo : ULONG_PTR
end
@[Extern]
struct HARDWAREINPUT
uMsg : DWORD
uParaml : WORD
uParamH : WORD
end
fun SendInput = "SendInput"(cInputs : UINT, pInputs : INPUT*, cbSize : Int32) : UINT
end
def press_key(key : UInt16)
inputs = [] of LibUser32::INPUT
# Key down
inputs << LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 0,
time: 0,
dwExtraInfo: 0
)
)
)
# Key up
inputs << LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 2, # KEYEVENTF_KEYUP
time: 0,
dwExtraInfo: 0
)
)
)
# Send inputs
LibUser32.SendInput(inputs.size.to_u32, inputs.to_unsafe, sizeof(LibUser32::INPUT))
end
keys = [87_u16, 83_u16, 65_u16, 68_u16] # Virtual key codes for 'w', 's', 'a', 'd'
10.times do
keys.each do |key|
puts "press #{key}"
puts "return #{press_key(key)}"
puts "last error: #{LibC.GetLastError}"
sleep 0.1
end
end
1 Like
Sunrise
September 19, 2024, 8:25am
3
Cool, that works. I really appreciate it.
Sunrise
September 19, 2024, 11:05am
4
I ran into a new issue when trying to simplify press_key
. The error code is 258(WAIT_TIMEOUT), what could be the reason?
def click_key(key : UInt16)
# inputs = Array.new(2, LibUser32::INPUT.new)
inputs = Slice.new(2, LibUser32::INPUT.new)
# Key down event
inputs[0].type = 1
inputs[0].input.ki.wVk = key
# Key up event
inputs[1].type = 1
inputs[1].input.ki.wVk = key
inputs[1].input.ki.dwFlags = 2 # Key up flag (KEYEVENTF_KEYUP)
# LibUser32.SendInput(2, inputs.to_unsafe, sizeof(LibUser32::INPUT))
LibUser32.SendInput(2, inputs, sizeof(LibUser32::INPUT))
end
aiac
September 19, 2024, 11:28am
5
you can add pp! inputs
to check if correct
aiac
September 19, 2024, 12:21pm
6
def click_key(key : UInt16)
inputs = uninitialized LibUser32::INPUT[2]
inputs[0] =
LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 0,
time: 0,
dwExtraInfo: 0
)
)
)
inputs[1] =
LibUser32::INPUT.new(
type: 1,
input: LibUser32::LPINPUT.new(
ki: LibUser32::KEYBDINPUT.new(
wVk: key,
wScan: 0,
dwFlags: 2, # Key up flag (KEYEVENTF_KEYUP)
time: 0,
dwExtraInfo: 0
)))
LibUser32.SendInput(2, inputs, sizeof(LibUser32::INPUT))
end
Sunrise
September 19, 2024, 1:46pm
7
Oh, thanks again. For some reason, I couldn’t just modify the assigned inputs[0].type
, but I did find a way to make it work without having to define the entire structure.
def click_key(key : UInt16)
input = LibUser32::INPUT.new
input.type = 1
# Key down event
input.input.ki.wVk = key
LibUser32.SendInput(1, pointerof(input), sizeof(LibUser32::INPUT))
# Key up event
input.input.ki.wVk = key
input.input.ki.dwFlags = 2 # Key up flag (KEYEVENTF_KEYUP)
LibUser32.SendInput(1, pointerof(input), sizeof(LibUser32::INPUT))
end
1 Like
You’re dealing with structs that are returned by value (unlike classes that always return a reference aka pointer). This means that inputs[0].type = 1
returns a copy of the input at index 0, then sets copy.type
to 1. The original struct in inputs
at index 0 is left unchanged.
In cases such as these, we must use pointers and enter unsafe land:
input = inputs.to_unsafe + index
input.value.type = 1
The Pointer#value
method is special: it does dereference the pointer but it won’t return a copy, which allows us to operate on the actual value pointed by the pointer.
Now I’m surprised that input.input.ki.wVk = key
is working
2 Likes