#region --- License --- /* Copyright (c) 2007 Stefanos Apostolopoulos * See license.txt for license information */ #endregion using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using OpenTK.Input; using System.Diagnostics; namespace OpenTK.Platform.Windows { /// /// Input driver for legacy (pre XP) Windows platforms. /// internal sealed class WMInput : NativeWindow, IInputDriver { // Driver supports only one keyboard and mouse; KeyboardDevice keyboard = new KeyboardDevice(); MouseDevice mouse = new MouseDevice(); IList keyboards = new List(1); IList mice = new List(1); internal static readonly WinKeyMap KeyMap = new WinKeyMap(); // Used to distinguish left and right control, alt and enter keys. const long ExtendedBit = 1 << 24; // Used to distinguish left and right shift keys. static readonly uint ShiftRightScanCode = Functions.MapVirtualKey(VirtualKeys.RSHIFT, 0); #region --- Constructor --- public WMInput(WinWindowInfo parent) { Debug.WriteLine("Initalizing WMInput driver."); Debug.Indent(); AssignHandle(parent.WindowHandle); Debug.Print("Input window attached to parent {0}", parent); Debug.Unindent(); keyboard.Description = "Standard Windows keyboard"; keyboard.NumberOfFunctionKeys = 12; keyboard.NumberOfKeys = 101; keyboard.NumberOfLeds = 3; mouse.Description = "Standard Windows mouse"; mouse.NumberOfButtons = 3; mouse.NumberOfWheels = 1; keyboards.Add(keyboard); mice.Add(mouse); } #endregion #region protected override void WndProc(ref Message msg) bool mouse_about_to_enter = false; protected override void WndProc(ref Message msg) { UIntPtr lparam, wparam; unsafe { lparam = (UIntPtr)(void*)msg.LParam; wparam = (UIntPtr)(void*)msg.WParam; } switch ((WindowMessage)msg.Msg) { // Mouse events: case WindowMessage.NCMOUSEMOVE: mouse_about_to_enter = true; // Used to simulate a mouse enter event. break; case WindowMessage.MOUSEMOVE: mouse.Position = new System.Drawing.Point( (int)(lparam.ToUInt32() & 0x0000FFFF), (int)(lparam.ToUInt32() & 0xFFFF0000) >> 16); if (mouse_about_to_enter) { Cursor.Current = Cursors.Default; mouse_about_to_enter = false; } return; case WindowMessage.MOUSEWHEEL: // This is due to inconsistent behavior of the WParam value on 64bit arch, whese // wparam = 0xffffffffff880000 or wparam = 0x00000000ff100000 mouse.Wheel += (int)((long)msg.WParam << 32 >> 48) / 120; return; case WindowMessage.LBUTTONDOWN: mouse[MouseButton.Left] = true; return; case WindowMessage.MBUTTONDOWN: mouse[MouseButton.Middle] = true; return; case WindowMessage.RBUTTONDOWN: mouse[MouseButton.Right] = true; return; case WindowMessage.XBUTTONDOWN: mouse[((wparam.ToUInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = true; return; case WindowMessage.LBUTTONUP: mouse[MouseButton.Left] = false; return; case WindowMessage.MBUTTONUP: mouse[MouseButton.Middle] = false; return; case WindowMessage.RBUTTONUP: mouse[MouseButton.Right] = false; return; case WindowMessage.XBUTTONUP: // TODO: Is this correct? mouse[((wparam.ToUInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = false; return; // Keyboard events: case WindowMessage.KEYDOWN: case WindowMessage.KEYUP: case WindowMessage.SYSKEYDOWN: case WindowMessage.SYSKEYUP: bool pressed = (WindowMessage)msg.Msg == WindowMessage.KEYDOWN || (WindowMessage)msg.Msg == WindowMessage.SYSKEYDOWN; // Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed // and released. It looks like neither key is released in this case, or that the wrong key is // released in the case of Control and Alt. // To combat this, we are going to release both keys when either is released. Hacky, but should work. // Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0). // In this case, both keys will be reported as pressed. bool extended = (msg.LParam.ToInt64() & ExtendedBit) != 0; switch ((VirtualKeys)wparam) { case VirtualKeys.SHIFT: // The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit // to distinguish between left and right keys. Moreover, pressing both keys and releasing one // may result in both keys being held down (but not always). // The only reliably way to solve this was reported by BlueMonkMN at the forums: we should // check the scancodes. It looks like GLFW does the same thing, so it should be reliable. // TODO: Not 100% reliable, when both keys are pressed at once. if (ShiftRightScanCode != 0) { unchecked { if (((lparam.ToUInt32() >> 16) & 0xFF) == ShiftRightScanCode) keyboard[Input.Key.ShiftRight] = pressed; else keyboard[Input.Key.ShiftLeft] = pressed; } } else { // Should only fall here on Windows 9x and NT4.0- keyboard[Input.Key.ShiftLeft] = pressed; } return; case VirtualKeys.CONTROL: if (extended) keyboard[Input.Key.ControlRight] = pressed; else keyboard[Input.Key.ControlLeft] = pressed; return; case VirtualKeys.MENU: if (extended) keyboard[Input.Key.AltRight] = pressed; else keyboard[Input.Key.AltLeft] = pressed; return; case VirtualKeys.RETURN: if (extended) keyboard[Key.KeypadEnter] = pressed; else keyboard[Key.Enter] = pressed; return; default: if (!WMInput.KeyMap.ContainsKey((VirtualKeys)msg.WParam)) { Debug.Print("Virtual key {0} ({1}) not mapped.", (VirtualKeys)msg.WParam, (int)msg.WParam); break; } else { keyboard[WMInput.KeyMap[(VirtualKeys)msg.WParam]] = pressed; return; } } break; case WindowMessage.KILLFOCUS: keyboard.ClearKeys(); break; case WindowMessage.DESTROY: Debug.Print("Input window detached from parent {0}.", Handle); ReleaseHandle(); break; case WindowMessage.QUIT: Debug.WriteLine("Input window quit."); this.Dispose(); break; } base.WndProc(ref msg); } #endregion #region IInputDriver Members public void Poll() { // No polling needed. } #endregion #region IKeyboardDriver Members public IList Keyboard { get { return keyboards; } } #endregion #region IMouseDriver Members public IList Mouse { get { return mice; } } #endregion #region --- IDisposable Members --- private bool disposed; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool manual) { if (!disposed) { if (manual) this.ReleaseHandle(); disposed = true; } } ~WMInput() { Dispose(false); } #endregion } }