I'm working on a free, simple, hopefully multi-platform hotkey launcher tool, written in Ruby.
Right now I'm struggling with some threading issues of the Windows implementation. The rough proof of concept code shown below already works reliably in my day-to-day use:
HotkeyProcessor#process_keypress (footnote 1) gets called by the C code when a key is pressed and subsequently handles the key event.
Problem
However, when process_keypress starts a thread that does some time consuming work (footnote 2), a subtle flaw shows up: The program spends most of its time in the C extension, waiting for get_message (footnote 3) to return - which by design never happens. Ruby threads aren't run in the meanwhile. Thread execution only happens while Ruby-level code is in control, thus only for the few miliseconds after every key event when #process_keypress is active.
Is continually concurrent execution possible in this context? Thanks for any suggestions!
require 'hotkey_processor'
def slowly_do_stuff
5.times { |i| sleep(0.1); print "#{i} " }
end
module HotkeyProcessor
def self.process_keypress(code, down) # footnote 1
print '. '
slowly_do_stuff if code == 65 and down # A pressed
Thread.new {slowly_do_stuff} if code == 66 and down # B pressed # footnote 2
true
end
end
puts 'Press A, B and other keys.'
HotkeyProcessor.start
HotkeyProcessor:
#include "ruby.h"
#include "windows.h"
#include <stdio.h>
int process_keypress_function;
VALUE HotkeyProcessor;
HHOOK keyboard_hook;
LRESULT CALLBACK callback_function(int Code, WPARAM wParam, LPARAM lParam)
{
PKBDLLHOOKSTRUCT kbd = (PKBDLLHOOKSTRUCT)lParam;
if (Code < 0 || Qtrue == rb_funcall(HotkeyProcessor,
process_keypress_function,
2,
INT2NUM(kbd->vkCode), /* code */
(wParam == WM_KEYDOWN ||
wParam == WM_SYSKEYDOWN) ?
Qtrue : Qfalse)) /* down */
{
return CallNextHookEx(keyboard_hook, Code, wParam, lParam);
}
else
return 1;
}
static BOOL get_message(MSG *msg)
{
return GetMessage(msg, 0, 0, 0);
}
static VALUE start(VALUE self)
{
HMODULE Module = GetModuleHandle(NULL);
MSG msg;
keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL,
(HOOKPROC) callback_function,
Module,
0);
get_message(&msg); /* footnote 3 */
/* Never get here. Wait forever for get_message() to return
to keep the program alive and responding to callbacks. */
return self;
}
static VALUE process_keypress(VALUE self, VALUE code)
{
return Qnil;
}
void Init_hotkey_processor() {
HotkeyProcessor = rb_define_module("HotkeyProcessor");
rb_define_module_function(HotkeyProcessor,
"process_keypress",
process_keypress,
1);
rb_define_module_function(HotkeyProcessor,
"start",
start,
0);
process_keypress_function = rb_intern("process_keypress");
}