Add SteamController implementation

This adds a Steam Shortcuts, Desktop Mode, and X360 Emulation
- Supports all Steam Shortcuts (including on-screen keyboard, and brightness)
- Supports Desktop mode (with a scroll on left pad and left stick), and trackpoint (on right stick)
- Supports X360 mode: hold Options for 1s to switch between Desktop and X360
- Holding Steam button enables Desktop like controls and stops passing all inputs to X360
This commit is contained in:
Kamil Trzciński 2022-11-24 22:37:24 +01:00
parent 203338b669
commit ecbd0407c0
41 changed files with 2486 additions and 34 deletions

16
CommonHelpers/Log.cs Normal file
View file

@ -0,0 +1,16 @@
using System.Diagnostics;
namespace CommonHelpers
{
public static class Log
{
public static void TraceLine(string format, params object?[] arg)
{
String line = string.Format(format, arg);
Trace.WriteLine(line);
if (Environment.UserInteractive)
Console.WriteLine(line);
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace ExternalHelpers
{
public static class OnScreenKeyboard
{
public const String TabTipPath = @"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe";
public static bool Toggle()
{
StartTabTip();
var type = Type.GetTypeFromCLSID(Guid.Parse("4ce576fa-83dc-4F88-951c-9d0782b4e376"));
if (type is null)
return false;
var instance = (ITipInvocation?)Activator.CreateInstance(type);
if (instance is null)
return false;
instance?.Toggle(GetDesktopWindow());
Marshal.ReleaseComObject(instance);
return true;
}
static void StartTabTip()
{
if (FindWindow("IPTIP_Main_Window", "") != IntPtr.Zero)
return;
Process.Start(TabTipPath);
for (int i = 0; i < 10 && FindWindow("IPTIP_Main_Window", "") == IntPtr.Zero; i++)
Thread.Sleep(100);
}
[ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
class UIHostNoLaunch
{
}
[ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ITipInvocation
{
void Toggle(IntPtr hwnd);
}
[DllImport("user32.dll", SetLastError = false)]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", EntryPoint = "FindWindow")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
}

View file

@ -45,7 +45,8 @@ namespace PowerControl.External
CONFIGURE_BT = 0x18,
}
public enum SDCButton0
[Flags]
public enum SDCButton0 : ushort
{
BTN_L5 = 0b1000000000000000,
BTN_OPTIONS = 0b0100000000000000,
@ -65,22 +66,25 @@ namespace PowerControl.External
BTN_R2 = 0b0000000000000001,
}
public enum SDCButton1
[Flags]
public enum SDCButton1 : byte
{
BTN_LSTICK_PRESS = 0b01000000,
BTN_RPAD_TOUCH = 0b00010000,
BTN_LPAD_TOUCH = 0b00001000,
BTN_RPAD_PRESS = 0b00000100,
BTN_LPAD_PRESS = 0b00000010,
BTN_RPAD_PRESS = 0b00010000,
BTN_RPAD_TOUCH = 0b00000100,
BTN_R5 = 0b00000001,
}
public enum SDCButton2
[Flags]
public enum SDCButton2 : byte
{
BTN_RSTICK_PRESS = 0b00000100,
}
public enum SDCButton4
[Flags]
public enum SDCButton4 : byte
{
BTN_LSTICK_TOUCH = 0b01000000,
BTN_RSTICK_TOUCH = 0b10000000,
@ -88,29 +92,30 @@ namespace PowerControl.External
BTN_L4 = 0b00000010,
}
public enum SDCButton5
[Flags]
public enum SDCButton5 : byte
{
BTN_QUICK_ACCESS = 0b00000100,
}
[StructLayout(LayoutKind.Sequential)]
public struct SDCInput
{
public byte ptype; //0x00
public byte _a1; //0x01
public byte _a2; //0x02
public byte _a1; //0x01
public byte _a2; //0x02
public byte _a3; //0x03
public uint seq; //0x04
public ushort buttons0; //0x09
public byte buttons1; //0x0A
public byte buttons2; //0x0C
public byte buttons3; //0x0D
public byte buttons4; //0x0E
public byte buttons5; //0x0E
public uint seq; //0x04
public SDCButton0 buttons0; //0x08
public SDCButton1 buttons1; //0x0A
public SDCButton2 buttons2; //0x0B
public byte buttons3; //0x0C
public SDCButton4 buttons4; //0x0D
public SDCButton5 buttons5; //0x0E
public byte buttons6; //0x0F
public short lpad_x; //0x10
public short lpad_y; //0x12
public short rpad_x; //0x13
public short rpad_x; //0x14
public short rpad_y; //0x16
public short accel_x; //0x18
public short accel_y; //0x1A
@ -136,7 +141,7 @@ namespace PowerControl.External
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
return (SDCInput)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(SDCInput));
return Marshal.PtrToStructure<SDCInput>(handle.AddrOfPinnedObject());
}
catch
{

View file

@ -12,6 +12,14 @@ namespace PowerControl.Helpers
/// </summary>
public static class WindowsSettingsBrightnessController
{
public static void Increase(int brightness)
{
var current = Get();
current += brightness;
current = Math.Clamp(current, 0, 100);
Set(current);
}
public static int Get(double roundValue = 10.0)
{
return (int)(Math.Round(Get() / roundValue) * roundValue);

View file

@ -88,7 +88,7 @@ namespace PowerControl
osdDismissTimer = new System.Windows.Forms.Timer(components);
osdDismissTimer.Interval = 3000;
osdDismissTimer.Tick += delegate(object ? sender, EventArgs e)
osdDismissTimer.Tick += delegate (object? sender, EventArgs e)
{
hideOSD();
};
@ -150,7 +150,7 @@ namespace PowerControl
{
GlobalHotKey.RegisterHotKey("VolumeUp", () =>
{
if ((neptuneDeviceState.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) != 0)
if (neptuneDeviceState.buttons5.HasFlag(SDCButton5.BTN_QUICK_ACCESS))
rootMenu.SelectNext("Brightness");
else
rootMenu.SelectNext("Volume");
@ -160,7 +160,7 @@ namespace PowerControl
GlobalHotKey.RegisterHotKey("VolumeDown", () =>
{
if ((neptuneDeviceState.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) != 0)
if (neptuneDeviceState.buttons5.HasFlag(SDCButton5.BTN_QUICK_ACCESS))
rootMenu.SelectPrev("Brightness");
else
rootMenu.SelectPrev("Volume");
@ -207,7 +207,7 @@ namespace PowerControl
}
// Consume only some events to avoid under-running SWICD
if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) != 0)
if (neptuneDeviceState.buttons5.HasFlag(SDCButton5.BTN_QUICK_ACCESS))
Thread.Sleep(1000 / 30);
else
Thread.Sleep(250);
@ -230,12 +230,12 @@ namespace PowerControl
return; // otherwise it did not yet trigger
// Reset sequence: 3 dots + L4|R4|L5|R5
if (input.buttons0 == (ushort)SDCButton0.BTN_L5 &&
input.buttons1 == (byte)SDCButton1.BTN_R5 &&
if (input.buttons0 == SDCButton0.BTN_L5 &&
input.buttons1 == SDCButton1.BTN_R5 &&
input.buttons2 == 0 &&
input.buttons3 == 0 &&
input.buttons4 == (byte)(SDCButton4.BTN_L4 | SDCButton4.BTN_R4) &&
input.buttons5 == (byte)SDCButton5.BTN_QUICK_ACCESS)
input.buttons4 == (SDCButton4.BTN_L4 | SDCButton4.BTN_R4) &&
input.buttons5 == SDCButton5.BTN_QUICK_ACCESS)
{
rootMenu.Show();
rootMenu.Reset();
@ -243,7 +243,7 @@ namespace PowerControl
return;
}
if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) == 0 || !RTSS.IsOSDForeground())
if (!neptuneDeviceState.buttons5.HasFlag(SDCButton5.BTN_QUICK_ACCESS) || !RTSS.IsOSDForeground())
{
// schedule next repeat far in the future
dismissNeptuneInput();
@ -258,19 +258,19 @@ namespace PowerControl
{
return;
}
else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_LEFT)
else if (input.buttons0 == SDCButton0.BTN_DPAD_LEFT)
{
rootMenu.SelectPrev();
}
else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_RIGHT)
else if (input.buttons0 == SDCButton0.BTN_DPAD_RIGHT)
{
rootMenu.SelectNext();
}
else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_UP)
else if (input.buttons0 == SDCButton0.BTN_DPAD_UP)
{
rootMenu.Prev();
}
else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_DOWN)
else if (input.buttons0 == SDCButton0.BTN_DPAD_DOWN)
{
rootMenu.Next();
}

View file

@ -1,3 +1,7 @@
## 0.5.x
- Introduce SteamController that provides 3 main modes of operation Desktop, X360 and Steam
## 0.4.x
- Highly risky: Allow to change CPU and GPU frequency (enable `EnableExperimentalFeatures` in `PowerControl.dll.config`)

View file

@ -0,0 +1,131 @@
using CommonHelpers;
using ExternalHelpers;
using SteamController.Profiles;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace SteamController
{
internal class Controller : IDisposable
{
public const String Title = "Steam Controller";
public readonly String TitleWithVersion = Title + " v" + Application.ProductVersion.ToString();
Container components = new Container();
NotifyIcon notifyIcon;
StartupManager startupManager = new StartupManager(Title);
Context context;
Thread? contextThread;
bool running = true;
[DllImport("sas.dll")]
static extern void SendSAS(bool asUser);
public Controller()
{
Instance.RunOnce(TitleWithVersion, "Global\\SteamController");
SendSAS(true);
context = new Context()
{
Profiles = {
new Profiles.SteamShortcutsProfile(),
new Profiles.DesktopProfile(),
new Profiles.X360Profile(),
new Profiles.DebugProfile()
}
};
var contextMenu = new ContextMenuStrip(components);
if (startupManager.IsAvailable)
{
var startupItem = new ToolStripMenuItem("Run On Startup");
startupItem.Checked = startupManager.Startup;
startupItem.Click += delegate
{
startupManager.Startup = !startupManager.Startup;
startupItem.Checked = startupManager.Startup;
};
contextMenu.Items.Add(startupItem);
}
var helpItem = contextMenu.Items.Add("&Help");
helpItem.Click += delegate
{
System.Diagnostics.Process.Start("explorer.exe", "http://github.com/ayufan-research/steam-deck-tools");
};
contextMenu.Items.Add(new ToolStripSeparator());
var exitItem = contextMenu.Items.Add("&Exit");
exitItem.Click += delegate
{
Application.Exit();
};
notifyIcon = new NotifyIcon(components);
notifyIcon.Icon = Resources.microsoft_xbox_controller_off;
notifyIcon.Text = TitleWithVersion;
notifyIcon.Visible = true;
notifyIcon.ContextMenuStrip = contextMenu;
var contextStateUpdate = new System.Windows.Forms.Timer(components);
contextStateUpdate.Interval = 250;
contextStateUpdate.Enabled = true;
contextStateUpdate.Tick += ContextStateUpdate_Tick;
contextThread = new Thread(ContextState_Update);
contextThread.Start();
}
private void ContextState_Update(object? obj)
{
while (running)
{
context.Update();
}
}
private void ContextStateUpdate_Tick(object? sender, EventArgs e)
{
context.X360.CreateClient();
if (!context.Mouse.Valid)
{
notifyIcon.Text = TitleWithVersion + ". Cannot send input";
notifyIcon.Icon = Resources.microsoft_xbox_controller_off_red;
}
else if (!context.X360.Valid)
{
notifyIcon.Text = TitleWithVersion + ". Missing ViGEm?";
notifyIcon.Icon = Resources.microsoft_xbox_controller_red;
}
else if (context.DesktopMode)
{
notifyIcon.Icon = Resources.microsoft_xbox_controller_off;
notifyIcon.Text = TitleWithVersion + ". Desktop mode";
}
else
{
notifyIcon.Icon = Resources.microsoft_xbox_controller;
notifyIcon.Text = TitleWithVersion;
}
}
public void Dispose()
{
running = false;
if (contextThread != null)
{
contextThread.Interrupt();
contextThread.Join();
}
using (context) { }
}
}
}

View file

@ -0,0 +1,95 @@
using WindowsInput;
namespace SteamController.Devices
{
public class KeyboardController : IDisposable
{
InputSimulator simulator = new InputSimulator();
HashSet<VirtualKeyCode> keyCodes = new HashSet<VirtualKeyCode>();
HashSet<VirtualKeyCode> lastKeyCodes = new HashSet<VirtualKeyCode>();
public KeyboardController()
{
}
public void Dispose()
{
}
public bool this[VirtualKeyCode button]
{
get { return keyCodes.Contains(button); }
set
{
if (value)
keyCodes.Add(button);
else
keyCodes.Remove(button);
}
}
public VirtualKeyCode[] DownKeys
{
get { return keyCodes.ToArray(); }
}
internal void BeforeUpdate()
{
lastKeyCodes = keyCodes;
keyCodes = new HashSet<VirtualKeyCode>();
}
internal void Update()
{
try
{
// Key Up: it is missing now
var keyUp = lastKeyCodes.Except(keyCodes).ToArray();
if (keyUp.Any())
simulator.Keyboard.KeyUp(keyUp);
// Key Down: new keys being down
var keyDown = keyCodes.Except(lastKeyCodes).ToArray();
if (keyDown.Any())
simulator.Keyboard.KeyUp(keyDown);
}
catch (InvalidOperationException)
{
}
}
public void KeyPress(params VirtualKeyCode[] keyCodes)
{
try
{
simulator.Keyboard.KeyPress(keyCodes);
}
catch (InvalidOperationException)
{
}
}
public void KeyPress(VirtualKeyCode modifierKey, params VirtualKeyCode[] keyCodes)
{
try
{
simulator.Keyboard.ModifiedKeyStroke(modifierKey, keyCodes);
}
catch (InvalidOperationException)
{
}
}
public void KeyPress(IEnumerable<VirtualKeyCode> modifierKeys, params VirtualKeyCode[] keyCodes)
{
try
{
simulator.Keyboard.ModifiedKeyStroke(modifierKeys, keyCodes);
}
catch (InvalidOperationException)
{
}
}
}
}

View file

@ -0,0 +1,362 @@
#define ACCUM_MOUSE
#define ACCUM_SCROLL
using WindowsInput;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public class MouseController : IDisposable
{
private struct Accum
{
double? last, now;
public bool Used
{
get { return now is not null; }
}
public void Tick()
{
last = now;
now = null;
}
public void Add(double delta)
{
now = (now ?? 0.0) + delta;
}
public int Consume()
{
double accum = ((now ?? 0.0) + (last ?? 0.0));
now = accum - (int)accum;
last = null;
return (int)accum;
}
}
// TODO: Unsure what it is
public const int XButtonID = 0;
public const int YButtonID = 1;
public const int UpdateValidInterval = 250;
InputSimulator simulator = new InputSimulator();
HashSet<Button> mouseButtons = new HashSet<Button>();
HashSet<Button> lastMouseButtons = new HashSet<Button>();
Accum movedX, movedY, verticalScroll, horizontalScroll;
bool? valid = null;
DateTime lastValid = DateTime.Now;
public enum Button
{
Left,
Right,
Middle,
X,
Y
}
public bool this[Button button]
{
get { return mouseButtons.Contains(button); }
set
{
if (value)
mouseButtons.Add(button);
else
mouseButtons.Remove(button);
}
}
public bool Valid
{
get { return valid ?? true; }
}
public Button[] DownButtons
{
get { return mouseButtons.ToArray(); }
}
public MouseController()
{
}
public void Dispose()
{
}
private void Safe(Func<bool> action)
{
try
{
if (action())
{
valid = true;
lastValid = DateTime.Now;
}
}
catch (InvalidOperationException)
{
valid = false;
lastValid = DateTime.Now;
}
}
private void UpdateValid()
{
Safe(() =>
{
if (valid is null || lastValid.AddMilliseconds(UpdateValidInterval) < DateTime.Now)
{
simulator.Mouse.MoveMouseBy(0, 0);
return true;
}
return false;
});
}
internal void BeforeUpdate()
{
lastMouseButtons = mouseButtons;
mouseButtons = new HashSet<Button>();
movedX.Tick();
movedY.Tick();
verticalScroll.Tick();
horizontalScroll.Tick();
}
internal void Update()
{
// Mouse Up: it is missing now
foreach (var button in lastMouseButtons.Except(mouseButtons))
{
Safe(() =>
{
switch (button)
{
case Button.Left:
simulator.Mouse.LeftButtonUp();
return true;
case Button.Right:
simulator.Mouse.RightButtonUp();
return true;
case Button.Middle:
simulator.Mouse.MiddleButtonUp();
return true;
case Button.X:
simulator.Mouse.XButtonUp(XButtonID);
return true;
case Button.Y:
simulator.Mouse.XButtonUp(YButtonID);
return true;
default:
return false;
}
});
}
// Key Down: new keys being down
foreach (var button in mouseButtons.Except(lastMouseButtons))
{
Safe(() =>
{
switch (button)
{
case Button.Left:
simulator.Mouse.LeftButtonDown();
return true;
case Button.Right:
simulator.Mouse.RightButtonDown();
return true;
case Button.Middle:
simulator.Mouse.MiddleButtonDown();
return true;
case Button.X:
simulator.Mouse.XButtonDown(XButtonID);
return true;
case Button.Y:
simulator.Mouse.XButtonDown(YButtonID);
return true;
default:
return false;
}
});
}
// Move cursor
if (movedX.Used || movedY.Used)
{
Safe(() =>
{
simulator.Mouse.MoveMouseBy(movedX.Consume(), movedY.Consume());
return true;
});
}
// Scroll
if (verticalScroll.Used)
{
Safe(() =>
{
int value = verticalScroll.Consume();
if (value != 0)
simulator.Mouse.VerticalScroll(value);
return true;
});
}
if (horizontalScroll.Used)
{
Safe(() =>
{
int value = horizontalScroll.Consume();
if (value != 0)
simulator.Mouse.HorizontalScroll(value);
return true;
});
}
UpdateValid();
}
public void MouseClick(Button button)
{
Safe(() =>
{
switch (button)
{
case Button.Left:
simulator.Mouse.LeftButtonClick();
return true;
case Button.Right:
simulator.Mouse.RightButtonClick();
return true;
case Button.Middle:
simulator.Mouse.MiddleButtonClick();
return true;
case Button.X:
simulator.Mouse.XButtonClick(XButtonID);
return true;
case Button.Y:
simulator.Mouse.XButtonClick(YButtonID);
return true;
default:
return false;
}
});
}
public void MouseDoubleClick(Button button)
{
Safe(() =>
{
switch (button)
{
case Button.Left:
simulator.Mouse.LeftButtonDoubleClick();
return true;
case Button.Right:
simulator.Mouse.RightButtonDoubleClick();
return true;
case Button.Middle:
simulator.Mouse.MiddleButtonDoubleClick();
return true;
case Button.X:
simulator.Mouse.XButtonDoubleClick(XButtonID);
return true;
case Button.Y:
simulator.Mouse.XButtonDoubleClick(YButtonID);
return true;
default:
return false;
}
});
}
public void MoveBy(double pixelDeltaX, double pixelDeltaY)
{
#if ACCUM_MOUSE
movedX.Add(pixelDeltaX);
movedY.Add(pixelDeltaY);
#else
if (pixelDeltaX == 0 && pixelDeltaY == 0)
return;
Safe(() =>
{
simulator.Mouse.MoveMouseBy((int)pixelDeltaX, (int)pixelDeltaY);
return true;
});
#endif
}
public void MoveTo(double absoluteX, double absoluteY)
{
Safe(() =>
{
simulator.Mouse.MoveMouseTo(absoluteX, absoluteY);
return true;
});
}
public void VerticalScroll(double scrollAmountInClicks)
{
#if ACCUM_SCROLL
verticalScroll.Add(scrollAmountInClicks);
#else
if (scrollAmountInClicks == 0)
return;
Safe(() =>
{
simulator.Mouse.VerticalScroll((int)scrollAmountInClicks);
return true;
});
#endif
}
public void HorizontalScroll(double scrollAmountInClicks)
{
#if ACCUM_SCROLL
horizontalScroll.Add(scrollAmountInClicks);
#else
if (scrollAmountInClicks == 0)
return;
Safe(() =>
{
simulator.Mouse.HorizontalScroll((int)scrollAmountInClicks);
return true;
});
#endif
}
}
}

View file

@ -0,0 +1,76 @@
using hidapi;
using PowerControl.External;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public partial class SteamController
{
public readonly SteamButton BtnL5 = new SteamButton2(0x08, SDCButton0.BTN_L5);
public readonly SteamButton BtnOptions = new SteamButton2(0x08, SDCButton0.BTN_OPTIONS);
public readonly SteamButton BtnSteam = new SteamButton2(0x08, SDCButton0.BTN_STEAM);
public readonly SteamButton BtnMenu = new SteamButton2(0x08, SDCButton0.BTN_MENU);
public readonly SteamButton BtnDpadDown = new SteamButton2(0x08, SDCButton0.BTN_DPAD_DOWN) { LizardButton = true };
public readonly SteamButton BtnDpadLeft = new SteamButton2(0x08, SDCButton0.BTN_DPAD_LEFT) { LizardButton = true };
public readonly SteamButton BtnDpadRight = new SteamButton2(0x08, SDCButton0.BTN_DPAD_RIGHT) { LizardButton = true };
public readonly SteamButton BtnDpadUp = new SteamButton2(0x08, SDCButton0.BTN_DPAD_UP) { LizardButton = true };
public readonly SteamButton BtnA = new SteamButton2(0x08, SDCButton0.BTN_A) { LizardButton = true };
public readonly SteamButton BtnX = new SteamButton2(0x08, SDCButton0.BTN_X) { LizardButton = true };
public readonly SteamButton BtnB = new SteamButton2(0x08, SDCButton0.BTN_B) { LizardButton = true };
public readonly SteamButton BtnY = new SteamButton2(0x08, SDCButton0.BTN_Y) { LizardButton = true };
public readonly SteamButton BtnL1 = new SteamButton2(0x08, SDCButton0.BTN_L1) { LizardButton = true };
public readonly SteamButton BtnL2 = new SteamButton2(0x08, SDCButton0.BTN_L2) { LizardButton = true };
public readonly SteamButton BtnR1 = new SteamButton2(0x08, SDCButton0.BTN_R1) { LizardButton = true };
public readonly SteamButton BtnR2 = new SteamButton2(0x08, SDCButton0.BTN_R2) { LizardButton = true };
public readonly SteamButton BtnLeftStickPress = new SteamButton2(0x0a, SDCButton1.BTN_LSTICK_PRESS);
public readonly SteamButton BtnLPadTouch = new SteamButton2(0x0a, SDCButton1.BTN_LPAD_TOUCH);
public readonly SteamButton BtnLPadPress = new SteamButton2(0x0a, SDCButton1.BTN_LPAD_PRESS);
public readonly SteamButton BtnRPadPress = new SteamButton2(0x0a, SDCButton1.BTN_RPAD_PRESS);
public readonly SteamButton BtnRPadTouch = new SteamButton2(0x0a, SDCButton1.BTN_RPAD_TOUCH);
public readonly SteamButton BtnR5 = new SteamButton2(0x0a, SDCButton1.BTN_R5);
public readonly SteamButton BtnRightStickPress = new SteamButton2(0x0B, SDCButton2.BTN_RSTICK_PRESS);
public readonly SteamButton BtnLStickTouch = new SteamButton2(0x0D, SDCButton4.BTN_LSTICK_TOUCH);
public readonly SteamButton BtnRStickTouch = new SteamButton2(0x0D, SDCButton4.BTN_RSTICK_TOUCH);
public readonly SteamButton BtnR4 = new SteamButton2(0x0D, SDCButton4.BTN_R4);
public readonly SteamButton BtnL4 = new SteamButton2(0x0D, SDCButton4.BTN_L4);
public readonly SteamButton BtnQuickAccess = new SteamButton2(0x0E, SDCButton5.BTN_QUICK_ACCESS);
public readonly SteamButton BtnVirtualLeftThumbUp = new SteamButton();
public readonly SteamButton BtnVirtualLeftThumbDown = new SteamButton();
public readonly SteamButton BtnVirtualLeftThumbLeft = new SteamButton();
public readonly SteamButton BtnVirtualLeftThumbRight = new SteamButton();
public readonly SteamAxis LPadX = new SteamAxis(0x10);
public readonly SteamAxis LPadY = new SteamAxis(0x12);
public readonly SteamAxis RPadX = new SteamAxis(0x14) { LizardMouse = true };
public readonly SteamAxis RPadY = new SteamAxis(0x16) { LizardMouse = true };
public readonly SteamAxis AccelX = new SteamAxis(0x18);
public readonly SteamAxis AccelY = new SteamAxis(0x1A);
public readonly SteamAxis AccelZ = new SteamAxis(0x1C);
public readonly SteamAxis GyroPitch = new SteamAxis(0x1E);
public readonly SteamAxis GyroYaw = new SteamAxis(0x20);
public readonly SteamAxis GyroRoll = new SteamAxis(0x22);
public readonly SteamAxis LeftTrigger = new SteamAxis(0x2C);
public readonly SteamAxis RightTrigger = new SteamAxis(0x2E);
public readonly SteamAxis LeftThumbX = new SteamAxis(0x30) { Deadzone = 5000, MinChange = 10 };
public readonly SteamAxis LeftThumbY = new SteamAxis(0x32) { Deadzone = 5000, MinChange = 10 };
public readonly SteamAxis RightThumbX = new SteamAxis(0x34) { Deadzone = 5000, MinChange = 10 };
public readonly SteamAxis RightThumbY = new SteamAxis(0x36) { Deadzone = 5000, MinChange = 10 };
public readonly SteamAxis LPadPressure = new SteamAxis(0x38);
public readonly SteamAxis RPadPressure = new SteamAxis(0x38);
private void InitializeButtons()
{
LPadX.ActiveButton = BtnLPadTouch;
LPadY.ActiveButton = BtnLPadTouch;
RPadX.ActiveButton = BtnRPadTouch;
RPadY.ActiveButton = BtnRPadTouch;
// map virtual key presses
LeftThumbX.VirtualLeft = BtnVirtualLeftThumbLeft;
LeftThumbX.VirtualRight = BtnVirtualLeftThumbRight;
LeftThumbY.VirtualLeft = BtnVirtualLeftThumbDown;
LeftThumbY.VirtualRight = BtnVirtualLeftThumbUp;
}
}
}

View file

@ -0,0 +1,65 @@
using hidapi;
using PowerControl.External;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public partial class SteamController : IDisposable
{
public const ushort SteamVendorID = 0x28DE;
public const ushort SteamProductID = 0x1205;
public const int ReadTimeout = 50;
private hidapi.HidDevice neptuneDevice;
public SteamController()
{
InitializeButtons();
InitializeActions();
neptuneDevice = new hidapi.HidDevice(SteamVendorID, SteamProductID, 64);
neptuneDevice.OpenDevice();
}
public void Dispose()
{
}
public bool Updated { get; private set; }
internal void Reset()
{
foreach (var action in AllActions)
action.Reset();
}
private void BeforeUpdate(byte[] buffer)
{
foreach (var action in AllActions)
action.BeforeUpdate(buffer, this);
}
internal void BeforeUpdate()
{
byte[] data = neptuneDevice.Read(ReadTimeout);
if (data == null)
{
Reset();
Updated = false;
return;
}
BeforeUpdate(data);
Updated = true;
}
internal void Update()
{
foreach (var action in AllActions)
action.Update();
UpdateLizardButtons();
UpdateLizardMouse();
}
}
}

View file

@ -0,0 +1,421 @@
using hidapi;
using PowerControl.External;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public partial class SteamController
{
public abstract class SteamAction
{
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, SteamController controller);
internal abstract void Update();
protected void UpdateTime()
{
var now = DateTime.Now;
DeltaTime = (now - LastUpdated).TotalSeconds;
LastUpdated = now;
}
protected bool UsedByLizard(SteamController controller)
{
if (LizardButton && controller.LizardButtons)
return true;
if (LizardMouse && controller.LizardMouse)
return true;
return false;
}
}
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);
public bool Value { get; private set; }
public bool LastValue { get; private set; }
/// 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 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()
{
LastValue = Value;
Value = false;
HoldSince = null;
HoldRepeated = null;
Consumed = null;
}
internal void SetValue(bool value)
{
LastValue = Value;
Value = value;
UpdateTime();
if (!LastValue && Value)
{
HoldSince = DateTime.Now;
HoldRepeated = null;
}
}
internal override bool BeforeUpdate(byte[] buffer, SteamController controller)
{
return true;
}
internal override void Update()
{
if (!Value)
Consumed = null;
}
}
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, SteamController controller)
{
if (UsedByLizard(controller))
return false;
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;
public SteamButton? ActiveButton { get; internal set; }
public SteamButton? VirtualLeft { get; internal set; }
public SteamButton? VirtualRight { get; internal set; }
public short Value { get; private set; }
public short LastValue { get; private set; }
public short Deadzone { get; set; }
public short MinChange { get; set; }
public SteamAxis(int offset)
{
this.offset = offset;
}
public static implicit operator bool(SteamAxis button) => button.Active;
public static implicit operator short(SteamAxis button) => button.Value;
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()
{
LastValue = Value;
Value = 0;
}
internal void SetValue(short value)
{
LastValue = Value;
Value = value;
UpdateTime();
// first time pressed, reset value as this is a Pad
if (ActiveButton is not null && ActiveButton.JustPressed())
LastValue = Value;
if (VirtualRight is not null)
VirtualRight.SetValue(value > VirtualRightThreshold);
if (VirtualLeft is not null)
VirtualLeft.SetValue(value < VirtualLeftThreshold);
}
internal override bool BeforeUpdate(byte[] buffer, SteamController controller)
{
if (UsedByLizard(controller))
return false;
if (offset + 1 < buffer.Length)
{
SetValue(BitConverter.ToInt16(buffer, offset));
return true;
}
else
{
SetValue(0);
return false;
}
}
internal override void Update()
{
}
}
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.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);
}
}
}
}

View file

@ -0,0 +1,73 @@
using hidapi;
using PowerControl.External;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public partial class SteamController
{
public const int LizardModeUpdateInterval = 250;
public bool LizardMouse { get; set; } = true;
public bool LizardButtons { get; set; } = true;
private bool? savedLizardMouse;
private bool? savedLizardButtons;
private DateTime lizardMouseUpdated = DateTime.Now;
private DateTime lizardButtonUpdated = DateTime.Now;
private void UpdateLizardMouse()
{
if (savedLizardMouse == LizardMouse)
{
// We need to explicitly disable lizard every some time
// but don't fight enabling it, as someone else might be taking control (Steam?)
if (LizardMouse || lizardMouseUpdated.AddMilliseconds(LizardModeUpdateInterval) > DateTime.Now)
return;
}
if (LizardMouse)
{
//Enable mouse emulation
byte[] data = new byte[] { 0x8e, 0x00 };
neptuneDevice.RequestFeatureReport(data);
}
else
{
//Disable mouse emulation
byte[] data = new byte[] { 0x87, 0x03, 0x08, 0x07 };
neptuneDevice.RequestFeatureReport(data);
}
savedLizardMouse = LizardMouse;
lizardMouseUpdated = DateTime.Now;
}
private void UpdateLizardButtons()
{
if (savedLizardButtons == LizardButtons)
{
// We need to explicitly disable lizard every some time
// but don't fight enabling it, as someone else might be taking control (Steam?)
if (LizardButtons || lizardButtonUpdated.AddMilliseconds(LizardModeUpdateInterval) > DateTime.Now)
return;
}
if (LizardButtons)
{
//Enable keyboard/mouse button emulation
byte[] data = new byte[] { 0x85, 0x00 };
neptuneDevice.RequestFeatureReport(data);
}
else
{
//Disable keyboard/mouse button emulation
byte[] data = new byte[] { 0x81, 0x00 };
neptuneDevice.RequestFeatureReport(data);
}
savedLizardButtons = LizardButtons;
lizardButtonUpdated = DateTime.Now;
}
}
}

View file

@ -0,0 +1,160 @@
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Exceptions;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.Xbox360;
using static CommonHelpers.Log;
namespace SteamController.Devices
{
public class Xbox360Controller : IDisposable
{
private ViGEmClient? client;
private IXbox360Controller? device;
private bool isConnected;
private bool submitReport;
public Xbox360Controller()
{
}
public void Dispose()
{
using (client) { }
}
internal bool CreateClient()
{
if (this.device is not null)
return true;
try
{
var client = new ViGEmClient();
var device = client.CreateXbox360Controller();
device.AutoSubmitReport = false;
device.FeedbackReceived += X360Device_FeedbackReceived;
this.device = device;
this.client = client;
return true;
}
catch (VigemBusNotFoundException)
{
return false;
}
}
internal void BeforeUpdate()
{
device?.ResetReport();
if (!isConnected)
{
FeedbackLargeMotor = 0;
FeedbackSmallMotor = 0;
LedNumber = 0;
}
submitReport = false;
}
private void UpdateConnected()
{
if (Connected == isConnected)
return;
if (Connected)
{
device?.Connect();
TraceLine("Connected X360 Controller.");
}
else
{
device?.Disconnect();
TraceLine("Disconnected X360 Controller.");
}
isConnected = Connected;
}
internal void Update()
{
UpdateConnected();
if (isConnected && submitReport)
{
device?.SubmitReport();
}
}
public bool Valid
{
get { return device is not null; }
}
public bool Connected { get; set; }
public byte FeedbackLargeMotor { get; internal set; }
public byte FeedbackSmallMotor { get; internal set; }
public byte LedNumber { get; internal set; }
public bool this[Xbox360Button button]
{
set
{
SetButtonState(button, value);
}
}
public short this[Xbox360Axis axis]
{
set
{
SetAxisValue(axis, value);
}
}
public short this[Xbox360Slider slider]
{
set
{
SetSliderValue(slider, value);
}
}
public void SetAxisValue(Xbox360Axis axis, short value)
{
device?.SetAxisValue(axis, value);
submitReport = true;
}
public void SetButtonState(Xbox360Button button, bool pressed)
{
device?.SetButtonState(button, pressed);
submitReport = true;
}
public void SetSliderValue(Xbox360Slider slider, byte value)
{
device?.SetSliderValue(slider, value);
submitReport = true;
}
public void SetSliderValue(Xbox360Slider slider, short value)
{
// rescale from -32767..32768 to 0..255
int result = value;
result -= short.MinValue;
result *= byte.MaxValue;
result /= ushort.MaxValue;
device?.SetSliderValue(slider, (byte)result);
submitReport = true;
}
private void X360Device_FeedbackReceived(object sender, Xbox360FeedbackReceivedEventArgs e)
{
FeedbackLargeMotor = e.LargeMotor;
FeedbackSmallMotor = e.SmallMotor;
LedNumber = e.LedNumber;
}
}
}

View file

@ -0,0 +1,91 @@
using static CommonHelpers.Log;
namespace SteamController.Profiles
{
public class Context : IDisposable
{
public const double JoystickToMouseSensitivity = 1200;
public const double PadToMouseSensitivity = 200;
public const double ThumbToWhellSensitivity = 2;
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 RequestDesktopMode { get; set; } = true;
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 bool Update()
{
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

@ -0,0 +1,64 @@
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,112 @@
using PowerControl.Helpers;
using WindowsInput;
namespace SteamController.Profiles
{
public sealed class DesktopProfile : Profile
{
public const bool LizardButtons = false;
public const bool LizardMouse = true;
public const String Consumed = "DesktopProfileOwner";
public DesktopProfile()
{
}
public override Status Run(Context c)
{
if (!c.DesktopMode)
{
return Status.Continue;
}
if (!c.Mouse.Valid)
{
// Failed to acquire secure context
// Enable emergency Lizard
c.Steam.LizardButtons = true;
c.Steam.LizardMouse = true;
return Status.Continue;
}
c.Steam.LizardButtons = LizardButtons;
c.Steam.LizardMouse = LizardMouse;
EmulateLizardButtons(c);
EmulateLizardMouse(c);
if (c.Steam.LPadX)
{
c.Mouse.HorizontalScroll(c.Steam.LPadX.Scaled(Context.ThumbToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
if (c.Steam.LPadY)
{
c.Mouse.VerticalScroll(c.Steam.LPadY.Scaled(Context.ThumbToWhellSensitivity, Devices.SteamController.SteamAxis.ScaledMode.Delta));
}
if (c.Steam.BtnVirtualLeftThumbUp.HoldRepeat(Consumed))
{
c.Mouse.VerticalScroll(Context.ThumbToWhellSensitivity);
}
else if (c.Steam.BtnVirtualLeftThumbDown.HoldRepeat(Consumed))
{
c.Mouse.VerticalScroll(-Context.ThumbToWhellSensitivity);
}
else if (c.Steam.BtnVirtualLeftThumbLeft.HoldRepeat(Consumed))
{
c.Mouse.HorizontalScroll(-Context.ThumbToWhellSensitivity);
}
else if (c.Steam.BtnVirtualLeftThumbRight.HoldRepeat(Consumed))
{
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)
{
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))
c.Keyboard.KeyPress(VirtualKeyCode.RIGHT);
if (c.Steam.BtnDpadUp.HoldRepeat(Consumed))
c.Keyboard.KeyPress(VirtualKeyCode.UP);
if (c.Steam.BtnDpadDown.HoldRepeat(Consumed))
c.Keyboard.KeyPress(VirtualKeyCode.DOWN);
#else
c.Keyboard[VirtualKeyCode.RETURN] = c.Steam.BtnA;
c.Keyboard[VirtualKeyCode.LEFT] = c.Steam.BtnDpadLeft;
c.Keyboard[VirtualKeyCode.RIGHT] = c.Steam.BtnDpadRight;
c.Keyboard[VirtualKeyCode.UP] = c.Steam.BtnDpadUp;
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

@ -0,0 +1,19 @@
namespace SteamController.Profiles
{
public abstract class Profile
{
public enum Status
{
Continue,
Stop
}
public bool RunAlways { get; set; }
public abstract Status Run(Context context);
public virtual void Skipped(Context context)
{
}
}
}

View file

@ -0,0 +1,166 @@
using System.Diagnostics;
using ExternalHelpers;
using PowerControl.Helpers;
using WindowsInput;
namespace SteamController.Profiles
{
public sealed class SteamShortcutsProfile : Profile
{
public const bool LizardButtons = true;
public const bool LizardMouse = false;
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))
{
c.Keyboard.KeyPress(new VirtualKeyCode[] { VirtualKeyCode.LCONTROL, VirtualKeyCode.LMENU }, VirtualKeyCode.DELETE);
}
if (c.Steam.BtnSteam.Hold(HoldForShorcuts, Consumed))
{
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.Continue;
}
private void SteamShortcuts(Context c)
{
c.Steam.LizardButtons = false;
c.Steam.LizardMouse = true;
if (c.Steam.BtnA.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.RETURN);
}
if (c.Steam.BtnB.HoldOnce(HoldForKill, Consumed))
{
// kill application
}
else if (c.Steam.BtnB.HoldOnce(HoldForClose, Consumed))
{
// close application
c.Keyboard.KeyPress(VirtualKeyCode.LMENU, VirtualKeyCode.F4);
}
if (c.Steam.BtnX.Pressed())
{
OnScreenKeyboard.Toggle();
}
if (c.Steam.BtnL1.Pressed())
{
if (Process.GetProcessesByName("Magnify").Any())
{
// close magnifier
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.ESCAPE);
}
else
{
// enable magnifier
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.OEM_PLUS);
}
}
if (c.Steam.BtnR1.Pressed())
{
// take screenshot
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.LizardMouse)
{
EmulateLizardMouse(c);
}
if (c.Steam.BtnVirtualLeftThumbUp.HoldRepeat(Consumed))
{
WindowsSettingsBrightnessController.Increase(5);
}
if (c.Steam.BtnVirtualLeftThumbDown.HoldRepeat(Consumed))
{
WindowsSettingsBrightnessController.Increase(-5);
}
if (c.Steam.BtnDpadRight.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.RETURN);
}
if (c.Steam.BtnDpadDown.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.TAB);
}
if (c.Steam.BtnDpadLeft.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.ESCAPE);
}
}
private void AdditionalShortcuts(Context c)
{
if (c.Steam.BtnMenu.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.LWIN, VirtualKeyCode.TAB);
}
if (c.Steam.BtnOptions.Pressed())
{
c.Keyboard.KeyPress(VirtualKeyCode.F11);
}
}
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

@ -0,0 +1,66 @@
using Nefarius.ViGEm.Client.Targets.Xbox360;
namespace SteamController.Profiles
{
public sealed class X360Profile : Profile
{
public override void Skipped(Context context)
{
if (!context.DesktopMode)
{
context.X360.Connected = true;
ControlButtons(context);
}
}
public override Status Run(Context context)
{
if (context.DesktopMode)
{
context.X360.Connected = false;
return Status.Continue;
}
context.Steam.LizardButtons = false;
context.Steam.LizardMouse = true;
context.X360.Connected = true;
// DPad
context.X360[Xbox360Button.Up] = context.Steam.BtnDpadUp;
context.X360[Xbox360Button.Down] = context.Steam.BtnDpadDown;
context.X360[Xbox360Button.Left] = context.Steam.BtnDpadLeft;
context.X360[Xbox360Button.Right] = context.Steam.BtnDpadRight;
// Buttons
context.X360[Xbox360Button.A] = context.Steam.BtnA;
context.X360[Xbox360Button.B] = context.Steam.BtnB;
context.X360[Xbox360Button.X] = context.Steam.BtnX;
context.X360[Xbox360Button.Y] = context.Steam.BtnY;
// Sticks
context.X360[Xbox360Axis.LeftThumbX] = context.Steam.LeftThumbX;
context.X360[Xbox360Axis.LeftThumbY] = context.Steam.LeftThumbY;
context.X360[Xbox360Axis.RightThumbX] = context.Steam.RightThumbX;
context.X360[Xbox360Axis.RightThumbY] = context.Steam.RightThumbY;
context.X360[Xbox360Button.LeftThumb] = context.Steam.BtnLeftStickPress;
context.X360[Xbox360Button.RightThumb] = context.Steam.BtnRightStickPress;
// Triggers
context.X360[Xbox360Slider.LeftTrigger] = context.Steam.LeftTrigger;
context.X360[Xbox360Slider.RightTrigger] = context.Steam.RightTrigger;
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;
}
}
}

View file

@ -0,0 +1,21 @@
namespace SteamController
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
using (var controller = new Controller())
{
Application.Run();
}
}
}
}

123
SteamController/Resources.Designer.cs generated Normal file
View file

@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SteamController {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SteamController.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller_off {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller_off", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller_off_red {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller_off_red", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller_off_white {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller_off_white", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller_red {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller_red", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon microsoft_xbox_controller_white {
get {
object obj = ResourceManager.GetObject("microsoft_xbox_controller_white", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

View file

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="microsoft_xbox_controller" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="microsoft_xbox_controller_off" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller-off.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="microsoft_xbox_controller_off_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller-off-red.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="microsoft_xbox_controller_off_white" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller-off-white.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="microsoft_xbox_controller_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller-red.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="microsoft_xbox_controller_white" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\microsoft-xbox-controller-white.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

26
SteamController/Settings.Designer.cs generated Normal file
View file

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SteamController {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

View file

@ -0,0 +1,68 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Resources\microsoft-xbox-controller.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="app.manifest" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\microsoft-xbox-controller.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="H.InputSimulator" Version="1.3.0" />
<PackageReference Include="Nefarius.ViGEm.Client" Version="1.21.232" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CommonHelpers\CommonHelpers.csproj" />
<ProjectReference Include="..\ExternalHelpers\ExternalHelpers.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="hidapi.net">
<HintPath>..\ExternalHelpers\hidapi.net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="0.1.0.0" name="PerformanceOverlay.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
<!--<requestedExecutionLevel level="requireAdministrator" uiAccess="true" />-->
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View file

@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "CommonHelp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerControl", "PowerControl\PowerControl.csproj", "{85A44F35-60C9-493E-B1A7-FB2284E5ACCF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExternalHelpers", "ExternalHelpers\ExternalHelpers.csproj", "{A3FD29A4-844F-42B1-80C5-0301BD053AC0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalHelpers", "ExternalHelpers\ExternalHelpers.csproj", "{A3FD29A4-844F-42B1-80C5-0301BD053AC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamController", "SteamController\SteamController.csproj", "{A5A9DCD4-4686-49A6-836A-81498226B94B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -83,6 +85,18 @@ Global
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x64.Build.0 = Release|Any CPU
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x86.ActiveCfg = Release|Any CPU
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x86.Build.0 = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|x64.Build.0 = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Debug|x86.Build.0 = Debug|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|Any CPU.Build.0 = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|x64.ActiveCfg = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|x64.Build.0 = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|x86.ActiveCfg = Release|Any CPU
{A5A9DCD4-4686-49A6-836A-81498226B94B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1 +1 @@
0.4
0.5