using hidapi; using PowerControl.External; using static CommonHelpers.Log; namespace SteamController.Devices { public partial class SteamController { public abstract class SteamAction { public SteamController? Controller { get; internal set; } public String Name { get; internal set; } = ""; /// This is action controlled by Lizard mode public bool LizardButton { get; set; } public bool LizardMouse { get; set; } public DateTime LastUpdated { get; protected set; } = DateTime.Now; public double DeltaTime { get; protected set; } internal abstract void Reset(); internal abstract bool BeforeUpdate(byte[] buffer); internal abstract void Update(); protected void UpdateTime() { var now = DateTime.Now; DeltaTime = (now - LastUpdated).TotalSeconds; LastUpdated = now; } protected bool ValueCanBeUsed { get { if (LizardButton && Controller?.LizardButtons == true) return false; if (LizardMouse && Controller?.LizardMouse == true) return false; return true; } } } public class SteamButton : SteamAction { public static readonly TimeSpan DefaultHoldDuration = TimeSpan.FromMilliseconds(10); public static readonly TimeSpan DefaultFirstHold = TimeSpan.FromMilliseconds(75); public static readonly TimeSpan DefaultRepeatHold = TimeSpan.FromMilliseconds(150); private bool rawValue, rawLastValue; public bool Value { get { return ValueCanBeUsed ? rawValue : false; } } public bool LastValue { get { return ValueCanBeUsed ? rawLastValue : false; } } /// Last press was already consumed by other public object? Consumed { get; private set; } /// Set on raising edge public DateTime? HoldSince { get; private set; } public DateTime? HoldRepeated { get; private set; } public SteamButton() { } public static implicit operator bool(SteamButton button) => button.Hold(DefaultHoldDuration, null); /// Generated when button is pressed for the first time public bool JustPressed() { if (!LastValue && Value) return true; return false; } /// Generated on failing edge of key press public bool Pressed(TimeSpan? duration = null) { // We expect Last to be true, and now to be false (failing edge) if (!(LastValue && !Value)) return false; if (Consumed is not null) return false; if (duration.HasValue && HoldSince?.Add(duration.Value) >= DateTime.Now) return false; return true; } public bool Consume(object consume) { if (Consumed is not null && Consumed != consume) return false; Consumed = consume; return true; } public bool Hold(object? consume = null) { return Hold(null, consume); } /// Generated when button was hold for a given period public bool Hold(TimeSpan? duration, object? consume = null) { if (!Value) return false; if (Consumed is not null && Consumed != consume) return false; if (duration.HasValue && HoldSince?.Add(duration.Value) >= DateTime.Now) return false; if (consume is not null) Consumed = consume; return true; } public bool HoldOnce(object consume) { return HoldOnce(null, consume); } /// Generated when button was hold for a given period /// but triggered exactly once public bool HoldOnce(TimeSpan? duration, object consume) { if (!Hold(duration)) return false; Consumed = consume; return true; } /// Generated when button was hold for a given period /// but triggered exactly after previously being hold public bool HoldNext(TimeSpan? duration, object previousConsume, object replaceConsme) { if (!Hold(duration, previousConsume)) return false; Consumed = replaceConsme; return true; } /// Generated when button was repeated for a given period /// but triggered exactly once public bool HoldRepeat(TimeSpan duration, TimeSpan repeatEvery, object consume) { // always generate at least one keypress if (Pressed(duration)) return true; if (!Hold(duration, consume)) return false; // first keypress if (!HoldRepeated.HasValue) { HoldRepeated = DateTime.Now; return true; } // repeated keypress if (HoldRepeated.Value.Add(repeatEvery) <= DateTime.Now) { HoldRepeated = DateTime.Now; return true; } return false; } public bool HoldRepeat(object consume) { return HoldRepeat(DefaultFirstHold, DefaultRepeatHold, consume); } internal override void Reset() { rawLastValue = rawValue; rawValue = false; HoldSince = null; HoldRepeated = null; Consumed = null; } internal void SetValue(bool newValue) { rawLastValue = rawValue; rawValue = newValue; UpdateTime(); if (!rawLastValue && rawValue) { HoldSince = DateTime.Now; HoldRepeated = null; } } internal override bool BeforeUpdate(byte[] buffer) { return true; } internal override void Update() { if (!Value) Consumed = null; } public override string? ToString() { if (Name != "") return String.Format("{0}: {1} (last: {2})", Name, Value, LastValue); return base.ToString(); } } public class SteamButton2 : SteamButton { private int offset; private uint mask; public SteamButton2(int offset, object mask) { this.offset = offset; this.mask = (uint)mask.GetHashCode(); while (this.mask > 0xFF) { this.mask >>= 8; this.offset++; } } internal override bool BeforeUpdate(byte[] buffer) { if (offset < buffer.Length) { SetValue((buffer[offset] & mask) != 0); return true; } else { SetValue((buffer[offset] & mask) != 0); return false; } } } public class SteamAxis : SteamAction { public const short VirtualLeftThreshold = short.MinValue / 2; public const short VirtualRightThreshold = short.MaxValue / 2; private int offset; private short rawValue, rawLastValue; public SteamButton? ActiveButton { get; internal set; } public SteamButton? VirtualLeft { get; internal set; } public SteamButton? VirtualRight { get; internal set; } public short Deadzone { get; set; } public short MinChange { get; set; } public short Value { get { return ValueCanBeUsed ? rawValue : (short)0; } } public short LastValue { get { return ValueCanBeUsed ? rawLastValue : (short)0; } } public SteamAxis(int offset) { this.offset = offset; } public static implicit operator bool(SteamAxis button) => button.Active; public static implicit operator short(SteamAxis button) { return Math.Abs(button.Value) > button.Deadzone ? button.Value : (short)0; } public bool Active { get { return ActiveButton?.Value ?? true; } } public enum ScaledMode { Absolute, AbsoluteTime, Delta, DeltaTime } public double Scaled(double min, double max, ScaledMode mode) { int value = 0; switch (mode) { case ScaledMode.Absolute: if (Math.Abs(Value) < Deadzone) return 0.0; value = Value; break; case ScaledMode.AbsoluteTime: if (Math.Abs(Value) < Deadzone) return 0.0; value = (int)(Value * DeltaTime); break; case ScaledMode.Delta: value = Value - LastValue; if (Math.Abs(Value) < MinChange) return 0.0; break; case ScaledMode.DeltaTime: value = Value - LastValue; if (Math.Abs(Value) < MinChange) return 0.0; value = (int)(value * DeltaTime); break; } if (value == 0) return 0.0; double factor = (double)(value - short.MinValue) / (short.MaxValue - short.MinValue); return factor * (max - min) + min; } public double Scaled(double range, ScaledMode mode) { return Scaled(-range, range, mode); } public int Scaled(int min, int max, ScaledMode mode) { return (int)Scaled((double)min, (double)max, mode); } public int Scaled(int range, ScaledMode mode) { return Scaled(-range, range, mode); } internal override void Reset() { rawLastValue = rawValue; rawValue = 0; } internal void SetValue(short newValue) { rawLastValue = rawValue; rawValue = newValue; UpdateTime(); // first time pressed, reset value as this is a Pad if (ActiveButton is not null && ActiveButton.JustPressed()) rawLastValue = newValue; if (VirtualRight is not null) VirtualRight.SetValue(newValue > VirtualRightThreshold); if (VirtualLeft is not null) VirtualLeft.SetValue(newValue < VirtualLeftThreshold); } internal override bool BeforeUpdate(byte[] buffer) { if (offset + 1 < buffer.Length) { SetValue(BitConverter.ToInt16(buffer, offset)); return true; } else { SetValue(0); return false; } } internal override void Update() { } public override string? ToString() { if (Name != "") return String.Format("{0}: {1} (last: {2})", Name, Value, LastValue); return base.ToString(); } } public SteamAction?[] AllActions { get; private set; } public SteamButton?[] AllButtons { get; private set; } public SteamAxis?[] AllAxises { get; private set; } public IEnumerable HoldButtons { get { foreach (var action in AllButtons) { if (action.Value) yield return action; } } } private void InitializeActions() { var allActions = GetType(). GetFields(). Where((field) => field.FieldType.IsSubclassOf(typeof(SteamAction))). Select((field) => Tuple.Create(field, field.GetValue(this) as SteamAction)). ToList(); allActions.ForEach((tuple) => tuple.Item2.Controller = this); allActions.ForEach((tuple) => tuple.Item2.Name = tuple.Item1.Name); AllActions = allActions.Select((tuple) => tuple.Item2).ToArray(); AllAxises = allActions.Where((tuple) => tuple.Item2 is SteamAxis).Select((tuple) => tuple.Item2 as SteamAxis).ToArray(); AllButtons = allActions.Where((tuple) => tuple.Item2 is SteamButton).Select((tuple) => tuple.Item2 as SteamButton).ToArray(); } public IEnumerable GetReport() { List report = new List(); var buttons = AllButtons.Where((button) => button.Value).Select((button) => button.Name); if (buttons.Any()) yield return String.Format("Buttons: {0}", String.Join(",", buttons)); foreach (var axis in AllAxises) { if (!axis.Active) continue; yield return String.Format("Axis: {0} = {1} [Delta: {2}]", axis.Name, axis.Value, axis.Value - axis.LastValue); } } } }