mirror of
https://github.com/ayufan/steam-deck-tools.git
synced 2026-04-04 22:07:40 +00:00
SteamController: Use Roslyn Scripting to compile UserProfiles
- This looks into `UserProfiles/` and compiles user profiles - This exposes a very minimal scripting interface as defined by `Dynamic.Globals`
This commit is contained in:
parent
b24ae302b1
commit
e5debff45b
8 changed files with 504 additions and 11 deletions
186
SteamController/Profiles/Dynamic/RoslynDynamicProfile.cs
Normal file
186
SteamController/Profiles/Dynamic/RoslynDynamicProfile.cs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
using System.Reflection;
|
||||
using CommonHelpers;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
|
||||
namespace SteamController.Profiles.Dynamic
|
||||
{
|
||||
public sealed partial class RoslynDynamicProfile : Profile
|
||||
{
|
||||
private const int ScriptTimeout = 10; // max 10ms
|
||||
|
||||
private String fileName;
|
||||
private Script? compiledScript;
|
||||
private Profile? inherited;
|
||||
private DateTime? lastModifiedTime;
|
||||
private System.Windows.Forms.Timer? watchTimer;
|
||||
|
||||
public RoslynDynamicProfile(string name, string fileName, Profile? inherited = null)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
this.inherited = inherited;
|
||||
this.Name = name;
|
||||
if (inherited is not null)
|
||||
this.Name = inherited.Name + ": " + name;
|
||||
this.Visible = inherited?.Visible ?? true;
|
||||
this.IsDesktop = inherited?.IsDesktop ?? false;
|
||||
}
|
||||
|
||||
private static ScriptOptions CompilationOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
var options = ScriptOptions.Default;
|
||||
|
||||
// Add Keyboard controls
|
||||
options = options.AddReferences(typeof(KeyModifiers).Assembly);
|
||||
options = options.AddImports(typeof(KeyModifiers).FullName);
|
||||
options = options.AddReferences(typeof(ProfilesSettings.VirtualKeyCode).Assembly);
|
||||
options = options.AddImports(typeof(ProfilesSettings.VirtualKeyCode).FullName);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Compile()
|
||||
{
|
||||
this.compiledScript = null;
|
||||
this.lastModifiedTime = null;
|
||||
this.Errors = null;
|
||||
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
this.Errors = new string[] { String.Format("File '{0}' does not exist.", fileName) };
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.lastModifiedTime = File.GetLastWriteTimeUtc(fileName);
|
||||
|
||||
using (var file = System.IO.File.OpenRead(fileName))
|
||||
{
|
||||
var options = CompilationOptions.WithFilePath(Path.GetFileName(fileName));
|
||||
var script = CSharpScript.Create(file, options, typeof(Globals));
|
||||
var compileResult = script.Compile();
|
||||
var errors = compileResult.Where((result) => result.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error);
|
||||
|
||||
if (!errors.Any())
|
||||
{
|
||||
this.compiledScript = script;
|
||||
return true;
|
||||
}
|
||||
|
||||
this.Errors = errors.Select((result) => result.ToString()).ToArray();
|
||||
OnErrorsChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Errors = new string[] { e.Message };
|
||||
OnErrorsChanged();
|
||||
}
|
||||
|
||||
Log.TraceLine("UserProfile: {0}: Compilation Error", fileName);
|
||||
foreach (var error in this.Errors)
|
||||
Log.TraceLine("\t{0}", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Watch()
|
||||
{
|
||||
if (this.lastModifiedTime is null)
|
||||
return;
|
||||
if (this.watchTimer is not null)
|
||||
return;
|
||||
|
||||
watchTimer = new System.Windows.Forms.Timer();
|
||||
watchTimer.Interval = 1000;
|
||||
watchTimer.Tick += delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.lastModifiedTime is null)
|
||||
return;
|
||||
|
||||
var latest = File.GetLastWriteTimeUtc(fileName);
|
||||
if (this.lastModifiedTime >= latest)
|
||||
return;
|
||||
|
||||
Log.TraceLine("UserProfile: {0}. Detected modification: '{1}' vs '{2}'", fileName, this.lastModifiedTime, latest);
|
||||
}
|
||||
catch (Exception) { return; }
|
||||
|
||||
Compile();
|
||||
};
|
||||
watchTimer.Start();
|
||||
}
|
||||
|
||||
public override bool Selected(Context context)
|
||||
{
|
||||
return (this.compiledScript is not null) && (inherited?.Selected(context) ?? true);
|
||||
}
|
||||
|
||||
public override Status Run(Context context)
|
||||
{
|
||||
if (inherited?.Run(context).IsDone ?? false)
|
||||
return Status.Done;
|
||||
|
||||
if (this.compiledScript is null)
|
||||
return Status.Continue;
|
||||
|
||||
try
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var task = this.compiledScript.RunAsync(new Globals(this, context), cancelToken.Token);
|
||||
if (!task.Wait(ScriptTimeout))
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
task.Wait();
|
||||
Log.TraceLine("UserProfile: {0}: Timedout. Canceled.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.TraceLine("UserProfile: {0}: {1}", fileName, e);
|
||||
}
|
||||
|
||||
return Status.Continue;
|
||||
}
|
||||
|
||||
public static IEnumerable<RoslynDynamicProfile> GetUserProfiles(Dictionary<String, Profile> preconfiguredProfiles)
|
||||
{
|
||||
var files = new Dictionary<String, String>();
|
||||
|
||||
foreach (var directory in GetUserProfilesPaths())
|
||||
{
|
||||
foreach (var profile in preconfiguredProfiles)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(directory, profile.Key))
|
||||
{
|
||||
String name = Path.GetFileNameWithoutExtension(file);
|
||||
name = Path.GetFileNameWithoutExtension(name);
|
||||
|
||||
yield return new RoslynDynamicProfile(name, file, profile.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetUserProfilesPaths()
|
||||
{
|
||||
var exePath = Assembly.GetExecutingAssembly().Location;
|
||||
var exeFolder = Path.GetDirectoryName(exePath);
|
||||
if (exeFolder is not null)
|
||||
{
|
||||
var exeUserProfiles = Path.Combine(exeFolder, "UserProfiles");
|
||||
if (Directory.Exists(exeUserProfiles))
|
||||
yield return exeUserProfiles;
|
||||
}
|
||||
|
||||
var documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
var steamControllerDocumentsFolder = Path.Combine(documentsFolder, "SteamController", "UserProfiles");
|
||||
if (Directory.Exists(steamControllerDocumentsFolder))
|
||||
yield return steamControllerDocumentsFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue