using System; using System.Collections.Generic; using System.Linq; using System.Text; using SharpDX.DirectInput; using System.Windows.Forms; using SharpDX; using System.Reflection; using System.Text.RegularExpressions; using SCJMapper_V2.Common; namespace SCJMapper_V2.Devices.Mouse { /// /// Handles one Mouse device as DXInput device /// In addition provide some static tools to handle Mouse props here in one place /// public class MouseCls : DeviceCls { private static readonly log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod( ).DeclaringType ); #region Static Items public new const string DeviceClass = "mouse"; // the device name used throughout this app public new const string DeviceID = "mo1_"; static public int RegisteredDevices = 0; // devices add here once they are created (though will not decrement as they are not deleted) public const string DevNameCIG = "Mouse"; // just a name... public const string DevGUIDCIG = "{10001000-0000-0000-0000-000000000000}"; // - Fixed for Mouse, we dont differentiate public new const string DisabledInput = DeviceID + DeviceCls.DisabledInput; static public new bool IsDisabledInput( string input ) { if ( input == DisabledInput ) return true; return false; } /// /// Returns the currently valid color /// /// A color static public System.Drawing.Color MouseColor() { return MyColors.MouseColor; } /// /// Returns true if the devicename is a joystick /// /// /// static new public bool IsDeviceClass( string deviceClass ) { return ( deviceClass == DeviceClass ); } /// /// Return this deviceClass if the input string starts with mo1_ /// /// /// static public new string DeviceClassFromInput( string devInput ) { if ( DevMatch( devInput ) ) return DeviceClass; // this else return DeviceCls.DeviceClass; // unknown } /// /// Create a DevInput string if the input does look like not having a device ID /// /// A mouse input /// DevInput static public new string DevInput( string input ) { if ( DevMatch( input ) ) return input; // already else return DeviceID + input; } /// /// Returns true if the input matches this device /// /// A devInput string /// True for a match static public new bool DevMatch( string devInput ) { return devInput.StartsWith( DeviceID ); } /// /// Returns true if a command is an axis command /// /// The command string /// True if it is an axis command static public new bool IsAxisCommand( string command ) { string cLower = command.ToLowerInvariant( ); return ( cLower.EndsWith( "_maxis_x" ) || cLower.EndsWith( "_maxis_y" ) ); } /// /// Reformat the input from AC1 style to AC2 style /// /// The AC1 input string /// An AC2 style input string static public string FromAC1( string input ) { // input is something like a mouse1 (TODO compositions like lctrl+mouse1 ??) // try easy: add mo1_ at the beginning string retVal = input.Replace( " ", "" ); if ( IsDisabledInput( input ) ) return input; return "mo1_" + retVal; } /// /// Format the various parts to a valid ctrl entry /// /// The input by the user /// Modifiers to be applied /// static public string MakeCtrl( string input, string modifiers ) { return DeviceID + modifiers + input; } #endregion private SharpDX.DirectInput.Mouse m_device; private MouseState m_state = new MouseState( ); private MouseState m_prevState = new MouseState( ); private IntPtr m_hwnd; private bool m_activated = false; private string m_lastItem = ""; private int m_senseLimit = 150; // axis jitter avoidance... /// /// Return the device instance number (which is always 1) /// public override int XmlInstance { get { return 1; } } // const 1 for mouse /// /// Return the DX device instance number (which is always 0) /// public override int DevInstance { get { return 0; } } /// /// The DeviceClass of this instance /// public override string DevClass { get { return MouseCls.DeviceClass; } } /// /// The JS ProductName property /// public override string DevName { get { return "Mouse"; } } // no props in directX /// /// The ProductGUID property /// public override string DevGUID { get { return $"{{{m_device.Information.ProductGuid.ToString( )}}}"; } } // @@@ tbd /// /// The JS Instance GUID for multiple device support (VJoy gets 2 of the same name) /// public override string DevInstanceGUID { get { return m_device.Information.InstanceGuid.ToString( ); } } /// /// Returns the mapping color for this device /// public override System.Drawing.Color MapColor { get { return MyColors.MouseColor; } } public override bool Activated { get { return Activated_low; } set { Activated_low = value; } } private bool Activated_low { get { return m_activated; } set { m_activated = value; if ( m_activated == false ) m_device.Unacquire( ); // explicitely if not longer active } } /// /// ctor and init /// /// A DXInput device /// The WinHandle of the main window public MouseCls( SharpDX.DirectInput.Mouse device, IntPtr hwnd ) { log.DebugFormat( "MouseCls cTor - Entry with {0}", device.Information.ProductName ); m_device = device; m_hwnd = hwnd; Activated_low = false; m_senseLimit = AppConfiguration.AppConfig.msSenseLimit; // can be changed in the app.config file if it is still too little // Set BufferSize in order to use buffered data. m_device.Properties.BufferSize = 128; log.Debug( "Get Mouse Object" ); try { // Set the data format to the c_dfDIJoystick pre-defined format. //m_device.SetDataFormat( DeviceDataFormat.Joystick ); // Set the cooperative level for the device. m_device.SetCooperativeLevel( m_hwnd, CooperativeLevel.NonExclusive | CooperativeLevel.Background ); // Enumerate all the objects on the device. } catch ( Exception ex ) { log.Error( "Get Mouse Object failed", ex ); } MouseCls.RegisteredDevices++; Activated_low = true; } /// /// Returns the number of buttons /// public int NumberOfButtons { get { return m_state.Buttons.Length; } } // Property Mapping from DXinput to CryEngine string private Dictionary m_axiesDx2Cry = new Dictionary( ) { {"X","maxis_x"}, {"Y","maxis_y"}, {"Z","mwheel_"}, }; /// /// returns the currently available input string /// (does not retrieve new data but uses what was collected by GetData()) /// NOTE: for Mouse when multiple inputs are available the sequence is /// axis > button > hat > slider (wher prio is max itemNum > min itemNum) /// /// An input string or an empty string if no input is available public override string GetCurrentInput() { string currentChange = ""; // get axis foreach ( KeyValuePair entry in m_axiesDx2Cry ) { PropertyInfo axisProperty = typeof( MouseState ).GetProperty( entry.Key ); if ( DidAxisChange2( (int)axisProperty.GetValue( m_state, null ), (int)axisProperty.GetValue( m_prevState, null ), true ) ) { currentChange = entry.Value; if ( entry.Key == "Z" ) currentChange += "down"; } else if ( DidAxisChange2( (int)axisProperty.GetValue( m_state, null ), (int)axisProperty.GetValue( m_prevState, null ), false ) ) { currentChange = entry.Value; if ( entry.Key == "Z" ) currentChange += "up"; } } // get prio button bool[] buttons = m_state.Buttons; bool[] prevButtons = m_prevState.Buttons; for ( int bi = 0; bi < buttons.Length; bi++ ) { if ( buttons[bi] ) currentChange = "mouse" + ( bi + 1 ).ToString( ); } return currentChange; } /// /// Find the last change the user did on that device /// maxis_x, maxis_y, maxis_z, mwheel_up, mwheel_down, mouse1, mouse2, mouse3, mouse4, mouse5, possibly mouseN /// Modifiers: NO modifiers found in defaultProfile... /// Z-axis, typically a wheel. If the mouse does not have a z-axis, the value is 0. /// /// The last action as CryEngine compatible string public override string GetLastChange() { // get changed axis foreach ( KeyValuePair entry in m_axiesDx2Cry ) { PropertyInfo axisProperty = typeof( MouseState ).GetProperty( entry.Key ); if ( DidAxisChange2( (int)axisProperty.GetValue( m_state, null ), (int)axisProperty.GetValue( m_prevState, null ), true ) ) { m_lastItem = entry.Value; if ( entry.Key == "Z" ) m_lastItem += "down"; } else if ( DidAxisChange2( (int)axisProperty.GetValue( m_state, null ), (int)axisProperty.GetValue( m_prevState, null ), false ) ) { m_lastItem = entry.Value; if ( entry.Key == "Z" ) m_lastItem += "up"; } } // get new button bool[] buttons = m_state.Buttons; bool[] prevButtons = m_prevState.Buttons; for ( int bi = 0; bi < buttons.Length; bi++ ) { if ( buttons[bi] && ( buttons[bi] != prevButtons[bi] ) ) m_lastItem = "mouse" + ( bi + 1 ).ToString( ); } return m_lastItem; } /// /// Figure out if an axis changed enough to consider it as a changed state /// The change is polled every 100ms (timer1) so the user has to change so much within that time /// Then an axis usually swings back when left alone - that is the real recording of a change. /// We know that the range is -1000 .. 1000 so we can judge absolute /// % relative is prone to small changes around 0 - which is likely the case with axes /// private bool DidAxisChange2( int current, int prev, bool posAxis ) { // determine if the axis drifts more than x units to account for bounce // old-new/old if ( current == prev ) return false; int change = Math.Abs( prev - current ); // if the axis has changed more than x units to it's last value if ( posAxis && ( current - prev ) > 0 ) return change > 2 ? true : false; else if ( ( !posAxis ) && ( current - prev ) < 0 ) return change > 2 ? true : false; else return false; } System.Drawing.Rectangle m_targetRect = Screen.PrimaryScreen.Bounds; /// /// Fudge - must have a target rectangle to scale the mouse input into the target /// /// public void SetTargetRectForCmdData( System.Drawing.Rectangle target ) { m_targetRect = target; } /// /// Collect the current data from the device (using WinForms.Cursor) /// public override void GetCmdData( string cmd, out int data ) { System.Drawing.Point cPt = Cursor.Position; // somewhere on all screens if ( m_targetRect.Contains( cPt ) ) { cPt = cPt - new System.Drawing.Size( m_targetRect.X, m_targetRect.Y ); // move the point relative to the target rect origin switch ( cmd ) { case "maxis_x": data = (int)( 2000 * cPt.X / m_targetRect.Width ) - 1000; break; // data should be -1000..1000 case "maxis_y": data = -1 * ( (int)( 2000 * cPt.Y / m_targetRect.Height ) - 1000 ); break; // data should be -1000..1000 default: data = 0; break; } } else { data = 0; } System.Diagnostics.Debug.Print( string.Format( "C:({0})-T({1})({2}) - data: {3}", Cursor.Position.ToString( ), m_targetRect.Location.ToString( ), m_targetRect.Size.ToString( ), data.ToString( ) ) ); } /// /// Collect the current data from the device /// public override void GetData() { // Make sure there is a valid device. if ( null == m_device ) return; // Poll the device for info. try { m_device.Poll( ); } catch ( SharpDXException e ) { if ( ( e.ResultCode == ResultCode.NotAcquired ) || ( e.ResultCode == ResultCode.InputLost ) ) { // Check to see if either the app needs to acquire the device, or // if the app lost the device to another process. try { // Acquire the device - if the (main)window is active if ( Activated ) m_device.Acquire( ); } catch ( SharpDXException ) { // Failed to acquire the device. This could be because the app doesn't have focus. return; // EXIT unaquired } } else { log.Error( "Unexpected Poll Exception", e ); return; // EXIT see ex code } } // Get the state of the device - retaining the previous state to find the lates change m_prevState = m_state; try { m_state = m_device.GetCurrentState( ); } // Catch any exceptions. None will be handled here, // any device re-aquisition will be handled above. catch ( SharpDXException ) { return; } } } }