Configure Steam to enable or disable Steam/X360 Controllers

- Allow to configure Steam controller blacklisting to enable X360 exclusive mode.
- This allows to switch seemlessly between different modes of operation.
- This also changes how application and when detects Steam.
This commit is contained in:
Kamil Trzciński 2022-11-27 13:41:28 +01:00
parent aafe040e12
commit 8fb4571b21
16 changed files with 469 additions and 158 deletions

View file

@ -146,7 +146,7 @@ Since when there's no Steam, there's no problem at all.
#### 4.1.1. The ideal setup - run Steam when running game via Playnite
It is **preffered** to configure Steam as described in [Configure Steam](#43-configure-steam).
It is **preferred** to configure Steam as described in [Configure Steam](#43-configure-steam).
The perfect way to use it:
@ -160,7 +160,17 @@ This allows to use `Steam Input` for Steam games.
Configure Playnite to close Steam after game session.
#### 4.1.2. Less ideal (quirky) - Run Steam in background
#### 4.1.2. Another great option - Always use X360 controller and completly disable Steam Input
It is **not needed** to configure Steam as described in [Configure Steam](#43-configure-steam).
In this mode you would use either Desktop or X360 controls, and all Steam games would use X360.
When running in this mode you would have to switch between Desktop and X360 mode with `Options` button
or with `PowerControl`.
Of course you will have access to all described shortcuts.
#### 4.1.3. Less ideal (quirky) - Run Steam in background
It is **required** to configure Steam as described in [Configure Steam](#43-configure-steam).
@ -177,19 +187,6 @@ and disable itself for the duration of game play. This allows to use `Steam Inpu
> of handling Steam running in background alongside `Steam Controller`. This will never
> be supported mode of operation.
#### 4.1.3. Optional - Always use X360 controller and completly disable Steam Input
It is **not needed** to configure Steam as described in [Configure Steam](#43-configure-steam).
Alternative way is to use [HidHide](https://github.com/ViGEm/HidHide) to hide `Valve Software Steam Controller`
from Steam. You would then have to disable detection of Steam via `Auto-disable on Steam` in `SteamController`.
In this mode you would use either Desktop or X360 controls, and all Steam games would use X360.
When running in this mode you would have to switch between Desktop and X360 mode with `Options` button
or with `PowerControl`.
Of course you will have access to all described shortcuts.
### 4.2. Mappings
| Button | Desktop | X360 (with Rumble) | Steam | Steam With Shortcuts |
@ -217,7 +214,7 @@ Of course you will have access to all described shortcuts.
| STEAM + R2 | Mouse Left Click | Mouse Left Click | | Mouse Left Click |
| STEAM + Left Pad Press | Mouse Right Click | Mouse Right Click | | Mouse Right Click |
| STEAM + Right Pad Press | Mouse Left Click | Mouse Left Click | | Mouse Left Click |
| Steam + Up Pad Press | Ctrl + Alt + U | Ctrl + Alt + U | | |
| STEAM + Up Pad Press | Ctrl + Alt + U | Ctrl + Alt + U | | |
| Left Pad | Mouse Scroll | | | |
| Left Joystick | Mouse Scroll | | | |
| Right Joystick | Mouse Trackpad | | | |

View file

@ -12,6 +12,8 @@
- Fix `LT/RT` to trigger up to 50%, instead of 100%
- Add mapping for `STEAM+DPadUp`
- Usage of `KeyboardController` will now generate key repeats
- Configure Steam to switch between Steam Input or X360 Controller mode
- Steam Games detection also works for X360 Controller mode
## 0.4.x

View file

@ -8,10 +8,7 @@
<userSettings>
<SteamController.Settings>
<setting name="EnableSteamDetection" serializeAs="String">
<value>True</value>
</setting>
<setting name="EnableHidHide" serializeAs="String">
<value>True</value>
<value>False</value>
</setting>
</SteamController.Settings>
</userSettings>

View file

@ -23,8 +23,8 @@ namespace SteamController
public bool RequestEnable { get; set; } = true;
public bool RequestDesktopMode { get; set; } = true;
public bool SteamRunning { get; set; } = false;
public bool SteamUsesController { get; set; } = false;
public bool SteamUsesX360Controller { get; set; } = false;
public bool SteamUsesSteamInput { get; set; } = false;
public event Action<Profiles.Profile> ProfileChanged;

View file

@ -42,6 +42,8 @@ namespace SteamController
public Controller()
{
var blacklist = Helpers.SteamConfiguration.GetControllerBlacklist();
Instance.RunOnce(TitleWithVersion, "Global\\SteamController");
var contextMenu = new ContextMenuStrip(components);
@ -80,15 +82,7 @@ namespace SteamController
contextMenu.Items.Add(new ToolStripSeparator());
#endif
var steamDetectionItem = new ToolStripMenuItem("Auto-disable on &Steam");
steamDetectionItem.Checked = Settings.Default.EnableSteamDetection;
steamDetectionItem.Click += delegate
{
Settings.Default.EnableSteamDetection = !Settings.Default.EnableSteamDetection;
Settings.Default.Save();
};
contextMenu.Opening += delegate { steamDetectionItem.Checked = Settings.Default.EnableSteamDetection; };
contextMenu.Items.Add(steamDetectionItem);
AddSteamOptions(contextMenu);
if (startupManager.IsAvailable)
{
@ -193,15 +187,18 @@ namespace SteamController
notifyIcon.Text = TitleWithVersion + ". Missing ViGEm?";
notifyIcon.Icon = Resources.microsoft_xbox_controller_red;
}
else if (context.Enabled && context.SteamUsesController)
{
notifyIcon.Icon = context.DesktopMode ? Resources.monitor_off : Resources.microsoft_xbox_controller_off;
notifyIcon.Text = TitleWithVersion + ". Steam Detected";
}
else if (context.Enabled)
{
notifyIcon.Icon = context.DesktopMode ? Resources.monitor : Resources.microsoft_xbox_controller;
notifyIcon.Text = TitleWithVersion;
if (context.SteamUsesSteamInput)
{
notifyIcon.Icon = context.DesktopMode ? Resources.monitor_off : Resources.microsoft_xbox_controller_off;
notifyIcon.Text = TitleWithVersion + ". Steam uses Steam Input";
}
else
{
notifyIcon.Icon = context.DesktopMode ? Resources.monitor : Resources.microsoft_xbox_controller;
notifyIcon.Text = TitleWithVersion;
}
var profile = context.GetCurrentProfile();
if (profile is not null)
@ -229,5 +226,115 @@ namespace SteamController
using (context) { }
}
private void AddSteamOptions(ContextMenuStrip contextMenu)
{
var ignoreSteamItem = new ToolStripMenuItem("&Ignore Steam");
ignoreSteamItem.ToolTipText = "Disable Steam detection. Ensures that neither Steam Controller or X360 Controller are not blacklisted.";
ignoreSteamItem.Click += delegate
{
ConfigureSteam(
"This will enable Steam Controller and X360 Controller in Steam.",
false, false, false
);
};
contextMenu.Items.Add(ignoreSteamItem);
var useX360WithSteamItem = new ToolStripMenuItem("Use &X360 Controller with Steam");
useX360WithSteamItem.ToolTipText = "Hide Steam Deck Controller from Steam, and uses X360 controller instead.";
useX360WithSteamItem.Click += delegate
{
ConfigureSteam(
"This will hide Steam Controller from Steam and use X360 Controller for all games.",
true, true, false
);
};
contextMenu.Items.Add(useX360WithSteamItem);
var useSteamInputItem = new ToolStripMenuItem("Use &Steam Input with Steam");
useSteamInputItem.ToolTipText = "Uses Steam Input and hides X360 Controller from Steam. Requires disabling ALL Steam Desktop Mode shortcuts.";
useSteamInputItem.Click += delegate
{
ConfigureSteam(
"This will hide X360 Controller from Steam, and will try to detect Steam presence " +
"to disable usage of this application when running Steam Games.\n\n" +
"This does REQUIRE disabling DESKTOP MODE shortcuts in Steam.\n" +
"Follow guide found at https://github.com/ayufan/steam-deck-tools.",
true, false, true
);
};
contextMenu.Items.Add(useSteamInputItem);
var steamSeparatorItem = new ToolStripSeparator();
contextMenu.Items.Add(steamSeparatorItem);
contextMenu.Opening += delegate
{
var blacklistedSteamController = Helpers.SteamConfiguration.IsControllerBlacklisted(
Devices.SteamController.VendorID,
Devices.SteamController.ProductID
);
ignoreSteamItem.Visible = blacklistedSteamController is not null;
useX360WithSteamItem.Visible = blacklistedSteamController is not null;
steamSeparatorItem.Visible = blacklistedSteamController is not null;
useSteamInputItem.Visible = blacklistedSteamController is not null;
ignoreSteamItem.Checked = !Settings.Default.EnableSteamDetection || blacklistedSteamController == null;
useX360WithSteamItem.Checked = Settings.Default.EnableSteamDetection && blacklistedSteamController == true;
useSteamInputItem.Checked = Settings.Default.EnableSteamDetection && blacklistedSteamController == false;
};
}
private void ConfigureSteam(String message, bool steamDetection, bool blacklistSteamController, bool blacklistX360Controller)
{
String text;
text = "This will change Steam configuration.\n\n";
text += "Close Steam before confirming as otherwise Steam will be forcefully closed.\n\n";
text += message;
var result = MessageBox.Show(
text,
TitleWithVersion,
MessageBoxButtons.OKCancel,
MessageBoxIcon.Exclamation
);
if (result != DialogResult.OK)
return;
Helpers.SteamConfiguration.KillSteam();
Helpers.SteamConfiguration.WaitForSteamClose(5000);
Helpers.SteamConfiguration.BackupSteamConfig();
var steamControllerUpdate = Helpers.SteamConfiguration.UpdateControllerBlacklist(
Devices.SteamController.VendorID,
Devices.SteamController.ProductID,
blacklistSteamController
);
var x360ControllerUpdate = Helpers.SteamConfiguration.UpdateControllerBlacklist(
Devices.Xbox360Controller.VendorID,
Devices.Xbox360Controller.ProductID,
blacklistX360Controller
);
Settings.Default.EnableSteamDetection = steamDetection;
Settings.Default.Save();
if (steamControllerUpdate && x360ControllerUpdate)
{
notifyIcon.ShowBalloonTip(
3000, TitleWithVersion,
"Steam Configuration changed. You can start Steam now.",
ToolTipIcon.Info
);
}
else
{
notifyIcon.ShowBalloonTip(
3000, TitleWithVersion,
"Steam Configuration was not updated. Maybe Steam is open?",
ToolTipIcon.Warning
);
}
}
}
}

View file

@ -7,8 +7,8 @@ namespace SteamController.Devices
{
public partial class SteamController : IDisposable
{
public const ushort SteamVendorID = 0x28DE;
public const ushort SteamProductID = 0x1205;
public const ushort VendorID = 0x28DE;
public const ushort ProductID = 0x1205;
private const int ReadTimeout = 50;
private hidapi.HidDevice neptuneDevice;
@ -18,7 +18,7 @@ namespace SteamController.Devices
InitializeButtons();
InitializeActions();
neptuneDevice = new hidapi.HidDevice(SteamVendorID, SteamProductID, 64);
neptuneDevice = new hidapi.HidDevice(VendorID, ProductID, 64);
neptuneDevice.OpenDevice();
}

View file

@ -9,6 +9,8 @@ namespace SteamController.Devices
public class Xbox360Controller : IDisposable
{
public readonly TimeSpan FeedbackTimeout = TimeSpan.FromMilliseconds(1000);
public const ushort VendorID = 0x045E;
public const ushort ProductID = 0x028E;
private ViGEmClient? client;
private IXbox360Controller? device;

View file

@ -0,0 +1,283 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace SteamController.Helpers
{
internal static class SteamConfiguration
{
public const String SteamKey = @"Software\Valve\Steam";
public const String RunningAppIDValue = @"RunningAppID";
public const String SteamExeValue = @"SteamExe";
public const String SteamPathValue = @"SteamPath";
public const String BigPictureInForegroundValue = @"BigPictureInForeground";
public const String ActiveProcessKey = @"Software\Valve\Steam\ActiveProcess";
public const String PIDValue = @"pid";
public const String RelativeConfigPath = @"config/config.vdf";
private static readonly Regex ControllerBlacklistRegex = new Regex("^(\\s*\"controller_blacklist\"\\s*\")([^\"]*)(\"\\s*)$");
public static bool? IsRunning
{
get
{
var value = GetValue<int>(ActiveProcessKey, PIDValue);
if (value is null)
return null;
try
{
using (var process = Process.GetProcessById(value.Value))
{
return !process.HasExited;
}
}
catch (ArgumentException)
{
return false;
}
}
}
public static bool? IsBigPictureMode
{
get
{
var value = GetValue<int>(SteamKey, BigPictureInForegroundValue);
return value.HasValue ? value != 0 : null;
}
}
public static bool? IsRunningGame
{
get
{
var value = GetValue<int>(SteamKey, RunningAppIDValue);
return value.HasValue ? value != 0 : null;
}
}
public static bool IsGamePadUI
{
get
{
var steamWindow = FindWindow("SDL_app", "SP");
if (steamWindow == null)
return false;
return GetForegroundWindow() == steamWindow;
}
}
public static String? SteamExe
{
get { return GetValue2<string>(SteamKey, SteamExeValue); }
}
public static String? SteamPath
{
get { return GetValue2<string>(SteamKey, SteamPathValue); }
}
public static String? SteamConfigPath
{
get
{
var path = SteamPath;
if (path is null)
return null;
return Path.Join(SteamPath, RelativeConfigPath);
}
}
private static Process? SteamProcess
{
get
{
var value = GetValue<int>(ActiveProcessKey, PIDValue);
if (value is null)
return null;
try
{
return Process.GetProcessById(value.Value);
}
catch (ArgumentException)
{
return null;
}
}
}
public static bool ShutdownSteam()
{
var steamExe = SteamExe;
if (steamExe is null)
return false;
var process = Process.Start(new ProcessStartInfo()
{
FileName = steamExe,
ArgumentList = { "-shutdown" },
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
CreateNoWindow = true
});
return process is not null;
}
public static bool KillSteam()
{
var process = SteamProcess;
if (process is null)
return true;
try
{
using (process) { process.Kill(); }
return true;
}
catch (System.ComponentModel.Win32Exception)
{
return false;
}
}
public static bool WaitForSteamClose(int timeout)
{
var waitTill = DateTimeOffset.Now.AddMilliseconds(timeout);
while (DateTimeOffset.Now < waitTill)
{
if (IsRunning != true)
return true;
Application.DoEvents();
Thread.Sleep(50);
}
return false;
}
public static HashSet<String>? GetControllerBlacklist()
{
var configPath = SteamConfigPath;
if (configPath is null)
return null;
foreach (var line in File.ReadLines(configPath))
{
var match = ControllerBlacklistRegex.Match(line);
if (!match.Success)
continue;
// matches `"controller_blacklist" "<value>"`
var value = match.Groups[2].Captures[0].Value;
return value.Split(',', StringSplitOptions.RemoveEmptyEntries).ToHashSet();
}
return null;
}
public static bool? IsControllerBlacklisted(ushort vendorId, ushort productId)
{
var controllers = GetControllerBlacklist();
if (controllers is null)
return null;
var id = String.Format("{0:x}/{1:x}", vendorId, productId);
return controllers.Contains(id);
}
public static void BackupSteamConfig()
{
var configPath = SteamConfigPath;
if (configPath is null)
return;
var suffix = DateTime.Now.ToString("yyyyMMddHHmmss");
File.Copy(configPath, String.Format("{0}.{1}.bak", configPath, suffix));
}
public static bool UpdateControllerBlacklist(ushort vendorId, ushort productId, bool add)
{
if (IsRunning == true)
return false;
var configPath = SteamConfigPath;
if (configPath is null)
return false;
var lines = File.ReadLines(configPath).ToArray();
bool result = true;
for (int i = 0; i < lines.Length; i++)
{
var match = ControllerBlacklistRegex.Match(lines[i]);
if (!match.Success)
continue;
var value = match.Groups[2].Captures[0].Value;
var controllers = value.Split(',', StringSplitOptions.RemoveEmptyEntries).ToHashSet();
var id = String.Format("{0:x}/{1:x}", vendorId, productId);
if (add)
controllers.Add(id);
else
controllers.Remove(id);
lines[i] = String.Format("{0}{1}{2}",
match.Groups[1].Captures[0].Value,
String.Join(',', controllers),
match.Groups[3].Captures[0].Value
);
result = true;
break;
}
if (result)
{
File.WriteAllLines(configPath, lines);
}
return result;
}
private static T? GetValue<T>(string key, string value) where T : struct
{
try
{
using (var registryKey = Registry.CurrentUser.OpenSubKey(key))
{
return registryKey?.GetValue(value) as T?;
}
}
catch (Exception)
{
return null;
}
}
private static T? GetValue2<T>(string key, string value) where T : class
{
try
{
using (var registryKey = Registry.CurrentUser.OpenSubKey(key))
{
return registryKey?.GetValue(value) as T;
}
}
catch (Exception)
{
return null;
}
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
}

View file

@ -1,86 +0,0 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace SteamController.Helpers
{
internal static class SteamProcess
{
public const String SteamKey = @"Software\Valve\Steam";
public const String RunningAppIDValue = @"RunningAppID";
public const String BigPictureInForegroundValue = @"BigPictureInForeground";
public const String ActiveProcessKey = @"Software\Valve\Steam\ActiveProcess";
public const String PIDValue = @"pid";
public static bool? IsRunning
{
get
{
var value = GetValue<int>(ActiveProcessKey, PIDValue);
if (value is null)
return null;
try
{
Process.GetProcessById(value.Value);
return true;
}
catch (ArgumentException)
{
return false;
}
}
}
public static bool? IsBigPictureMode
{
get
{
var value = GetValue<int>(SteamKey, BigPictureInForegroundValue);
return value.HasValue ? value != 0 : null;
}
}
public static bool? IsRunningGame
{
get
{
var value = GetValue<int>(SteamKey, RunningAppIDValue);
return value.HasValue ? value != 0 : null;
}
}
public static bool IsGamePadUI
{
get
{
var steamWindow = FindWindow("SDL_app", "SP");
if (steamWindow == null)
return false;
return GetForegroundWindow() == steamWindow;
}
}
private static T? GetValue<T>(string key, string value) where T : struct
{
try
{
using (var registryKey = Registry.CurrentUser.OpenSubKey(key))
{
return registryKey?.GetValue(value) as T?;
}
}
catch (Exception)
{
return null;
}
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
}

View file

@ -5,31 +5,55 @@ namespace SteamController.Managers
{
public sealed class SteamManager : Manager
{
private bool lastState;
public override void Tick(Context context)
{
if (!Settings.Default.EnableSteamDetection)
{
context.SteamRunning = true;
context.SteamUsesController = false;
context.SteamUsesSteamInput = false;
context.SteamUsesX360Controller = false;
lastState = false;
return;
}
var usesController = UsesController();
var usesController = UsesController() ?? false;
if (lastState == usesController)
return;
// if controller is used, disable due to Steam unless it is hidden
context.SteamRunning = usesController is not null;
context.SteamUsesController = usesController ?? false;
if (usesController)
{
context.SteamUsesSteamInput = Helpers.SteamConfiguration.IsControllerBlacklisted(
Devices.SteamController.VendorID,
Devices.SteamController.ProductID
) != true;
context.SteamUsesX360Controller = Helpers.SteamConfiguration.IsControllerBlacklisted(
Devices.Xbox360Controller.VendorID,
Devices.Xbox360Controller.ProductID
) != true;
context.ToggleDesktopMode(false);
}
else
{
context.SteamUsesSteamInput = false;
context.SteamUsesX360Controller = false;
context.ToggleDesktopMode(true);
}
lastState = usesController;
}
private bool? UsesController()
{
if (!SteamProcess.IsRunning.GetValueOrDefault(false))
if (!SteamConfiguration.IsRunning.GetValueOrDefault(false))
return null;
return
SteamProcess.IsBigPictureMode.GetValueOrDefault(false) ||
SteamProcess.IsRunningGame.GetValueOrDefault(false) ||
SteamProcess.IsGamePadUI;
SteamConfiguration.IsBigPictureMode.GetValueOrDefault(false) ||
SteamConfiguration.IsRunningGame.GetValueOrDefault(false) ||
SteamConfiguration.IsGamePadUI;
}
}
}

View file

@ -13,7 +13,7 @@ namespace SteamController.Profiles
public override bool Selected(Context context)
{
return context.Enabled && context.DesktopMode && !context.SteamUsesController;
return context.Enabled && context.DesktopMode;
}
public override Status Run(Context c)

View file

@ -10,7 +10,7 @@ namespace SteamController.Profiles
public override bool Selected(Context context)
{
return context.Enabled && context.SteamUsesController;
return context.Enabled && context.SteamUsesSteamInput;
}
public override Status Run(Context context)

View file

@ -10,7 +10,7 @@ namespace SteamController.Profiles
public override bool Selected(Context context)
{
return context.Enabled && context.SteamUsesController;
return context.Enabled && context.SteamUsesSteamInput;
}
public override Status Run(Context context)

View file

@ -6,7 +6,7 @@ namespace SteamController.Profiles
{
public override bool Selected(Context context)
{
return context.Enabled && !context.DesktopMode;
return context.Enabled && !context.DesktopMode && !context.SteamUsesSteamInput;
}
public override Status Run(Context context)

View file

@ -25,7 +25,7 @@ namespace SteamController {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool EnableSteamDetection {
get {
return ((bool)(this["EnableSteamDetection"]));
@ -34,17 +34,5 @@ namespace SteamController {
this["EnableSteamDetection"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool EnableHidHide {
get {
return ((bool)(this["EnableHidHide"]));
}
set {
this["EnableHidHide"] = value;
}
}
}
}

View file

@ -3,10 +3,7 @@
<Profiles />
<Settings>
<Setting Name="EnableSteamDetection" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="EnableHidHide" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>