├── README.md ├── src ├── Artemis.Plugins.Modules.OBS │ ├── obs.png │ ├── plugin.json │ ├── Properties │ │ └── launchSettings.json │ ├── DataModels │ │ └── ObsDataModel.cs │ ├── Artemis.Plugins.Modules.OBS.csproj │ └── ObsModule.cs ├── Artemis.Plugins.Modules.Discord │ ├── discord.png │ ├── DiscordPackets │ │ ├── CommandData │ │ │ ├── Authorize.cs │ │ │ ├── Error.cs │ │ │ ├── SpeakingStartStop.cs │ │ │ ├── Pan.cs │ │ │ ├── AudioDevice.cs │ │ │ ├── VoiceChannelSelect.cs │ │ │ ├── ReadyConfig.cs │ │ │ ├── Subscription.cs │ │ │ ├── VoiceState.cs │ │ │ ├── User.cs │ │ │ ├── Ready.cs │ │ │ ├── Notification.cs │ │ │ ├── UserVoiceState.cs │ │ │ ├── InputOutput.cs │ │ │ ├── Authenticate.cs │ │ │ ├── VoiceSettingsMode.cs │ │ │ ├── Shortcut.cs │ │ │ ├── Application.cs │ │ │ ├── SelectedVoiceChannel.cs │ │ │ ├── VoiceConnectionStatus.cs │ │ │ ├── VoiceSettings.cs │ │ │ └── Message.cs │ │ ├── IDiscordMessage.cs │ │ ├── DiscordResponse.cs │ │ ├── DiscordRequest.cs │ │ └── DiscordEvent.cs │ ├── DataModels │ │ ├── DiscordVoiceModeType.cs │ │ ├── DiscordKeyType.cs │ │ ├── DiscordShortcut.cs │ │ ├── DiscordDataModel.cs │ │ ├── DiscordVoiceMode.cs │ │ ├── DiscordNotificationEventArgs.cs │ │ ├── DiscordVoiceDataModel.cs │ │ ├── DiscordUserDataModel.cs │ │ ├── DiscordVoiceConnectionStatusDataModel.cs │ │ ├── DiscordVoiceChannelState.cs │ │ ├── DiscordVoiceChannelDataModel.cs │ │ ├── DiscordVoiceSettingsDataModel.cs │ │ └── DiscordVoiceChannelMember.cs │ ├── Enums │ │ ├── RpcPacketType.cs │ │ ├── DiscordRpcCommand.cs │ │ └── DiscordRpcEvent.cs │ ├── DiscordRpcHeader.cs │ ├── plugin.json │ ├── DiscordPluginConfiguration │ │ ├── DiscordRpcProvider.cs │ │ ├── DiscordPluginConfigurationView.axaml.cs │ │ ├── DiscordPluginConfigurationViewModel.cs │ │ └── DiscordPluginConfigurationView.axaml │ ├── Authentication │ │ ├── IDiscordAuthClient.cs │ │ ├── TokenResponse.cs │ │ ├── DiscordAuthClientBase.cs │ │ ├── StreamKitAuthClient.cs │ │ ├── SteelseriesAuthClient.cs │ │ ├── LogitechAuthClient.cs │ │ ├── RazerAuthClient.cs │ │ └── DiscordAuthClient.cs │ ├── Properties │ │ └── launchSettings.json │ ├── DiscordPluginBootstrapper.cs │ ├── Transport │ │ ├── IDiscordTransport.cs │ │ ├── DiscordWebSocketTransport.cs │ │ └── DiscordPipeTransport.cs │ ├── DiscordRpcClientException.cs │ ├── Artemis.Plugins.Modules.Discord.csproj │ └── IDiscordRpcClient.cs ├── Artemis.Plugins.Modules.Spotify │ ├── Spotify.zip │ ├── no-user.png │ ├── Constants.cs │ ├── plugin.json │ ├── SpotifyPluginBootstrapper.cs │ ├── ConfigurationDialog │ │ ├── SpotifyConfigurationDialogView.axaml.cs │ │ ├── SpotifyConfigurationDialogView.axaml │ │ └── SpotifyConfigurationDialogViewModel.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Artemis.Plugins.Modules.Spotify.csproj │ ├── DataModels │ │ └── SpotifyDataModel.cs │ └── SpotifyModule.cs ├── Artemis.Plugins.LayerBrushes.Chroma │ ├── profile.zip │ ├── ChromaPluginBootstrapper.cs │ ├── Properties │ │ └── launchSettings.json │ ├── plugin.json │ ├── LayerBrushes │ │ ├── ChromaLayerBrushProvider.cs │ │ ├── PropertyGroups │ │ │ └── ChromaPropertyGroup.cs │ │ ├── ChromaLayerBrush.cs │ │ └── OverwatchColorCorrection.cs │ ├── Services │ │ ├── RazerSdkInfo.cs │ │ ├── ChromaRegistryService.cs │ │ └── ChromaService.cs │ ├── Module │ │ ├── ChromaDataModel.cs │ │ └── ChromaModule.cs │ ├── Artemis.Plugins.LayerBrushes.Chroma.csproj │ ├── RzDeviceType.cs │ ├── Prerequisites │ │ ├── ChromaSdkPluginPrerequisite.cs │ │ └── DownloadAndInstallChromaSdkAction.cs │ ├── Scripts │ │ └── download-razer-chroma-sdk.ps1 │ └── DefaultChromaLedMap.cs ├── Artemis.Plugins.Modules.VoiceMeeter │ ├── DataModels │ │ ├── VoiceMeeterBussesDataModel.cs │ │ ├── VoiceMeeterStripsDataModel.cs │ │ ├── VoiceMeeterAppDataModel.cs │ │ ├── VoiceMeeterInfoDataModel.cs │ │ ├── VoiceMeeterDataModel.cs │ │ ├── VoiceMeeterLevelDataModel.cs │ │ ├── VoiceMeeterBusDataModel.cs │ │ └── VoiceMeeterStripDataModel.cs │ ├── plugin.json │ ├── Properties │ │ └── launchSettings.json │ ├── VoiceMeeterPluginBootstrapper.cs │ ├── Artemis.Plugins.Modules.VoiceMeeter.csproj │ ├── Prerequisites │ │ └── VoiceMeeterInstalledPrerequisite.cs │ ├── VoiceMeeterRemote.cs │ └── VoiceMeeterModule.cs ├── Artemis.Plugins.Nodes.Ping │ ├── PingNodeProvider.cs │ ├── Properties │ │ └── launchSettings.json │ ├── plugin.json │ ├── Artemis.Plugins.Nodes.Ping.csproj │ └── PingNode.cs ├── Artemis.Plugins.LayerBrushes.Gif │ ├── plugin.json │ ├── Properties │ │ └── launchSettings.json │ ├── Views │ │ ├── FilePathPropertyDisplayView.axaml.cs │ │ └── FilePathPropertyDisplayView.axaml │ ├── PropertyGroups │ │ └── MainPropertyGroup.cs │ ├── GifLayerBrushProvider.cs │ ├── Artemis.Plugins.LayerBrushes.Gif.csproj │ ├── ViewModels │ │ └── FilePathPropertyDisplayViewModel.cs │ └── GifLayerBrush.cs ├── Artemis.Plugins.Modules.KeyboardLayout │ ├── Properties │ │ └── launchSettings.json │ ├── KeyboardLayoutDataModel.cs │ ├── plugin.json │ ├── Artemis.Plugins.Modules.KeyboardLayout.csproj │ └── KeyboardLayoutModule.cs └── Artemis.Plugins.diogotr7.sln ├── .github ├── workflows │ └── build.yml └── dependabot.yml └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | Artemis.Plugins 2 | 3 | [Download link](https://nightly.link/diogotr7/Artemis.Plugins/workflows/dotnet-core/master) 4 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogotr7/Artemis.Plugins/HEAD/src/Artemis.Plugins.Modules.OBS/obs.png -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogotr7/Artemis.Plugins/HEAD/src/Artemis.Plugins.Modules.Discord/discord.png -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/Spotify.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogotr7/Artemis.Plugins/HEAD/src/Artemis.Plugins.Modules.Spotify/Spotify.zip -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/no-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogotr7/Artemis.Plugins/HEAD/src/Artemis.Plugins.Modules.Spotify/no-user.png -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/profile.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogotr7/Artemis.Plugins/HEAD/src/Artemis.Plugins.LayerBrushes.Chroma/profile.zip -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Authorize.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record Authorize(string Code); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Error.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record Error(int Code, string Message); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/SpeakingStartStop.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record SpeakingStartStop(string UserId); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Pan.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record Pan( 4 | double Left, 5 | double Right 6 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceModeType.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DataModels; 2 | 3 | public enum DiscordVoiceModeType 4 | { 5 | PUSH_TO_TALK, 6 | VOICE_ACTIVITY 7 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/AudioDevice.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record AudioDevice 4 | ( 5 | string Id, 6 | string Name 7 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterBussesDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | public class VoiceMeeterBussesDataModel : DataModel { } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterStripsDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | public class VoiceMeeterStripsDataModel : DataModel { } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterAppDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | public class VoiceMeeterAppDataModel : DataModel 6 | { 7 | 8 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordKeyType.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DataModels; 2 | 3 | public enum DiscordKeyType 4 | { 5 | KEYBOARD_KEY, 6 | MOUSE_BUTTON, 7 | KEYBOARD_MODIFIER_KEY, 8 | GAMEPAD_BUTTON 9 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/VoiceChannelSelect.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record VoiceChannelSelect 4 | ( 5 | string? GuildId, 6 | string? ChannelId 7 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Enums/RpcPacketType.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.Enums; 2 | 3 | public enum RpcPacketType : int 4 | { 5 | HANDSHAKE = 0, 6 | FRAME = 1, 7 | CLOSE = 2, 8 | PING = 3, 9 | PONG = 4, 10 | } 11 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/ReadyConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record ReadyConfig 4 | ( 5 | string CdnHost, 6 | string ApiEndpoint, 7 | string Enviroment 8 | ); 9 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Subscription.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record Subscription( 6 | [JsonProperty("evt")] 7 | string Event 8 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/VoiceState.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record VoiceState( 4 | bool Mute, 5 | bool Deaf, 6 | bool SelfMute, 7 | bool SelfDeaf, 8 | bool Suppress 9 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Spotify; 2 | 3 | internal static class Constants 4 | { 5 | internal const string SPOTIFY_CLIENT_ID = "78a0fd8e919e4ff58c4ddd7979bf00bf"; 6 | internal const string SPOTIFY_AUTH_SETTING = "Authorization"; 7 | } 8 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/User.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record User 4 | ( 5 | string Username, 6 | string Discriminator, 7 | string Id, 8 | string Avatar, 9 | bool? Bot 10 | ); 11 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Ready.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record Ready 6 | ( 7 | [JsonProperty("v")] int Version, 8 | ReadyConfig Config, 9 | User User 10 | ); 11 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Notification.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record Notification 4 | ( 5 | string ChannelId, 6 | Message Message, 7 | string IconUrl, 8 | string Title, 9 | string Body 10 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/UserVoiceState.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record UserVoiceState( 4 | string Nick, 5 | bool Mute, 6 | int Volume, 7 | Pan Pan, 8 | VoiceState VoiceState, 9 | User User 10 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/InputOutput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record InputOutput 6 | ( 7 | List AvailableDevices, 8 | string DeviceId, 9 | float Volume 10 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Authenticate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record Authenticate 6 | ( 7 | User User, 8 | Application Application, 9 | DateTime Expires, 10 | string AccessToken 11 | ); 12 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/VoiceSettingsMode.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record VoiceSettingsMode 4 | ( 5 | string Type, 6 | bool AutoThreshold, 7 | float Threshold, 8 | Shortcut[] Shortcut, 9 | float Delay 10 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordShortcut.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DataModels; 4 | 5 | public class DiscordShortcut : DataModel 6 | { 7 | public DiscordKeyType Type { get; set; } 8 | public int Code { get; set; } 9 | public string? Name { get; set; } 10 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordRpcHeader.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.Modules.Discord.Enums; 2 | 3 | namespace Artemis.Plugins.Modules.Discord; 4 | 5 | #pragma warning disable CS0649 6 | 7 | internal readonly struct DiscordRpcHeader 8 | { 9 | public readonly RpcPacketType PacketType; 10 | public readonly int PacketLength; 11 | } 12 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Nodes.Ping/PingNodeProvider.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Nodes; 2 | 3 | namespace Artemis.Plugins.Nodes.Ping; 4 | 5 | public class PingNodeProvider : NodeProvider 6 | { 7 | public override void Enable() 8 | { 9 | RegisterNodeType(); 10 | } 11 | 12 | public override void Disable() 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "c95778ca-cc41-4633-98f7-012a500ba77f", 3 | "Api": "1.0", 4 | "Name": "Discord", 5 | "Author": "diogotr7", 6 | "Icon": "discord.png", 7 | "Description": "Plugin to get useful data from a local discord client", 8 | "Version": "1.1.0.3", 9 | "Main": "Artemis.Plugins.Modules.Discord.dll" 10 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Shortcut.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record Shortcut 4 | ( 5 | //0 - KEYBOARD_KEY 6 | //1 - MOUSE_BUTTON 7 | //2 - KEYBOARD_MODIFIER_KEY 8 | //3 - GAMEPAD_BUTTON 9 | int Type, 10 | int Code, 11 | string Name 12 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "76d139ad-32da-4929-9694-a9759248a53f", 3 | "Api": "1.0", 4 | "Name": "Spotify", 5 | "Author": "diogotr7", 6 | "Icon": "Spotify", 7 | "Description": "Provides track and player data about your current Spotify listening session.", 8 | "Version": "1.0.0.4", 9 | "Main": "Artemis.Plugins.Modules.Spotify.dll" 10 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterInfoDataModel.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 2 | 3 | public class VoiceMeeterInfoDataModel 4 | { 5 | public VoiceMeeterType VoiceMeeterType { get; set; } 6 | public string? VoiceMeeterVersion { get; set; } 7 | public int StripCount { get; set; } 8 | public int BusCount { get; set; } 9 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "93e77647-43f5-4e24-8843-9ea8c51dafce", 3 | "Api": "1.0", 4 | "Name": "VoiceMeeter", 5 | "Icon": "TuneVerticalVariant", 6 | "Author": "diogotr7", 7 | "Description": "VoiceMeeter integration", 8 | "Version": "1.0.0.2", 9 | "Main": "Artemis.Plugins.Modules.VoiceMeeter.dll", 10 | "Platforms": "Windows" 11 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "bf9cf3ac-9f97-4328-b32f-aa39df1698ff", 3 | "Api": "1.0", 4 | "Name": "Gif Layer Brush", 5 | "Author": "diogotr7", 6 | "Icon": "AnimationPlay", 7 | "Description": "Provides a layer that allows Artemis to display GIFs on your devices", 8 | "Version": "1.0.0.1", 9 | "Main": "Artemis.Plugins.LayerBrushes.Gif.dll" 10 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "56b422de-c9d5-4488-b1f3-97064decaab5", 3 | "Api": "1.0", 4 | "Name": "Open Broadcaster Software", 5 | "Author": "diogotr7", 6 | "Icon": "obs.png", 7 | "Description": "Provides OBS information like streaming and recording status, current scene, skipped frames, etc.", 8 | "Version": "2.0.0.1", 9 | "Main": "Artemis.Plugins.Modules.OBS.dll" 10 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPluginConfiguration/DiscordRpcProvider.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPluginConfiguration; 4 | 5 | public enum DiscordRpcProvider 6 | { 7 | //streamkit is the default 8 | [Description("StreamKit (Recommended)")] 9 | StreamKit = 0, 10 | Custom, 11 | Razer, 12 | Steelseries, 13 | Logitech, 14 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/IDiscordAuthClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.Authentication; 5 | 6 | public interface IDiscordAuthClient : IDisposable 7 | { 8 | string ClientId { get; } 9 | string Origin { get; } 10 | string? AccessToken { get; } 11 | Task GetAccessTokenAsync(string challengeCode); 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/ChromaPluginBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Plugins.LayerBrushes.Chroma.Prerequisites; 3 | 4 | namespace Artemis.Plugins.LayerBrushes.Chroma; 5 | 6 | public class ChromaPluginBootstrapper : PluginBootstrapper 7 | { 8 | public override void OnPluginLoaded(Plugin plugin) 9 | { 10 | AddPluginPrerequisite(new ChromaSdkPluginPrerequisite()); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Application.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record Application 6 | ( 7 | string Id, 8 | string Name, 9 | string Icon, 10 | string Description, 11 | string Summary, 12 | bool Hook, 13 | List RpcOrigins, 14 | string VerifyKey 15 | ); 16 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/SelectedVoiceChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record SelectedVoiceChannel( 6 | string Id, 7 | string GuildId, 8 | string Name, 9 | int Type, 10 | int Bitrate, 11 | int UserLimit, 12 | int Position, 13 | List VoiceStates 14 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/SpotifyPluginBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.UI.Shared; 3 | 4 | namespace Artemis.Plugins.Modules.Spotify; 5 | 6 | public class SpotifyPluginBootstrapper : PluginBootstrapper 7 | { 8 | public override void OnPluginLoaded(Plugin plugin) 9 | { 10 | plugin.ConfigurationDialog = new PluginConfigurationDialog(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Nodes.Ping/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/ConfigurationDialog/SpotifyConfigurationDialogView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Markup.Xaml; 2 | using Avalonia.ReactiveUI; 3 | 4 | namespace Artemis.Plugins.Modules.Spotify; 5 | 6 | public partial class SpotifyConfigurationDialogView : ReactiveUserControl 7 | { 8 | public SpotifyConfigurationDialogView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.KeyboardLayout/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Artemis\\Artemis.UI.Windows.exe" 6 | }, 7 | "Windows Dev": { 8 | "commandName": "Executable", 9 | "executablePath": "$(SolutionDir)\\..\\..\\Artemis\\src\\Artemis.UI.Windows\\bin\\Artemis.UI.Windows.exe" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/VoiceMeeterPluginBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Plugins.Modules.VoiceMeeter.Prerequisites; 3 | 4 | namespace Artemis.Plugins.Modules.VoiceMeeter; 5 | 6 | public class VoiceMeeterPluginBootstrapper : PluginBootstrapper 7 | { 8 | public override void OnPluginLoaded(Plugin plugin) 9 | { 10 | AddPluginPrerequisite(new VoiceMeeterInstalledPrerequisite()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "0cb99d89-915b-407e-82ac-8316d0559c4e", 3 | "Api": "1.0", 4 | "Name": "Chroma Reader", 5 | "Author": "diogotr7", 6 | "Icon": "Snake", 7 | "Description": "Provides a layer that displays lighting from Razer Chroma supported games on all devices Artemis supports.", 8 | "Version": "1.3.0.3", 9 | "Main": "Artemis.Plugins.LayerBrushes.Chroma.dll", 10 | "Platforms": "Windows" 11 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.Modules; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordDataModel : DataModel 7 | { 8 | public DiscordUserDataModel User { get; } = new(); 9 | 10 | public DiscordVoiceDataModel Voice { get; } = new(); 11 | 12 | public DataModelEvent Notification { get; } = new(); 13 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | public class VoiceMeeterDataModel : DataModel 6 | { 7 | public VoiceMeeterInfoDataModel Information { get; set; } = new(); 8 | 9 | public VoiceMeeterStripsDataModel Strips { get; set; } = new(); 10 | 11 | public VoiceMeeterBussesDataModel Busses { get; set; } = new(); 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/VoiceConnectionStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | public record Ping 6 | ( 7 | ulong Time, 8 | uint Value 9 | ); 10 | 11 | public record VoiceConnectionStatus 12 | ( 13 | string State, 14 | string Hostname, 15 | List Pings, 16 | float AveragePing, 17 | float? LastPing 18 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/VoiceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | 3 | public record VoiceSettings 4 | ( 5 | bool AutomaticGainControl, 6 | bool EchoCancellation, 7 | bool NoiseSuppression, 8 | bool Qos, 9 | bool SilenceWarning, 10 | bool Deaf, 11 | bool Mute, 12 | VoiceSettingsMode Mode, 13 | InputOutput Input, 14 | InputOutput Output 15 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.KeyboardLayout/KeyboardLayoutDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.KeyboardLayout; 4 | 5 | public class KeyboardLayoutDataModel : DataModel 6 | { 7 | public uint Hkl { get; set; } 8 | public ushort HklLowWord { get; set; } 9 | public ushort HklHighWord { get; set; } 10 | public string Klid { get; set; } = string.Empty; 11 | public string Name { get; set; } = string.Empty; 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPluginConfiguration/DiscordPluginConfigurationView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Markup.Xaml; 2 | using Avalonia.ReactiveUI; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DiscordPluginConfiguration; 5 | 6 | public partial class DiscordPluginConfigurationView : ReactiveUserControl 7 | { 8 | public DiscordPluginConfigurationView() 9 | { 10 | InitializeComponent(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/Views/FilePathPropertyDisplayView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.LayerBrushes.Gif.ViewModels; 2 | using Avalonia.Markup.Xaml; 3 | using Avalonia.ReactiveUI; 4 | 5 | namespace Artemis.Plugins.LayerBrushes.Gif.Views; 6 | 7 | public partial class FilePathPropertyDisplayView : ReactiveUserControl 8 | { 9 | public FilePathPropertyDisplayView() 10 | { 11 | InitializeComponent(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceMode.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DataModels; 4 | 5 | public class DiscordVoiceMode : DataModel 6 | { 7 | public DiscordVoiceModeType Type { get; set; } 8 | public bool AutoThreshold { get; set; } 9 | public float Threshold { get; set; } 10 | public DiscordShortcut[] Shortcut { get; set; } = System.Array.Empty(); 11 | public float Delay { get; set; } 12 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPluginBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Plugins.Modules.Discord.DiscordPluginConfiguration; 3 | using Artemis.UI.Shared; 4 | 5 | namespace Artemis.Plugins.Modules.Discord; 6 | 7 | public class DiscordPluginBootstrapper : PluginBootstrapper 8 | { 9 | public override void OnPluginLoaded(Plugin plugin) 10 | { 11 | plugin.ConfigurationDialog = new PluginConfigurationDialog(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/LayerBrushes/ChromaLayerBrushProvider.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.LayerBrushes; 2 | 3 | namespace Artemis.Plugins.LayerBrushes.Chroma.LayerBrushes; 4 | 5 | public class ChromaLayerBrushProvider : LayerBrushProvider 6 | { 7 | public override void Enable() 8 | { 9 | RegisterLayerBrushDescriptor("Chroma Reader Layer", "Allows you to have Razer Chroma lighting on all devices.", "Robber"); 10 | } 11 | 12 | public override void Disable() 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Services/RazerSdkInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.LayerBrushes.Chroma.Services; 2 | 3 | public class RazerSdkInfo 4 | { 5 | public string Version { get; } 6 | 7 | public bool AppsEnabled { get; } 8 | 9 | public string[] PriorityList { get; } 10 | 11 | public RazerSdkInfo(string version, bool appsEnabled, string[] priorityList) 12 | { 13 | Version = version; 14 | AppsEnabled = appsEnabled; 15 | PriorityList = priorityList; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/TokenResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.Authentication; 5 | 6 | #pragma warning disable CS8618 7 | 8 | [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] 9 | public class TokenResponse 10 | { 11 | public string AccessToken { get; set; } 12 | public long ExpiresIn { get; set; } 13 | public string RefreshToken { get; set; } 14 | public string Scope { get; set; } 15 | public string TokenType { get; set; } 16 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordNotificationEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordNotificationEventArgs : DataModelEventArgs 7 | { 8 | public string ChannelId { get; set; } = string.Empty; 9 | public User? Author { get; set; } = default!; 10 | public string IconUrl { get; set; } = string.Empty; 11 | public string Title { get; set; } = string.Empty; 12 | public string Body { get; set; } = string.Empty; 13 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Plugins 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-latest 8 | 9 | steps: 10 | - name: Clone Plugins 11 | uses: actions/checkout@v3 12 | 13 | - name: Build Plugins 14 | run: dotnet publish -c Release src 15 | 16 | - name: Install Plugin Uploader 17 | run: dotnet tool install ArtemisRGB.Tools.PluginUploader --global 18 | 19 | - name: Upload 20 | if: github.ref == 'refs/heads/master' 21 | run: artemis-plugin-uploader upload-all --pat ${{ secrets.WORKSHOP_PAT }} --folder src -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.Modules; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordVoiceDataModel : DataModel 7 | { 8 | public DataModelEvent Connected { get; } = new(); 9 | 10 | public DataModelEvent Disconnected { get; } = new(); 11 | 12 | public DiscordVoiceConnectionStatusDataModel Connection { get; } = new(); 13 | 14 | public DiscordVoiceSettingsDataModel Settings { get; } = new(); 15 | 16 | public DiscordVoiceChannelDataModel Channel { get; } = new(); 17 | } 18 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Nodes.Ping/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "83C94EF3-553A-45CA-BA4F-59A56A3F94BC", 3 | "Name": "Ping Node", 4 | "Description": "Adds a node that can ping a host and return the result", 5 | "Author": "diogotr7", 6 | "Website": "https://github.com/diogotr7/Artemis.Plugins", 7 | "Repository": "https://github.com/diogotr7/Artemis.Plugins", 8 | "Icon": "QuestionMark", 9 | "Version": "1.0.0.1", 10 | "Main": "Artemis.Plugins.Nodes.Ping.dll", 11 | "AutoEnableProperties": true, 12 | "RequiresAdmin": false, 13 | "Platforms": "Windows,Linux,OSX", 14 | "Api": "1.0.0" 15 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Transport/IDiscordTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Artemis.Plugins.Modules.Discord.Enums; 5 | 6 | namespace Artemis.Plugins.Modules.Discord.Transport; 7 | 8 | public interface IDiscordTransport : IDisposable 9 | { 10 | bool IsConnected { get; } 11 | Task Connect(CancellationToken cancellationToken = default); 12 | Task SendPacketAsync(string stringData, RpcPacketType rpcPacketType, CancellationToken cancellationToken = default); 13 | Task<(RpcPacketType, string)> ReadMessageAsync(CancellationToken cancellationToken = default); 14 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/CommandData/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 4 | 5 | //TODO: i have no idea what the hell they did with this data structure. 6 | //The docs don't seem to be correct. 7 | public record Message 8 | ( 9 | string Id, 10 | string Content, 11 | string Nick, 12 | DateTime Timestamp, 13 | DateTime? EditedTimestamp, 14 | bool Tts, 15 | int Type, 16 | User Author, 17 | bool Pinned 18 | 19 | //"mentions": [], 20 | //"mention_roles": [], 21 | //"embeds": [], 22 | //"attachments": [], 23 | //"content_parsed" 24 | ); -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.KeyboardLayout/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Guid": "D34A47E6-341B-4E64-A570-32B34367A48E", 3 | "Name": "Keyboard Layout", 4 | "Description": "Adds a module with the current selected keyboard layout in windows", 5 | "Author": "diogotr7", 6 | "Website": "https://github.com/diogotr7/Artemis.Plugins", 7 | "Repository": "https://github.com/diogotr7/Artemis.Plugins", 8 | "Icon": "QuestionMark", 9 | "Version": "1.0.0.4", 10 | "Main": "Artemis.Plugins.Modules.KeyboardLayout.dll", 11 | "AutoEnableProperties": true, 12 | "RequiresAdmin": false, 13 | "Platforms": "Windows", 14 | "Api": "1.0.0" 15 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | allow: 13 | - dependency-name: "ArtemisRGB*" 14 | ignore: 15 | - dependency-name: "*" 16 | update-types: ["version-update:semver-major"] 17 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/PropertyGroups/MainPropertyGroup.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | 3 | namespace Artemis.Plugins.LayerBrushes.Gif.PropertyGroups; 4 | 5 | #pragma warning disable CS8618 6 | 7 | public class MainPropertyGroup : LayerPropertyGroup 8 | { 9 | [PropertyDescription(DisableKeyframes = true)] 10 | public LayerProperty FileName { get; set; } 11 | 12 | protected override void PopulateDefaults() 13 | { 14 | FileName.DefaultValue = ""; 15 | } 16 | 17 | protected override void EnableProperties() 18 | { 19 | } 20 | 21 | protected override void DisableProperties() 22 | { 23 | } 24 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Enums/DiscordRpcCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.Enums; 2 | 3 | public enum DiscordRpcCommand 4 | { 5 | DISPATCH, 6 | AUTHORIZE, 7 | AUTHENTICATE, 8 | GET_GUILD, 9 | GET_GUILDS, 10 | GET_CHANNEL, 11 | GET_CHANNELS, 12 | SUBSCRIBE, 13 | UNSUBSCRIBE, 14 | SET_USER_VOICE_SETTINGS, 15 | SELECT_VOICE_CHANNEL, 16 | GET_SELECTED_VOICE_CHANNEL, 17 | SELECT_TEXT_CHANNEL, 18 | GET_VOICE_SETTINGS, 19 | SET_VOICE_SETTINGS, 20 | CAPTURE_SHORTCUT, 21 | SET_CERTIFIED_DEVICES, 22 | SET_ACTIVITY, 23 | SEND_ACTIVITY_JOIN_INVITE, 24 | CLOSE_ACTIVITY_REQUEST 25 | } 26 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Enums/DiscordRpcEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.Enums; 2 | 3 | public enum DiscordRpcEvent 4 | { 5 | READY, 6 | ERROR, 7 | GUILD_STATUS, 8 | GUILD_CREATE, 9 | CHANNEL_CREATE, 10 | VOICE_CHANNEL_SELECT, 11 | VOICE_STATE_CREATE, 12 | VOICE_STATE_UPDATE, 13 | VOICE_STATE_DELETE, 14 | VOICE_SETTINGS_UPDATE, 15 | VOICE_CONNECTION_STATUS, 16 | SPEAKING_START, 17 | SPEAKING_STOP, 18 | MESSAGE_CREATE, 19 | MESSAGE_UPDATE, 20 | MESSAGE_DELETE, 21 | NOTIFICATION_CREATE, 22 | CAPTURE_SHORTCUT_CHANGE, 23 | ACTIVITY_JOIN, 24 | ACTIVITY_SPECTATE, 25 | ACTIVITY_JOIN_REQUEST, 26 | } 27 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordUserDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordUserDataModel : DataModel 7 | { 8 | public string Username { get; set; } = string.Empty; 9 | 10 | public string Discriminator { get; set; } = string.Empty; 11 | 12 | public string Id { get; set; } = string.Empty; 13 | 14 | public bool IsBot { get; set; } 15 | 16 | internal void Apply(User user) 17 | { 18 | Username = user.Username; 19 | Discriminator = user.Discriminator; 20 | Id = user.Id; 21 | IsBot = user.Bot ?? false; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceConnectionStatusDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | using System; 4 | 5 | namespace Artemis.Plugins.Modules.Discord.DataModels; 6 | 7 | public class DiscordVoiceConnectionStatusDataModel : DataModel 8 | { 9 | public DiscordVoiceChannelState State { get; set; } 10 | 11 | public string Hostname { get; set; } = string.Empty; 12 | 13 | public float? Ping { get; set; } 14 | 15 | public bool IsConnected => State == DiscordVoiceChannelState.VOICE_CONNECTED; 16 | 17 | internal void Apply(VoiceConnectionStatus e) 18 | { 19 | State = Enum.Parse(e.State); 20 | Ping = e.LastPing; 21 | Hostname = e.Hostname; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/Views/FilePathPropertyDisplayView.axaml: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/DataModels/ObsDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using OBSWebsocketDotNet.Types; 3 | 4 | namespace Artemis.Plugins.Modules.OBS.DataModels; 5 | 6 | public class ObsDataModel : DataModel 7 | { 8 | public bool IsConnected { get; set; } 9 | public ObsStats? Stats { get; set; } 10 | public OutputStatus? StreamingStatus { get; set; } 11 | public RecordingStatus? RecordingStatus { get; set; } 12 | public GetProfileListInfo? ProfileListInfo { get; set; } 13 | public GetSceneListInfo? SceneListInfo { get; set; } 14 | 15 | 16 | public void Reset() 17 | { 18 | IsConnected = false; 19 | Stats = null; 20 | StreamingStatus = null; 21 | RecordingStatus = null; 22 | ProfileListInfo = null; 23 | SceneListInfo = null; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Module/ChromaDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using SkiaSharp; 3 | using System; 4 | using System.Collections.Generic; 5 | using Artemis.Plugins.LayerBrushes.Chroma.Services; 6 | 7 | namespace Artemis.Plugins.LayerBrushes.Chroma.Module; 8 | 9 | public class ChromaDataModel : DataModel 10 | { 11 | public bool IsActive => Service.IsActive; 12 | public string? CurrentApplication => Service.CurrentApp; 13 | public string[] ApplicationList => Service.AppNames; 14 | public uint[] PidList => Service.AppIds; 15 | public string[] PriorityList { get; internal set; } = []; 16 | 17 | internal ChromaService Service { get; set; } = null!; 18 | } 19 | 20 | public class ChromaDeviceDataModel : DataModel 21 | { 22 | } 23 | 24 | public class ChromaLedDataModel : DataModel 25 | { 26 | public SKColor Color { get; set; } 27 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/GifLayerBrushProvider.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.LayerBrushes; 2 | using Artemis.Plugins.LayerBrushes.Gif.ViewModels; 3 | using Artemis.UI.Shared.Services.PropertyInput; 4 | 5 | namespace Artemis.Plugins.LayerBrushes.Gif; 6 | 7 | public class GifLayerBrushProvider : LayerBrushProvider 8 | { 9 | private readonly IPropertyInputService _propertyInputService; 10 | 11 | public GifLayerBrushProvider(IPropertyInputService profileEditorService) 12 | { 13 | _propertyInputService = profileEditorService; 14 | } 15 | 16 | public override void Enable() 17 | { 18 | _propertyInputService.RegisterPropertyInput(Plugin); 19 | RegisterLayerBrushDescriptor("Gif layer brush", "Gif layer brush", "Gif"); 20 | } 21 | 22 | public override void Disable() 23 | { 24 | } 25 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/IDiscordMessage.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.Modules.Discord.Enums; 2 | using JsonSubTypes; 3 | using Newtonsoft.Json; 4 | using static JsonSubTypes.JsonSubtypes; 5 | 6 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets; 7 | 8 | [JsonConverter(typeof(JsonSubtypes), "cmd")] 9 | [KnownSubType(typeof(DiscordEvent), DiscordRpcCommand.DISPATCH)] 10 | [KnownSubType(typeof(DiscordAuthorizeResponse), DiscordRpcCommand.AUTHORIZE)] 11 | [KnownSubType(typeof(DiscordAuthenticateResponse), DiscordRpcCommand.AUTHENTICATE)] 12 | [KnownSubType(typeof(DiscordGetVoiceSettingsResponse), DiscordRpcCommand.GET_VOICE_SETTINGS)] 13 | [KnownSubType(typeof(DiscordSubscribeResponse), DiscordRpcCommand.SUBSCRIBE)] 14 | [KnownSubType(typeof(DiscordGetSelectedVoiceChannelResponse), DiscordRpcCommand.GET_SELECTED_VOICE_CHANNEL)] 15 | public interface IDiscordMessage 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordRpcClientException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Artemis.Plugins.Modules.Discord; 4 | 5 | public class DiscordRpcClientException : Exception 6 | { 7 | public bool ShouldReconnect { get; } 8 | 9 | public DiscordRpcClientException(string message, bool shouldReconnect = false) : base(message) 10 | { 11 | ShouldReconnect = shouldReconnect; 12 | } 13 | 14 | public DiscordRpcClientException(string message, Exception innerException, bool shouldReconnect = false) : base(message, innerException) 15 | { 16 | ShouldReconnect = shouldReconnect; 17 | } 18 | } 19 | 20 | public class DiscordException : Exception 21 | { 22 | public DiscordException(string message) : base(message) 23 | { 24 | } 25 | 26 | public DiscordException(string message, Exception innerException) : base(message, innerException) 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Nodes.Ping/Artemis.Plugins.Nodes.Ping.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/Artemis.Plugins.LayerBrushes.Gif.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.KeyboardLayout/Artemis.Plugins.Modules.KeyboardLayout.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/DiscordResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets; 5 | 6 | public class DiscordResponse : IDiscordMessage 7 | { 8 | public Guid Nonce { get; set; } 9 | } 10 | 11 | public abstract class DiscordResponse : DiscordResponse 12 | { 13 | #pragma warning disable CS8618 14 | public T Data { get; init; } 15 | #pragma warning restore CS8618 16 | } 17 | 18 | public sealed class DiscordAuthorizeResponse : DiscordResponse { } 19 | public sealed class DiscordAuthenticateResponse : DiscordResponse { } 20 | public sealed class DiscordGetVoiceSettingsResponse : DiscordResponse { } 21 | public sealed class DiscordSubscribeResponse : DiscordResponse { } 22 | public sealed class DiscordGetSelectedVoiceChannelResponse : DiscordResponse { } 23 | public sealed class DiscordErrorResponse : DiscordResponse { } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/DiscordAuthClientBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Artemis.Core; 5 | 6 | namespace Artemis.Plugins.Modules.Discord.Authentication; 7 | 8 | public abstract class DiscordAuthClientBase : IDiscordAuthClient 9 | { 10 | protected readonly HttpClient HttpClient = new(); 11 | protected readonly PluginSetting Token; 12 | protected DiscordAuthClientBase(PluginSetting token) 13 | { 14 | Token = token; 15 | } 16 | 17 | public abstract string ClientId { get; } 18 | public abstract string Origin { get; } 19 | public string? AccessToken => Token.Value; 20 | public abstract Task GetAccessTokenAsync(string challengeCode); 21 | 22 | protected void SaveToken(TokenResponse newToken) 23 | { 24 | Token.Value = newToken.AccessToken; 25 | Token.Save(); 26 | } 27 | 28 | public void Dispose() 29 | { 30 | HttpClient.Dispose(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/LayerBrushes/PropertyGroups/ChromaPropertyGroup.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | 3 | namespace Artemis.Plugins.LayerBrushes.Chroma.LayerBrushes.PropertyGroups; 4 | 5 | #pragma warning disable CS8618 6 | public class ChromaPropertyGroup : LayerPropertyGroup 7 | { 8 | [PropertyDescription(Description = "Colors unmapped LEDs with the default color")] 9 | public BoolLayerProperty UseDefaultLed { get; set; } 10 | 11 | [PropertyDescription(Description = "Turns black LEDs transparent")] 12 | public BoolLayerProperty TransparentBlack { get; set; } 13 | 14 | [PropertyDescription(Description = "Enhances colors to be more vibrant")] 15 | public BoolLayerProperty OverwatchEnhanceColors { get; set; } 16 | 17 | protected override void PopulateDefaults() 18 | { 19 | UseDefaultLed.DefaultValue = true; 20 | TransparentBlack.DefaultValue = false; 21 | OverwatchEnhanceColors.DefaultValue = false; 22 | } 23 | 24 | protected override void EnableProperties() 25 | { 26 | } 27 | 28 | protected override void DisableProperties() 29 | { 30 | } 31 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Artemis.Plugins.LayerBrushes.Chroma.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | CA1416 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/Artemis.Plugins.Modules.VoiceMeeter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | CA1416 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Artemis.Plugins.Modules.Discord.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/Artemis.Plugins.Modules.OBS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterLevelDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 6 | 7 | public class VoiceMeeterLevelDataModel : DataModel 8 | { 9 | private readonly DynamicChild[] _children; 10 | private readonly int _type; 11 | private readonly int _index; 12 | 13 | public float Value => _children.Select(x => x.Value).Average(); 14 | 15 | public VoiceMeeterLevelDataModel(int type, int index, int channels) 16 | { 17 | _type = type; 18 | _index = index; 19 | _children = new DynamicChild[channels]; 20 | for (int i = 0; i < channels; i++) 21 | _children[i] = AddDynamicChild(i.ToString(), 0f, $"Channel {i + 1}"); 22 | } 23 | 24 | public void Update() 25 | { 26 | for (int i = 0; i < _children.Length; i++) 27 | { 28 | var result = VoiceMeeterRemote.GetLevel(_type, _index + i, out var val); 29 | 30 | if (result != 0) 31 | throw new Exception(); 32 | 33 | _children[i].Value = val; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceChannelState.cs: -------------------------------------------------------------------------------- 1 | namespace Artemis.Plugins.Modules.Discord.DataModels; 2 | 3 | public enum DiscordVoiceChannelState 4 | { 5 | /// 6 | /// TCP disconnected 7 | /// 8 | DISCONNECTED, 9 | /// 10 | /// Waiting for voice endpoint 11 | /// 12 | AWAITING_ENDPOINT, 13 | 14 | /// 15 | /// TCP authenticating 16 | /// 17 | AUTHENTICATING, 18 | 19 | /// 20 | /// TCP connecting 21 | /// 22 | CONNECTING,//TCP connecting 23 | 24 | /// 25 | /// TCP connected 26 | /// 27 | CONNECTED, 28 | 29 | /// 30 | /// TCP connected, Voice disconnected 31 | /// 32 | VOICE_DISCONNECTED, 33 | 34 | /// 35 | /// TCP connected, Voice connecting 36 | /// 37 | VOICE_CONNECTING, 38 | 39 | /// 40 | /// TCP connected, Voice connected 41 | /// 42 | VOICE_CONNECTED, 43 | 44 | /// 45 | /// No route to host 46 | /// 47 | NO_ROUTE, 48 | 49 | /// 50 | /// WebRTC ice checking 51 | /// 52 | ICE_CHECKING, 53 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/RzDeviceType.cs: -------------------------------------------------------------------------------- 1 | using RazerSdkReader.Structures; 2 | 3 | namespace Artemis.Plugins.LayerBrushes.Chroma; 4 | 5 | public enum RzDeviceType 6 | { 7 | Mousepad, 8 | Mouse, 9 | Keypad, 10 | Keyboard, 11 | Headset, 12 | ChromaLink, 13 | } 14 | 15 | public static class EnumExtensions 16 | { 17 | public static string ToStringFast(this RzDeviceType value) => value switch 18 | { 19 | RzDeviceType.Mousepad => nameof(RzDeviceType.Mousepad), 20 | RzDeviceType.Mouse => nameof(RzDeviceType.Mouse), 21 | RzDeviceType.Keypad => nameof(RzDeviceType.Keypad), 22 | RzDeviceType.Keyboard => nameof(RzDeviceType.Keyboard), 23 | RzDeviceType.Headset => nameof(RzDeviceType.Headset), 24 | RzDeviceType.ChromaLink => nameof(RzDeviceType.ChromaLink), 25 | _ => value.ToString(), 26 | }; 27 | 28 | public static int GetLength(this RzDeviceType value) => value switch 29 | { 30 | RzDeviceType.Mousepad => ChromaMousepad.COUNT, 31 | RzDeviceType.Mouse => ChromaMouse.COUNT, 32 | RzDeviceType.Keypad => ChromaKeypad.COUNT, 33 | RzDeviceType.Keyboard => ChromaKeyboard.COUNT, 34 | RzDeviceType.Headset => ChromaHeadset.COUNT, 35 | RzDeviceType.ChromaLink => ChromaLink.COUNT, 36 | _ => 0, 37 | }; 38 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/Artemis.Plugins.Modules.Spotify.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | x64 5 | true 6 | enable 7 | False 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/ViewModels/FilePathPropertyDisplayViewModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.UI.Shared.Services; 3 | using Artemis.UI.Shared.Services.ProfileEditor; 4 | using Artemis.UI.Shared.Services.PropertyInput; 5 | using ReactiveUI; 6 | using System.Reactive; 7 | using System.Threading.Tasks; 8 | 9 | namespace Artemis.Plugins.LayerBrushes.Gif.ViewModels; 10 | 11 | public class FilePathPropertyDisplayViewModel : PropertyInputViewModel 12 | { 13 | private readonly IWindowService _windowService; 14 | 15 | public FilePathPropertyDisplayViewModel(LayerProperty layerProperty, 16 | IProfileEditorService profileEditorService, 17 | IPropertyInputService propertyInputService, 18 | IWindowService windowService) : base(layerProperty, profileEditorService, propertyInputService) 19 | { 20 | _windowService = windowService; 21 | 22 | Browse = ReactiveCommand.CreateFromTask(ExecuteBrowse); 23 | } 24 | 25 | public ReactiveCommand Browse { get; } 26 | 27 | private async Task ExecuteBrowse() 28 | { 29 | var dialog = _windowService.CreateOpenFileDialog() 30 | .WithTitle("Pick gif") 31 | .HavingFilter(f => f.WithExtension("gif")); 32 | 33 | var files = await dialog.ShowAsync(); 34 | if (files?.Length == 1) 35 | { 36 | InputValue = files[0]; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceChannelDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordVoiceChannelDataModel : DataModel 7 | { 8 | public string Id { get; set; } = string.Empty; 9 | public string GuildId { get; set; } = string.Empty; 10 | public string Name { get; set; } = string.Empty; 11 | public int Type { get; set; } 12 | public int Bitrate { get; set; } 13 | public int UserLimit { get; set; } 14 | public bool IsAnyoneElseSpeaking { get; internal set; } 15 | public DiscordVoiceChannelMember Me { get; internal set; } = new(); 16 | [DataModelProperty(Name = "Other Members")] 17 | public DiscordVoiceChannelMembers Members { get; } = new(); 18 | 19 | internal void Apply(SelectedVoiceChannel e) 20 | { 21 | Id = e.Id; 22 | GuildId = e.GuildId; 23 | Name = e.Name; 24 | Type = e.Type; 25 | Bitrate = e.Bitrate; 26 | UserLimit = e.UserLimit; 27 | } 28 | 29 | public void Reset() 30 | { 31 | Id = string.Empty; 32 | GuildId = string.Empty; 33 | Name = string.Empty; 34 | Type = 0; 35 | Bitrate = 0; 36 | UserLimit = 0; 37 | IsAnyoneElseSpeaking = false; 38 | Me.Reset(); 39 | Members.ClearDynamicChildren(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/IDiscordRpcClient.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.Modules.Discord.DiscordPackets; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | using Artemis.Plugins.Modules.Discord.Enums; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace Artemis.Plugins.Modules.Discord; 8 | 9 | public interface IDiscordRpcClient : IDisposable 10 | { 11 | event EventHandler Authenticated; 12 | event EventHandler Error; 13 | event EventHandler NotificationReceived; 14 | event EventHandler SpeakingStarted; 15 | event EventHandler SpeakingStopped; 16 | event EventHandler UnhandledEventReceived; 17 | event EventHandler VoiceChannelUpdated; 18 | event EventHandler VoiceConnectionStatusUpdated; 19 | event EventHandler VoiceSettingsUpdated; 20 | event EventHandler VoiceStateCreated; 21 | event EventHandler VoiceStateDeleted; 22 | event EventHandler VoiceStateUpdated; 23 | 24 | Task Connect(int timeoutMs = 500); 25 | Task GetAsync(DiscordRpcCommand command, params (string Key, object Value)[] parameters) where T : class; 26 | Task SubscribeAsync(DiscordRpcEvent evt, params (string Key, object Value)[] parameters); 27 | Task UnsubscribeAsync(DiscordRpcEvent evt, params (string Key, object Value)[] parameters); 28 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Prerequisites/ChromaSdkPluginPrerequisite.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Artemis.Core; 4 | using Microsoft.Win32; 5 | 6 | namespace Artemis.Plugins.LayerBrushes.Chroma.Prerequisites; 7 | 8 | public class ChromaSdkPluginPrerequisite : PluginPrerequisite 9 | { 10 | public override string Name => "Razer Chroma SDK Core"; 11 | 12 | public override string Description => "Services needed for Chroma games to send lighting"; 13 | 14 | public override List InstallActions { get; } 15 | 16 | public override List UninstallActions { get; } 17 | 18 | public override bool IsMet() 19 | { 20 | using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Razer Chroma SDK"); 21 | 22 | return key != null; 23 | } 24 | 25 | public ChromaSdkPluginPrerequisite() 26 | { 27 | var programFilesX86 = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86); 28 | var uninstallerPath = Path.Combine(programFilesX86, "Razer Chroma SDK", "Razer_Chroma_SDK_Uninstaller.exe"); 29 | 30 | InstallActions = new List 31 | { 32 | new DownloadAndInstallChromaSdkAction(), 33 | }; 34 | 35 | UninstallActions = new List 36 | { 37 | new ExecuteFileAction("Uninstall Chroma SDK", uninstallerPath, elevate: true, arguments: "/S") 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceSettingsDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace Artemis.Plugins.Modules.Discord.DataModels; 7 | 8 | public class DiscordVoiceSettingsDataModel : DataModel 9 | { 10 | public bool Muted { get; set; } 11 | public bool Deafened { get; set; } 12 | public bool AutomaticGainControl { get; set; } 13 | public bool EchoCancellation { get; set; } 14 | public bool NoiseSuppression { get; set; } 15 | public bool Qos { get; set; } 16 | public bool SilenceWarning { get; set; } 17 | public DiscordVoiceMode Mode { get; set; } = new DiscordVoiceMode(); 18 | 19 | internal void Apply(VoiceSettings voiceData) 20 | { 21 | AutomaticGainControl = voiceData.AutomaticGainControl; 22 | EchoCancellation = voiceData.EchoCancellation; 23 | NoiseSuppression = voiceData.NoiseSuppression; 24 | Qos = voiceData.Qos; 25 | SilenceWarning = voiceData.SilenceWarning; 26 | Deafened = voiceData.Deaf; 27 | Muted = voiceData.Mute; 28 | Mode.Type = Enum.Parse(voiceData.Mode.Type); 29 | Mode.AutoThreshold = voiceData.Mode.AutoThreshold; 30 | Mode.Threshold = voiceData.Mode.Threshold; 31 | Mode.Shortcut = voiceData.Mode.Shortcut 32 | .Select(ds => new DiscordShortcut 33 | { 34 | Type = (DiscordKeyType)ds.Type, 35 | Code = ds.Code, 36 | Name = ds.Name 37 | }) 38 | .ToArray(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DataModels/DiscordVoiceChannelMember.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 3 | 4 | namespace Artemis.Plugins.Modules.Discord.DataModels; 5 | 6 | public class DiscordVoiceChannelMembers : DataModel 7 | { 8 | 9 | 10 | //DynamicDataModel with the other members 11 | } 12 | 13 | public class DiscordVoiceChannelMember 14 | { 15 | public DiscordUserDataModel User { get; } = new(); 16 | public string Nickname { get; set; } = string.Empty; 17 | public int Volume { get; set; } 18 | public bool IsServerMuted { get; set; } 19 | public bool IsSelfMuted { get; set; } 20 | public bool IsServerDeafened { get; set; } 21 | public bool IsSelfDeafened { get; set; } 22 | public bool IsSpeaking { get; set; } 23 | public bool IsSupressed { get; set; } 24 | public bool IsMutedByMe { get; set; } 25 | 26 | internal void Apply(UserVoiceState e) 27 | { 28 | User.Apply(e.User); 29 | Nickname = e.Nick; 30 | Volume = e.Volume; 31 | IsServerMuted = e.VoiceState.Mute; 32 | IsSelfMuted = e.VoiceState.SelfMute; 33 | IsServerDeafened = e.VoiceState.Deaf; 34 | IsSelfDeafened = e.VoiceState.SelfDeaf; 35 | IsSupressed = e.VoiceState.Suppress; 36 | IsMutedByMe = e.Mute; 37 | } 38 | 39 | public void Reset() 40 | { 41 | IsServerMuted = false; 42 | IsSelfMuted = false; 43 | IsServerDeafened = false; 44 | IsSelfDeafened = false; 45 | IsSpeaking = false; 46 | IsSupressed = false; 47 | IsMutedByMe = false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/StreamKitAuthClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Artemis.Core; 7 | using Newtonsoft.Json; 8 | 9 | namespace Artemis.Plugins.Modules.Discord.Authentication; 10 | 11 | public class StreamKitAuthClient : DiscordAuthClientBase 12 | { 13 | public StreamKitAuthClient(PluginSettings token) : base(token.GetSetting("DiscordAccessTokenStreamKit")) 14 | { 15 | } 16 | 17 | public override string ClientId => "207646673902501888"; 18 | public override string Origin => "https://streamkit.discord.com"; 19 | 20 | public override async Task GetAccessTokenAsync(string challengeCode) 21 | { 22 | var body = new StringContent(JsonConvert.SerializeObject(new { code = challengeCode }), Encoding.UTF8, "application/json"); 23 | 24 | using var response = await HttpClient.PostAsync("https://streamkit.discord.com/overlay/token", body); 25 | 26 | var responseString = await response.Content.ReadAsStringAsync(); 27 | 28 | if (!response.IsSuccessStatusCode) 29 | { 30 | throw new UnauthorizedAccessException(responseString); 31 | } 32 | 33 | var token = JsonConvert.DeserializeObject(responseString)!; 34 | SaveToken(token); 35 | 36 | return token; 37 | } 38 | 39 | // public override Task RefreshAccessTokenAsync() 40 | // { 41 | // // Streamkit tokens don't support refreshing, or at least I can't find anything about it 42 | // return Task.CompletedTask; 43 | // } 44 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPluginConfiguration/DiscordPluginConfigurationViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using Artemis.Core; 4 | using Artemis.UI.Shared; 5 | using ReactiveUI; 6 | using System.Reactive.Disposables; 7 | using System.Threading.Tasks; 8 | 9 | namespace Artemis.Plugins.Modules.Discord.DiscordPluginConfiguration; 10 | 11 | public class DiscordPluginConfigurationViewModel : PluginConfigurationViewModel 12 | { 13 | private readonly PluginSettings _pluginSettings; 14 | private readonly Plugin _plugin; 15 | 16 | public DiscordPluginConfigurationViewModel(Plugin plugin, PluginSettings pluginSettings) : base(plugin) 17 | { 18 | _pluginSettings = pluginSettings; 19 | _plugin = plugin; 20 | 21 | this.WhenActivated(d => 22 | { 23 | Disposable.Create(() => 24 | { 25 | Task.Run(async () => 26 | { 27 | _pluginSettings.SaveAllSettings(); 28 | 29 | var feature = _plugin.GetFeature(); 30 | 31 | feature?.DisconnectFromDiscord(); 32 | await Task.Delay(1000); 33 | feature?.ConnectToDiscord(); 34 | }); 35 | }).DisposeWith(d); 36 | }); 37 | } 38 | 39 | public PluginSetting ClientIdSetting => _pluginSettings.GetSetting("DiscordClientId", string.Empty); 40 | public PluginSetting ClientSecretSetting => _pluginSettings.GetSetting("DiscordClientSecret", string.Empty); 41 | public PluginSetting Provider => _pluginSettings.GetSetting("DiscordRpcProvider", DiscordRpcProvider.StreamKit); 42 | } 43 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/DiscordRequest.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.Modules.Discord.Enums; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | 7 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets; 8 | 9 | public class DiscordRequest 10 | { 11 | public Guid Nonce { get; } 12 | 13 | [JsonProperty("args")] 14 | public JObject Arguments { get; } 15 | 16 | [JsonProperty("cmd"), JsonConverter(typeof(StringEnumConverter))] 17 | public DiscordRpcCommand Command { get; } 18 | 19 | public DiscordRequest(DiscordRpcCommand command, params (string Key, object Value)[] parameters) 20 | { 21 | Nonce = Guid.NewGuid(); 22 | Command = command; 23 | Arguments = new JObject(); 24 | 25 | foreach (var (Key, Value) in parameters) 26 | { 27 | Arguments.Add(Key, JToken.FromObject(Value)); 28 | } 29 | } 30 | } 31 | 32 | public class DiscordSubscribe : DiscordRequest 33 | { 34 | [JsonProperty("evt"), JsonConverter(typeof(StringEnumConverter))] 35 | public DiscordRpcEvent Event { get; } 36 | 37 | public DiscordSubscribe(DiscordRpcEvent e, params (string Key, object Value)[] parameters) 38 | : base(DiscordRpcCommand.SUBSCRIBE, parameters) 39 | { 40 | Event = e; 41 | } 42 | } 43 | 44 | public class DiscordUnsubscribe : DiscordRequest 45 | { 46 | [JsonProperty("evt"), JsonConverter(typeof(StringEnumConverter))] 47 | public DiscordRpcEvent Event { get; } 48 | 49 | public DiscordUnsubscribe(DiscordRpcEvent e, params (string Key, object Value)[] parameters) 50 | : base(DiscordRpcCommand.UNSUBSCRIBE, parameters) 51 | { 52 | Event = e; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/SteelseriesAuthClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Artemis.Core; 5 | using Newtonsoft.Json; 6 | 7 | namespace Artemis.Plugins.Modules.Discord.Authentication; 8 | 9 | public class SteelseriesAuthClient : DiscordAuthClientBase 10 | { 11 | private const string TokenEndpoint = "https://id.steelseries.com/discord/auth"; 12 | 13 | public SteelseriesAuthClient(PluginSettings token) : base(token.GetSetting("DiscordAccessTokenSteelseries")) 14 | { 15 | } 16 | 17 | public override string ClientId => "211138759029293067"; 18 | public override string Origin => "https://steelseries.com"; 19 | 20 | public override async Task GetAccessTokenAsync(string challengeCode) 21 | { 22 | var values = new Dictionary 23 | { 24 | ["client_id"] = ClientId, 25 | ["grant_type"] = "authorization_code", 26 | ["code"] = challengeCode 27 | }; 28 | 29 | using var response = await HttpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(values)); 30 | var responseString = await response.Content.ReadAsStringAsync(); 31 | var token = JsonConvert.DeserializeObject(responseString); 32 | SaveToken(token); 33 | return token; 34 | } 35 | 36 | // public override async Task RefreshAccessTokenAsync() 37 | // { 38 | // var values = new Dictionary 39 | // { 40 | // ["client_id"] = ClientId, 41 | // ["grant_type"] = "refresh_token", 42 | // ["refresh_token"] = Token.Value.RefreshToken 43 | // }; 44 | // 45 | // using var response = await HttpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(values)); 46 | // var responseString = await response.Content.ReadAsStringAsync(); 47 | // var token = JsonConvert.DeserializeObject(responseString); 48 | // SaveToken(token); 49 | // } 50 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/ConfigurationDialog/SpotifyConfigurationDialogView.axaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 35 | 36 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/Prerequisites/VoiceMeeterInstalledPrerequisite.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Microsoft.Win32; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace Artemis.Plugins.Modules.VoiceMeeter.Prerequisites; 9 | 10 | public class VoiceMeeterInstalledPrerequisite : PluginPrerequisite 11 | { 12 | public override string Name => "VoiceMeeter Installed"; 13 | 14 | public override string Description => "Checks if VoiceMeeter is installed and its files can be found."; 15 | 16 | public override List InstallActions { get; } = new(); 17 | 18 | public override List UninstallActions { get; } = new(); 19 | 20 | public override bool IsMet() 21 | { 22 | if (!TryGetVoiceMeeterDllPath(out var path)) 23 | return false; 24 | 25 | if (NativeLibrary.Load(path) == IntPtr.Zero) 26 | return false; 27 | 28 | return true; 29 | } 30 | 31 | private static bool TryGetVoiceMeeterDllPath(out string path) 32 | { 33 | // Find current version from the registry 34 | const string INSTALLED_PROGRAMS = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\VB:Voicemeeter {17359A74-1236-5467}"; 35 | const string INSTALLED_PROGRAMS32 = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\VB:Voicemeeter {17359A74-1236-5467}"; 36 | const string UNINSTALL_KEY = "UninstallString"; 37 | 38 | path = string.Empty; 39 | 40 | using var voiceMeeterSubKey = Registry.LocalMachine.OpenSubKey(INSTALLED_PROGRAMS) ?? Registry.LocalMachine.OpenSubKey(INSTALLED_PROGRAMS32); 41 | if (voiceMeeterSubKey == null) 42 | return false; 43 | 44 | var voiceMeeterPath = voiceMeeterSubKey.GetValue(UNINSTALL_KEY)?.ToString(); 45 | if (string.IsNullOrWhiteSpace(voiceMeeterPath)) 46 | return false; 47 | 48 | path = Path.Combine(Path.GetDirectoryName(voiceMeeterPath)!, "VoicemeeterRemote64.dll"); 49 | return File.Exists(path); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/LogitechAuthClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Artemis.Core; 6 | using Newtonsoft.Json; 7 | 8 | namespace Artemis.Plugins.Modules.Discord.Authentication; 9 | 10 | public class LogitechAuthClient : DiscordAuthClientBase 11 | { 12 | private const string TokenEndpoint = "https://ymj1tb3arf.execute-api.us-east-1.amazonaws.com/prod/create_discord_access_token"; 13 | 14 | public LogitechAuthClient(PluginSettings token) : base(token.GetSetting("DiscordAccessTokenLogitech")) 15 | { 16 | } 17 | 18 | public override string ClientId => "227491271223017472"; 19 | public override string Origin => "http://localhost"; 20 | 21 | public override async Task GetAccessTokenAsync(string challengeCode) 22 | { 23 | var values = new Dictionary 24 | { 25 | ["code"] = challengeCode 26 | }; 27 | 28 | using var httpContent = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json"); 29 | using var response = await HttpClient.PostAsync(TokenEndpoint, httpContent); 30 | var responseString = await response.Content.ReadAsStringAsync(); 31 | var token = JsonConvert.DeserializeObject(responseString); 32 | SaveToken(token); 33 | return token; 34 | } 35 | 36 | // public override async Task RefreshAccessTokenAsync() 37 | // { 38 | // var values = new Dictionary 39 | // { 40 | // ["refresh_token"] = Token.Value.RefreshToken 41 | // }; 42 | // 43 | // using var httpContent = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json"); 44 | // using var response = await HttpClient.PostAsync(TokenEndpoint, httpContent); 45 | // var responseString = await response.Content.ReadAsStringAsync(); 46 | // var token = JsonConvert.DeserializeObject(responseString); 47 | // SaveToken(token); 48 | // } 49 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/RazerAuthClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Artemis.Core; 5 | using Newtonsoft.Json; 6 | 7 | namespace Artemis.Plugins.Modules.Discord.Authentication; 8 | 9 | public class RazerAuthClient : DiscordAuthClientBase 10 | { 11 | private const string RefreshEndpoint = "https://chroma.razer.com/discord/refreshtoken.php"; 12 | private const string GrantEndpoint = "https://chroma.razer.com/discord/grant.php"; 13 | private const string RedirectUri = "http://chroma.razer.com/discord/"; 14 | 15 | public RazerAuthClient(PluginSettings token) : base(token.GetSetting("DiscordAccessTokenRazer")) 16 | { 17 | } 18 | 19 | public override string ClientId => "331511201655685132"; 20 | public override string Origin => "http://chroma.razer.com"; 21 | 22 | public override async Task GetAccessTokenAsync(string challengeCode) 23 | { 24 | var values = new Dictionary 25 | { 26 | ["client_id"] = ClientId, 27 | ["grant_type"] = "authorization_code", 28 | ["code"] = challengeCode, 29 | ["redirect_uri"] = RedirectUri 30 | }; 31 | 32 | using var response = await HttpClient.PostAsync(GrantEndpoint, new FormUrlEncodedContent(values)); 33 | var responseString = await response.Content.ReadAsStringAsync(); 34 | var token = JsonConvert.DeserializeObject(responseString); 35 | SaveToken(token); 36 | return token; 37 | } 38 | 39 | // public override async Task RefreshAccessTokenAsync() 40 | // { 41 | // var values = new Dictionary 42 | // { 43 | // ["client_id"] = ClientId, 44 | // ["grant_type"] = "refresh_token", 45 | // ["refresh_token"] = Token.Value.RefreshToken, 46 | // ["redirect_uri"] = RedirectUri 47 | // }; 48 | // 49 | // using var response = await HttpClient.PostAsync(RefreshEndpoint, new FormUrlEncodedContent(values)); 50 | // var responseString = await response.Content.ReadAsStringAsync(); 51 | // var tkn = JsonConvert.DeserializeObject(responseString); 52 | // SaveToken(tkn); 53 | // } 54 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Nodes.Ping/PingNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.NetworkInformation; 3 | using System.Threading.Tasks; 4 | using System.Timers; 5 | using Artemis.Core; 6 | 7 | namespace Artemis.Plugins.Nodes.Ping; 8 | 9 | [Node("Ping", "Outputs the round-trip ping time for a given host", "Network", OutputType = typeof(string))] 10 | public class PingNode : Node 11 | { 12 | #region Constructors 13 | 14 | public PingNode() 15 | { 16 | Name = "Ping"; 17 | Host = CreateInputPin("Host"); 18 | Output = CreateOutputPin("Ping (ms)"); 19 | 20 | //update ping every 5 seconds 21 | _timer = new Timer(TimeSpan.FromSeconds(5)); 22 | _timer.Elapsed += async (_, _) => await UpdatePing(); 23 | _timer.Start(); 24 | } 25 | 26 | #endregion 27 | 28 | #region Properties & Fields 29 | 30 | private readonly Timer _timer; 31 | private DateTime _lastEvaluateRequest; 32 | 33 | public InputPin Host { get; } 34 | public OutputPin Output { get; } 35 | 36 | #endregion 37 | 38 | #region Methods 39 | 40 | public override void Evaluate() 41 | { 42 | //keep track of the last time the node requested an evaluation 43 | //so we can decide whether or not to update the ping 44 | _lastEvaluateRequest = DateTime.UtcNow; 45 | } 46 | 47 | #endregion 48 | 49 | /// 50 | /// Updates the ping value 51 | /// 52 | private async Task UpdatePing() 53 | { 54 | // If the last time the node requested an evaluation was more than 5 seconds ago, don't update the ping 55 | if (DateTime.UtcNow - _lastEvaluateRequest > TimeSpan.FromSeconds(5)) 56 | return; 57 | 58 | if (string.IsNullOrEmpty(Host.Value)) 59 | { 60 | Output.Value = -1; 61 | return; 62 | } 63 | 64 | try 65 | { 66 | System.Net.NetworkInformation.Ping ping = new(); 67 | PingReply reply = await ping.SendPingAsync(Host.Value); 68 | if (reply.Status == IPStatus.Success) 69 | { 70 | Output.Value = reply.RoundtripTime; 71 | return; 72 | } 73 | } 74 | catch 75 | { 76 | // ignored, let pings fail silently 77 | } 78 | 79 | Output.Value = -1; 80 | } 81 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPackets/DiscordEvent.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Plugins.Modules.Discord.DiscordPackets.CommandData; 2 | using Artemis.Plugins.Modules.Discord.Enums; 3 | using JsonSubTypes; 4 | using Newtonsoft.Json; 5 | using static JsonSubTypes.JsonSubtypes; 6 | 7 | namespace Artemis.Plugins.Modules.Discord.DiscordPackets; 8 | 9 | [JsonConverter(typeof(JsonSubtypes), "evt")] 10 | [KnownSubType(typeof(DiscordReadyEvent), DiscordRpcEvent.READY)] 11 | [KnownSubType(typeof(DiscordVoiceSettingsUpdateEvent), DiscordRpcEvent.VOICE_SETTINGS_UPDATE)] 12 | [KnownSubType(typeof(DiscordVoiceConnectionStatusEvent), DiscordRpcEvent.VOICE_CONNECTION_STATUS)] 13 | [KnownSubType(typeof(DiscordNotificationCreateEvent), DiscordRpcEvent.NOTIFICATION_CREATE)] 14 | [KnownSubType(typeof(DiscordSpeakingStopEvent), DiscordRpcEvent.SPEAKING_STOP)] 15 | [KnownSubType(typeof(DiscordSpeakingStartEvent), DiscordRpcEvent.SPEAKING_START)] 16 | [KnownSubType(typeof(DiscordVoiceChannelSelectEvent), DiscordRpcEvent.VOICE_CHANNEL_SELECT)] 17 | [KnownSubType(typeof(DiscordVoiceStateCreateEvent), DiscordRpcEvent.VOICE_STATE_CREATE)] 18 | [KnownSubType(typeof(DiscordVoiceStateUpdateEvent), DiscordRpcEvent.VOICE_STATE_UPDATE)] 19 | [KnownSubType(typeof(DiscordVoiceStateDeleteEvent), DiscordRpcEvent.VOICE_STATE_DELETE)] 20 | public class DiscordEvent : IDiscordMessage 21 | { 22 | [JsonProperty("evt")] 23 | public DiscordRpcEvent Event { get; set; } 24 | } 25 | 26 | public abstract class DiscordEvent : DiscordEvent 27 | { 28 | #pragma warning disable CS8618 29 | public T Data { get; set; } 30 | #pragma warning restore CS8618 31 | } 32 | 33 | public sealed class DiscordReadyEvent : DiscordEvent { } 34 | public sealed class DiscordVoiceSettingsUpdateEvent : DiscordEvent { } 35 | public sealed class DiscordVoiceConnectionStatusEvent : DiscordEvent { } 36 | public sealed class DiscordNotificationCreateEvent : DiscordEvent { } 37 | public sealed class DiscordSpeakingStartEvent : DiscordEvent { } 38 | public sealed class DiscordSpeakingStopEvent : DiscordEvent { } 39 | public sealed class DiscordVoiceChannelSelectEvent : DiscordEvent { } 40 | public sealed class DiscordVoiceStateCreateEvent : DiscordEvent { } 41 | public sealed class DiscordVoiceStateUpdateEvent : DiscordEvent { } 42 | public sealed class DiscordVoiceStateDeleteEvent : DiscordEvent { } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/VoiceMeeterRemote.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace Artemis.Plugins.Modules.VoiceMeeter; 5 | 6 | public enum VoiceMeeterLoginResponse 7 | { 8 | OK = 0, 9 | OkVoicemeeterNotRunning = 1, 10 | NoClient = -1, 11 | AlreadyLoggedIn = -2, 12 | } 13 | 14 | public enum VoiceMeeterType 15 | { 16 | VoiceMeeter = 1, 17 | VoiceMeeterBanana = 2, 18 | VoiceMeeterPotato = 3 19 | } 20 | 21 | internal static class VoiceMeeterRemote 22 | { 23 | private const string DLL = "VoicemeeterRemote64.dll"; 24 | 25 | [DllImport(DLL, EntryPoint = "VBVMR_Login", CallingConvention = CallingConvention.StdCall)] 26 | public static extern VoiceMeeterLoginResponse Login(); 27 | 28 | [DllImport(DLL, EntryPoint = "VBVMR_Logout", CallingConvention = CallingConvention.StdCall)] 29 | public static extern VoiceMeeterLoginResponse Logout(); 30 | 31 | [DllImport(DLL, EntryPoint = "VBVMR_GetVoicemeeterType", CallingConvention = CallingConvention.StdCall)] 32 | public static extern int GetVoicemeeterType(out VoiceMeeterType type); 33 | 34 | [DllImport(DLL, EntryPoint = "VBVMR_GetVoicemeeterVersion", CallingConvention = CallingConvention.StdCall)] 35 | public static extern int GetVoicemeeterVersion(out int value); 36 | 37 | 38 | [DllImport(DLL, EntryPoint = "VBVMR_IsParametersDirty", CallingConvention = CallingConvention.StdCall)] 39 | public static extern int IsParametersDirty(); 40 | 41 | [DllImport(DLL, EntryPoint = "VBVMR_GetParameterFloat", CallingConvention = CallingConvention.StdCall)] 42 | private static extern int GetParameter([MarshalAs(UnmanagedType.LPStr)] string szParamName, out float value); 43 | 44 | [DllImport(DLL, EntryPoint = "VBVMR_GetParameterStringW", CallingConvention = CallingConvention.StdCall)] 45 | private static extern int GetParameter([MarshalAs(UnmanagedType.LPStr)] string szParamName, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder value); 46 | 47 | [DllImport(DLL, EntryPoint = "VBVMR_GetLevel", CallingConvention = CallingConvention.StdCall)] 48 | public static extern int GetLevel(int nType, int nuChannel, out float value); 49 | 50 | public static float GetFloat(string parameter) 51 | { 52 | var ret = GetParameter(parameter, out float value); 53 | //if (ret != 0) 54 | // throw new Exception(); 55 | 56 | return value; 57 | } 58 | 59 | public static string GetString(string parameter) 60 | { 61 | var sb = new StringBuilder(); 62 | var ret = GetParameter(parameter, sb); 63 | //if (ret != 0) 64 | // throw new Exception(); 65 | 66 | return sb.ToString(); 67 | } 68 | 69 | public static bool GetBool(string parameter) => GetFloat(parameter) == 1; 70 | 71 | internal static int GetInt(string parameter) => (int)GetFloat(parameter); 72 | } 73 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/LayerBrushes/ChromaLayerBrush.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Artemis.Core; 3 | using Artemis.Core.LayerBrushes; 4 | using Artemis.Plugins.LayerBrushes.Chroma.Services; 5 | using Artemis.Plugins.LayerBrushes.Chroma.LayerBrushes.PropertyGroups; 6 | using RGB.NET.Core; 7 | using SkiaSharp; 8 | using System.Collections.Generic; 9 | 10 | namespace Artemis.Plugins.LayerBrushes.Chroma.LayerBrushes; 11 | 12 | public class ChromaLayerBrush : PerLedLayerBrush 13 | { 14 | private readonly ChromaService _chroma; 15 | private readonly Dictionary _colors; 16 | private readonly object _lock; 17 | private bool _shouldRender; 18 | private bool _playingOverwatch; 19 | 20 | public ChromaLayerBrush(ChromaService chroma) 21 | { 22 | _chroma = chroma; 23 | _colors = new(); 24 | _lock = new(); 25 | } 26 | 27 | public override void EnableLayerBrush() 28 | { 29 | _chroma.MatrixUpdated += OnMatrixUpdated; 30 | _chroma.AppListUpdated += OnAppListUpdated; 31 | OnAppListUpdated(this, EventArgs.Empty); 32 | } 33 | 34 | private void OnAppListUpdated(object? sender, EventArgs e) 35 | { 36 | _shouldRender = _chroma.IsActive; 37 | _playingOverwatch = _chroma.CurrentApp == "Overwatch.exe"; 38 | } 39 | 40 | public override void DisableLayerBrush() 41 | { 42 | _chroma.MatrixUpdated -= OnMatrixUpdated; 43 | _chroma.AppListUpdated -= OnAppListUpdated; 44 | } 45 | 46 | private void OnMatrixUpdated(object? sender, MatrixUpdatedEventArgs e) 47 | { 48 | var (deviceType, matrix) = e; 49 | var dict = DefaultChromaLedMap.GetDeviceMap(deviceType); 50 | 51 | lock (_lock) 52 | { 53 | for (var i = 0; i < matrix.Length; i++) 54 | { 55 | _colors[dict[i]] = matrix[i]; 56 | } 57 | } 58 | } 59 | 60 | public override void Update(double deltaTime) 61 | { 62 | } 63 | 64 | public override SKColor GetColor(ArtemisLed led, SKPoint renderPoint) 65 | { 66 | if (!_shouldRender) 67 | return SKColor.Empty; 68 | 69 | lock (_lock) 70 | { 71 | if (_colors.TryGetValue(led.RgbLed.Id, out var color)) 72 | return ProcessColor(color); 73 | 74 | //According to razer docs, chromaLink1 is the "catchall" ledId. If an LED doesn't have a mapping, use this color. 75 | if (Properties.UseDefaultLed && _colors.TryGetValue(LedId.LedStripe1, out var chromaLink1)) 76 | return ProcessColor(chromaLink1); 77 | } 78 | 79 | return SKColor.Empty; 80 | } 81 | 82 | private SKColor ProcessColor(SKColor color) 83 | { 84 | if (Properties.TransparentBlack && color == SKColors.Black) 85 | return SKColor.Empty; 86 | 87 | if (_playingOverwatch && Properties.OverwatchEnhanceColors && OverwatchColorCorrection.ColorMap.TryGetValue(color, out var correctedColor)) 88 | return correctedColor; 89 | 90 | return color; 91 | } 92 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.KeyboardLayout/KeyboardLayoutModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using Artemis.Core.Modules; 6 | 7 | namespace Artemis.Plugins.Modules.KeyboardLayout; 8 | 9 | public class KeyboardLayoutModule : Module 10 | { 11 | public override List? ActivationRequirements { get; } = new(); 12 | 13 | public override void Enable() 14 | { 15 | AddTimedUpdate(TimeSpan.FromMilliseconds(300), UpdateKeyboardLayout); 16 | } 17 | 18 | public override void Disable() 19 | { 20 | } 21 | 22 | public override void Update(double deltaTime) 23 | { 24 | } 25 | 26 | private const int KL_NAMELENGTH = 32; 27 | 28 | [DllImport("user32.dll", EntryPoint = "GetKeyboardLayoutNameA", CharSet = CharSet.Ansi)] 29 | private static extern bool GetKeyboardLayoutNameA([Out] StringBuilder pwszKLID); 30 | 31 | [DllImport("user32.dll")] 32 | private static extern nint GetKeyboardLayout(uint idThread); 33 | 34 | [DllImport("user32.dll")] 35 | private static extern nint GetForegroundWindow(); 36 | 37 | [DllImport("user32.dll")] 38 | private static extern uint GetWindowThreadProcessId(nint hWnd, nint ProcessId); 39 | 40 | [DllImport("user32.dll")] 41 | private static extern int ActivateKeyboardLayout(uint HKL, int flags); 42 | 43 | public void UpdateKeyboardLayout(double deltaTime) 44 | { 45 | var hkl = GetHkl(); 46 | var lowWord = (ushort)hkl; 47 | var highWord = (ushort)(hkl >> 16); 48 | var klid = GetKlidFromHkl(hkl); 49 | var name = GetNameFromKlid(klid); 50 | 51 | DataModel.Hkl = hkl; 52 | DataModel.Klid = klid; 53 | DataModel.Name = name; 54 | DataModel.HklLowWord = lowWord; 55 | DataModel.HklHighWord = highWord; 56 | } 57 | 58 | private static uint GetHkl() 59 | { 60 | return (uint)GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nint.Zero)); 61 | } 62 | 63 | private readonly Dictionary _klidToName = new(); 64 | public string GetNameFromKlid(string klid) 65 | { 66 | if (_klidToName.TryGetValue(klid, out var x)) 67 | return x; 68 | 69 | using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Keyboard Layouts\" + klid); 70 | var r = key?.GetValue("Layout Text") as string ?? klid; 71 | _klidToName.Add(klid, r); 72 | return r; 73 | } 74 | 75 | private readonly Dictionary _hklToKlid = new(); 76 | public string GetKlidFromHkl(uint hkl) 77 | { 78 | if (_hklToKlid.TryGetValue(hkl.ToString(), out var x)) 79 | return x; 80 | 81 | var previousLayout = ActivateKeyboardLayout(hkl, 100); 82 | if (previousLayout == 0) 83 | return string.Empty; 84 | 85 | StringBuilder sb = new(KL_NAMELENGTH); 86 | GetKeyboardLayoutNameA(sb); 87 | _hklToKlid.Add(hkl.ToString(), sb.ToString()); 88 | return sb.ToString(); 89 | } 90 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Transport/DiscordWebSocketTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Artemis.Plugins.Modules.Discord.Enums; 8 | 9 | namespace Artemis.Plugins.Modules.Discord.Transport; 10 | 11 | public sealed class DiscordWebSocketTransport : IDiscordTransport 12 | { 13 | private const string WebsocketUri = "ws://localhost:6463"; 14 | 15 | private readonly ClientWebSocket _webSocket; 16 | private readonly string _clientId; 17 | private readonly string _origin; 18 | 19 | public bool IsConnected => _webSocket.State == WebSocketState.Open; 20 | 21 | public DiscordWebSocketTransport(string clientId, string origin) 22 | { 23 | _webSocket = new ClientWebSocket(); 24 | _clientId = clientId; 25 | _origin = origin; 26 | } 27 | 28 | public async Task Connect(CancellationToken cancellationToken = default) 29 | { 30 | _webSocket.Options.SetRequestHeader("Origin", _origin); 31 | await _webSocket.ConnectAsync(new Uri($"{WebsocketUri}?v=1&client_id={_clientId}"), cancellationToken); 32 | } 33 | 34 | public async Task SendPacketAsync(string stringData, RpcPacketType rpcPacketType, CancellationToken cancellationToken = default) 35 | { 36 | var length = Encoding.UTF8.GetByteCount(stringData); 37 | var rent = ArrayPool.Shared.Rent(length); 38 | Encoding.UTF8.GetBytes(stringData, 0, stringData.Length, rent, 0); 39 | 40 | try 41 | { 42 | await _webSocket.SendAsync(rent.AsMemory(0, length), WebSocketMessageType.Text, true, cancellationToken); 43 | } 44 | finally 45 | { 46 | ArrayPool.Shared.Return(rent); 47 | } 48 | } 49 | 50 | public async Task<(RpcPacketType, string)> ReadMessageAsync(CancellationToken cancellationToken = default) 51 | { 52 | //this is a large enough initial chunk to hold most messages 53 | var buffer = ArrayPool.Shared.Rent(2048); 54 | var read = 0; 55 | try 56 | { 57 | ValueWebSocketReceiveResult result; 58 | do 59 | { 60 | result = await _webSocket.ReceiveAsync(buffer.AsMemory(read), cancellationToken); 61 | read += result.Count; 62 | 63 | //if we need more space, double the size 64 | if (read == buffer.Length) 65 | { 66 | var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); 67 | buffer.CopyTo(newBuffer, 0); 68 | ArrayPool.Shared.Return(buffer); 69 | 70 | buffer = newBuffer; 71 | } 72 | } while (!result.EndOfMessage); 73 | 74 | var packetData = Encoding.UTF8.GetString(buffer, 0, read); 75 | 76 | return (RpcPacketType.FRAME, packetData); 77 | } 78 | finally 79 | { 80 | ArrayPool.Shared.Return(buffer); 81 | } 82 | } 83 | 84 | public void Dispose() 85 | { 86 | _webSocket.Dispose(); 87 | } 88 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Scripts/download-razer-chroma-sdk.ps1: -------------------------------------------------------------------------------- 1 | function Get-DownloadUrl { 2 | $env = "prod" 3 | $discoveryUrl = "https://discovery.razerapi.com/user/endpoints" 4 | $discoveryResponse = Invoke-WebRequest -Uri $discoveryUrl -UseBasicParsing 5 | $discoveryJson = $discoveryResponse.Content | ConvertFrom-Json 6 | $prodEndpoint = $null 7 | 8 | foreach ($endpoint in $discoveryJson.endpoints) { 9 | if ($endpoint.name -eq "prod") { 10 | $prodEndpoint = $endpoint 11 | break 12 | } 13 | } 14 | 15 | #if url is null, then the env is not found 16 | if ($null -eq $prodEndpoint) { 17 | Write-Error "Environment not found" 18 | } 19 | 20 | $body = @" 21 | 22 | 23 | 64 24 | en 25 | Generic-MFR 26 | Generic-MDL 27 | Windows 28 | 10 29 | Generic-SKU 30 | 31 | 32 | "@ 33 | $productListUrl = "https://manifest.razerapi.com/api/legacy/$($prodEndpoint.hash)/$env/productlist/get" 34 | #body 35 | $productListResponse = Invoke-WebRequest -Method "POST" -Uri $productListUrl -Body $body -ContentType "application/xml" 36 | 37 | #load response into xml 38 | $productListXml = [xml]$productListResponse.Content 39 | #select nodes '//Module' 40 | $modules = $productListXml.SelectNodes("//Module") 41 | #iterate, find "ChromaBroadcast" module 42 | foreach ($module in $modules) { 43 | if ($module.Name -eq "CHROMABROADCASTER") { 44 | return $module.DownloadURL 45 | } 46 | } 47 | return "" 48 | } 49 | 50 | $projectDir = $args[0] 51 | $tempDir = "$projectDir\temp" 52 | $targetFileName = "$projectDir\Resources\RazerChromaSdkCoreSetup.exe" 53 | 54 | #if file exists, do nothing 55 | if (Test-Path $targetFileName) { 56 | return 57 | } 58 | 59 | #check if 7z is installed 60 | if (!(Get-Command "7z" -ErrorAction SilentlyContinue)) { 61 | Write-Error "7z is not installed" 62 | return 63 | } 64 | 65 | $url = Get-DownloadUrl 66 | #get file name from url 67 | $downloadedFile = $url.Split("/")[-1] 68 | 69 | #download if file does not exist 70 | if (!(Test-Path $downloadedFile)) { 71 | Invoke-WebRequest -Uri $url -OutFile $downloadedFile 72 | } 73 | 74 | #use 7zip to extract 75 | "7z e $downloadedFile -o$tempDir -aoa" | Invoke-Expression 76 | 77 | #find in out folder file that starts with "Razer_Chroma_SDK_Core_" 78 | $files = Get-ChildItem -Path $tempDir -Filter "Razer_Chroma_SDK_Core_*" 79 | 80 | if ($files.Count -ne 1) { 81 | Write-Error "Could not find file in out folder" 82 | return 83 | } 84 | 85 | #create Resources folder if it does not exist 86 | if (!(Test-Path "$projectDir/Resources")) { 87 | New-Item -ItemType Directory -Path "$projectDir\Resources" 88 | } 89 | 90 | #copy file to current directory 91 | Copy-Item -Path "$tempDir\$($files[0].Name)" -Destination $targetFileName -Recurse -Force 92 | 93 | #remove out folder 94 | Remove-Item -Path $tempDir -Recurse 95 | 96 | #remove 7z file 97 | Remove-Item -Path $downloadedFile -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Module/ChromaModule.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.Modules; 3 | using Artemis.Plugins.LayerBrushes.Chroma.Services; 4 | using RGB.NET.Core; 5 | using Serilog; 6 | using SkiaSharp; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Artemis.Plugins.LayerBrushes.Chroma.Module; 11 | 12 | [PluginFeature(Name = "Chroma")] 13 | public class ChromaModule : Module 14 | { 15 | public override List ActivationRequirements { get; } = []; 16 | 17 | private readonly ILogger _logger; 18 | private readonly ChromaService _chroma; 19 | private readonly ChromaRegistryService _registry; 20 | private readonly Dictionary> _colorsCache = new(); 21 | 22 | public ChromaModule(ChromaService chroma, ChromaRegistryService registry, ILogger logger) 23 | { 24 | _chroma = chroma; 25 | _registry = registry; 26 | _logger = logger; 27 | } 28 | 29 | public override void Enable() 30 | { 31 | DataModel.Service = _chroma; 32 | 33 | CreateStructure(); 34 | 35 | _chroma.MatrixUpdated += OnMatrixUpdated; 36 | 37 | try 38 | { 39 | DataModel.PriorityList = _registry.GetRazerSdkInfo().PriorityList; 40 | var artemis = DataModel.PriorityList.IndexOf("Artemis.UI.Windows.exe"); 41 | if (artemis != -1 && artemis != DataModel.PriorityList.Length - 1) 42 | { 43 | _logger.Error("Artemis is not the last item in the Razer Chroma SDK priority list, the Chroma grabber may not work correctly."); 44 | } 45 | } 46 | catch (Exception e) 47 | { 48 | _logger.Error(e, "Error setting priority list."); 49 | DataModel.PriorityList = []; 50 | } 51 | 52 | AddDefaultProfile(DefaultCategoryName.Games, Plugin.ResolveRelativePath("profile.zip")); 53 | } 54 | 55 | public override void Disable() 56 | { 57 | _chroma.MatrixUpdated -= OnMatrixUpdated; 58 | } 59 | 60 | public override void Update(double deltaTime) 61 | { 62 | } 63 | 64 | private void OnMatrixUpdated(object? sender, MatrixUpdatedEventArgs e) 65 | { 66 | var (deviceType, colors) = e; 67 | 68 | var map = DefaultChromaLedMap.GetDeviceMap(deviceType); 69 | 70 | for (var i = 0; i < colors.Length; i++) 71 | { 72 | var ledId = map[i]; 73 | if (ledId == LedId.Invalid) 74 | continue; 75 | 76 | _colorsCache[ledId].Value = colors[i]; 77 | } 78 | } 79 | 80 | private void CreateStructure() 81 | { 82 | DataModel.ClearDynamicChildren(); 83 | foreach (var rzDeviceType in Enum.GetValues()) 84 | { 85 | var deviceDataModel = DataModel.AddDynamicChild(rzDeviceType.ToStringFast(), new ChromaDeviceDataModel()); 86 | 87 | var map = DefaultChromaLedMap.GetDeviceMap(rzDeviceType); 88 | foreach (var ledId in map) 89 | { 90 | if (ledId == LedId.Invalid) 91 | continue; 92 | 93 | _colorsCache.Add(ledId, deviceDataModel.Value.AddDynamicChild(ledId.ToString(), default)); 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Gif/GifLayerBrush.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.LayerBrushes; 2 | using Artemis.Plugins.LayerBrushes.Gif.PropertyGroups; 3 | using SkiaSharp; 4 | using System.IO; 5 | 6 | namespace Artemis.Plugins.LayerBrushes.Gif; 7 | 8 | public class GifLayerBrush : LayerBrush 9 | { 10 | private readonly object myLock = new(); 11 | private int frameCount; 12 | private int currentFrame; 13 | private int[]? durations; 14 | private int elapsed; 15 | private SKBitmap[]? originals; 16 | 17 | public override void EnableLayerBrush() 18 | { 19 | Properties.FileName.PropertyChanged += OnFileNamePropertyChanged; 20 | LoadGifData(); 21 | } 22 | 23 | private void OnFileNamePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) 24 | { 25 | LoadGifData(); 26 | } 27 | 28 | private void LoadGifData() 29 | { 30 | if (!File.Exists(Properties.FileName.BaseValue)) 31 | return; 32 | 33 | lock (myLock) 34 | { 35 | using SKCodec codec = SKCodec.Create(Properties.FileName.BaseValue); 36 | 37 | SKImageInfo info = new SKImageInfo(codec.Info.Width, codec.Info.Height, SKImageInfo.PlatformColorType, SKAlphaType.Premul); 38 | 39 | frameCount = codec.FrameCount; 40 | currentFrame = 0; 41 | originals = new SKBitmap[frameCount]; 42 | durations = new int[frameCount]; 43 | 44 | for (int i = 0; i < frameCount; i++) 45 | { 46 | durations[i] = codec.FrameInfo[i].Duration; 47 | originals[i] = new SKBitmap(new SKImageInfo(codec.Info.Width, codec.Info.Height)); 48 | codec.GetPixels(info, originals[i].GetPixels(), new SKCodecOptions(i)); 49 | } 50 | } 51 | } 52 | 53 | public override void DisableLayerBrush() 54 | { 55 | if (originals != null) 56 | { 57 | foreach (SKBitmap bm in originals) 58 | bm?.Dispose(); 59 | } 60 | 61 | Properties.FileName.PropertyChanged -= OnFileNamePropertyChanged; 62 | } 63 | 64 | public override void Update(double deltaTime) 65 | { 66 | if (deltaTime < 0) 67 | deltaTime = 0; 68 | 69 | if (durations is null) 70 | { 71 | LoadGifData(); 72 | return; 73 | } 74 | 75 | if (elapsed > durations[currentFrame]) 76 | { 77 | currentFrame++; 78 | elapsed = 0; 79 | } 80 | else 81 | { 82 | elapsed += (int)(deltaTime * 1000); 83 | } 84 | 85 | if (currentFrame == frameCount) 86 | currentFrame = 0; 87 | } 88 | 89 | public override void Render(SKCanvas canvas, SKRect bounds, SKPaint paint) 90 | { 91 | if (originals is null) 92 | { 93 | LoadGifData(); 94 | return; 95 | } 96 | 97 | if (bounds.Width == 0 || bounds.Height == 0) 98 | return; 99 | 100 | lock (myLock) 101 | { 102 | var bitmap = originals[currentFrame]; 103 | canvas.DrawBitmap(bitmap, new SKRect(0, 0, bitmap.Width, bitmap.Height), bounds); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Services/ChromaRegistryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Artemis.Core; 4 | using Artemis.Core.Services; 5 | using Microsoft.Win32; 6 | using Serilog; 7 | 8 | namespace Artemis.Plugins.LayerBrushes.Chroma.Services; 9 | 10 | /// 11 | /// Service that handles data stored in the registry. 12 | /// 13 | public sealed class ChromaRegistryService : IPluginService, IDisposable 14 | { 15 | private readonly ILogger _logger; 16 | 17 | private readonly RegistryKey _razerSdkKey; 18 | private readonly RegistryKey _appsSdkKey; 19 | 20 | public ChromaRegistryService(ILogger logger) 21 | { 22 | _logger = logger; 23 | 24 | using var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); 25 | _razerSdkKey = localMachine.OpenSubKey(@"Software\Razer Chroma SDK") ?? throw new ArtemisPluginException("Razer Chroma SDK not installed"); 26 | _appsSdkKey = _razerSdkKey.OpenSubKey("Apps") ?? throw new ArtemisPluginException("Razer Chroma SDK not installed"); 27 | } 28 | 29 | public void SetPriorityList(string[] priorityList) 30 | { 31 | var priorityListString = string.Join(";", priorityList); 32 | _appsSdkKey.SetValue("PriorityList", priorityListString); 33 | } 34 | 35 | public RazerSdkInfo GetRazerSdkInfo() 36 | { 37 | var version = _razerSdkKey.GetValue("ProductVersion")?.ToString() 38 | ?? throw new ArtemisPluginException("Could not find version in Razer Chroma SDK registry key"); 39 | var priorityList = _appsSdkKey.GetValue("PriorityList")?.ToString()?.Split(';') 40 | ?? throw new ArtemisPluginException("Could not find priority list in Razer Chroma SDK registry key"); 41 | var enabledKey = _appsSdkKey.GetValue("Enable") as int? 42 | ?? throw new ArtemisPluginException("Could not find enabled in Razer Chroma SDK registry key"); 43 | var enabled = enabledKey == 1; 44 | 45 | return new RazerSdkInfo(version, enabled, priorityList); 46 | } 47 | 48 | public void EnsureValidPriorityList() 49 | { 50 | _logger.Verbose("Ensuring valid priority list"); 51 | var priorityList = GetRazerSdkInfo().PriorityList; 52 | var artemisIndex = priorityList.IndexOf("Artemis.UI.Windows.exe"); 53 | 54 | if (artemisIndex == -1) 55 | { 56 | _logger.Verbose("Artemis not in priority list, nothing to do"); 57 | return; 58 | } 59 | 60 | if (artemisIndex == priorityList.Length - 1) 61 | { 62 | _logger.Verbose("Artemis last in priority list, nothing to do"); 63 | return; 64 | } 65 | 66 | _logger.Verbose("Artemis not last in priority list, moving it to the end"); 67 | // if artemis is not last, we need to move it to the end 68 | var newPriorityList = priorityList.Where((_, i) => i != artemisIndex).Append("Artemis.UI.Windows.exe").ToArray(); 69 | 70 | _logger.Verbose("Setting new priority list: {newPriorityList}", newPriorityList); 71 | SetPriorityList(newPriorityList); 72 | } 73 | 74 | public void Dispose() 75 | { 76 | _razerSdkKey.Dispose(); 77 | _appsSdkKey.Dispose(); 78 | } 79 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Authentication/DiscordAuthClient.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | namespace Artemis.Plugins.Modules.Discord.Authentication; 10 | 11 | public class DiscordAuthClient : DiscordAuthClientBase 12 | { 13 | public override string ClientId { get; } 14 | public override string Origin => throw new NotSupportedException("Discord does not support a custom origin"); 15 | private readonly string _clientSecret; 16 | 17 | public DiscordAuthClient(PluginSettings settings) : base(settings.GetSetting("DiscordAccessToken")) 18 | { 19 | var clientIdSetting = settings.GetSetting("DiscordClientId"); 20 | var clientSecretSetting = settings.GetSetting("DiscordClientSecret"); 21 | 22 | if (!AreClientIdAndSecretValid(clientIdSetting, clientSecretSetting)) 23 | throw new InvalidOperationException("Invalid client id or secret. Please check your settings."); 24 | 25 | ClientId = clientIdSetting.Value!; 26 | _clientSecret = clientSecretSetting.Value!; 27 | } 28 | 29 | private static bool AreClientIdAndSecretValid(PluginSetting clientId, PluginSetting clientSecret) 30 | { 31 | return clientId.Value?.All(char.IsDigit) == true && clientSecret.Value?.Length > 0; 32 | } 33 | 34 | public override async Task GetAccessTokenAsync(string challengeCode) 35 | { 36 | Dictionary values = new() 37 | { 38 | ["grant_type"] = "authorization_code", 39 | ["code"] = challengeCode, 40 | ["client_id"] = ClientId, 41 | ["client_secret"] = _clientSecret 42 | }; 43 | 44 | using var response = await HttpClient.PostAsync("https://discord.com/api/oauth2/token", new FormUrlEncodedContent(values)); 45 | var responseString = await response.Content.ReadAsStringAsync(); 46 | if (!response.IsSuccessStatusCode) 47 | { 48 | throw new UnauthorizedAccessException(responseString); 49 | } 50 | 51 | var token = JsonConvert.DeserializeObject(responseString)!; 52 | SaveToken(token); 53 | return token; 54 | } 55 | 56 | // public override async Task RefreshAccessTokenAsync() 57 | // { 58 | // if (!HasToken) 59 | // throw new InvalidOperationException("No token to refresh"); 60 | // 61 | // Dictionary values = new() 62 | // { 63 | // ["grant_type"] = "refresh_token", 64 | // ["refresh_token"] = Token.Value!.RefreshToken, 65 | // ["client_id"] = ClientId, 66 | // ["client_secret"] = _clientSecret 67 | // }; 68 | // 69 | // using var response = await HttpClient.PostAsync("https://discord.com/api/oauth2/token", new FormUrlEncodedContent(values)); 70 | // var responseString = await response.Content.ReadAsStringAsync(); 71 | // if (!response.IsSuccessStatusCode) 72 | // { 73 | // throw new UnauthorizedAccessException(responseString); 74 | // } 75 | // 76 | // var token = JsonConvert.DeserializeObject(responseString)!; 77 | // SaveToken(token); 78 | // } 79 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.OBS/ObsModule.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.Modules; 3 | using Artemis.Plugins.Modules.OBS.DataModels; 4 | using OBSWebsocketDotNet; 5 | using System; 6 | using System.Collections.Generic; 7 | using Serilog; 8 | 9 | namespace Artemis.Plugins.Modules.OBS; 10 | 11 | [PluginFeature(AlwaysEnabled = true, Name = "OBS")] 12 | public class ObsModule : Module 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public override List ActivationRequirements { get; } = 17 | [new ProcessActivationRequirement(PROCESS_NAME)]; 18 | 19 | private const string OBS_URI = "ws://127.0.0.1:4455"; 20 | private const string OBS_PASSWORD = ""; 21 | private const string PROCESS_NAME = "obs64"; 22 | 23 | private OBSWebsocket? _obs; 24 | 25 | public ObsModule(ILogger logger) 26 | { 27 | _logger = logger; 28 | } 29 | 30 | public override void ModuleActivated(bool isOverride) 31 | { 32 | Connect(); 33 | } 34 | 35 | public override void ModuleDeactivated(bool isOverride) 36 | { 37 | Disconnect(); 38 | DataModel.Reset(); 39 | } 40 | 41 | public override void Enable() 42 | { 43 | } 44 | 45 | public override void Disable() 46 | { 47 | } 48 | 49 | public override void Update(double deltaTime) 50 | { 51 | } 52 | 53 | private void Connect() 54 | { 55 | _obs = new OBSWebsocket(); 56 | _obs.WSTimeout = TimeSpan.FromSeconds(2); 57 | _obs.Connected += OnObsConnected; 58 | 59 | try 60 | { 61 | _obs.ConnectAsync(OBS_URI, OBS_PASSWORD); 62 | } 63 | catch (Exception e) 64 | { 65 | _logger.Error(e, "Failed to connect to OBS"); 66 | DataModel.IsConnected = false; 67 | } 68 | } 69 | 70 | private void Disconnect() 71 | { 72 | if (_obs != null) 73 | { 74 | Unsubscribe(); 75 | _obs.Connected -= OnObsConnected; 76 | _obs.Disconnect(); 77 | } 78 | } 79 | 80 | private void OnObsConnected(object? sender, EventArgs e) 81 | { 82 | if (_obs is null) 83 | return; 84 | 85 | Subscribe(); 86 | DataModel.IsConnected = true; 87 | 88 | AddTimedUpdate(TimeSpan.FromSeconds(1), GetInitialData); 89 | } 90 | 91 | private void GetInitialData(double deltaTime) 92 | { 93 | if (_obs is null) 94 | return; 95 | 96 | try 97 | { 98 | DataModel.Stats = _obs.GetStats(); 99 | DataModel.StreamingStatus = _obs.GetStreamStatus(); 100 | DataModel.RecordingStatus = _obs.GetRecordStatus(); 101 | DataModel.ProfileListInfo = _obs.GetProfileList(); 102 | DataModel.SceneListInfo = _obs.GetSceneList(); 103 | } 104 | catch (Exception e) 105 | { 106 | _logger.Error(e, "Failed to get initial data from OBS"); 107 | DataModel.Reset(); 108 | } 109 | } 110 | 111 | private void Subscribe() 112 | { 113 | if (_obs is null) 114 | return; 115 | 116 | //TODO 117 | } 118 | 119 | private void Unsubscribe() 120 | { 121 | if (_obs is null) 122 | return; 123 | 124 | //TODO 125 | } 126 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Prerequisites/DownloadAndInstallChromaSdkAction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Xml; 9 | using Artemis.Core; 10 | 11 | namespace Artemis.Plugins.LayerBrushes.Chroma.Prerequisites; 12 | 13 | public class DownloadAndInstallChromaSdkAction : PluginPrerequisiteAction 14 | { 15 | public DownloadAndInstallChromaSdkAction() : base("Install Chroma SDK") 16 | { 17 | } 18 | 19 | public override async Task Execute(CancellationToken cancellationToken) 20 | { 21 | using var httpClient = new HttpClient(); 22 | var downloadLink = await GetDownloadUrlAsync(httpClient); 23 | var tempFolder = Path.GetTempPath(); 24 | var tempPath = Path.Combine(tempFolder, "RazerChromaSdkInstaller.exe"); 25 | 26 | using (var response = await httpClient.GetStreamAsync(downloadLink, cancellationToken)) 27 | { 28 | await using (var fileStream = new FileStream(tempPath, FileMode.Create)) 29 | { 30 | await response.CopyToAsync(fileStream, cancellationToken); 31 | } 32 | } 33 | 34 | var childAction = new ExecuteFileAction("Install Chroma SDK", tempPath, elevate: true, arguments: "/S", waitForExit: true); 35 | await childAction.Execute(cancellationToken); 36 | 37 | File.Delete(tempPath); 38 | } 39 | 40 | private async Task GetDownloadUrlAsync(HttpClient httpClient) 41 | { 42 | //a bunch of nullable overrides here but i don't care 43 | const string ENDPOINT = "prod"; 44 | var endpointsJson = await httpClient.GetStringAsync("https://discovery.razerapi.com/user/endpoints"); 45 | var endpoints = JsonSerializer.Deserialize(endpointsJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); 46 | var prodEndpoint = endpoints!.endpoints.Find(ep => ep.name == ENDPOINT); 47 | 48 | const string PLATFORM_DATA = """ 49 | 50 | 51 | 64 52 | en 53 | Generic-MFR 54 | Generic-MDL 55 | Windows 56 | 10 57 | Generic-SKU 58 | 59 | 60 | """; 61 | using var response2 = await httpClient.PostAsync( 62 | $"https://manifest.razerapi.com/api/legacy/{prodEndpoint!.hash}/{ENDPOINT}/productlist/get", 63 | new StringContent(PLATFORM_DATA, Encoding.UTF8, "application/xml")); 64 | var a = await response2.Content.ReadAsStringAsync(); 65 | 66 | var xml = new XmlDocument(); 67 | xml.LoadXml(a); 68 | 69 | foreach (XmlNode node in xml!.DocumentElement!.SelectNodes("//Module")!) 70 | { 71 | if (node["Name"]!.InnerText == "CHROMABROADCASTER") 72 | return node["DownloadURL"]!.InnerText; 73 | } 74 | 75 | throw new ArtemisPluginException("Failed to retrieve Razer API download URL"); 76 | } 77 | 78 | private record RazerEndpoint(string name, string hash); 79 | 80 | private record RazerRoot(List endpoints); 81 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterBusDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | #pragma warning disable CS8618 6 | 7 | public class VoiceMeeterBusDataModel : DataModel 8 | { 9 | private readonly string _updateHeader; 10 | 11 | public VoiceMeeterBusDataModel(int idx, VoiceMeeterLevelDataModel level) 12 | { 13 | _updateHeader = $"Bus[{idx}]"; 14 | Level = level; 15 | } 16 | 17 | public int Mono { get; set; } 18 | public bool Mute { get; set; } 19 | public bool EQon { get; set; } 20 | public bool EQAB { get; set; } 21 | public float Gain { get; set; } 22 | public int normal { get; set; } 23 | public int Amix { get; set; } 24 | public int Bmix { get; set; } 25 | public int Repeat { get; set; } 26 | public int Composite { get; set; } 27 | public int TVMix { get; set; } 28 | public int UpMix21 { get; set; } 29 | public int UpMix41 { get; set; } 30 | public int UpMix61 { get; set; } 31 | public int CenterOnly { get; set; } 32 | public int LFEOnly { get; set; } 33 | public int RearOnly { get; set; } 34 | //public int EQ.channel[j].cell[k].on { get; set; } 35 | //public int EQ.channel[j].cell[k].type { get; set; } 36 | //public int EQ.channel[j].cell[k].f { get; set; } 37 | //public int EQ.channel[j].cell[k].gain { get; set; } 38 | //public int EQ.channel[j].cell[k].q { get; set; } 39 | public string FadeTo { get; set; } 40 | public string FadeBy { get; set; } 41 | public bool Sel { get; set; } 42 | public int ReturnReverb { get; set; } 43 | public int ReturnDelay { get; set; } 44 | public int ReturnFx1 { get; set; } 45 | public int ReturnFx2 { get; set; } 46 | public int Monitor { get; set; } 47 | 48 | public VoiceMeeterLevelDataModel Level { get; } 49 | 50 | internal void Update() 51 | { 52 | Mono = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Mono)}"); 53 | Mute = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(Mute)}"); 54 | EQon = VoiceMeeterRemote.GetBool($"{_updateHeader}.{"EQ.On"}"); 55 | EQAB = VoiceMeeterRemote.GetBool($"{_updateHeader}.{"EQ.AB"}"); 56 | Gain = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Gain)}"); 57 | 58 | //bus modes 59 | normal = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(normal)}"); 60 | Amix = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(Amix)}"); 61 | Bmix = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(Bmix)}"); 62 | Repeat = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(Repeat)}"); 63 | Composite = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(Composite)}"); 64 | TVMix = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(TVMix)}"); 65 | UpMix21 = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(UpMix21)}"); 66 | UpMix41 = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(UpMix41)}"); 67 | UpMix61 = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(UpMix61)}"); 68 | CenterOnly = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(CenterOnly)}"); 69 | LFEOnly = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(LFEOnly)}"); 70 | RearOnly = VoiceMeeterRemote.GetInt($"{_updateHeader}.mode.{nameof(RearOnly)}"); 71 | 72 | FadeTo = VoiceMeeterRemote.GetString($"{_updateHeader}.{nameof(FadeTo)}"); 73 | FadeBy = VoiceMeeterRemote.GetString($"{_updateHeader}.{nameof(FadeBy)}"); 74 | Sel = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(Sel)}"); 75 | ReturnReverb = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(ReturnReverb)}"); 76 | ReturnDelay = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(ReturnDelay)}"); 77 | ReturnFx1 = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(ReturnFx1)}"); 78 | ReturnFx2 = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(ReturnFx2)}"); 79 | Monitor = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Monitor)}"); 80 | } 81 | 82 | internal void UpdateLevels() 83 | { 84 | Level.Update(); 85 | } 86 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/DataModels/SpotifyDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.ColorScience; 2 | using Artemis.Core.Modules; 3 | using Artemis.Core.Services; 4 | using SpotifyAPI.Web; 5 | using System; 6 | using System.ComponentModel; 7 | 8 | namespace Artemis.Plugins.Modules.Spotify.DataModels; 9 | 10 | #pragma warning disable CS8618 11 | 12 | public class SpotifyDataModel : DataModel 13 | { 14 | public SpotifyPlayerDataModel Player { get; set; } = new SpotifyPlayerDataModel(); 15 | public SpotifyTrackDataModel Track { get; set; } = new SpotifyTrackDataModel(); 16 | public SpotifyDeviceDataModel Device { get; set; } = new SpotifyDeviceDataModel(); 17 | } 18 | 19 | public class SpotifyPlayerDataModel : DataModel 20 | { 21 | public bool IsPlaying { get; set; } 22 | public bool Shuffle { get; set; } 23 | public int Volume { get; set; } 24 | public RepeatState RepeatState { get; set; } 25 | public ContextType ContextType { get; set; } 26 | public string? ContextName { get; set; } 27 | } 28 | 29 | public class SpotifyTrackDataModel : DataModel 30 | { 31 | public string Title { get; set; } 32 | public string Album { get; set; } 33 | public string Artist { get; set; } 34 | public int Popularity { get; set; } 35 | public TimeSpan Duration { get; set; } 36 | public TimeSpan Progress { get; set; } 37 | public ColorSwatch Colors { get; set; } 38 | public SpotifyTrackFeaturesDataModel Features { get; set; } = new SpotifyTrackFeaturesDataModel(); 39 | public SpotifyTrackAnalysisDataModel Analysis { get; set; } = new SpotifyTrackAnalysisDataModel(); 40 | public string AlbumArtUrl { get; set; } 41 | } 42 | 43 | public class SpotifyTrackFeaturesDataModel : DataModel 44 | { 45 | [DataModelProperty(Affix = "%", Description = "Level of confidence the track is acoustic")] 46 | public float Acousticness { get; set; } 47 | 48 | [DataModelProperty(Affix = "%", Description = "How suitable a track is for dancing")] 49 | public float Danceability { get; set; } 50 | 51 | [DataModelProperty(Affix = "%", Description = "How energetic / intense a track feels")] 52 | public float Energy { get; set; } 53 | 54 | [DataModelProperty(Affix = "%", Description = "How likely a track is to contain no spoken words")] 55 | public float Instrumentalness { get; set; } 56 | 57 | [DataModelProperty(Affix = "%", Description = "How likely a track is to have a live audience")] 58 | public float Liveness { get; set; } 59 | 60 | [DataModelProperty(Affix = "dB", Description = "Overall perceived loudness of a track")] 61 | public float Loudness { get; set; } 62 | 63 | [DataModelProperty(Affix = "%", Description = "How likely a track is to contain spoken words")] 64 | public float Speechiness { get; set; } 65 | 66 | [DataModelProperty(Affix = "%", Description = "How musically positive the track is (happy, cheerful)")] 67 | public float Valence { get; set; } 68 | 69 | [DataModelProperty(Affix = "BMP", Description = "Overall estimated tempo of a track in beats per minute")] 70 | public float Tempo { get; set; } 71 | 72 | [DataModelProperty(Description = "Estimated overall key of the track.")] 73 | public Key Key { get; set; } 74 | 75 | public Mode Mode { get; set; } 76 | 77 | public int TimeSignature { get; set; } 78 | } 79 | 80 | public enum Key 81 | { 82 | None = -1, 83 | C = 0, 84 | [Description("C#")] Cs = 1, 85 | D = 2, 86 | [Description("D#")] Ds = 3, 87 | E = 4, 88 | F = 5, 89 | [Description("F#")] Fs = 6, 90 | G = 7, 91 | [Description("G#")] Gs = 8, 92 | A = 9, 93 | [Description("A#")] As = 10, 94 | B = 11, 95 | } 96 | 97 | public enum Mode 98 | { 99 | Minor = 0, 100 | Major = 1 101 | } 102 | 103 | public enum ContextType 104 | { 105 | None, 106 | Playlist, 107 | Album, 108 | Artist 109 | } 110 | 111 | public enum RepeatState 112 | { 113 | Off, 114 | Context, 115 | Track 116 | } 117 | 118 | public class SpotifyTrackAnalysisDataModel : DataModel 119 | { 120 | public Section CurrentSection { get; internal set; } 121 | public Segment CurrentSegment { get; internal set; } 122 | } 123 | 124 | public class SpotifyDeviceDataModel : DataModel 125 | { 126 | public string Name { get; set; } 127 | public string Type { get; set; } 128 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.diogotr7.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.LayerBrushes.Gif", "Artemis.Plugins.LayerBrushes.Gif\Artemis.Plugins.LayerBrushes.Gif.csproj", "{DDB60A1E-BA5F-45FA-9FF8-D038B503C1FA}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.LayerBrushes.Chroma", "Artemis.Plugins.LayerBrushes.Chroma\Artemis.Plugins.LayerBrushes.Chroma.csproj", "{3FF2D4CC-3798-4B91-A6D4-6A29D6A54A61}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Modules.Discord", "Artemis.Plugins.Modules.Discord\Artemis.Plugins.Modules.Discord.csproj", "{69EC267A-EDA1-4166-AC28-011E28ECE78F}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Modules.Spotify", "Artemis.Plugins.Modules.Spotify\Artemis.Plugins.Modules.Spotify.csproj", "{E403010D-EAF8-4D29-B33A-EF58E9341BE7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Modules.OBS", "Artemis.Plugins.Modules.OBS\Artemis.Plugins.Modules.OBS.csproj", "{D006F4BF-1724-4FF1-AD9F-A21605DDCBC4}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Modules.VoiceMeeter", "Artemis.Plugins.Modules.VoiceMeeter\Artemis.Plugins.Modules.VoiceMeeter.csproj", "{476B5980-33CC-4E9A-B8EB-E7E9E7564609}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.Nodes.Ping", "Artemis.Plugins.Nodes.Ping\Artemis.Plugins.Nodes.Ping.csproj", "{D68D4879-F5DA-43E5-B7A6-C70259DF7E43}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.Modules.KeyboardLayout", "Artemis.Plugins.Modules.KeyboardLayout\Artemis.Plugins.Modules.KeyboardLayout.csproj", "{1F7083C5-440E-4E95-A0C3-4E183BCBED08}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GithubActions", "GithubActions", "{87BB1199-B6B2-4FF5-8891-F8E4C951ABCA}" 23 | ProjectSection(SolutionItems) = preProject 24 | ..\.github\workflows\build.yml = ..\.github\workflows\build.yml 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|x64 = Debug|x64 30 | Release|x64 = Release|x64 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {DDB60A1E-BA5F-45FA-9FF8-D038B503C1FA}.Debug|x64.ActiveCfg = Debug|x64 34 | {DDB60A1E-BA5F-45FA-9FF8-D038B503C1FA}.Debug|x64.Build.0 = Debug|x64 35 | {DDB60A1E-BA5F-45FA-9FF8-D038B503C1FA}.Release|x64.ActiveCfg = Release|x64 36 | {DDB60A1E-BA5F-45FA-9FF8-D038B503C1FA}.Release|x64.Build.0 = Release|x64 37 | {3FF2D4CC-3798-4B91-A6D4-6A29D6A54A61}.Debug|x64.ActiveCfg = Debug|x64 38 | {3FF2D4CC-3798-4B91-A6D4-6A29D6A54A61}.Debug|x64.Build.0 = Debug|x64 39 | {3FF2D4CC-3798-4B91-A6D4-6A29D6A54A61}.Release|x64.ActiveCfg = Release|x64 40 | {3FF2D4CC-3798-4B91-A6D4-6A29D6A54A61}.Release|x64.Build.0 = Release|x64 41 | {69EC267A-EDA1-4166-AC28-011E28ECE78F}.Debug|x64.ActiveCfg = Debug|x64 42 | {69EC267A-EDA1-4166-AC28-011E28ECE78F}.Debug|x64.Build.0 = Debug|x64 43 | {69EC267A-EDA1-4166-AC28-011E28ECE78F}.Release|x64.ActiveCfg = Release|x64 44 | {69EC267A-EDA1-4166-AC28-011E28ECE78F}.Release|x64.Build.0 = Release|x64 45 | {E403010D-EAF8-4D29-B33A-EF58E9341BE7}.Debug|x64.ActiveCfg = Debug|x64 46 | {E403010D-EAF8-4D29-B33A-EF58E9341BE7}.Debug|x64.Build.0 = Debug|x64 47 | {E403010D-EAF8-4D29-B33A-EF58E9341BE7}.Release|x64.ActiveCfg = Release|x64 48 | {E403010D-EAF8-4D29-B33A-EF58E9341BE7}.Release|x64.Build.0 = Release|x64 49 | {D006F4BF-1724-4FF1-AD9F-A21605DDCBC4}.Debug|x64.ActiveCfg = Debug|x64 50 | {D006F4BF-1724-4FF1-AD9F-A21605DDCBC4}.Debug|x64.Build.0 = Debug|x64 51 | {D006F4BF-1724-4FF1-AD9F-A21605DDCBC4}.Release|x64.ActiveCfg = Release|x64 52 | {D006F4BF-1724-4FF1-AD9F-A21605DDCBC4}.Release|x64.Build.0 = Release|x64 53 | {476B5980-33CC-4E9A-B8EB-E7E9E7564609}.Debug|x64.ActiveCfg = Debug|x64 54 | {476B5980-33CC-4E9A-B8EB-E7E9E7564609}.Debug|x64.Build.0 = Debug|x64 55 | {476B5980-33CC-4E9A-B8EB-E7E9E7564609}.Release|x64.ActiveCfg = Release|x64 56 | {476B5980-33CC-4E9A-B8EB-E7E9E7564609}.Release|x64.Build.0 = Release|x64 57 | {D68D4879-F5DA-43E5-B7A6-C70259DF7E43}.Debug|x64.ActiveCfg = Debug|x64 58 | {D68D4879-F5DA-43E5-B7A6-C70259DF7E43}.Debug|x64.Build.0 = Debug|x64 59 | {D68D4879-F5DA-43E5-B7A6-C70259DF7E43}.Release|x64.ActiveCfg = Release|x64 60 | {D68D4879-F5DA-43E5-B7A6-C70259DF7E43}.Release|x64.Build.0 = Release|x64 61 | {1F7083C5-440E-4E95-A0C3-4E183BCBED08}.Debug|x64.ActiveCfg = Debug|x64 62 | {1F7083C5-440E-4E95-A0C3-4E183BCBED08}.Debug|x64.Build.0 = Debug|x64 63 | {1F7083C5-440E-4E95-A0C3-4E183BCBED08}.Release|x64.ActiveCfg = Release|x64 64 | {1F7083C5-440E-4E95-A0C3-4E183BCBED08}.Release|x64.Build.0 = Release|x64 65 | EndGlobalSection 66 | GlobalSection(SolutionProperties) = preSolution 67 | HideSolutionNode = FALSE 68 | EndGlobalSection 69 | GlobalSection(ExtensibilityGlobals) = postSolution 70 | SolutionGuid = {D87FE810-F20D-476A-8FE6-41BD3AC75D49} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/Transport/DiscordPipeTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Artemis.Plugins.Modules.Discord.Enums; 10 | using Newtonsoft.Json; 11 | 12 | namespace Artemis.Plugins.Modules.Discord.Transport; 13 | 14 | public sealed class DiscordPipeTransport : IDiscordTransport 15 | { 16 | private const string RpcVersion = "1"; 17 | private const int HeaderSize = 8; 18 | 19 | private readonly byte[] _headerBuffer = new byte[8]; 20 | private readonly string _clientId; 21 | private NamedPipeClientStream? _pipe; 22 | public bool IsConnected => _pipe?.IsConnected == true; 23 | 24 | public DiscordPipeTransport(string clientId) 25 | { 26 | _clientId = clientId; 27 | } 28 | 29 | public async Task Connect(CancellationToken cancellationToken = default) 30 | { 31 | const int MAX_TRIES = 10; 32 | for (var i = 0; i < MAX_TRIES; i++) 33 | { 34 | try 35 | { 36 | _pipe = new NamedPipeClientStream(".", GetPipeName(i), PipeDirection.InOut, PipeOptions.Asynchronous); 37 | await _pipe.ConnectAsync(cancellationToken); 38 | 39 | var handshake = JsonConvert.SerializeObject(new { v = RpcVersion, client_id = _clientId }); 40 | 41 | await SendPacketAsync(handshake, RpcPacketType.HANDSHAKE, cancellationToken); 42 | return; 43 | } 44 | catch 45 | { 46 | await Task.Delay(1000, cancellationToken); 47 | } 48 | } 49 | 50 | throw new DiscordRpcClientException("Could not connect to any pipe."); 51 | } 52 | 53 | public async Task SendPacketAsync(string stringData, RpcPacketType rpcPacketType, CancellationToken cancellationToken = default) 54 | { 55 | int stringByteLength = Encoding.UTF8.GetByteCount(stringData); 56 | int bufferSize = HeaderSize + stringByteLength; 57 | byte[] buffer = ArrayPool.Shared.Rent(bufferSize); 58 | 59 | try 60 | { 61 | if (!BitConverter.TryWriteBytes(buffer.AsSpan(0, 4), (int)rpcPacketType)) 62 | throw new DiscordRpcClientException("Error writing rpc packet type."); 63 | 64 | if (!BitConverter.TryWriteBytes(buffer.AsSpan(4, 4), stringByteLength)) 65 | throw new DiscordRpcClientException("Error writing string byte length."); 66 | 67 | if (Encoding.UTF8.GetBytes(stringData, 0, stringData.Length, buffer, HeaderSize) != stringData.Length) 68 | throw new DiscordRpcClientException("Wrote wrong number of characters."); 69 | 70 | await _pipe!.WriteAsync(buffer.AsMemory(0, bufferSize), cancellationToken); 71 | } 72 | finally 73 | { 74 | ArrayPool.Shared.Return(buffer); 75 | } 76 | } 77 | 78 | public async Task<(RpcPacketType, string)> ReadMessageAsync(CancellationToken cancellationToken = default) 79 | { 80 | byte[]? dataBuffer = null; 81 | try 82 | { 83 | int headerReadBytes = await _pipe!.ReadAsync(_headerBuffer.AsMemory(0, HeaderSize), cancellationToken); 84 | 85 | if (headerReadBytes < HeaderSize) 86 | throw new DiscordRpcClientException("Read less than 4 bytes for the header"); 87 | 88 | var header = MemoryMarshal.AsRef(_headerBuffer); 89 | 90 | if (header.PacketLength == 0) 91 | throw new DiscordRpcClientException("Read zero bytes from the pipe"); 92 | 93 | dataBuffer = ArrayPool.Shared.Rent(header.PacketLength); 94 | 95 | var readAsync = await _pipe!.ReadAsync(dataBuffer.AsMemory(0, header.PacketLength), cancellationToken); 96 | 97 | if (readAsync < header.PacketLength) 98 | throw new DiscordRpcClientException("Read less bytes than expected"); 99 | 100 | return (header.PacketType, Encoding.UTF8.GetString(dataBuffer.AsSpan(0, header.PacketLength))); 101 | } 102 | finally 103 | { 104 | if (dataBuffer != null) 105 | ArrayPool.Shared.Return(dataBuffer); 106 | } 107 | } 108 | 109 | public void Dispose() 110 | { 111 | _pipe!.Dispose(); 112 | } 113 | 114 | private static string GetPipeName(int index) 115 | { 116 | var pipeName = $"discord-ipc-{index}"; 117 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 118 | return pipeName; 119 | 120 | return Path.Combine(GetTemporaryDirectory(), pipeName); 121 | } 122 | 123 | private static string GetTemporaryDirectory() 124 | { 125 | //source: https://github.com/Lachee/discord-rpc-csharp/ 126 | //try all these possible paths it could be, depending on system configuration 127 | return Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR") ?? 128 | Environment.GetEnvironmentVariable("TMPDIR") ?? 129 | Environment.GetEnvironmentVariable("TMP") ?? 130 | Environment.GetEnvironmentVariable("TEMP") ?? 131 | "/tmp"; 132 | } 133 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Discord/DiscordPluginConfiguration/DiscordPluginConfigurationView.axaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Discord Rpc Provider 22 | 23 | 24 | Sets the provider used for authentication. Custom allows you to use your own client id and secret. Check the wiki for more information. 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Client Id 38 | 39 | 40 | Only required if you use the custom provider. Used to authenticate your application with discord. You can find it in the discord developer portal. 41 | 42 | 43 | 44 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Client Secret 61 | 62 | 63 | Only required if you use the custom provider. Used to authenticate your application with discord. You can find it in the discord developer portal. 64 | 65 | 66 | 67 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Wiki 85 | 86 | 87 | Check the wiki for information before making any changes, this should work out of the box. 88 | 89 | 90 | 91 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/Services/ChromaService.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Services; 2 | using SkiaSharp; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.Intrinsics; 8 | using Artemis.Core; 9 | using RazerSdkReader; 10 | using RazerSdkReader.Structures; 11 | 12 | namespace Artemis.Plugins.LayerBrushes.Chroma.Services; 13 | 14 | public record MatrixUpdatedEventArgs(RzDeviceType DeviceType, SKColor[] Matrix); 15 | 16 | // ReSharper disable once ClassNeverInstantiated.Global 17 | public sealed class ChromaService : IPluginService, IDisposable 18 | { 19 | private readonly SKColor[][] _matrices; 20 | private readonly ChromaReader _reader; 21 | private readonly Profiler _profiler; 22 | private readonly object _lock; 23 | private readonly MatrixUpdatedEventArgs[] _matrixUpdatedEventArgs; 24 | 25 | public event EventHandler? MatrixUpdated; 26 | public event EventHandler? AppListUpdated; 27 | 28 | public bool IsActive => !string.IsNullOrWhiteSpace(CurrentApp) && CurrentApp != "Artemis.UI.Windows.exe"; 29 | public string? CurrentApp { get; private set; } 30 | public uint? CurrentAppId { get; private set; } 31 | public string[] AppNames { get; } 32 | public uint[] AppIds { get; } 33 | 34 | public ChromaService(Plugin plugin) 35 | { 36 | _profiler = plugin.GetProfiler("Chroma Service"); 37 | _lock = new object(); 38 | AppNames = new string[50]; 39 | AppIds = new uint[50]; 40 | 41 | var deviceTypes = Enum.GetValues(); 42 | _matrices = deviceTypes.Select(deviceType => new SKColor[deviceType.GetLength()]).ToArray(); 43 | _matrixUpdatedEventArgs = deviceTypes.Select(deviceType => new MatrixUpdatedEventArgs(deviceType, _matrices[(int)deviceType])).ToArray(); 44 | 45 | _reader = new ChromaReader(); 46 | _reader.KeyboardUpdated += RazerEmulatorReaderOnKeyboardUpdated; 47 | _reader.MouseUpdated += RazerEmulatorReaderOnMouseUpdated; 48 | _reader.MousepadUpdated += RazerEmulatorReaderOnMousepadUpdated; 49 | _reader.KeypadUpdated += RazerEmulatorReaderOnKeypadUpdated; 50 | _reader.HeadsetUpdated += RazerEmulatorReaderOnHeadsetUpdated; 51 | _reader.ChromaLinkUpdated += RazerEmulatorReaderOnChromaLinkUpdated; 52 | _reader.AppDataUpdated += RazerEmulatorReaderOnAppDataUpdated; 53 | _reader.Start(); 54 | } 55 | 56 | private void RazerEmulatorReaderOnAppDataUpdated(object? sender, in ChromaAppData e) => UpdateAppListData(in e); 57 | 58 | private void RazerEmulatorReaderOnChromaLinkUpdated(object? sender, in ChromaLink e) => UpdateMatrix(RzDeviceType.ChromaLink, in e); 59 | 60 | private void RazerEmulatorReaderOnHeadsetUpdated(object? sender, in ChromaHeadset e) => UpdateMatrix(RzDeviceType.Headset, in e); 61 | 62 | private void RazerEmulatorReaderOnKeypadUpdated(object? sender, in ChromaKeypad e) => UpdateMatrix(RzDeviceType.Keypad, in e); 63 | 64 | private void RazerEmulatorReaderOnMousepadUpdated(object? sender, in ChromaMousepad e) => UpdateMatrix(RzDeviceType.Mousepad, in e); 65 | 66 | private void RazerEmulatorReaderOnMouseUpdated(object? sender, in ChromaMouse e) => UpdateMatrix(RzDeviceType.Mouse, in e); 67 | 68 | private void RazerEmulatorReaderOnKeyboardUpdated(object? sender, in ChromaKeyboard e) => UpdateMatrix(RzDeviceType.Keyboard, in e); 69 | 70 | private void UpdateMatrix(RzDeviceType deviceType, in T data) where T : IColorProvider 71 | { 72 | var profilerName = deviceType.ToStringFast(); 73 | 74 | _profiler.StartMeasurement(profilerName); 75 | 76 | lock (_lock) 77 | { 78 | var matrix = _matrices[(int)deviceType]; 79 | //SKColor and ChromaColor are the same size and layout, 80 | //so we can just write to it directly. 81 | var colors = MemoryMarshal.Cast(matrix); 82 | data.GetColors(colors); 83 | 84 | MatrixUpdated?.Invoke(this, _matrixUpdatedEventArgs[(int)deviceType]); 85 | } 86 | 87 | _profiler.StopMeasurement(profilerName); 88 | } 89 | 90 | private void UpdateAppListData(in ChromaAppData app) 91 | { 92 | _profiler.StartMeasurement("AppList"); 93 | 94 | CurrentAppId = app.CurrentAppId; 95 | CurrentApp = app.CurrentAppName; 96 | 97 | ReadOnlySpan apps = app.AppInfo; 98 | for (var i = 0; i < apps.Length; i++) 99 | { 100 | ref readonly var appInfo = ref apps[i]; 101 | AppNames[i] = appInfo.AppName; 102 | AppIds[i] = appInfo.AppId; 103 | } 104 | 105 | AppListUpdated?.Invoke(this, EventArgs.Empty); 106 | 107 | _profiler.StopMeasurement("AppList"); 108 | } 109 | 110 | public void Dispose() 111 | { 112 | _reader.KeyboardUpdated -= RazerEmulatorReaderOnKeyboardUpdated; 113 | _reader.MouseUpdated -= RazerEmulatorReaderOnMouseUpdated; 114 | _reader.MousepadUpdated -= RazerEmulatorReaderOnMousepadUpdated; 115 | _reader.KeypadUpdated -= RazerEmulatorReaderOnKeypadUpdated; 116 | _reader.HeadsetUpdated -= RazerEmulatorReaderOnHeadsetUpdated; 117 | _reader.ChromaLinkUpdated -= RazerEmulatorReaderOnChromaLinkUpdated; 118 | _reader.AppDataUpdated -= RazerEmulatorReaderOnAppDataUpdated; 119 | _reader.Dispose(); 120 | } 121 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/DataModels/VoiceMeeterStripDataModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core.Modules; 2 | 3 | namespace Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | 5 | #pragma warning disable CS8618 6 | 7 | public class VoiceMeeterStripDataModel : DataModel 8 | { 9 | private readonly string _updateHeader; 10 | 11 | public VoiceMeeterStripDataModel(int idx, VoiceMeeterLevelDataModel level) 12 | { 13 | _updateHeader = $"Strip[{idx}]"; 14 | Level = level; 15 | } 16 | 17 | public bool Mono { get; set; } 18 | public bool Mute { get; set; } 19 | public bool Solo { get; set; } 20 | public bool MC { get; set; } 21 | public float Gain { get; set; } 22 | //public int[] GainLayer { get; set; } 23 | public float Pan_x { get; set; } 24 | public float Pan_y { get; set; } 25 | public float Color_x { get; set; } 26 | public float Color_y { get; set; } 27 | public float fx_x { get; set; } 28 | public float fx_y { get; set; } 29 | public int Audibility { get; set; } 30 | public int Comp { get; set; } 31 | public int Gate { get; set; } 32 | public int Karaoke { get; set; } 33 | public float Limit { get; set; } 34 | public float EQGain1 { get; set; } 35 | public float EQGain2 { get; set; } 36 | public float EQGain3 { get; set; } 37 | public string Label { get; set; } 38 | public bool A1 { get; set; } 39 | public bool A2 { get; set; } 40 | public bool A3 { get; set; } 41 | public bool A4 { get; set; } 42 | public bool A5 { get; set; } 43 | public bool B1 { get; set; } 44 | public bool B2 { get; set; } 45 | public bool B3 { get; set; } 46 | public string FadeTo { get; set; } 47 | public string FadeBy { get; set; } 48 | public int Reverb { get; set; } 49 | public int Delay { get; set; } 50 | public int Fx1 { get; set; } 51 | public int Fx2 { get; set; } 52 | public bool PostReverb { get; set; } 53 | public bool PostDelay { get; set; } 54 | public bool PostFx1 { get; set; } 55 | public bool PostFx2 { get; set; } 56 | 57 | //public int App[k].Gain { get ; set; } 58 | //public int App[k].Mute { get; set; } 59 | //public int AppGain { get; set; } 60 | //public int AppMute { get; set; } 61 | 62 | public VoiceMeeterLevelDataModel Level { get; } 63 | 64 | public void Update() 65 | { 66 | Mono = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(Mono)}"); 67 | Mute = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(Mute)}"); 68 | Solo = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(Solo)}"); 69 | MC = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(MC)}"); 70 | Gain = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Gain)}"); 71 | Pan_x = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Pan_x)}"); 72 | Pan_y = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Pan_y)}"); 73 | Color_x = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Color_x)}"); 74 | Color_y = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Color_y)}"); 75 | fx_x = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(fx_x)}"); 76 | fx_y = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(fx_y)}"); 77 | Audibility = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Audibility)}"); 78 | Comp = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Comp)}"); 79 | Gate = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Gate)}"); 80 | Karaoke = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Karaoke)}"); 81 | Limit = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(Limit)}"); 82 | EQGain1 = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(EQGain1)}"); 83 | EQGain2 = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(EQGain2)}"); 84 | EQGain3 = VoiceMeeterRemote.GetFloat($"{_updateHeader}.{nameof(EQGain3)}"); 85 | Label = VoiceMeeterRemote.GetString($"{_updateHeader}.{nameof(Label)}"); 86 | A1 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(A1)}"); 87 | A2 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(A2)}"); 88 | A3 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(A3)}"); 89 | A4 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(A4)}"); 90 | A5 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(A5)}"); 91 | B1 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(B1)}"); 92 | B2 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(B2)}"); 93 | B3 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(B3)}"); 94 | FadeTo = VoiceMeeterRemote.GetString($"{_updateHeader}.{nameof(FadeTo)}"); 95 | FadeBy = VoiceMeeterRemote.GetString($"{_updateHeader}.{nameof(FadeBy)}"); 96 | Reverb = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Reverb)}"); 97 | Delay = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Delay)}"); 98 | Fx1 = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Fx1)}"); 99 | Fx2 = VoiceMeeterRemote.GetInt($"{_updateHeader}.{nameof(Fx2)}"); 100 | PostReverb = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(PostReverb)}"); 101 | PostDelay = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(PostDelay)}"); 102 | PostFx1 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(PostFx1)}"); 103 | PostFx2 = VoiceMeeterRemote.GetBool($"{_updateHeader}.{nameof(PostFx2)}"); 104 | } 105 | 106 | internal void UpdateLevels() 107 | { 108 | Level.Update(); 109 | } 110 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/ConfigurationDialog/SpotifyConfigurationDialogViewModel.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.UI.Shared; 3 | using Avalonia.Media.Imaging; 4 | using Avalonia.Threading; 5 | using SpotifyAPI.Web; 6 | using SpotifyAPI.Web.Auth; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net.Http; 12 | using System.Threading.Tasks; 13 | 14 | namespace Artemis.Plugins.Modules.Spotify; 15 | 16 | public class SpotifyConfigurationDialogViewModel : PluginConfigurationViewModel 17 | { 18 | private readonly HttpClient _client = new(); 19 | private readonly PluginSetting _token; 20 | private readonly SpotifyModule _dataModelExpansion; 21 | 22 | private static EmbedIOAuthServer? _server; 23 | private static EmbedIOAuthServer Server => _server ??= new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000); 24 | 25 | private Bitmap? _profilePicture; 26 | 27 | public Bitmap? ProfilePicture 28 | { 29 | get => _profilePicture; 30 | set => RaiseAndSetIfChanged(ref _profilePicture, value); 31 | } 32 | 33 | private string? _username; 34 | 35 | public string? Username 36 | { 37 | get => _username; 38 | set => RaiseAndSetIfChanged(ref _username, value); 39 | } 40 | 41 | private bool _logInVisibility; 42 | 43 | public bool LogInVisibility 44 | { 45 | get => _logInVisibility; 46 | set => RaiseAndSetIfChanged(ref _logInVisibility, value); 47 | } 48 | 49 | private bool _logOutVisibility; 50 | 51 | public bool LogOutVisibility 52 | { 53 | get => _logOutVisibility; 54 | set => RaiseAndSetIfChanged(ref _logOutVisibility, value); 55 | } 56 | 57 | private string? _verifier; 58 | private string? _challenge; 59 | private string? _loginUrl; 60 | private bool _waitingForUser; 61 | 62 | public SpotifyConfigurationDialogViewModel(Plugin plugin, PluginSettings settings) : base(plugin) 63 | { 64 | _token = settings.GetSetting(Constants.SPOTIFY_AUTH_SETTING); 65 | _dataModelExpansion = Plugin.GetFeature()!; 66 | UpdateProfilePicture(); 67 | UpdateButtonVisibility(); 68 | } 69 | 70 | public async Task Login() 71 | { 72 | if (_waitingForUser) 73 | return; 74 | 75 | _waitingForUser = true; 76 | (_verifier, _challenge) = PKCEUtil.GenerateCodes(); 77 | 78 | await Server.Start(); 79 | Server.AuthorizationCodeReceived += OnAuthorizationCodeReceived; 80 | 81 | var request = new LoginRequest(Server.BaseUri, Constants.SPOTIFY_CLIENT_ID, LoginRequest.ResponseType.Code) 82 | { 83 | CodeChallenge = _challenge, 84 | CodeChallengeMethod = "S256", 85 | Scope = new List { Scopes.UserReadCurrentlyPlaying, Scopes.UserReadPlaybackState } 86 | }; 87 | _loginUrl = request.ToUri().ToString(); 88 | 89 | if (_loginUrl is not null) 90 | Utilities.OpenUrl(_loginUrl); 91 | } 92 | 93 | public void Logout() 94 | { 95 | _token.Value = null; 96 | _token.Save(); 97 | _dataModelExpansion.Logout(); 98 | UpdateProfilePicture(); 99 | UpdateButtonVisibility(); 100 | } 101 | 102 | private async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response) 103 | { 104 | Server.AuthorizationCodeReceived -= OnAuthorizationCodeReceived; 105 | await Server.Stop(); 106 | 107 | var tokenRequest = new PKCETokenRequest(Constants.SPOTIFY_CLIENT_ID, response.Code, Server.BaseUri, _verifier!); 108 | 109 | _token.Value = await new OAuthClient().RequestToken(tokenRequest); 110 | _token.Save(); 111 | 112 | _dataModelExpansion.Login(); 113 | UpdateProfilePicture(); 114 | UpdateButtonVisibility(); 115 | _waitingForUser = false; 116 | _loginUrl = null; 117 | } 118 | 119 | private void UpdateButtonVisibility() 120 | { 121 | LogInVisibility = !_dataModelExpansion.LoggedIn; 122 | LogOutVisibility = _dataModelExpansion.LoggedIn; 123 | } 124 | 125 | private void UpdateProfilePicture() 126 | { 127 | Dispatcher.UIThread.Post(async () => 128 | { 129 | try 130 | { 131 | if (_dataModelExpansion.LoggedIn) 132 | { 133 | var user = await _dataModelExpansion.GetUserInfo(); 134 | if (user is null) 135 | return; 136 | Username = user.DisplayName; 137 | 138 | //skiaSharp crashes on linux, fix later TODO 139 | if (OperatingSystem.IsLinux()) 140 | return; 141 | 142 | if (user.Images.Count < 1) 143 | return; 144 | 145 | try 146 | { 147 | var response = await _client.GetAsync(user.Images.Last().Url, HttpCompletionOption.ResponseContentRead); 148 | var imageStream = await response.Content.ReadAsStreamAsync(); 149 | ProfilePicture = new Bitmap(imageStream); 150 | } 151 | catch (Exception e) 152 | { 153 | ProfilePicture = new Bitmap(new FileStream(Plugin.ResolveRelativePath("no-user.png"), FileMode.Open, FileAccess.Read)); 154 | } 155 | 156 | return; 157 | } 158 | 159 | Username = "Not logged in"; 160 | } 161 | catch (Exception e) 162 | { 163 | //failed to get user info, ignore 164 | } 165 | }); 166 | } 167 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.VoiceMeeter/VoiceMeeterModule.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.Modules; 3 | using Artemis.Plugins.Modules.VoiceMeeter.DataModels; 4 | using Serilog; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Artemis.Plugins.Modules.VoiceMeeter; 10 | 11 | [PluginFeature(AlwaysEnabled = true, Name = "VoiceMeeter")] 12 | public class VoiceMeeterModule : Module 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public VoiceMeeterModule(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | public override List ActivationRequirements { get; } = new() 22 | { 23 | new ProcessActivationRequirement("voicemeeter"), //voicemeeter 24 | new ProcessActivationRequirement("voicemeeterpro"), //voicemeeter banana 25 | new ProcessActivationRequirement("voicemeeter8"), //voicemeeter potato 32 bit 26 | new ProcessActivationRequirement("voicemeeter8x64"),//voicemeeter potato 64 bit 27 | }; 28 | 29 | public override void Enable() 30 | { 31 | var vmLogin = VoiceMeeterRemote.Login(); 32 | if (vmLogin is not VoiceMeeterLoginResponse.OK and not VoiceMeeterLoginResponse.AlreadyLoggedIn) 33 | throw new ArtemisPluginException($"Error connecting to voicemeeter: {vmLogin}"); 34 | 35 | GetInitialInformation(); 36 | } 37 | 38 | public override void Update(double deltaTime) 39 | { 40 | try 41 | { 42 | if (VoiceMeeterRemote.IsParametersDirty() == 1) 43 | UpdateParameters(); 44 | 45 | UpdateLevels(); 46 | } 47 | catch (Exception e) 48 | { 49 | _logger.Error(e, "Error Updating VoiceMeeter data."); 50 | //we will probably Disable ourselves soon, i think. 51 | } 52 | } 53 | 54 | public override void Disable() 55 | { 56 | VoiceMeeterRemote.Logout(); 57 | } 58 | 59 | private void GetInitialInformation() 60 | { 61 | VoiceMeeterRemote.GetVoicemeeterVersion(out var version); 62 | 63 | var v1 = (version & 0xFF000000) >> 24; 64 | var v2 = (version & 0x00FF0000) >> 16; 65 | var v3 = (version & 0x0000FF00) >> 8; 66 | var v4 = version & 0x000000FF; 67 | 68 | DataModel.Information.VoiceMeeterVersion = $"{v1}.{v2}.{v3}.{v4}"; 69 | 70 | VoiceMeeterRemote.GetVoicemeeterType(out var voiceMeeterType); 71 | 72 | DataModel.Information.VoiceMeeterType = voiceMeeterType; 73 | DataModel.Information.StripCount = GetStripCount(voiceMeeterType); 74 | DataModel.Information.BusCount = GetBusCount(voiceMeeterType); 75 | 76 | var totalStripChannels = 0; 77 | for (int i = 0; i < DataModel.Information.StripCount; i++) 78 | { 79 | int channels = i < GetPhysicalStripCount(DataModel.Information.VoiceMeeterType) ? 80 | CHANNELS_PER_PHYSICAL_STRIP : CHANNELS_PER_VIRTUAL_STRIP; 81 | 82 | var level = new VoiceMeeterLevelDataModel(POST_MUTE_INPUT_LEVELS, totalStripChannels, channels); 83 | var strip = new VoiceMeeterStripDataModel(i, level); 84 | 85 | strip.Update(); 86 | strip.UpdateLevels(); 87 | 88 | var name = string.IsNullOrWhiteSpace(strip.Label) ? $"Strip {i + 1}" : strip.Label; 89 | DataModel.Strips.AddDynamicChild(i.ToString(), strip, name); 90 | 91 | totalStripChannels += channels; 92 | } 93 | 94 | var names = GetBusNames(DataModel.Information.VoiceMeeterType).ToArray(); 95 | for (int i = 0; i < DataModel.Information.BusCount; i++) 96 | { 97 | var level = new VoiceMeeterLevelDataModel(OUTPUT_LEVELS, CHANNELS_PER_BUS * i, CHANNELS_PER_BUS); 98 | var bus = new VoiceMeeterBusDataModel(i, level); 99 | 100 | bus.Update(); 101 | bus.UpdateLevels(); 102 | 103 | DataModel.Busses.AddDynamicChild(i.ToString(), bus, names[i]); 104 | } 105 | } 106 | 107 | private void UpdateParameters() 108 | { 109 | foreach (var strip in DataModel.Strips.DynamicChildren.Values.OfType>()) 110 | strip.Value.Update(); 111 | 112 | foreach (var bus in DataModel.Busses.DynamicChildren.Values.OfType>()) 113 | bus.Value.Update(); 114 | } 115 | 116 | private void UpdateLevels() 117 | { 118 | foreach (var strip in DataModel.Strips.DynamicChildren.Values.OfType>()) 119 | strip.Value.UpdateLevels(); 120 | 121 | foreach (var bus in DataModel.Busses.DynamicChildren.Values.OfType>()) 122 | bus.Value.UpdateLevels(); 123 | } 124 | 125 | private static int GetPhysicalStripCount(VoiceMeeterType type) => type switch 126 | { 127 | VoiceMeeterType.VoiceMeeter => 2, 128 | VoiceMeeterType.VoiceMeeterBanana => 3, 129 | VoiceMeeterType.VoiceMeeterPotato => 5, 130 | _ => 0, 131 | }; 132 | 133 | private static int GetVirtualStripCount(VoiceMeeterType type) => type switch 134 | { 135 | VoiceMeeterType.VoiceMeeter => 1, 136 | VoiceMeeterType.VoiceMeeterBanana => 2, 137 | VoiceMeeterType.VoiceMeeterPotato => 3, 138 | _ => 0, 139 | }; 140 | 141 | private static int GetPhysicalBusCount(VoiceMeeterType type) => type switch 142 | { 143 | VoiceMeeterType.VoiceMeeter => 1, 144 | VoiceMeeterType.VoiceMeeterBanana => 3, 145 | VoiceMeeterType.VoiceMeeterPotato => 5, 146 | _ => 0, 147 | }; 148 | 149 | private static int GetVirtualBusCount(VoiceMeeterType type) => type switch 150 | { 151 | VoiceMeeterType.VoiceMeeter => 1, 152 | VoiceMeeterType.VoiceMeeterBanana => 2, 153 | VoiceMeeterType.VoiceMeeterPotato => 3, 154 | _ => 0, 155 | }; 156 | 157 | private static int GetStripCount(VoiceMeeterType type) => GetPhysicalStripCount(type) + GetVirtualStripCount(type); 158 | 159 | private static int GetBusCount(VoiceMeeterType type) => GetPhysicalBusCount(type) + GetVirtualBusCount(type); 160 | 161 | private const int CHANNELS_PER_PHYSICAL_STRIP = 2; 162 | private const int CHANNELS_PER_VIRTUAL_STRIP = 8; 163 | private const int CHANNELS_PER_BUS = 8; 164 | 165 | private const int PRE_FADER_INPUT_LEVELS = 0; 166 | private const int POST_FADER_INPUT_LEVELS = 1; 167 | private const int POST_MUTE_INPUT_LEVELS = 2; 168 | private const int OUTPUT_LEVELS = 3; 169 | 170 | private static IEnumerable GetBusNames(VoiceMeeterType type) 171 | { 172 | foreach (var item in Enumerable.Range(0, GetPhysicalBusCount(type))) 173 | yield return $"A{item + 1}"; 174 | 175 | foreach (var item in Enumerable.Range(0, GetVirtualBusCount(type))) 176 | yield return $"B{item + 1}"; 177 | } 178 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/LayerBrushes/OverwatchColorCorrection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SkiaSharp; 3 | 4 | namespace Artemis.Plugins.LayerBrushes.Chroma.LayerBrushes; 5 | 6 | public static class OverwatchColorCorrection 7 | { 8 | /// 9 | /// Maps Overwatch background colors to the more vibrant peripheral equivalent. 10 | /// These colors may also be used to detect which hero is currently selected. 11 | /// 12 | public static Dictionary ColorMap = new() 13 | { 14 | [SKColor.Parse("#371e00")] = SKColor.Parse("#dd7900"), //Base, 15 | [SKColor.Parse("#3f1e2f")] = SKColor.Parse("#fc79bd"), //Dva, 16 | [SKColor.Parse("#190703")] = SKColor.Parse("#661e0f"), //Doomfist, 17 | [SKColor.Parse("#152733")] = SKColor.Parse("#579fcf"), //JunkerQueen, 18 | [SKColor.Parse("#041b01")] = SKColor.Parse("#106f04"), //Orisa, 19 | [SKColor.Parse("#1f1531")] = SKColor.Parse("#7d55c7"), //Rammatra, 20 | [SKColor.Parse("#1f2223")] = SKColor.Parse("#7c8b8c"), //Reinhardt, 21 | [SKColor.Parse("#2b1b07")] = SKColor.Parse("#ae6f1c"), //Roadhog, 22 | [SKColor.Parse("#1f2223")] = SKColor.Parse("#7c8b8c"), //Sigma, 23 | [SKColor.Parse("#23242b")] = SKColor.Parse("#8f92ae"), //Winston, 24 | [SKColor.Parse("#381e02")] = SKColor.Parse("#e2790a"), //WreckingBall, 25 | [SKColor.Parse("#3d1729")] = SKColor.Parse("#f65ea6"), //Zarya, 26 | [SKColor.Parse("#0f0f0e")] = SKColor.Parse("#3e3c3a"), //Sshe, 27 | [SKColor.Parse("#161c14")] = SKColor.Parse("#5b7351"), //Bastion, 28 | [SKColor.Parse("#290a09")] = SKColor.Parse("#a62927"), //Cassidy, 29 | [SKColor.Parse("#22323f")] = SKColor.Parse("#89c8ff"), //Echo, 30 | [SKColor.Parse("#203e00")] = SKColor.Parse("#80fb00"), //Genji, 31 | [SKColor.Parse("#2c2a19")] = SKColor.Parse("#b2a865"), //Hanzo, 32 | [SKColor.Parse("#3d2c05")] = SKColor.Parse("#f7b217"), //Junkrat, 33 | [SKColor.Parse("#11263c")] = SKColor.Parse("#469af0"), //Mei, 34 | [SKColor.Parse("#00162f")] = SKColor.Parse("#0058bc"), //Pharah, 35 | [SKColor.Parse("#170006")] = SKColor.Parse("#5e001a"), //Reaper, 36 | [SKColor.Parse("#350f0b")] = SKColor.Parse("#d73e2c"), //Sojourn, 37 | [SKColor.Parse("#11141d")] = SKColor.Parse("#445275"), //Soldier76, 38 | [SKColor.Parse("#140a2a")] = SKColor.Parse("#5128a9"), //Sombra, 39 | [SKColor.Parse("#1d2d32")] = SKColor.Parse("#76b4c9"), //Symmetra, 40 | [SKColor.Parse("#2e130f")] = SKColor.Parse("#ba4c3f"), //Torbjorn, 41 | [SKColor.Parse("#371e00")] = SKColor.Parse("#de7a00"), //Tracer, 42 | [SKColor.Parse("#220f23")] = SKColor.Parse("#8b3f8f"), //Widowmaker, 43 | [SKColor.Parse("#121a27")] = SKColor.Parse("#48699e"), //Ana, 44 | [SKColor.Parse("#0a2930")] = SKColor.Parse("#28a5c3"), //Baptiste, 45 | [SKColor.Parse("#1c0c0a")] = SKColor.Parse("#72332a"), //Brigitte, 46 | [SKColor.Parse("#292315")] = SKColor.Parse("#a58c54"), //Illari, 47 | [SKColor.Parse("#341115")] = SKColor.Parse("#d04656"), //Kiriko, 48 | [SKColor.Parse("#38292e")] = SKColor.Parse("#e1a5ba"), //Lifeweaver, 49 | [SKColor.Parse("#193106")] = SKColor.Parse("#67c519"), //Lucio, 50 | [SKColor.Parse("#3e3c2b")] = SKColor.Parse("#faf2ad"), //Mercy, 51 | [SKColor.Parse("#201239")] = SKColor.Parse("#804be5"), //Moira, 52 | [SKColor.Parse("#3f3b16")] = SKColor.Parse("#fcee5a"), //Zenyatta, 53 | }; 54 | 55 | public static Dictionary HeroesMap = new() 56 | { 57 | [SKColor.Parse("#dd7900")] = OverwatchHeroes.Base, 58 | [SKColor.Parse("#fc79bd")] = OverwatchHeroes.Dva, 59 | [SKColor.Parse("#661e0f")] = OverwatchHeroes.Doomfist, 60 | [SKColor.Parse("#579fcf")] = OverwatchHeroes.JunkerQueen, 61 | [SKColor.Parse("#106f04")] = OverwatchHeroes.Orisa, 62 | [SKColor.Parse("#7d55c7")] = OverwatchHeroes.Rammatra, 63 | [SKColor.Parse("#7c8b8c")] = OverwatchHeroes.Reinhardt, 64 | [SKColor.Parse("#ae6f1c")] = OverwatchHeroes.Roadhog, 65 | //[SKColor.Parse("#7c8b8c")] = OverwatchHeroes.Sigma, 66 | [SKColor.Parse("#8f92ae")] = OverwatchHeroes.Winston, 67 | [SKColor.Parse("#e2790a")] = OverwatchHeroes.WreckingBall, 68 | [SKColor.Parse("#f65ea6")] = OverwatchHeroes.Zarya, 69 | [SKColor.Parse("#3e3c3a")] = OverwatchHeroes.Ashe, 70 | [SKColor.Parse("#5b7351")] = OverwatchHeroes.Bastion, 71 | [SKColor.Parse("#a62927")] = OverwatchHeroes.Cassidy, 72 | [SKColor.Parse("#89c8ff")] = OverwatchHeroes.Echo, 73 | [SKColor.Parse("#80fb00")] = OverwatchHeroes.Genji, 74 | [SKColor.Parse("#b2a865")] = OverwatchHeroes.Hanzo, 75 | [SKColor.Parse("#f7b217")] = OverwatchHeroes.Junkrat, 76 | [SKColor.Parse("#469af0")] = OverwatchHeroes.Mei, 77 | [SKColor.Parse("#0058bc")] = OverwatchHeroes.Pharah, 78 | [SKColor.Parse("#5e001a")] = OverwatchHeroes.Reaper, 79 | [SKColor.Parse("#d73e2c")] = OverwatchHeroes.Sojourn, 80 | [SKColor.Parse("#445275")] = OverwatchHeroes.Soldier76, 81 | [SKColor.Parse("#5128a9")] = OverwatchHeroes.Sombra, 82 | [SKColor.Parse("#76b4c9")] = OverwatchHeroes.Symmetra, 83 | [SKColor.Parse("#ba4c3f")] = OverwatchHeroes.Torbjorn, 84 | [SKColor.Parse("#de7a00")] = OverwatchHeroes.Tracer, 85 | [SKColor.Parse("#8b3f8f")] = OverwatchHeroes.Widowmaker, 86 | [SKColor.Parse("#48699e")] = OverwatchHeroes.Ana, 87 | [SKColor.Parse("#28a5c3")] = OverwatchHeroes.Baptiste, 88 | [SKColor.Parse("#72332a")] = OverwatchHeroes.Brigitte, 89 | [SKColor.Parse("#a58c54")] = OverwatchHeroes.Illari, 90 | [SKColor.Parse("#d04656")] = OverwatchHeroes.Kiriko, 91 | [SKColor.Parse("#e1a5ba")] = OverwatchHeroes.Lifeweaver, 92 | [SKColor.Parse("#67c519")] = OverwatchHeroes.Lucio, 93 | [SKColor.Parse("#faf2ad")] = OverwatchHeroes.Mercy, 94 | [SKColor.Parse("#804be5")] = OverwatchHeroes.Moira, 95 | [SKColor.Parse("#fcee5a")] = OverwatchHeroes.Zenyatta, 96 | }; 97 | 98 | public enum OverwatchHeroes 99 | { 100 | Base, 101 | Dva, 102 | Doomfist, 103 | JunkerQueen, 104 | Orisa, 105 | Rammatra, 106 | Reinhardt, 107 | Roadhog, 108 | Sigma, 109 | Winston, 110 | WreckingBall, 111 | Zarya, 112 | Ashe, 113 | Bastion, 114 | Cassidy, 115 | Echo, 116 | Genji, 117 | Hanzo, 118 | Junkrat, 119 | Mei, 120 | Pharah, 121 | Reaper, 122 | Sojourn, 123 | Soldier76, 124 | Sombra, 125 | Symmetra, 126 | Torbjorn, 127 | Tracer, 128 | Widowmaker, 129 | Ana, 130 | Baptiste, 131 | Brigitte, 132 | Illari, 133 | Kiriko, 134 | Lifeweaver, 135 | Lucio, 136 | Mercy, 137 | Moira, 138 | Zenyatta, 139 | } 140 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio,dotnetcore 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,dotnetcore 4 | 5 | ### DotnetCore ### 6 | # .NET Core build folders 7 | bin/ 8 | obj/ 9 | 10 | # Common node modules locations 11 | /node_modules 12 | /wwwroot/node_modules 13 | 14 | ### VisualStudio ### 15 | ## Ignore Visual Studio temporary files, build results, and 16 | ## files generated by popular Visual Studio add-ons. 17 | ## 18 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 19 | 20 | # User-specific files 21 | *.rsuser 22 | *.suo 23 | *.user 24 | *.userosscache 25 | *.sln.docstates 26 | 27 | # User-specific files (MonoDevelop/Xamarin Studio) 28 | *.userprefs 29 | 30 | # Mono auto generated files 31 | mono_crash.* 32 | 33 | # Build results 34 | [Dd]ebug/ 35 | [Dd]ebugPublic/ 36 | [Rr]elease/ 37 | [Rr]eleases/ 38 | x64/ 39 | x86/ 40 | [Aa][Rr][Mm]/ 41 | [Aa][Rr][Mm]64/ 42 | bld/ 43 | [Bb]in/ 44 | [Oo]bj/ 45 | [Ll]og/ 46 | [Ll]ogs/ 47 | 48 | # Visual Studio 2015/2017 cache/options directory 49 | .vs/ 50 | # Uncomment if you have tasks that create the project's static files in wwwroot 51 | #wwwroot/ 52 | 53 | # Visual Studio 2017 auto generated files 54 | Generated\ Files/ 55 | 56 | # MSTest test Results 57 | [Tt]est[Rr]esult*/ 58 | [Bb]uild[Ll]og.* 59 | 60 | # NUnit 61 | *.VisualState.xml 62 | TestResult.xml 63 | nunit-*.xml 64 | 65 | # Build Results of an ATL Project 66 | [Dd]ebugPS/ 67 | [Rr]eleasePS/ 68 | dlldata.c 69 | 70 | # Benchmark Results 71 | BenchmarkDotNet.Artifacts/ 72 | 73 | # .NET Core 74 | project.lock.json 75 | project.fragment.lock.json 76 | artifacts/ 77 | 78 | # StyleCop 79 | StyleCopReport.xml 80 | 81 | # Files built by Visual Studio 82 | *_i.c 83 | *_p.c 84 | *_h.h 85 | *.ilk 86 | *.meta 87 | *.obj 88 | *.iobj 89 | *.pch 90 | *.pdb 91 | *.ipdb 92 | *.pgc 93 | *.pgd 94 | *.rsp 95 | *.sbr 96 | *.tlb 97 | *.tli 98 | *.tlh 99 | *.tmp 100 | *.tmp_proj 101 | *_wpftmp.csproj 102 | *.log 103 | *.vspscc 104 | *.vssscc 105 | .builds 106 | *.pidb 107 | *.svclog 108 | *.scc 109 | 110 | # Chutzpah Test files 111 | _Chutzpah* 112 | 113 | # Visual C++ cache files 114 | ipch/ 115 | *.aps 116 | *.ncb 117 | *.opendb 118 | *.opensdf 119 | *.sdf 120 | *.cachefile 121 | *.VC.db 122 | *.VC.VC.opendb 123 | 124 | # Visual Studio profiler 125 | *.psess 126 | *.vsp 127 | *.vspx 128 | *.sap 129 | 130 | # Visual Studio Trace Files 131 | *.e2e 132 | 133 | # TFS 2012 Local Workspace 134 | $tf/ 135 | 136 | # Guidance Automation Toolkit 137 | *.gpState 138 | 139 | # ReSharper is a .NET coding add-in 140 | _ReSharper*/ 141 | *.[Rr]e[Ss]harper 142 | *.DotSettings.user 143 | 144 | # TeamCity is a build add-in 145 | _TeamCity* 146 | 147 | # DotCover is a Code Coverage Tool 148 | *.dotCover 149 | 150 | # AxoCover is a Code Coverage Tool 151 | .axoCover/* 152 | !.axoCover/settings.json 153 | 154 | # Coverlet is a free, cross platform Code Coverage Tool 155 | coverage*[.json, .xml, .info] 156 | 157 | # Visual Studio code coverage results 158 | *.coverage 159 | *.coveragexml 160 | 161 | # NCrunch 162 | _NCrunch_* 163 | .*crunch*.local.xml 164 | nCrunchTemp_* 165 | 166 | # MightyMoose 167 | *.mm.* 168 | AutoTest.Net/ 169 | 170 | # Web workbench (sass) 171 | .sass-cache/ 172 | 173 | # Installshield output folder 174 | [Ee]xpress/ 175 | 176 | # DocProject is a documentation generator add-in 177 | DocProject/buildhelp/ 178 | DocProject/Help/*.HxT 179 | DocProject/Help/*.HxC 180 | DocProject/Help/*.hhc 181 | DocProject/Help/*.hhk 182 | DocProject/Help/*.hhp 183 | DocProject/Help/Html2 184 | DocProject/Help/html 185 | 186 | # Click-Once directory 187 | publish/ 188 | 189 | # Publish Web Output 190 | *.[Pp]ublish.xml 191 | *.azurePubxml 192 | # Note: Comment the next line if you want to checkin your web deploy settings, 193 | # but database connection strings (with potential passwords) will be unencrypted 194 | *.pubxml 195 | *.publishproj 196 | 197 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 198 | # checkin your Azure Web App publish settings, but sensitive information contained 199 | # in these scripts will be unencrypted 200 | PublishScripts/ 201 | 202 | # NuGet Packages 203 | *.nupkg 204 | # NuGet Symbol Packages 205 | *.snupkg 206 | # The packages folder can be ignored because of Package Restore 207 | **/[Pp]ackages/* 208 | # except build/, which is used as an MSBuild target. 209 | !**/[Pp]ackages/build/ 210 | # Uncomment if necessary however generally it will be regenerated when needed 211 | #!**/[Pp]ackages/repositories.config 212 | # NuGet v3's project.json files produces more ignorable files 213 | *.nuget.props 214 | *.nuget.targets 215 | 216 | # Microsoft Azure Build Output 217 | csx/ 218 | *.build.csdef 219 | 220 | # Microsoft Azure Emulator 221 | ecf/ 222 | rcf/ 223 | 224 | # Windows Store app package directories and files 225 | AppPackages/ 226 | BundleArtifacts/ 227 | Package.StoreAssociation.xml 228 | _pkginfo.txt 229 | *.appx 230 | *.appxbundle 231 | *.appxupload 232 | 233 | # Visual Studio cache files 234 | # files ending in .cache can be ignored 235 | *.[Cc]ache 236 | # but keep track of directories ending in .cache 237 | !?*.[Cc]ache/ 238 | 239 | # Others 240 | ClientBin/ 241 | ~$* 242 | *~ 243 | *.dbmdl 244 | *.dbproj.schemaview 245 | *.jfm 246 | *.pfx 247 | *.publishsettings 248 | orleans.codegen.cs 249 | 250 | # Including strong name files can present a security risk 251 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 252 | #*.snk 253 | 254 | # Since there are multiple workflows, uncomment next line to ignore bower_components 255 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 256 | #bower_components/ 257 | 258 | # RIA/Silverlight projects 259 | Generated_Code/ 260 | 261 | # Backup & report files from converting an old project file 262 | # to a newer Visual Studio version. Backup files are not needed, 263 | # because we have git ;-) 264 | _UpgradeReport_Files/ 265 | Backup*/ 266 | UpgradeLog*.XML 267 | UpgradeLog*.htm 268 | ServiceFabricBackup/ 269 | *.rptproj.bak 270 | 271 | # SQL Server files 272 | *.mdf 273 | *.ldf 274 | *.ndf 275 | 276 | # Business Intelligence projects 277 | *.rdl.data 278 | *.bim.layout 279 | *.bim_*.settings 280 | *.rptproj.rsuser 281 | *- [Bb]ackup.rdl 282 | *- [Bb]ackup ([0-9]).rdl 283 | *- [Bb]ackup ([0-9][0-9]).rdl 284 | 285 | # Microsoft Fakes 286 | FakesAssemblies/ 287 | 288 | # GhostDoc plugin setting file 289 | *.GhostDoc.xml 290 | 291 | # Node.js Tools for Visual Studio 292 | .ntvs_analysis.dat 293 | node_modules/ 294 | 295 | # Visual Studio 6 build log 296 | *.plg 297 | 298 | # Visual Studio 6 workspace options file 299 | *.opt 300 | 301 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 302 | *.vbw 303 | 304 | # Visual Studio LightSwitch build output 305 | **/*.HTMLClient/GeneratedArtifacts 306 | **/*.DesktopClient/GeneratedArtifacts 307 | **/*.DesktopClient/ModelManifest.xml 308 | **/*.Server/GeneratedArtifacts 309 | **/*.Server/ModelManifest.xml 310 | _Pvt_Extensions 311 | 312 | # Paket dependency manager 313 | .paket/paket.exe 314 | paket-files/ 315 | 316 | # FAKE - F# Make 317 | .fake/ 318 | 319 | # CodeRush personal settings 320 | .cr/personal 321 | 322 | # Python Tools for Visual Studio (PTVS) 323 | __pycache__/ 324 | *.pyc 325 | 326 | # Cake - Uncomment if you are using it 327 | # tools/** 328 | # !tools/packages.config 329 | 330 | # Tabs Studio 331 | *.tss 332 | 333 | # Telerik's JustMock configuration file 334 | *.jmconfig 335 | 336 | # BizTalk build output 337 | *.btp.cs 338 | *.btm.cs 339 | *.odx.cs 340 | *.xsd.cs 341 | 342 | # OpenCover UI analysis results 343 | OpenCover/ 344 | 345 | # Azure Stream Analytics local run output 346 | ASALocalRun/ 347 | 348 | # MSBuild Binary and Structured Log 349 | *.binlog 350 | 351 | # NVidia Nsight GPU debugger configuration file 352 | *.nvuser 353 | 354 | # MFractors (Xamarin productivity tool) working folder 355 | .mfractor/ 356 | 357 | # Local History for Visual Studio 358 | .localhistory/ 359 | 360 | # BeatPulse healthcheck temp database 361 | healthchecksdb 362 | 363 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 364 | MigrationBackup/ 365 | 366 | # Ionide (cross platform F# VS Code tools) working folder 367 | .ionide/ 368 | 369 | # End of https://www.toptal.com/developers/gitignore/api/visualstudio,dotnetcore 370 | src/.idea/ 371 | 372 | src/Artemis.Plugins.LayerBrushes.Chroma/Resources/ 373 | -------------------------------------------------------------------------------- /src/Artemis.Plugins.LayerBrushes.Chroma/DefaultChromaLedMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RGB.NET.Core; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Artemis.Plugins.LayerBrushes.Chroma; 7 | 8 | //https://developer.razer.com/works-with-chroma/razer-chroma-led-profiles/ 9 | public static class DefaultChromaLedMap 10 | { 11 | private static readonly LedId[] Keyboard = 12 | [ 13 | LedId.Invalid, 14 | LedId.Keyboard_Escape, 15 | LedId.Invalid, 16 | LedId.Keyboard_F1, 17 | LedId.Keyboard_F2, 18 | LedId.Keyboard_F3, 19 | LedId.Keyboard_F4, 20 | LedId.Keyboard_F5, 21 | LedId.Keyboard_F6, 22 | LedId.Keyboard_F7, 23 | LedId.Keyboard_F8, 24 | LedId.Keyboard_F9, 25 | LedId.Keyboard_F10, 26 | LedId.Keyboard_F11, 27 | LedId.Keyboard_F12, 28 | LedId.Keyboard_PrintScreen, 29 | LedId.Keyboard_ScrollLock, 30 | LedId.Keyboard_PauseBreak, 31 | LedId.Invalid, 32 | LedId.Invalid, 33 | LedId.Logo, 34 | LedId.Invalid, 35 | LedId.Keyboard_Macro1, 36 | LedId.Keyboard_GraveAccentAndTilde, 37 | LedId.Keyboard_1, 38 | LedId.Keyboard_2, 39 | LedId.Keyboard_3, 40 | LedId.Keyboard_4, 41 | LedId.Keyboard_5, 42 | LedId.Keyboard_6, 43 | LedId.Keyboard_7, 44 | LedId.Keyboard_8, 45 | LedId.Keyboard_9, 46 | LedId.Keyboard_0, 47 | LedId.Keyboard_MinusAndUnderscore, 48 | LedId.Keyboard_EqualsAndPlus, 49 | LedId.Keyboard_Backspace, 50 | LedId.Keyboard_Insert, 51 | LedId.Keyboard_Home, 52 | LedId.Keyboard_PageUp, 53 | LedId.Keyboard_NumLock, 54 | LedId.Keyboard_NumSlash, 55 | LedId.Keyboard_NumAsterisk, 56 | LedId.Keyboard_NumMinus, 57 | LedId.Keyboard_Macro2, 58 | LedId.Keyboard_Tab, 59 | LedId.Keyboard_Q, 60 | LedId.Keyboard_W, 61 | LedId.Keyboard_E, 62 | LedId.Keyboard_R, 63 | LedId.Keyboard_T, 64 | LedId.Keyboard_Y, 65 | LedId.Keyboard_U, 66 | LedId.Keyboard_I, 67 | LedId.Keyboard_O, 68 | LedId.Keyboard_P, 69 | LedId.Keyboard_BracketLeft, 70 | LedId.Keyboard_BracketRight, 71 | LedId.Keyboard_Backslash, 72 | LedId.Keyboard_Delete, 73 | LedId.Keyboard_End, 74 | LedId.Keyboard_PageDown, 75 | LedId.Keyboard_Num7, 76 | LedId.Keyboard_Num8, 77 | LedId.Keyboard_Num9, 78 | LedId.Keyboard_NumPlus, 79 | LedId.Keyboard_Macro3, 80 | LedId.Keyboard_CapsLock, 81 | LedId.Keyboard_A, 82 | LedId.Keyboard_S, 83 | LedId.Keyboard_D, 84 | LedId.Keyboard_F, 85 | LedId.Keyboard_G, 86 | LedId.Keyboard_H, 87 | LedId.Keyboard_J, 88 | LedId.Keyboard_K, 89 | LedId.Keyboard_L, 90 | LedId.Keyboard_SemicolonAndColon, 91 | LedId.Keyboard_ApostropheAndDoubleQuote, 92 | LedId.Keyboard_NonUsTilde, 93 | LedId.Keyboard_Enter, 94 | LedId.Invalid, 95 | LedId.Invalid, 96 | LedId.Invalid, 97 | LedId.Keyboard_Num4, 98 | LedId.Keyboard_Num5, 99 | LedId.Keyboard_Num6, 100 | LedId.Invalid, 101 | LedId.Keyboard_Macro4, 102 | LedId.Keyboard_LeftShift, 103 | LedId.Keyboard_NonUsBackslash, 104 | LedId.Keyboard_Z, 105 | LedId.Keyboard_X, 106 | LedId.Keyboard_C, 107 | LedId.Keyboard_V, 108 | LedId.Keyboard_B, 109 | LedId.Keyboard_N, 110 | LedId.Keyboard_M, 111 | LedId.Keyboard_CommaAndLessThan, 112 | LedId.Keyboard_PeriodAndBiggerThan, 113 | LedId.Keyboard_SlashAndQuestionMark, 114 | LedId.Invalid, 115 | LedId.Keyboard_RightShift, 116 | LedId.Invalid, 117 | LedId.Keyboard_ArrowUp, 118 | LedId.Invalid, 119 | LedId.Keyboard_Num1, 120 | LedId.Keyboard_Num2, 121 | LedId.Keyboard_Num3, 122 | LedId.Keyboard_NumEnter, 123 | LedId.Keyboard_Macro5, 124 | LedId.Keyboard_LeftCtrl, 125 | LedId.Keyboard_LeftGui, 126 | LedId.Keyboard_LeftAlt, 127 | LedId.Invalid, 128 | LedId.Invalid, 129 | LedId.Invalid, 130 | LedId.Keyboard_Space, 131 | LedId.Invalid, 132 | LedId.Invalid, 133 | LedId.Invalid, 134 | LedId.Keyboard_RightAlt, 135 | LedId.Keyboard_Function, 136 | LedId.Keyboard_Application, 137 | LedId.Keyboard_RightCtrl, 138 | LedId.Keyboard_ArrowLeft, 139 | LedId.Keyboard_ArrowDown, 140 | LedId.Keyboard_ArrowRight, 141 | LedId.Invalid, 142 | LedId.Keyboard_Num0, 143 | LedId.Keyboard_NumPeriodAndDelete, 144 | LedId.Invalid 145 | ]; 146 | 147 | private static readonly LedId[] Mouse = 148 | [ 149 | LedId.Invalid, 150 | LedId.Invalid, 151 | LedId.Invalid, 152 | LedId.Invalid, 153 | LedId.Invalid, 154 | LedId.Invalid, 155 | LedId.Invalid, 156 | LedId.Mouse1, 157 | LedId.Invalid, 158 | LedId.Invalid, 159 | LedId.Invalid, 160 | LedId.Invalid, 161 | LedId.Invalid, 162 | LedId.Mouse2, 163 | LedId.Mouse3, 164 | LedId.Invalid, 165 | LedId.Invalid, 166 | LedId.Mouse4, 167 | LedId.Invalid, 168 | LedId.Invalid, 169 | LedId.Mouse5, 170 | LedId.Mouse6, 171 | LedId.Invalid, 172 | LedId.Invalid, 173 | LedId.Invalid, 174 | LedId.Invalid, 175 | LedId.Invalid, 176 | LedId.Mouse7, 177 | LedId.Mouse8, 178 | LedId.Invalid, 179 | LedId.Invalid, 180 | LedId.Mouse9, 181 | LedId.Invalid, 182 | LedId.Invalid, 183 | LedId.Mouse10, 184 | LedId.Mouse11, 185 | LedId.Invalid, 186 | LedId.Invalid, 187 | LedId.Invalid, 188 | LedId.Invalid, 189 | LedId.Invalid, 190 | LedId.Mouse12, 191 | LedId.Mouse13, 192 | LedId.Invalid, 193 | LedId.Invalid, 194 | LedId.Invalid, 195 | LedId.Invalid, 196 | LedId.Invalid, 197 | LedId.Mouse14, 198 | LedId.Mouse15, 199 | LedId.Invalid, 200 | LedId.Invalid, 201 | LedId.Mouse16, 202 | LedId.Invalid, 203 | LedId.Invalid, 204 | LedId.Mouse17, 205 | LedId.Invalid, 206 | LedId.Mouse18, 207 | LedId.Mouse19, 208 | LedId.Mouse20, 209 | LedId.Mouse21, 210 | LedId.Mouse22, 211 | LedId.Invalid 212 | ]; 213 | 214 | private static readonly LedId[] Mousepad = 215 | [ 216 | LedId.Mousepad20, 217 | LedId.Mousepad19, 218 | LedId.Mousepad18, 219 | LedId.Mousepad17, 220 | LedId.Mousepad16, 221 | LedId.Mousepad15, 222 | LedId.Mousepad14, 223 | LedId.Mousepad13, 224 | LedId.Mousepad12, 225 | LedId.Mousepad11, 226 | LedId.Mousepad10, 227 | LedId.Mousepad9, 228 | LedId.Mousepad8, 229 | LedId.Mousepad7, 230 | LedId.Mousepad6, 231 | LedId.Mousepad5, 232 | LedId.Mousepad4, 233 | LedId.Mousepad3, 234 | LedId.Mousepad2, 235 | LedId.Mousepad1 236 | ]; 237 | 238 | private static readonly LedId[] Headset = 239 | [ 240 | LedId.Headset1, 241 | LedId.Headset2, 242 | LedId.Headset3, 243 | LedId.Headset4, 244 | LedId.Headset5 245 | ]; 246 | 247 | private static readonly LedId[] Keypad = 248 | [ 249 | LedId.Keypad1, 250 | LedId.Keypad2, 251 | LedId.Keypad3, 252 | LedId.Keypad4, 253 | LedId.Keypad5, 254 | LedId.Keypad6, 255 | LedId.Keypad7, 256 | LedId.Keypad8, 257 | LedId.Keypad9, 258 | LedId.Keypad10, 259 | LedId.Keypad11, 260 | LedId.Keypad12, 261 | LedId.Keypad13, 262 | LedId.Keypad14, 263 | LedId.Keypad15, 264 | LedId.Keypad16, 265 | LedId.Keypad17, 266 | LedId.Keypad18, 267 | LedId.Keypad19, 268 | LedId.Keypad20 269 | ]; 270 | 271 | private static readonly LedId[] ChromaLink = 272 | [ 273 | LedId.LedStripe1, 274 | LedId.LedStripe2, 275 | LedId.LedStripe3, 276 | LedId.LedStripe4, 277 | LedId.LedStripe5 278 | ]; 279 | 280 | public static LedId[] GetDeviceMap(RzDeviceType r) => r switch 281 | { 282 | RzDeviceType.Mouse => Mouse, 283 | RzDeviceType.Mousepad => Mousepad, 284 | RzDeviceType.Headset => Headset, 285 | RzDeviceType.Keypad => Keypad, 286 | RzDeviceType.Keyboard => Keyboard, 287 | RzDeviceType.ChromaLink => ChromaLink, 288 | _ => throw new ArgumentOutOfRangeException(nameof(r), r, null) 289 | }; 290 | } -------------------------------------------------------------------------------- /src/Artemis.Plugins.Modules.Spotify/SpotifyModule.cs: -------------------------------------------------------------------------------- 1 | using Artemis.Core; 2 | using Artemis.Core.ColorScience; 3 | using Artemis.Core.Modules; 4 | using Artemis.Plugins.Modules.Spotify.DataModels; 5 | using Serilog; 6 | using SkiaSharp; 7 | using SpotifyAPI.Web; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Net.Http; 13 | using System.Threading.Tasks; 14 | using Artemis.Core.Services; 15 | 16 | namespace Artemis.Plugins.Modules.Spotify; 17 | 18 | [PluginFeature(Name = "Spotify", AlwaysEnabled = true)] 19 | public class SpotifyModule : Module 20 | { 21 | public override List ActivationRequirements { get; } = new(); 22 | 23 | private readonly ILogger _logger; 24 | private readonly PluginSetting _token; 25 | private readonly PluginSetting> _cache; 26 | private readonly HttpClient _httpClient; 27 | 28 | public SpotifyModule(PluginSettings settings, ILogger logger) 29 | { 30 | _logger = logger; 31 | _token = settings.GetSetting(Constants.SPOTIFY_AUTH_SETTING); 32 | _cache = settings.GetSetting>("AlbumArtCache", new()); 33 | _httpClient = new HttpClient 34 | { 35 | Timeout = TimeSpan.FromSeconds(2) 36 | }; 37 | } 38 | 39 | private SpotifyClient? _spotify; 40 | private string? _trackId; 41 | private string? _contextId; 42 | private string? _albumArtUrl; 43 | private TrackAudioAnalysis? _analysis; 44 | 45 | public override void Enable() 46 | { 47 | try 48 | { 49 | try 50 | { 51 | Login(); 52 | } 53 | catch (Exception e) 54 | { 55 | _logger.Error(e, "Failed spotify authentication, login in the settings dialog"); 56 | } 57 | 58 | AddTimedUpdate(TimeSpan.FromSeconds(2), UpdatePlayback, nameof(UpdatePlayback)); 59 | AddDefaultProfile(DefaultCategoryName.Applications, Plugin.ResolveRelativePath("Spotify.zip")); 60 | } 61 | catch (Exception e) 62 | { 63 | _logger.Error(e, "Failed spotify authentication, login in the settings dialog"); 64 | } 65 | } 66 | 67 | public override void Disable() 68 | { 69 | _spotify = null; 70 | _trackId = null; 71 | _contextId = null; 72 | _albumArtUrl = null; 73 | } 74 | 75 | public override void Update(double deltaTime) 76 | { 77 | if (!DataModel.Player.IsPlaying) 78 | return; 79 | 80 | DataModel.Track.Progress += TimeSpan.FromSeconds(deltaTime); 81 | 82 | if (_analysis is null) 83 | return; 84 | 85 | var currentSeconds = DataModel.Track.Progress.TotalSeconds; 86 | var currentSection = _analysis.Sections.Find(s => currentSeconds > s.Start && 87 | currentSeconds < s.Start + s.Duration); 88 | if (currentSection != null) 89 | { 90 | DataModel.Track.Analysis.CurrentSection = currentSection; 91 | } 92 | 93 | var currentSegment = _analysis.Segments.Find(s => currentSeconds > s.Start && 94 | currentSeconds < s.Start + s.Duration); 95 | if (currentSegment != null) 96 | { 97 | DataModel.Track.Analysis.CurrentSegment = currentSegment; 98 | } 99 | } 100 | 101 | private async Task UpdatePlayback(double deltaTime) 102 | { 103 | //this will be null before authentication 104 | if (_spotify is null) 105 | return; 106 | 107 | CurrentlyPlayingContext? playing; 108 | try 109 | { 110 | playing = await _spotify.Player.GetCurrentPlayback(); 111 | } 112 | catch (Exception e) 113 | { 114 | _logger.Error(e, "Error updating playback"); 115 | return; 116 | } 117 | 118 | if (playing is null || DataModel is null) 119 | return; //weird 120 | 121 | try 122 | { 123 | await UpdatePlayerInfo(playing); 124 | } 125 | catch (Exception e) 126 | { 127 | _logger.Error(e, "Error updating player info"); 128 | } 129 | 130 | //in theory this can also be FullEpisode for podcasts 131 | //but it does not seem to work correctly. 132 | if (playing.Item is FullTrack track) 133 | { 134 | await UpdateTrackInfo(track); 135 | } 136 | } 137 | 138 | private async Task UpdateTrackInfo(FullTrack track) 139 | { 140 | if (_spotify is null) 141 | return; 142 | 143 | if (track.Id == _trackId) 144 | return; 145 | 146 | UpdateBasicTrackInfo(track); 147 | 148 | try 149 | { 150 | TrackAudioFeatures features = await _spotify.Tracks.GetAudioFeatures(track.Id); 151 | UpdateTrackFeatures(features); 152 | } 153 | catch (Exception e) 154 | { 155 | _logger.Error(e, "Error updating track audio features"); 156 | } 157 | 158 | try 159 | { 160 | _analysis = await _spotify.Tracks.GetAudioAnalysis(track.Id); 161 | } 162 | catch (Exception e) 163 | { 164 | _logger.Error(e, "Error getting track audio analysis"); 165 | } 166 | 167 | Image image = track.Album.Images.First(); 168 | if (image.Url != _albumArtUrl) 169 | { 170 | await UpdateAlbumArtColors(image.Url); 171 | _albumArtUrl = image.Url; 172 | } 173 | 174 | _trackId = track.Id; 175 | } 176 | 177 | private async Task UpdatePlayerInfo(CurrentlyPlayingContext playing) 178 | { 179 | if (_spotify is null) 180 | return; 181 | 182 | DataModel.Device.Name = playing.Device.Name; 183 | DataModel.Device.Type = playing.Device.Type; 184 | DataModel.Player.Shuffle = playing.ShuffleState; 185 | DataModel.Player.RepeatState = Enum.Parse(playing.RepeatState, true); 186 | DataModel.Player.Volume = playing.Device.VolumePercent ?? -1; 187 | DataModel.Player.IsPlaying = playing.IsPlaying; 188 | DataModel.Track.Progress = TimeSpan.FromMilliseconds(playing.ProgressMs); 189 | 190 | if (playing.Context != null && Enum.TryParse(playing.Context.Type, true, out ContextType contextType)) 191 | { 192 | DataModel.Player.ContextType = contextType; 193 | string contextId = playing.Context.Uri.Split(':').Last(); 194 | if (contextId != _contextId) 195 | { 196 | DataModel.Player.ContextName = contextType switch 197 | { 198 | ContextType.Artist => (await _spotify.Artists.Get(contextId)).Name, 199 | ContextType.Album => (await _spotify.Albums.Get(contextId)).Name, 200 | ContextType.Playlist => (await _spotify.Playlists.Get(contextId)).Name, 201 | _ => string.Empty 202 | }; 203 | 204 | _contextId = contextId; 205 | } 206 | } 207 | else 208 | { 209 | DataModel.Player.ContextType = ContextType.None; 210 | DataModel.Player.ContextName = string.Empty; 211 | _contextId = string.Empty; 212 | } 213 | } 214 | 215 | private async Task UpdateAlbumArtColors(string albumArtUrl) 216 | { 217 | var uri = albumArtUrl.Split('/').Last(); 218 | DataModel.Track.AlbumArtUrl = albumArtUrl; 219 | 220 | lock (_cache) 221 | { 222 | if (_cache.Value.TryGetValue(uri, out var swatch)) 223 | { 224 | DataModel.Track.Colors = swatch; 225 | return; 226 | } 227 | } 228 | 229 | try 230 | { 231 | var newSwatch = await GetAlbumColorsFromUri(albumArtUrl); 232 | lock (_cache) 233 | { 234 | _cache.Value[uri] = newSwatch; 235 | DataModel.Track.Colors = newSwatch; 236 | _cache.Save(); 237 | } 238 | } 239 | catch (Exception e) 240 | { 241 | _logger.Error(e, "Failed to get album art colors"); 242 | } 243 | } 244 | 245 | private async Task GetAlbumColorsFromUri(string uri) 246 | { 247 | using Stream stream = await _httpClient.GetStreamAsync(uri); 248 | using SKBitmap skbm = SKBitmap.Decode(stream); 249 | SKColor[] skClrs = ColorQuantizer.Quantize(skbm.Pixels, 256); 250 | return ColorQuantizer.FindAllColorVariations(skClrs, true); 251 | } 252 | 253 | private void UpdateBasicTrackInfo(FullTrack track) 254 | { 255 | DataModel.Track.Title = track.Name; 256 | DataModel.Track.Album = track.Album.Name; 257 | DataModel.Track.Artist = string.Join(", ", track.Artists.Select(a => a.Name)); 258 | DataModel.Track.Popularity = track.Popularity; 259 | DataModel.Track.Duration = TimeSpan.FromMilliseconds(track.DurationMs); 260 | } 261 | 262 | private void UpdateTrackFeatures(TrackAudioFeatures features) 263 | { 264 | DataModel.Track.Features.Acousticness = features.Acousticness * 100; 265 | DataModel.Track.Features.Danceability = features.Danceability * 100; 266 | DataModel.Track.Features.Energy = features.Energy * 100; 267 | DataModel.Track.Features.Instrumentalness = features.Instrumentalness * 100; 268 | DataModel.Track.Features.Liveness = features.Liveness * 100; 269 | DataModel.Track.Features.Loudness = features.Loudness; 270 | DataModel.Track.Features.Speechiness = features.Speechiness * 100; 271 | DataModel.Track.Features.Valence = features.Valence * 100; 272 | DataModel.Track.Features.Tempo = features.Tempo; 273 | DataModel.Track.Features.Key = (Key)features.Key; 274 | DataModel.Track.Features.Mode = (Mode)features.Mode; 275 | DataModel.Track.Features.TimeSignature = features.TimeSignature; 276 | } 277 | 278 | internal bool LoggedIn => _spotify != null; 279 | 280 | internal void Login() 281 | { 282 | PKCEAuthenticator authenticator = new PKCEAuthenticator(Constants.SPOTIFY_CLIENT_ID, _token.Value); 283 | authenticator.TokenRefreshed += (_, t) => 284 | { 285 | _token.Value = t; 286 | _token.Save(); 287 | _logger.Information("Refreshed spotify token!"); 288 | }; 289 | 290 | SpotifyClientConfig config = SpotifyClientConfig.CreateDefault() 291 | .WithAuthenticator(authenticator); 292 | 293 | _spotify = new SpotifyClient(config); 294 | } 295 | 296 | internal void Logout() 297 | { 298 | _spotify = null; 299 | } 300 | 301 | internal async Task GetUserInfo() 302 | { 303 | if (_spotify is null) 304 | return null; 305 | 306 | try 307 | { 308 | return await _spotify.UserProfile.Current(); 309 | } 310 | catch 311 | { 312 | return null; 313 | } 314 | } 315 | } --------------------------------------------------------------------------------