From 1fc495a06f2679ac1806e29eab947bb775729e72 Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Wed, 24 Nov 2010 23:49:40 +0000 Subject: [PATCH] Implemented new multi-mouse API on Mac OS X. --- Source/OpenTK/IntPtrEqualityComparer.cs | 49 ++++ Source/OpenTK/OpenTK.csproj | 3 + Source/OpenTK/Platform/MacOS/HIDInput.cs | 301 ++++++++++++++++++++--- 3 files changed, 319 insertions(+), 34 deletions(-) create mode 100755 Source/OpenTK/IntPtrEqualityComparer.cs diff --git a/Source/OpenTK/IntPtrEqualityComparer.cs b/Source/OpenTK/IntPtrEqualityComparer.cs new file mode 100755 index 00000000..6ddb2472 --- /dev/null +++ b/Source/OpenTK/IntPtrEqualityComparer.cs @@ -0,0 +1,49 @@ +#region License +// +// The Open Toolkit Library License +// +// Copyright (c) 2006 - 2010 the Open Toolkit library. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +#endregion + +using System; +using System.Collections.Generic; + +namespace OpenTK +{ + // Simple equality comparer to allow IntPtrs as keys in dictionaries + // without causing boxing/garbage generation. + // Seriously, Microsoft, shouldn't this have been in the BCL out of the box? + class IntPtrEqualityComparer : IEqualityComparer + { + public bool Equals(IntPtr x, IntPtr y) + { + return x == y; + } + + public int GetHashCode(IntPtr obj) + { + return obj.GetHashCode(); + } + } +} + diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 1db398dc..9cd3f603 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -57,6 +57,7 @@ 4 AllRules.ruleset full + true true @@ -80,6 +81,7 @@ 4 true TRACE; + true true @@ -766,6 +768,7 @@ Always + diff --git a/Source/OpenTK/Platform/MacOS/HIDInput.cs b/Source/OpenTK/Platform/MacOS/HIDInput.cs index 292ac438..dabcc218 100755 --- a/Source/OpenTK/Platform/MacOS/HIDInput.cs +++ b/Source/OpenTK/Platform/MacOS/HIDInput.cs @@ -36,9 +36,13 @@ namespace OpenTK.Platform.MacOS using Carbon; using CFAllocatorRef = System.IntPtr; using CFDictionaryRef = System.IntPtr; + using CFIndex = System.IntPtr; using CFRunLoop = System.IntPtr; using CFString = System.IntPtr; + using CFStringRef = System.IntPtr; // Here used interchangeably with the CFString + using CFTypeRef = System.IntPtr; using IOHIDDeviceRef = System.IntPtr; + using IOHIDElementRef = System.IntPtr; using IOHIDManagerRef = System.IntPtr; using IOHIDValueRef = System.IntPtr; using IOOptionBits = System.IntPtr; @@ -49,40 +53,19 @@ namespace OpenTK.Platform.MacOS #region Fields readonly IOHIDManagerRef hidmanager; - //readonly static CFRunLoop RunLoop = CF.CFRunLoopGetCurrent(); - //readonly static CFString InputLoopMode = CF.CFSTR("opentkInputMode"); - readonly static CFRunLoop RunLoop = CF.CFRunLoopGetMain(); - readonly static CFString InputLoopMode = CF.RunLoopModeDefault; - readonly static CFDictionary DeviceTypes = new CFDictionary(); + readonly Dictionary MouseDevices = + new Dictionary(new IntPtrEqualityComparer()); + readonly Dictionary MouseIndexToDevice = + new Dictionary(); - readonly static NativeMethods.IOHIDDeviceCallback HandleDeviceAdded = delegate( - IntPtr context, IOReturn res, IntPtr sender, IOHIDDeviceRef device) - { - Debug.Print("Device {0} discovered", device); + readonly CFRunLoop RunLoop = CF.CFRunLoopGetMain(); + readonly CFString InputLoopMode = CF.RunLoopModeDefault; + readonly CFDictionary DeviceTypes = new CFDictionary(); - // IOReturn.Zero is kIOReturnSuccess - if (NativeMethods.IOHIDDeviceOpen(device, IOOptionBits.Zero) == IOReturn.Zero) - { - Debug.Print("Device {0} connected", device); - - NativeMethods.IOHIDDeviceRegisterInputValueCallback(device, HandleValue, IntPtr.Zero); - NativeMethods.IOHIDDeviceScheduleWithRunLoop(device, - RunLoop, InputLoopMode); - } - }; - readonly static NativeMethods.IOHIDDeviceCallback HandleDeviceRemoved = delegate( - IntPtr context, IOReturn res, IntPtr sender, IOHIDDeviceRef device) - { - Debug.Print("Device {0} disconnected", device); - NativeMethods.IOHIDDeviceRegisterInputValueCallback(device, null, IntPtr.Zero); - //NativeMethods.IOHIDDeviceScheduleWithRunLoop(device, IntPtr.Zero, IntPtr.Zero); - }; - readonly static NativeMethods.IOHIDValueCallback HandleValue = delegate( - IntPtr context, IOReturn res, IntPtr sender, IOHIDValueRef val) - { - Debug.Print("Value {0}:{1} received", sender, val); - }; + readonly NativeMethods.IOHIDDeviceCallback HandleDeviceAdded; + readonly NativeMethods.IOHIDDeviceCallback HandleDeviceRemoved; + readonly NativeMethods.IOHIDValueCallback HandleDeviceValueReceived; #endregion @@ -92,6 +75,10 @@ namespace OpenTK.Platform.MacOS { Debug.Print("Using {0}.", typeof(HIDInput).Name); + HandleDeviceAdded = DeviceAdded; + HandleDeviceRemoved = DeviceRemoved; + HandleDeviceValueReceived = DeviceValueReceived; + hidmanager = CreateHIDManager(); RegisterHIDCallbacks(hidmanager); } @@ -100,14 +87,14 @@ namespace OpenTK.Platform.MacOS #region Private Members - static IOHIDManagerRef CreateHIDManager() + IOHIDManagerRef CreateHIDManager() { return NativeMethods.IOHIDManagerCreate(IntPtr.Zero, IntPtr.Zero); } // Registers callbacks for device addition and removal. These callbacks // are called when we run the loop in CheckDevicesMode - static void RegisterHIDCallbacks(IOHIDManagerRef hidmanager) + void RegisterHIDCallbacks(IOHIDManagerRef hidmanager) { NativeMethods.IOHIDManagerRegisterDeviceMatchingCallback( hidmanager, HandleDeviceAdded, IntPtr.Zero); @@ -120,17 +107,113 @@ namespace OpenTK.Platform.MacOS NativeMethods.IOHIDManagerOpen(hidmanager, IOOptionBits.Zero); } + void DeviceAdded(IntPtr context, IOReturn res, IntPtr sender, IOHIDDeviceRef device) + { + Debug.Print("Device {0} discovered", device); + + if (NativeMethods.IOHIDDeviceOpen(device, IOOptionBits.Zero) == IOReturn.Zero) + { + Debug.Print("Device {0} connected", device); + + if (NativeMethods.IOHIDDeviceConformsTo(device, HIDPage.GenericDesktop, (int)HIDUsageGD.Mouse)) + { + MouseState state = new MouseState(); + state.IsConnected = true; + MouseIndexToDevice.Add(MouseDevices.Count, device); + MouseDevices.Add(device, state); + } + + NativeMethods.IOHIDDeviceRegisterInputValueCallback(device, + HandleDeviceValueReceived, IntPtr.Zero); + NativeMethods.IOHIDDeviceScheduleWithRunLoop(device, RunLoop, InputLoopMode); + } + } + + void DeviceRemoved(IntPtr context, IOReturn res, IntPtr sender, IOHIDDeviceRef device) + { + Debug.Print("Device {0} disconnected", device); + + if (NativeMethods.IOHIDDeviceConformsTo(device, HIDPage.GenericDesktop, (int)HIDUsageGD.Mouse) && + MouseDevices.ContainsKey(device)) + { + // Keep the device in case it comes back later on + MouseState state = MouseDevices[device]; + state.IsConnected = false; + MouseDevices[device] = state; + } + + NativeMethods.IOHIDDeviceRegisterInputValueCallback(device, null, IntPtr.Zero); + NativeMethods.IOHIDDeviceUnscheduleWithRunLoop(device, RunLoop, InputLoopMode); + } + + void DeviceValueReceived(IntPtr context, IOReturn res, IntPtr sender, IOHIDValueRef val) + { + MouseState mouse; + if (MouseDevices.TryGetValue(sender, out mouse)) + { + MouseDevices[sender] = UpdateMouse(mouse, val); + } + } + + static MouseState UpdateMouse(MouseState state, IOHIDValueRef val) + { + IOHIDElementRef elem = NativeMethods.IOHIDValueGetElement(val); + int v_int = NativeMethods.IOHIDValueGetIntegerValue(val).ToInt32(); + //double v_physical = NativeMethods.IOHIDValueGetScaledValue(val, IOHIDValueScaleType.Physical); + //double v_calbrated = NativeMethods.IOHIDValueGetScaledValue(val, IOHIDValueScaleType.Calibrated); + HIDPage page = NativeMethods.IOHIDElementGetUsagePage(elem); + int usage = NativeMethods.IOHIDElementGetUsage(elem); + + switch (page) + { + case HIDPage.GenericDesktop: + switch ((HIDUsageGD)usage) + { + case HIDUsageGD.X: + state.X += v_int; + break; + + case HIDUsageGD.Y: + state.Y += v_int; + break; + + case HIDUsageGD.Wheel: + state.WheelPrecise += v_int; + break; + } + break; + + case HIDPage.Button: + state[OpenTK.Input.MouseButton.Left + usage - 1] = v_int == 1; + break; + } + + return state; + } + #endregion #region IMouseDriver2 Members public MouseState GetState() { - return new MouseState(); + MouseState master = new MouseState(); + foreach (KeyValuePair item in MouseDevices) + { + master.MergeBits(item.Value); + } + + return master; } public MouseState GetState(int index) { + IntPtr device; + if (MouseIndexToDevice.TryGetValue(index, out device)) + { + return MouseDevices[device]; + } + return new MouseState(); } @@ -145,6 +228,9 @@ namespace OpenTK.Platform.MacOS class NativeMethods { const string hid = "/System/Library/Frameworks/IOKit.framework/Versions/Current/IOKit"; + public static readonly CFString IOHIDDeviceUsageKey = CF.CFSTR("DeviceUsage"); + public static readonly CFString IOHIDDeviceUsagePageKey = CF.CFSTR("DeviceUsagePage"); + public static readonly CFString IOHIDDeviceUsagePairsKey = CF.CFSTR("DeviceUsagePairs"); [DllImport(hid)] public static extern IOHIDManagerRef IOHIDManagerCreate( @@ -185,6 +271,17 @@ namespace OpenTK.Platform.MacOS IOHIDDeviceRef manager, IOOptionBits opts); + [DllImport(hid)] + public static extern CFTypeRef IOHIDDeviceGetProperty( + IOHIDDeviceRef device, + CFStringRef key); + + [DllImport(hid)] + public static extern bool IOHIDDeviceConformsTo( + IOHIDDeviceRef inIOHIDDeviceRef, // IOHIDDeviceRef for the HID device + HIDPage inUsagePage, // the usage page to test conformance with + int inUsage); // the usage to test conformance with + [DllImport(hid)] public static extern void IOHIDDeviceRegisterInputValueCallback( IOHIDDeviceRef device, @@ -197,11 +294,147 @@ namespace OpenTK.Platform.MacOS CFRunLoop inCFRunLoop, CFString inCFRunLoopMode); + [DllImport(hid)] + public static extern void IOHIDDeviceUnscheduleWithRunLoop( + IOHIDDeviceRef device, + CFRunLoop inCFRunLoop, + CFString inCFRunLoopMode); + + [DllImport(hid)] + public static extern IOHIDElementRef IOHIDValueGetElement(IOHIDValueRef @value); + + [DllImport(hid)] + public static extern CFIndex IOHIDValueGetIntegerValue(IOHIDValueRef @value); + + [DllImport(hid)] + public static extern double IOHIDValueGetScaledValue( + IOHIDValueRef @value, + IOHIDValueScaleType type) ; + + [DllImport(hid)] + public static extern int IOHIDElementGetUsage(IOHIDElementRef elem); + + [DllImport(hid)] + public static extern HIDPage IOHIDElementGetUsagePage(IOHIDElementRef elem); public delegate void IOHIDDeviceCallback(IntPtr ctx, IOReturn res, IntPtr sender, IOHIDDeviceRef device); public delegate void IOHIDValueCallback(IntPtr ctx, IOReturn res, IntPtr sender, IOHIDValueRef val); } + enum IOHIDValueScaleType + { + Physical, // [device min, device max] + Calibrated // [-1, +1] + } + + enum HIDPage + { + Undefined = 0x00, + GenericDesktop = 0x01, + Simulation = 0x02, + VR = 0x03, + Sport = 0x04, + Game = 0x05, + /* Reserved 0x06 */ + KeyboardOrKeypad = 0x07, /* USB Device Class Definition for Human Interface Devices (HID). Note: the usage type for all key codes is Selector (Sel). */ + LEDs = 0x08, + Button = 0x09, + Ordinal = 0x0A, + Telephony = 0x0B, + Consumer = 0x0C, + Digitizer = 0x0D, + /* Reserved 0x0E */ + PID = 0x0F, /* USB Physical Interface Device definitions for force feedback and related devices. */ + Unicode = 0x10, + /* Reserved 0x11 - 0x13 */ + AlphanumericDisplay = 0x14, + /* Reserved 0x15 - 0x7F */ + /* Monitor 0x80 - 0x83 USB Device Class Definition for Monitor Devices */ + /* Power 0x84 - 0x87 USB Device Class Definition for Power Devices */ + PowerDevice = 0x84, /* Power Device Page */ + BatterySystem = 0x85, /* Battery System Page */ + /* Reserved 0x88 - 0x8B */ + BarCodeScanner = 0x8C, /* (Point of Sale) USB Device Class Definition for Bar Code Scanner Devices */ + WeighingDevice = 0x8D, /* (Point of Sale) USB Device Class Definition for Weighing Devices */ + Scale = 0x8D, /* (Point of Sale) USB Device Class Definition for Scale Devices */ + MagneticStripeReader = 0x8E, + /* ReservedPointofSalepages 0x8F */ + CameraControl = 0x90, /* USB Device Class Definition for Image Class Devices */ + Arcade = 0x91, /* OAAF Definitions for arcade and coinop related Devices */ + /* Reserved 0x92 - 0xFEFF */ + /* VendorDefined 0xFF00 - 0xFFFF */ + VendorDefinedStart = 0xFF00 + } + + // Generic desktop usage + enum HIDUsageGD + { + Pointer = 0x01, /* Physical Collection */ + Mouse = 0x02, /* Application Collection */ + /* 0x03 Reserved */ + Joystick = 0x04, /* Application Collection */ + GamePad = 0x05, /* Application Collection */ + Keyboard = 0x06, /* Application Collection */ + Keypad = 0x07, /* Application Collection */ + MultiAxisController = 0x08, /* Application Collection */ + /* 0x09 - 0x2F Reserved */ + X = 0x30, /* Dynamic Value */ + Y = 0x31, /* Dynamic Value */ + Z = 0x32, /* Dynamic Value */ + Rx = 0x33, /* Dynamic Value */ + Ry = 0x34, /* Dynamic Value */ + Rz = 0x35, /* Dynamic Value */ + Slider = 0x36, /* Dynamic Value */ + Dial = 0x37, /* Dynamic Value */ + Wheel = 0x38, /* Dynamic Value */ + Hatswitch = 0x39, /* Dynamic Value */ + CountedBuffer = 0x3A, /* Logical Collection */ + ByteCount = 0x3B, /* Dynamic Value */ + MotionWakeup = 0x3C, /* One-Shot Control */ + Start = 0x3D, /* On/Off Control */ + Select = 0x3E, /* On/Off Control */ + /* 0x3F Reserved */ + Vx = 0x40, /* Dynamic Value */ + Vy = 0x41, /* Dynamic Value */ + Vz = 0x42, /* Dynamic Value */ + Vbrx = 0x43, /* Dynamic Value */ + Vbry = 0x44, /* Dynamic Value */ + Vbrz = 0x45, /* Dynamic Value */ + Vno = 0x46, /* Dynamic Value */ + /* 0x47 - 0x7F Reserved */ + SystemControl = 0x80, /* Application Collection */ + SystemPowerDown = 0x81, /* One-Shot Control */ + SystemSleep = 0x82, /* One-Shot Control */ + SystemWakeUp = 0x83, /* One-Shot Control */ + SystemContextMenu = 0x84, /* One-Shot Control */ + SystemMainMenu = 0x85, /* One-Shot Control */ + SystemAppMenu = 0x86, /* One-Shot Control */ + SystemMenuHelp = 0x87, /* One-Shot Control */ + SystemMenuExit = 0x88, /* One-Shot Control */ + SystemMenu = 0x89, /* Selector */ + SystemMenuRight = 0x8A, /* Re-Trigger Control */ + SystemMenuLeft = 0x8B, /* Re-Trigger Control */ + SystemMenuUp = 0x8C, /* Re-Trigger Control */ + SystemMenuDown = 0x8D, /* Re-Trigger Control */ + /* 0x8E - 0x8F Reserved */ + DPadUp = 0x90, /* On/Off Control */ + DPadDown = 0x91, /* On/Off Control */ + DPadRight = 0x92, /* On/Off Control */ + DPadLeft = 0x93, /* On/Off Control */ + /* 0x94 - 0xFFFF Reserved */ + Reserved = 0xFFFF + } + + enum HIDButton + { + Button_1 = 0x01, /* (primary/trigger) */ + Button_2 = 0x02, /* (secondary) */ + Button_3 = 0x03, /* (tertiary) */ + Button_4 = 0x04, /* 4th button */ + /* ... */ + Button_65535 = 0xFFFF + } + #endregion } }