Introduce inheritable Profiles and Managers

- 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
This commit is contained in:
Kamil Trzciński 2022-11-25 10:56:17 +01:00
parent 10d6c055da
commit ab5bc370df
21 changed files with 530 additions and 311 deletions

View file

@ -1,120 +0,0 @@
using static CommonHelpers.Log;
namespace SteamController.Profiles
{
public class Context : IDisposable
{
public const double JoystickToMouseSensitivity = 1200;
public const double PadToMouseSensitivity = 200;
public const double PadToWhellSensitivity = 4;
public const double ThumbToWhellSensitivity = 4;
public static readonly TimeSpan ThumbToWhellFirstRepeat = TimeSpan.FromMilliseconds(30 * ThumbToWhellSensitivity);
public static readonly TimeSpan ThumbToWhellRepeat = TimeSpan.FromMilliseconds(30 * ThumbToWhellSensitivity);
public Devices.SteamController Steam { get; private set; }
public Devices.Xbox360Controller X360 { get; private set; }
public Devices.KeyboardController Keyboard { get; private set; }
public Devices.MouseController Mouse { get; private set; }
public List<Profile> Profiles { get; } = new List<Profile>();
public bool RequestEnable { get; set; } = true;
public bool RequestDesktopMode { get; set; } = true;
public bool DisableDueToSteam { get; set; } = false;
public bool Enabled
{
get { return RequestEnable && !DisableDueToSteam; }
}
public bool DesktopMode
{
get
{
return RequestDesktopMode || !X360.Valid || !Mouse.Valid;
}
}
public Context()
{
Steam = new Devices.SteamController();
X360 = new Devices.Xbox360Controller();
Keyboard = new Devices.KeyboardController();
Mouse = new Devices.MouseController();
}
public void Dispose()
{
using (Steam) { }
using (X360) { }
using (Keyboard) { }
using (Mouse) { }
}
public void Tick()
{
X360.Tick();
foreach (Profile profile in Profiles)
{
try { profile.Tick(this); }
catch (Exception e) { TraceLine("Profile: {0}. Exception: {1}", e); }
}
}
public bool Update()
{
if (!Enabled)
{
X360.Connected = false;
Steam.LizardButtons = true;
Steam.LizardMouse = true;
return true;
}
Steam.BeforeUpdate();
X360.BeforeUpdate();
Keyboard.BeforeUpdate();
Mouse.BeforeUpdate();
try
{
bool skip = false;
foreach (Profile profile in Profiles)
{
if (!profile.RunAlways && skip)
{
profile.Skipped(this);
continue;
}
try
{
var status = profile.Run(this);
if (status == Profile.Status.Stop)
skip = true;
}
catch (Exception e)
{
TraceLine("Profile: Exception: {0}", e.Message);
}
}
return true;
}
catch (Exception e)
{
TraceLine("Controller: Exception: {0}", e);
return false;
}
finally
{
Steam.Update();
X360.Update();
Keyboard.Update();
Mouse.Update();
}
}
}
}

View file

@ -1,64 +0,0 @@
using static CommonHelpers.Log;
namespace SteamController.Profiles
{
public sealed class DebugProfile : Profile
{
public DebugProfile()
{
RunAlways = true;
}
List<string> lastItems = new List<string>();
public override Status Run(Context c)
{
var items = new List<string>();
if (c.DesktopMode)
items.Add("[DESKTOP]");
else
items.Add("[CONTROLLER]");
if (c.Steam.LizardButtons)
items.Add("[LB]");
if (c.Steam.LizardMouse)
items.Add("[LM]");
if (c.X360.Connected)
items.Add("[X360]");
else if (c.X360.Valid)
items.Add("[no-X360]");
foreach (var button in c.Steam.AllButtons)
{
if (button is null || !button.LastValue)
continue;
String text = button.Name;
if (button.Consumed is not null)
text += String.Format("[{0}]", button.Consumed);
if (button.Value)
text += "[P]";
items.Add(text);
}
foreach (var key in c.Keyboard.DownKeys)
{
items.Add(String.Format("Key{0}", key));
}
foreach (var mouse in c.Mouse.DownButtons)
{
items.Add(String.Format("Mouse{0}", mouse));
}
if (!items.SequenceEqual(lastItems))
{
TraceLine("DEBUG: {0}", String.Join(" ", items));
lastItems = items;
}
return Status.Continue;
}
}
}

View file

@ -0,0 +1,78 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using ExternalHelpers;
using WindowsInput;
namespace SteamController.Profiles
{
public abstract class DefaultShortcutsProfile : Profile
{
public const String ShortcutConsumed = "ShortcutsProfile";
public readonly TimeSpan HoldForShorcuts = TimeSpan.FromMilliseconds(200);
private readonly TimeSpan HoldToSwitchProfile = TimeSpan.FromSeconds(1);
private readonly TimeSpan HoldToSwitchDesktop = TimeSpan.FromSeconds(3);
public override Status Run(Context c)
{
// Steam + 3 dots simulate CTRL+ALT+DELETE
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, ShortcutConsumed) && c.Steam.BtnQuickAccess.HoldOnce(HoldForShorcuts, ShortcutConsumed))
{
// TODO: Not working due to missing `uiAccess=true`
c.Keyboard.KeyPress(new VirtualKeyCode[] { VirtualKeyCode.LCONTROL, VirtualKeyCode.LMENU }, VirtualKeyCode.DELETE);
SendSAS(true);
return Status.Done;
}
// Hold options for 1s to use next profile, or 3 seconds to switch between desktop-mode
if (c.Steam.BtnOptions.HoldOnce(HoldToSwitchProfile, ShortcutConsumed))
{
if (!c.SelectNext())
{
c.RequestDesktopMode = !c.RequestDesktopMode;
}
return Status.Done;
}
else if (c.Steam.BtnOptions.HoldNext(HoldToSwitchDesktop, ShortcutConsumed, "SwitchToDesktop"))
{
c.RequestDesktopMode = !c.RequestDesktopMode;
return Status.Done;
}
// Always consume 3 dots
if (c.Steam.BtnQuickAccess.Hold(HoldForShorcuts, ShortcutConsumed))
{
return Status.Done;
}
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, ShortcutConsumed))
{
if (AdditionalShortcuts(c))
{
return Status.Done;
}
}
return Status.Continue;
}
private bool AdditionalShortcuts(Context c)
{
if (c.Steam.BtnMenu.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.TAB);
return true;
}
if (c.Steam.BtnOptions.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.F11);
return true;
}
return false;
}
[DllImport("sas.dll")]
private static extern void SendSAS(bool asUser);
}
}

View file

@ -1,24 +1,25 @@
using PowerControl.Helpers;
using WindowsInput;
namespace SteamController.Profiles
{
public sealed class DesktopProfile : Profile
public sealed class DesktopProfile : SteamShortcutsProfile
{
public const bool LizardButtons = false;
public const bool LizardMouse = true;
public const String Consumed = "DesktopProfileOwner";
private const String Consumed = "DesktopProfileOwner";
public DesktopProfile()
{
}
public override bool Selected(Context context)
{
return context.Enabled && context.DesktopMode && !context.SteamUsesController;
}
public override Status Run(Context c)
{
if (!c.DesktopMode)
if (base.Run(c).IsDone)
{
return Status.Continue;
return Status.Done;
}
if (!c.Mouse.Valid)
@ -27,24 +28,32 @@ namespace SteamController.Profiles
// Enable emergency Lizard
c.Steam.LizardButtons = true;
c.Steam.LizardMouse = true;
return Status.Continue;
return Status.Done;
}
c.Steam.LizardButtons = LizardButtons;
c.Steam.LizardMouse = LizardMouse;
c.Steam.LizardButtons = SteamModeLizardButtons;
c.Steam.LizardMouse = SteamModeLizardMouse;
EmulateLizardButtons(c);
EmulateLizardMouse(c);
EmulateScrollOnLPad(c);
EmulateScrollOnLStick(c);
EmulateMouseOnRPad(c);
EmulateMouseOnRStick(c);
EmulateDPadArrows(c);
if (c.Steam.LPadX)
if (c.Steam.BtnA.Pressed())
{
c.Mouse.HorizontalScroll(c.Steam.LPadX.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
c.Keyboard.KeyPress(VirtualKeyCode.RETURN);
}
if (c.Steam.LPadY)
if (c.Steam.BtnB.Pressed())
{
c.Mouse.VerticalScroll(c.Steam.LPadY.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
c.Keyboard.KeyPress(VirtualKeyCode.BACK);
}
return Status.Continue;
}
private void EmulateScrollOnLStick(Context c)
{
if (c.Steam.BtnVirtualLeftThumbUp.HoldRepeat(Context.ThumbToWhellFirstRepeat, Context.ThumbToWhellRepeat, Consumed))
{
c.Mouse.VerticalScroll(Context.ThumbToWhellSensitivity);
@ -61,26 +70,11 @@ namespace SteamController.Profiles
{
c.Mouse.HorizontalScroll(Context.ThumbToWhellSensitivity);
}
if (c.Steam.BtnRStickTouch && (c.Steam.RightThumbX || c.Steam.RightThumbY))
{
c.Mouse.MoveBy(
c.Steam.RightThumbX.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime),
-c.Steam.RightThumbY.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime)
);
}
return Status.Continue;
}
private void EmulateLizardButtons(Context c)
private void EmulateDPadArrows(Context c)
{
c.Mouse[Devices.MouseController.Button.Right] = c.Steam.BtnL2 || c.Steam.BtnLPadPress;
c.Mouse[Devices.MouseController.Button.Left] = c.Steam.BtnR2 || c.Steam.BtnRPadPress;
#if true
if (c.Steam.BtnA.Pressed())
c.Keyboard.KeyPress(VirtualKeyCode.RETURN);
if (c.Steam.BtnDpadLeft.HoldRepeat(Consumed))
c.Keyboard.KeyPress(VirtualKeyCode.LEFT);
if (c.Steam.BtnDpadRight.HoldRepeat(Consumed))
@ -97,16 +91,5 @@ namespace SteamController.Profiles
c.Keyboard[VirtualKeyCode.DOWN] = c.Steam.BtnDpadDown;
#endif
}
private void EmulateLizardMouse(Context c)
{
if (c.Steam.RPadX || c.Steam.RPadY)
{
c.Mouse.MoveBy(
c.Steam.RPadX.Scaled(Context.PadToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta),
-c.Steam.RPadY.Scaled(Context.PadToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta)
);
}
}
}
}

View file

@ -1,52 +0,0 @@
using System.Diagnostics;
namespace SteamController.Profiles
{
public sealed class ProcessProfile : Profile
{
public static readonly String[] ActivationProcessNames = new String[]
{
"Playnite.FullscreenApp"
};
private bool activated;
private Process? FindActivationProcess()
{
foreach (var processName in ActivationProcessNames)
{
var process = Process.GetProcessesByName(processName).FirstOrDefault();
if (process is not null)
return process;
}
return null;
}
public override void Tick(Context context)
{
// React to state change
if (FindActivationProcess() is not null)
{
if (!activated)
{
activated = true;
context.RequestDesktopMode = false;
}
}
else
{
if (activated)
{
activated = false;
context.RequestDesktopMode = true;
}
}
}
public override Status Run(Context context)
{
return Status.Continue;
}
}
}

View file

@ -2,22 +2,18 @@ namespace SteamController.Profiles
{
public abstract class Profile
{
public enum Status
public struct Status
{
Continue,
Stop
public static readonly Status Continue = new Status() { IsDone = false };
public static readonly Status Done = new Status() { IsDone = true };
public bool IsDone { get; set; }
}
public bool RunAlways { get; set; }
public String Name { get; set; } = "";
public abstract bool Selected(Context context);
public abstract Status Run(Context context);
public virtual void Tick(Context context)
{
}
public virtual void Skipped(Context context)
{
}
}
}

View file

@ -1,35 +0,0 @@
using System.Diagnostics;
using SteamController.Helpers;
namespace SteamController.Profiles
{
public sealed class SteamDetectProfile : Profile
{
public override void Tick(Context context)
{
if (!Settings.Default.EnableSteamDetection)
{
context.DisableDueToSteam = false;
return;
}
var usesController = UsesController();
// if controller is used, disable due to Steam
context.DisableDueToSteam = usesController ?? true;
}
private bool? UsesController()
{
if (!SteamManager.IsRunning.GetValueOrDefault(false))
return null;
return SteamManager.IsBigPictureMode.GetValueOrDefault(false) || SteamManager.IsRunningGame.GetValueOrDefault(false);
}
public override Status Run(Context context)
{
return Status.Continue;
}
}
}

View file

@ -0,0 +1,26 @@
using Nefarius.ViGEm.Client.Targets.Xbox360;
namespace SteamController.Profiles
{
public sealed class SteamProfile : DefaultShortcutsProfile
{
public override bool Selected(Context context)
{
return context.Enabled && context.SteamUsesController;
}
public override Status Run(Context context)
{
// Steam does not use Lizard
context.Steam.LizardButtons = false;
context.Steam.LizardMouse = false;
if (base.Run(context).IsDone)
{
return Status.Done;
}
return Status.Continue;
}
}
}

View file

@ -1,52 +1,30 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using ExternalHelpers;
using PowerControl.Helpers;
using WindowsInput;
namespace SteamController.Profiles
{
public sealed class SteamShortcutsProfile : Profile
public abstract class SteamShortcutsProfile : DefaultShortcutsProfile
{
public const bool LizardButtons = true;
public const bool LizardMouse = false;
public static bool SteamModeLizardButtons = false;
public static bool SteamModeLizardMouse = true;
public const String Consumed = "SteamShortcutsProfile";
public readonly TimeSpan HoldForShorcuts = TimeSpan.FromMilliseconds(200);
public readonly TimeSpan HoldForKill = TimeSpan.FromSeconds(3);
public readonly TimeSpan HoldForClose = TimeSpan.FromSeconds(1);
public readonly TimeSpan HoldToSwitchDesktop = TimeSpan.FromSeconds(1);
public SteamShortcutsProfile()
{
RunAlways = true;
}
public override Status Run(Context c)
{
// Steam + 3 dots simulate CTRL+ALT+DELETE
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, Consumed) && c.Steam.BtnQuickAccess.HoldOnce(HoldForShorcuts, Consumed))
if (base.Run(c).IsDone)
{
c.Keyboard.KeyPress(new VirtualKeyCode[] { VirtualKeyCode.LCONTROL, VirtualKeyCode.LMENU }, VirtualKeyCode.DELETE);
return Status.Done;
}
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, Consumed))
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, ShortcutConsumed))
{
c.Steam.LizardButtons = LizardButtons;
c.Steam.LizardMouse = LizardMouse;
SteamShortcuts(c);
AdditionalShortcuts(c);
return Status.Stop;
}
if (c.Steam.BtnOptions.HoldOnce(HoldToSwitchDesktop, Consumed))
{
c.RequestDesktopMode = !c.RequestDesktopMode;
}
if (c.Steam.BtnQuickAccess.Hold(HoldForShorcuts, Consumed))
{
// nothing there, just consume
return Status.Stop;
return Status.Done;
}
return Status.Continue;
@ -54,19 +32,23 @@ namespace SteamController.Profiles
private void SteamShortcuts(Context c)
{
c.Steam.LizardButtons = false;
c.Steam.LizardMouse = true;
c.Steam.LizardButtons = SteamModeLizardButtons;
c.Steam.LizardMouse = SteamModeLizardMouse;
EmulateScrollOnLPad(c);
EmulateMouseOnRPad(c);
EmulateMouseOnRStick(c);
if (c.Steam.BtnA.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.RETURN);
}
if (c.Steam.BtnB.HoldOnce(HoldForKill, Consumed))
if (c.Steam.BtnB.HoldOnce(HoldForKill, ShortcutConsumed))
{
// kill application
}
else if (c.Steam.BtnB.HoldOnce(HoldForClose, Consumed))
else if (c.Steam.BtnB.HoldOnce(HoldForClose, ShortcutConsumed))
{
// close application
c.Keyboard.KeyPress(VirtualKeyCode.LMENU, VirtualKeyCode.F4);
@ -97,34 +79,12 @@ namespace SteamController.Profiles
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.SNAPSHOT);
}
c.Mouse[Devices.MouseController.Button.Right] = c.Steam.BtnL2 || c.Steam.BtnLPadPress;
c.Mouse[Devices.MouseController.Button.Left] = c.Steam.BtnR2 || c.Steam.BtnRPadPress;
if (c.Steam.BtnRStickTouch && (c.Steam.RightThumbX || c.Steam.RightThumbY))
{
c.Mouse.MoveBy(
c.Steam.RightThumbX.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime),
-c.Steam.RightThumbY.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime)
);
}
if (c.Steam.LPadX)
{
c.Mouse.HorizontalScroll(c.Steam.LPadX.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
if (c.Steam.LPadY)
{
c.Mouse.VerticalScroll(c.Steam.LPadY.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
EmulateLizardMouse(c);
if (c.Steam.BtnVirtualLeftThumbUp.HoldRepeat(Consumed))
if (c.Steam.BtnVirtualLeftThumbUp.HoldRepeat(ShortcutConsumed))
{
WindowsSettingsBrightnessController.Increase(5);
}
if (c.Steam.BtnVirtualLeftThumbDown.HoldRepeat(Consumed))
if (c.Steam.BtnVirtualLeftThumbDown.HoldRepeat(ShortcutConsumed))
{
WindowsSettingsBrightnessController.Increase(-5);
}
@ -145,21 +105,42 @@ namespace SteamController.Profiles
}
}
private void AdditionalShortcuts(Context c)
protected void EmulateScrollOnLPad(Context c)
{
if (c.Steam.BtnMenu.Pressed())
if (c.Steam.LPadX)
{
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.TAB);
c.Mouse.HorizontalScroll(c.Steam.LPadX.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
if (c.Steam.BtnOptions.Pressed())
if (c.Steam.LPadY)
{
c.Keyboard.KeyPress(VirtualKeyCode.F11);
c.Mouse.VerticalScroll(c.Steam.LPadY.Scaled(Context.PadToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
}
private void EmulateLizardMouse(Context c)
protected void EmulateMouseOnRStick(Context c)
{
if (c.Steam.RightThumbX || c.Steam.RightThumbY)
{
c.Mouse.MoveBy(
c.Steam.RightThumbX.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime),
-c.Steam.RightThumbY.Scaled(Context.JoystickToMouseSensitivity, Devices.SteamController.SteamAxis.ScaledMode.AbsoluteTime)
);
}
}
protected void EmulateMouseOnRPad(Context c, bool useButtonTriggers = true)
{
if (useButtonTriggers)
{
c.Mouse[Devices.MouseController.Button.Right] = c.Steam.BtnL2 || c.Steam.BtnLPadPress;
c.Mouse[Devices.MouseController.Button.Left] = c.Steam.BtnR2 || c.Steam.BtnRPadPress;
}
else
{
c.Mouse[Devices.MouseController.Button.Right] = c.Steam.BtnLPadPress;
c.Mouse[Devices.MouseController.Button.Left] = c.Steam.BtnRPadPress;
}
if (c.Steam.RPadX || c.Steam.RPadY)
{
c.Mouse.MoveBy(

View file

@ -2,28 +2,33 @@ using Nefarius.ViGEm.Client.Targets.Xbox360;
namespace SteamController.Profiles
{
public sealed class X360Profile : Profile
public sealed class X360Profile : SteamShortcutsProfile
{
public override void Skipped(Context context)
public override bool Selected(Context context)
{
if (!context.DesktopMode)
{
context.X360.Connected = true;
ControlButtons(context);
}
return context.Enabled && !context.DesktopMode;
}
public override Status Run(Context context)
{
if (context.DesktopMode)
context.Steam.LizardButtons = false;
context.Steam.LizardMouse = SteamModeLizardMouse;
context.X360.Connected = true;
// Controls
context.X360[Xbox360Button.Guide] = context.Steam.BtnSteam.Pressed();
context.X360[Xbox360Button.Back] = context.Steam.BtnMenu;
context.X360[Xbox360Button.Start] = context.Steam.BtnOptions;
if (base.Run(context).IsDone)
{
context.X360.Connected = false;
return Status.Continue;
return Status.Done;
}
context.Steam.LizardButtons = false;
context.Steam.LizardMouse = true;
context.X360.Connected = true;
// Default emulation
EmulateScrollOnLPad(context);
EmulateMouseOnRStick(context);
EmulateMouseOnRPad(context, false);
// DPad
context.X360[Xbox360Button.Up] = context.Steam.BtnDpadUp;
@ -51,16 +56,7 @@ namespace SteamController.Profiles
context.X360[Xbox360Button.LeftShoulder] = context.Steam.BtnL1;
context.X360[Xbox360Button.RightShoulder] = context.Steam.BtnR1;
ControlButtons(context);
return Status.Continue;
}
private void ControlButtons(Context context)
{
// Controls
context.X360[Xbox360Button.Guide] = context.Steam.BtnSteam.Pressed();
context.X360[Xbox360Button.Back] = context.Steam.BtnMenu;
context.X360[Xbox360Button.Start] = context.Steam.BtnOptions;
}
}
}