ScpControl/ScpProxy.cs (244 lines of code) (raw):

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reflection; using System.ServiceModel; using log4net; using ReactiveSockets; using ScpControl.Properties; using ScpControl.Rx; using ScpControl.ScpCore; using ScpControl.Shared.Core; using ScpControl.Shared.XInput; using ScpControl.Wcf; namespace ScpControl { public sealed partial class ScpProxy : Component { #region XInput extensions /// <summary> /// Used by ScpXInputBridge to request pressure sensitive button information. /// </summary> /// <param name="dwUserIndex">The pad index to request data from (zero-based).</param> /// <returns>The pressure sensitive button/axis information.</returns> public SCP_EXTN GetExtended(uint dwUserIndex) { ScpHidReport inputReport; var extended = default(SCP_EXTN); try { inputReport = _packetCache[(DsPadId) dwUserIndex]; } catch (KeyNotFoundException) { return extended; } switch (inputReport.Model) { case DsModel.None: break; case DsModel.DS3: // translate and wrap button/axis information extended = new SCP_EXTN { SCP_UP = inputReport[Ds3Axis.Up].Pressure, SCP_RIGHT = inputReport[Ds3Axis.Right].Pressure, SCP_DOWN = inputReport[Ds3Axis.Down].Pressure, SCP_LEFT = inputReport[Ds3Axis.Left].Pressure, SCP_LX = inputReport[Ds3Axis.Lx].Axis, SCP_LY = -inputReport[Ds3Axis.Ly].Axis, SCP_L1 = inputReport[Ds3Axis.L1].Pressure, SCP_L2 = inputReport[Ds3Axis.L2].Pressure, SCP_L3 = inputReport[Ds3Button.L3].Pressure, SCP_RX = inputReport[Ds3Axis.Rx].Axis, SCP_RY = -inputReport[Ds3Axis.Ry].Axis, SCP_R1 = inputReport[Ds3Axis.R1].Pressure, SCP_R2 = inputReport[Ds3Axis.R2].Pressure, SCP_R3 = inputReport[Ds3Button.R3].Pressure, SCP_T = inputReport[Ds3Axis.Triangle].Pressure, SCP_C = inputReport[Ds3Axis.Circle].Pressure, SCP_X = inputReport[Ds3Axis.Cross].Pressure, SCP_S = inputReport[Ds3Axis.Square].Pressure, SCP_SELECT = inputReport[Ds3Button.Select].Pressure, SCP_START = inputReport[Ds3Button.Start].Pressure, SCP_PS = inputReport[Ds3Button.Ps].Pressure }; break; case DsModel.DS4: extended = new SCP_EXTN { SCP_UP = inputReport[Ds4Button.Up].Pressure, SCP_RIGHT = inputReport[Ds4Button.Right].Pressure, SCP_DOWN = inputReport[Ds4Button.Down].Pressure, SCP_LEFT = inputReport[Ds4Button.Left].Pressure, SCP_LX = inputReport[Ds4Axis.Lx].Value, SCP_LY = inputReport[Ds4Axis.Ly].Value, SCP_L1 = inputReport[Ds4Button.L1].Pressure, SCP_L2 = inputReport[Ds4Axis.L2].Pressure, SCP_L3 = inputReport[Ds4Button.L3].Pressure, SCP_RX = inputReport[Ds4Axis.Rx].Value, SCP_RY = inputReport[Ds4Axis.Ry].Value, SCP_R1 = inputReport[Ds4Button.R1].Pressure, SCP_R2 = inputReport[Ds4Axis.R2].Pressure, SCP_R3 = inputReport[Ds4Button.R3].Pressure, SCP_T = inputReport[Ds4Button.Triangle].Pressure, SCP_C = inputReport[Ds4Button.Circle].Pressure, SCP_X = inputReport[Ds4Button.Cross].Pressure, SCP_S = inputReport[Ds4Button.Square].Pressure, SCP_SELECT = inputReport[Ds4Button.Share].Pressure, SCP_START = inputReport[Ds4Button.Options].Pressure, SCP_PS = inputReport[Ds4Button.Ps].Pressure }; break; } return extended; } public ScpHidReport GetReport(uint dwUserIndex) { try { return _packetCache[(DsPadId) dwUserIndex]; } catch (KeyNotFoundException) { return null; } } #endregion #region Private fields private readonly ReactiveClient _rxFeedClient = new ReactiveClient(Settings.Default.RootHubNativeFeedHost, Settings.Default.RootHubNativeFeedPort); private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // caches the latest HID report for every pad in a thread-save dictionary private readonly IDictionary<DsPadId, ScpHidReport> _packetCache = new ConcurrentDictionary<DsPadId, ScpHidReport>(); private IScpCommandService _rootHub; #endregion #region Public properties public bool IsActive { get; private set; } /// <summary> /// Checks if the native feed is available. /// </summary> public bool IsNativeFeedAvailable { get { return _rootHub.IsNativeFeedAvailable(); } } public IList<string> StatusData { get { return _rootHub.GetStatusData().ToList(); } } #endregion #region WCF methods public void PromotePad(byte pad) { _rootHub.PromotePad(pad); } public GlobalConfiguration ReadConfig() { return _rootHub.RequestConfiguration(); } public void WriteConfig(GlobalConfiguration config) { _rootHub.SubmitConfiguration(config); } /// <summary> /// Receives details about the provided pad. /// </summary> /// <param name="pad">The pad ID to query details for.</param> /// <returns>The pad details returned from the root hub.</returns> public DualShockPadMeta Detail(DsPadId pad) { return _rootHub.GetPadDetail(pad); } /// <summary> /// Submit a rumble request for a specified pad. /// </summary> /// <param name="pad">The target pad.</param> /// <param name="large">Rumble with the large (typically left) motor.</param> /// <param name="small">Rumble with the small (typically right) motor.</param> /// <returns>Returns request status.</returns> public bool Rumble(DsPadId pad, byte large, byte small) { return _rootHub.Rumble(pad, large, small); } public IEnumerable<DualShockProfile> GetProfiles() { return _rootHub.GetProfiles(); } public void SubmitProfile(DualShockProfile profile) { _rootHub.SubmitProfile(profile); } public void RemoveProfile(DualShockProfile profile) { _rootHub.RemoveProfile(profile); } #endregion #region Component actions public bool Start() { try { if (!IsActive) { #region WCF client var address = new EndpointAddress(new Uri("net.tcp://localhost:26760/ScpRootHubService")); var binding = new NetTcpBinding { TransferMode = TransferMode.Streamed, Security = new NetTcpSecurity {Mode = SecurityMode.None} }; var factory = new ChannelFactory<IScpCommandService>(binding, address); _rootHub = factory.CreateChannel(address); #endregion #region Feed client var rootHubFeedChannel = new ScpNativeFeedChannel(_rxFeedClient); rootHubFeedChannel.Receiver.SubscribeOn(TaskPoolScheduler.Default).Subscribe(buffer => { if (buffer.Length <= 0) return; OnFeedPacketReceived(new ScpHidReport(buffer)); }); _rxFeedClient.ConnectAsync(); #endregion IsActive = true; } } catch (Exception ex) { Log.ErrorFormat("Unexpected error: {0}", ex); } return IsActive; } public bool Stop() { // TODO: refactor useless bits try { if (IsActive) { IsActive = false; //_rxFeedClient.Disconnect(); } } catch (Exception ex) { Log.ErrorFormat("Unexpected error: {0}", ex); } return !IsActive; } #endregion #region Ctors public ScpProxy() { InitializeComponent(); } public ScpProxy(IContainer container) : this() { container.Add(this); } #endregion #region Public events public event EventHandler<ScpHidReport> NativeFeedReceived; public event EventHandler<EventArgs> RootHubDisconnected; #endregion #region Event methods private void OnFeedPacketReceived(ScpHidReport data) { _packetCache[data.PadId] = data; if (NativeFeedReceived != null) { NativeFeedReceived(this, data); } } private void OnRootHubDisconnected(object sender, EventArgs args) { if (RootHubDisconnected != null) { RootHubDisconnected(sender, args); } } #endregion } }