using System; using System.Collections.Generic; using System.Drawing; using System.Text; namespace uDrawLib { public class PS3InputDevice : IInputDevice, IDisposable { #region Declarations private const int _VENDOR_ID = 0x20D6; private const int _PRODUCT_ID = 0xCB17; private const int _MULTITOUCH_SENSITIVITY = 30; private const int _UNKNOWN_DATA1_SIZE = 4; private HIDDevice _device; private enum RawPressureType { NotPressed = 0x00, PenPressed = 0x40, FingerPressed = 0x80 }; /// /// Indicates the pressed state of every non-directional button on the tablet. /// public TabletButtonState ButtonState { get; set; } /// /// Indicates the pressed state of each direction on the directional pad. /// public TabletDPadState DPadState { get; set; } /// /// Indicates whether the nib/stylus, fingers, or nothing are currently pressing on the tablet. /// public TabletPressureType PressureType { get; set; } /// /// The raw pressure being applied by the pen. Only valid with TabletPressureType.PenPressed. /// public ushort PenPressure { get; set; } /// /// Indicates the distance between fingers. Only valid with TabletPressureType.Multitouch. /// public ushort MultitouchDistance { get; set; } /// /// Indicates the absolute coordinates (from top-left corner) of the current pressure point. /// Only valid with TabletPressureType.PenPressed and TabletPressureType.FingerPressed. /// public Point PressurePoint { get; set; } /// /// Unknown data that doesn't appear to change from 80 80 80 80... /// public byte[] UnknownData1 { get; set; } /// /// Raw X-, Y-, and Z-axis data from the accelerometer. /// public TabletAccelerometerData AccelerometerData { get; set; } private int _multitouchTimer; private ushort _previousMultitouchDistance; #endregion #region Constructors / Teardown public PS3InputDevice() { ButtonState = new TabletButtonState(); DPadState = new TabletDPadState(); UnknownData1 = new byte[_UNKNOWN_DATA1_SIZE]; AccelerometerData = new TabletAccelerometerData(); _device = new HIDDevice(_VENDOR_ID, _PRODUCT_ID); _device.DataReceived += _device_DataReceived; } #endregion #region Public Events public event EventHandler ButtonStateChanged; public event EventHandler DPadStateChanged; #endregion #region Public Properties /// /// Indicates whether pressure is being applied to the tablet. /// public bool IsPressed { get { bool ret = false; if (PressureType != TabletPressureType.NotPressed) ret = true; return ret; } } /// /// Indicates whether the user is currently zooming out by multitouch. /// public bool IsZooming { get { bool ret = false; if (MultitouchDistance > _previousMultitouchDistance) ret = true; return ret; } } /// /// Indicates whether the user is currently pinching by multitouch. /// public bool IsPinching { get { bool ret = false; if (MultitouchDistance < _previousMultitouchDistance) ret = true; return ret; } } #endregion #region Public Methods /// /// Detects whether the PS3 uDraw tablet device is currently attached to the PC. /// This is only detecting the presence of the USB dongle, NOT whether it is sync'd with the tablet. /// I don't know how to do that. /// /// public static bool IsDetected() { return HIDDevice.IsDetected(_VENDOR_ID, _PRODUCT_ID); } /// /// Disconnects the driver from the USB dongle. /// public void Disconnect() { _device.Disconnect(); } public void Dispose() { try { Disconnect(); } catch { //Don't care... } } #endregion #region Event Handlers private void _device_DataReceived(object sender, DataReceivedEventArgs e) { const int UNKNOWN_DATA1_OFFSET = 3; const int MULTITOUCH_DISTANCE_OFFSET = 12; const int PEN_PRESSURE_OFFSET = 13; const int PRESSURE_DATA_OFFSET = 15; const int ACCELEROMETER_X_OFFSET = 19; const int ACCELEROMETER_Y_OFFSET = 21; const int ACCELEROMETER_Z_OFFSET = 23; //Save the unknown data Array.Copy(e.Data, UNKNOWN_DATA1_OFFSET, UnknownData1, 0, _UNKNOWN_DATA1_SIZE); //Get the pressure state if (e.Data[11] == (byte)RawPressureType.NotPressed) PressureType = TabletPressureType.NotPressed; else if (e.Data[11] == (byte)RawPressureType.PenPressed) PressureType = TabletPressureType.PenPressed; else if (e.Data[11] == (byte)RawPressureType.FingerPressed) PressureType = TabletPressureType.FingerPressed; else PressureType = TabletPressureType.Multitouch; //Get the pen pressure PenPressure = (ushort)(e.Data[PEN_PRESSURE_OFFSET]); //Get the multitouch distance _multitouchTimer++; if (_multitouchTimer > _MULTITOUCH_SENSITIVITY) { _multitouchTimer = 0; _previousMultitouchDistance = MultitouchDistance; } MultitouchDistance = e.Data[MULTITOUCH_DISTANCE_OFFSET]; //Get the (singular) pressure point PressurePoint = new Point(e.Data[PRESSURE_DATA_OFFSET] * 0x100 + e.Data[PRESSURE_DATA_OFFSET+2], e.Data[PRESSURE_DATA_OFFSET+1] * 0x100 + e.Data[PRESSURE_DATA_OFFSET+3]); //Get the accelerometer data const ushort X_MIN = 0x1EA; const ushort X_MAX = 0x216; const ushort Y_MIN = 0x1EA; const ushort Y_MAX = 0x216; const ushort Z_MIN = 0x1EC; const ushort Z_MAX = 0x218; ushort x = (ushort)(e.Data[ACCELEROMETER_X_OFFSET] | (e.Data[ACCELEROMETER_X_OFFSET+1] << 8)); if (x < X_MIN) x = X_MIN; if (x > X_MAX) x = X_MAX; AccelerometerData.XAxis = (ushort)((x - X_MIN) / (X_MAX - X_MIN)); ushort y = (ushort)(e.Data[ACCELEROMETER_Y_OFFSET] | (e.Data[ACCELEROMETER_Y_OFFSET+1] << 8)); if (y < Y_MIN) y = Y_MIN; if (y > Y_MAX) y = Y_MAX; AccelerometerData.YAxis = (ushort)((y - Y_MIN) / (Y_MAX - Y_MIN)); ushort z = (ushort)(e.Data[ACCELEROMETER_Z_OFFSET] | (e.Data[ACCELEROMETER_Z_OFFSET+1] << 8)); if (z < Z_MIN) z = Z_MIN; if (z > Z_MAX) z = Z_MAX; AccelerometerData.ZAxis = (ushort)((z - Z_MIN) / (Z_MAX - Z_MIN)); //Parse raw data for buttons bool changed = false; bool raw = (e.Data[0] & 0x01) > 0; changed |= ButtonState.SquareHeld != raw; ButtonState.SquareHeld = raw; raw = (e.Data[0] & 0x02) > 0; changed |= ButtonState.CrossHeld != raw; ButtonState.CrossHeld = raw; raw = (e.Data[0] & 0x04) > 0; changed |= ButtonState.CircleHeld != raw; ButtonState.CircleHeld = raw; raw = (e.Data[0] & 0x08) > 0; changed |= ButtonState.TriangleHeld != raw; ButtonState.TriangleHeld = raw; raw = (e.Data[1] & 0x01) > 0; changed |= ButtonState.SelectHeld != raw; ButtonState.SelectHeld = raw; raw = (e.Data[1] & 0x02) > 0; changed |= ButtonState.StartHeld != raw; ButtonState.StartHeld = raw; raw = (e.Data[1] & 0x10) > 0; changed |= ButtonState.PSHeld != raw; ButtonState.PSHeld = raw; if (changed && ButtonStateChanged != null) ButtonStateChanged(this, EventArgs.Empty); //Now parse raw data for D-pad changes changed = false; raw = (e.Data[2] == 0x0) || (e.Data[2] == 0x1) || (e.Data[2] == 0x7); changed |= DPadState.UpHeld != raw; DPadState.UpHeld = raw; raw = (e.Data[2] == 0x3) || (e.Data[2] == 0x4) || (e.Data[2] == 0x5); changed |= DPadState.DownHeld != raw; DPadState.DownHeld = raw; raw = (e.Data[2] == 0x5) || (e.Data[2] == 0x6) || (e.Data[2] == 0x7); changed |= DPadState.LeftHeld != raw; DPadState.LeftHeld = raw; raw = (e.Data[2] == 0x1) || (e.Data[2] == 0x2) || (e.Data[2] == 0x3); changed |= DPadState.RightHeld != raw; DPadState.RightHeld = raw; if (changed && DPadStateChanged != null) DPadStateChanged(this, EventArgs.Empty); } #endregion } }