Skip to content

Commit

Permalink
Merge pull request #188 from ionite34/discord-rich-presence
Browse files Browse the repository at this point in the history
  • Loading branch information
ionite34 authored Aug 4, 2023
2 parents cd9ea27 + 21c8bdc commit 2e37d3f
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2

## v2.1.1

### Added
- Discord Rich Presence support can now be enabled in Settings

### Fixed
- Launch Page selected package now persists in settings

Expand Down
8 changes: 7 additions & 1 deletion StabilityMatrix.Avalonia/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void ShowMainWindow()

mainWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects ?
ExtendClientAreaChromeHints.NoChrome : ExtendClientAreaChromeHints.PreferSystemChrome;

var settingsManager = Services.GetRequiredService<ISettingsManager>();
var windowSettings = settingsManager.Settings.WindowSettings;
if (windowSettings != null && !Program.Args.ResetWindowPosition)
Expand Down Expand Up @@ -207,6 +207,7 @@ internal static void ConfigurePageViewModels(IServiceCollection services)

services.AddSingleton<MainWindowViewModel>(provider =>
new MainWindowViewModel(provider.GetRequiredService<ISettingsManager>(),
provider.GetRequiredService<IDiscordRichPresenceService>(),
provider.GetRequiredService<ServiceManager<ViewModelBase>>())
{
Pages =
Expand Down Expand Up @@ -323,6 +324,11 @@ private static IServiceCollection ConfigureServices()
services.AddSingleton<INotificationService, NotificationService>();
services.AddSingleton<IPyRunner, PyRunner>();
services.AddSingleton<IUpdateHelper, UpdateHelper>();

// Rich presence
services.AddSingleton<IDiscordRichPresenceService, DiscordRichPresenceService>();
services.AddSingleton<IDisposable>(provider =>
provider.GetRequiredService<IDiscordRichPresenceService>());

Config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
Expand Down
3 changes: 2 additions & 1 deletion StabilityMatrix.Avalonia/DesignData/DesignData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public static void Initialize()
.AddSingleton<INotificationService, MockNotificationService>()
.AddSingleton<ISharedFolders, MockSharedFolders>()
.AddSingleton<IDownloadService, MockDownloadService>()
.AddSingleton<IHttpClientFactory, MockHttpClientFactory>();
.AddSingleton<IHttpClientFactory, MockHttpClientFactory>()
.AddSingleton<IDiscordRichPresenceService, MockDiscordRichPresenceService>();

// Placeholder services that nobody should need during design time
services
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using StabilityMatrix.Avalonia.Services;

namespace StabilityMatrix.Avalonia.DesignData;

public class MockDiscordRichPresenceService : IDiscordRichPresenceService
{
/// <inheritdoc />
public void Dispose()
{
GC.SuppressFinalize(this);
}

/// <inheritdoc />
public void UpdateState()
{
}
}
176 changes: 176 additions & 0 deletions StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System;
using DiscordRPC;
using DiscordRPC.Logging;
using DiscordRPC.Message;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Services;

namespace StabilityMatrix.Avalonia.Services;

public class DiscordRichPresenceService : IDiscordRichPresenceService
{
private const string ApplicationId = "1134669805237059615";

private readonly ILogger<DiscordRichPresenceService> logger;
private readonly ISettingsManager settingsManager;
private readonly DiscordRpcClient client;
private readonly string appDetails;
private bool isDisposed;

private RichPresence DefaultPresence => new()
{
Details = appDetails,
Assets = new DiscordRPC.Assets
{
LargeImageKey = "stabilitymatrix-logo-1",
LargeImageText = $"Stability Matrix {appDetails}",
},
Buttons = new[]
{
new Button
{
Label = "GitHub",
Url = "https://github.com/LykosAI/StabilityMatrix",
}
}
};

public DiscordRichPresenceService(
ILogger<DiscordRichPresenceService> logger,
ISettingsManager settingsManager)
{
this.logger = logger;
this.settingsManager = settingsManager;

appDetails = $"v{Compat.AppVersion.WithoutMetadata()}";

client = new DiscordRpcClient(ApplicationId);
client.Logger = new NullLogger();
client.OnReady += OnReady;
client.OnError += OnError;
client.OnClose += OnClose;
client.OnPresenceUpdate += OnPresenceUpdate;

settingsManager.SettingsPropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(settingsManager.Settings.IsDiscordRichPresenceEnabled))
{
UpdateState();
}
};

EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged;
}

private void OnReady(object sender, ReadyMessage args)
{
logger.LogInformation("Received Ready from user {User}", args.User.Username);
}

private void OnError(object sender, ErrorMessage args)
{
logger.LogWarning("Received Error: {Message}", args.Message);
}

private void OnClose(object sender, CloseMessage args)
{
logger.LogInformation("Received Close: {Reason}", args.Reason);
}

private void OnPresenceUpdate(object sender, PresenceMessage args)
{
logger.LogDebug("Received Update: {Presence}", args.Presence.ToString());
}

private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs args)
{
if (!client.IsInitialized || !settingsManager.Settings.IsDiscordRichPresenceEnabled) return;

if (args.CurrentPackagePair is null)
{
client.SetPresence(DefaultPresence);
}
else
{
var presence = DefaultPresence;

var packageTitle = args.CurrentPackagePair.BasePackage switch
{
A3WebUI => "Automatic1111 Web UI",
VladAutomatic => "SD.Next Web UI",
ComfyUI => "ComfyUI",
VoltaML => "VoltaML",
InvokeAI => "InvokeAI",
_ => "Stable Diffusion"
};

presence.State = $"Running {packageTitle}";

presence.Assets.SmallImageText = presence.State;
presence.Assets.SmallImageKey = args.CurrentPackagePair.BasePackage switch
{
ComfyUI => "fa_diagram_project",
VoltaML => "ic_package_voltaml",
InvokeAI => "ic_package_invokeai",
_ => "ic_fluent_box_512_filled"
};

presence.WithTimestamps(new Timestamps
{
StartUnixMilliseconds = (ulong?) DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
});

client.SetPresence(presence);
}
}

public void UpdateState()
{
// Set initial rich presence
if (settingsManager.Settings.IsDiscordRichPresenceEnabled)
{
lock (client)
{
if (!client.IsInitialized)
{
client.Initialize();
client.SetPresence(DefaultPresence);
}
}
}
else
{
lock (client)
{
if (client.IsInitialized)
{
client.ClearPresence();
client.Deinitialize();
}
}
}
}

public void Dispose()
{
if (!isDisposed)
{
if (client.IsInitialized)
{
client.ClearPresence();
}
client.Dispose();
EventManager.Instance.RunningPackageStatusChanged -= OnRunningPackageStatusChanged;
}

isDisposed = true;
GC.SuppressFinalize(this);
}

~DiscordRichPresenceService()
{
Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace StabilityMatrix.Avalonia.Services;

public interface IDiscordRichPresenceService : IDisposable
{
public void UpdateState();
}
3 changes: 2 additions & 1 deletion StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon>./Assets/Icon.ico</ApplicationIcon>
<Version>2.1.0-dev.1</Version>
<Version>2.1.1-dev.1</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
Expand All @@ -24,6 +24,7 @@
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0.1" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0" />
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.207" />
<PackageReference Include="FluentIcons.FluentAvalonia" Version="1.1.207" />
Expand Down
3 changes: 3 additions & 0 deletions StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ protected virtual async Task LaunchImpl(string? command)

await basePackage.RunPackage(packagePath, command, userArgsString);
RunningPackage = basePackage;

EventManager.Instance.OnRunningPackageStatusChanged(new PackagePair(activeInstall, basePackage));
}

// Unpacks sitecustomize.py to the target venv
Expand Down Expand Up @@ -388,6 +390,7 @@ public void OpenWebUi()

private void OnProcessExited(object? sender, int exitCode)
{
EventManager.Instance.OnRunningPackageStatusChanged(null);
Dispatcher.UIThread.InvokeAsync(async () =>
{
logger.LogTrace("Process exited ({Code}) at {Time:g}",
Expand Down
10 changes: 9 additions & 1 deletion StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public partial class MainWindowViewModel : ViewModelBase
{
private readonly ISettingsManager settingsManager;
private readonly ServiceManager<ViewModelBase> dialogFactory;
private readonly IDiscordRichPresenceService discordRichPresenceService;
public string Greeting => "Welcome to Avalonia!";

[ObservableProperty]
Expand All @@ -40,10 +41,14 @@ public partial class MainWindowViewModel : ViewModelBase
public ProgressManagerViewModel ProgressManagerViewModel { get; init; }
public UpdateViewModel UpdateViewModel { get; init; }

public MainWindowViewModel(ISettingsManager settingsManager, ServiceManager<ViewModelBase> dialogFactory)
public MainWindowViewModel(
ISettingsManager settingsManager,
IDiscordRichPresenceService discordRichPresenceService,
ServiceManager<ViewModelBase> dialogFactory)
{
this.settingsManager = settingsManager;
this.dialogFactory = dialogFactory;
this.discordRichPresenceService = discordRichPresenceService;

ProgressManagerViewModel = dialogFactory.Get<ProgressManagerViewModel>();
UpdateViewModel = dialogFactory.Get<UpdateViewModel>();
Expand Down Expand Up @@ -74,6 +79,9 @@ public override async Task OnLoadedAsync()
return;
}

// Initialize Discord Rich Presence (this needs LibraryDir so is set here)
discordRichPresenceService.UpdateState();

// Index checkpoints if we dont have
Task.Run(() => settingsManager.IndexCheckpoints()).SafeFireAndForget();

Expand Down
11 changes: 10 additions & 1 deletion StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public partial class SettingsViewModel : PageViewModelBase
private readonly IPrerequisiteHelper prerequisiteHelper;
private readonly IPyRunner pyRunner;
private readonly ServiceManager<ViewModelBase> dialogFactory;

public SharedState SharedState { get; }

public override string Title => "Settings";
Expand All @@ -66,6 +67,9 @@ public partial class SettingsViewModel : PageViewModelBase
// Shared folder options
[ObservableProperty] private bool removeSymlinksOnShutdown;

// Integrations section
[ObservableProperty] private bool isDiscordRichPresenceEnabled;

// Debug section
[ObservableProperty] private string? debugPaths;
[ObservableProperty] private string? debugCompatInfo;
Expand All @@ -90,14 +94,19 @@ public SettingsViewModel(
this.prerequisiteHelper = prerequisiteHelper;
this.pyRunner = pyRunner;
this.dialogFactory = dialogFactory;

SharedState = sharedState;

SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1];
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;

settingsManager.RelayPropertyFor(this,
vm => vm.SelectedTheme,
settings => settings.Theme);

settingsManager.RelayPropertyFor(this,
vm => vm.IsDiscordRichPresenceEnabled,
settings => settings.IsDiscordRichPresenceEnabled);
}

partial void OnSelectedThemeChanged(string? value)
Expand Down
Loading

0 comments on commit 2e37d3f

Please sign in to comment.