private void JoystickThreadMain() { // get the current state var state = new JoystickState(); try { js.GetCurrentState(ref state); } catch { // An error occurred. The joystick might have been unplugged. // Tell the main loop to rebuild the joystick list, then exit // the thread. win.BeginInvoke((Action) delegate { win.OnJoystickError(this); }); return; } // start with all buttons off var buttons = new ButtonState[state.Buttons.Length].Select(b => new ButtonState()).ToArray(); // wait handles - we wait for a joystick state change or an exit signal var handles = new WaitHandle[] { hWait, exitThreadsEvent }; // figure the key repeat time parameters in milliseconds int keyDelay = (SystemInformation.KeyboardDelay + 1) * 250; int keySpeed = (int)Math.Round(1000.0 / (SystemInformation.KeyboardSpeed + 2.5)); // Key event invoker generator. We need to dispatch events in a loop over // the buttons, so we need to generate the invoker closure with a call to // a nested function. The nested function call captures the current button // index as a parameter to the function; if we didn't do this, the index // in the invoker would bind to the live local index variable in this method, // which can change by the time the handler is actually invoked. Func <int, bool, Action> invoker = (i, repeat) => { var handler = JoystickButtonChanged; return(delegate { if (handler != null) { handler(this, new JoystickEventArgs(i, buttons[i].down, repeat)); } }); }; // monitor the joystick while (true) { // Figure the next timeout time. If any buttons are down, we'll // time out at the earliest auto-repeat time. Otherwise we'll // wait indefinitely. DateTime endTime = buttons.Min(b => b.down ? b.repeat : DateTime.MaxValue); DateTime now = DateTime.Now; int timeout = endTime == DateTime.MaxValue ? Timeout.Infinite : endTime < now ? 0 : (int)(endTime - DateTime.Now).TotalMilliseconds; // wait for an event switch (WaitHandle.WaitAny(handles, timeout)) { case WaitHandle.WaitTimeout: break; case 0: // joystick event JoystickEventHandler handler = JoystickButtonChanged; if (handler != null) { // get the current joystick state try { js.GetCurrentState(ref state); } catch { // An error occurred. The joystick might have been unplugged. // Tell the main loop to rebuild the joystick list, then exit // the thread. win.BeginInvoke((Action) delegate { win.OnJoystickError(this); }); return; } // Ignore events from Pinscape devices when the Pinscape config // tool is running. The device sends special status reports to // the config tool while it's running; these are piggybacked on // the joystick interface, so they look like random garbage to // joystick readers. if (isPinscape && Program.configToolRunning) { break; } // scan for button changes for (int i = 0; i < buttons.Length; ++i) { // if the button has changed, update it and fire an event if (buttons[i].down != state.Buttons[i]) { // Set the new button state internally buttons[i].down = state.Buttons[i]; // If the button is newly down, set the first auto-repeat // interval if (state.Buttons[i]) { buttons[i].repeat = DateTime.Now.AddMilliseconds(keyDelay); } // Fire the event. Note that we need to use Invoke to post the // event to the UI thread. win.BeginInvoke(invoker(i, false)); } } } break; case 1: // thread exit event return; } // Fire any autorepeat key events. Skip this if the config tool is // running, for the same reason we don't send primary key events. if (!(isPinscape && Program.configToolRunning)) { var t = DateTime.Now; for (int i = 0; i < buttons.Length; ++i) { var b = buttons[i]; if (b.down && t >= b.repeat) { // fire the event win.BeginInvoke(invoker(i, true)); // set the next repeat time b.repeat = DateTime.Now.AddMilliseconds(keySpeed); } } } } }