mirror of
https://github.com/ayufan/steam-deck-tools.git
synced 2025-12-06 07:12:01 +01:00
- There's always a single Profile choosen - There are many Managers changing settings depending on environment - Improve and re-use mappings between profiles - Introduce Steam Profile to be used when in Steam Big Picture or Steam Game
465 lines
15 KiB
C#
465 lines
15 KiB
C#
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<SteamButton> 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<string> GetReport()
|
|
{
|
|
List<string> report = new List<string>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|