From f1a9e44736f7967e9227991528373e56746409e1 Mon Sep 17 00:00:00 2001 From: Cazzar Date: Tue, 20 May 2025 00:52:45 +1000 Subject: [PATCH 1/2] Add expression toggle action and expression caching for Stream Deck plugin Implemented the ExpressionToggleAction to handle toggling VTube Studio expressions via Stream Deck, supported by a new ExpressionCache class. Added related API requests, events, and models to VTube Studio API integration, and updated the plugin to target .NET 9.0. --- StreamdeckLib/Models/SetStateRequest.cs | 2 +- StreamdeckLib/Models/StatePayload.cs | 2 +- StreamdeckLib/StreamDeckConnection.cs | 6 +- VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs | 30 ++++++ VTubeStudioAPI/Models/Expression.cs | 24 +++++ VTubeStudioAPI/Requests/ApiRequest.cs | 2 + .../Requests/ExpressionActivationRequest.cs | 16 +++ .../Requests/ExpressionStateRequest.cs | 10 ++ .../Responses/ExpressionStateResponse.cs | 19 ++++ VTubeStudioAPI/Responses/ResponseType.cs | 4 + VTubeStudioAPI/VTubeStudioWebsocketClient.cs | 22 +++- .../Actions/ExpressionToggleAction.cs | 72 +++++++++++++ streamdeck-vtubestudio/ExpressionCache.cs | 99 ++++++++++++++++++ streamdeck-vtubestudio/Program.cs | 10 +- .../src/pages/ExpressionToggle/App.vue | 68 ++++++++++++ .../src/pages/ExpressionToggle/main.js | 26 +++++ streamdeck-vtubestudio/manifest.json | 20 ++++ streamdeck-vtubestudio/nlog.config | 5 +- .../streamdeck-vtubestudio.csproj | 2 +- .../vts_logo_transparent_darker.png | Bin 0 -> 55587 bytes 20 files changed, 423 insertions(+), 16 deletions(-) create mode 100644 VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs create mode 100644 VTubeStudioAPI/Models/Expression.cs create mode 100644 VTubeStudioAPI/Requests/ExpressionActivationRequest.cs create mode 100644 VTubeStudioAPI/Requests/ExpressionStateRequest.cs create mode 100644 VTubeStudioAPI/Responses/ExpressionStateResponse.cs create mode 100644 streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs create mode 100644 streamdeck-vtubestudio/ExpressionCache.cs create mode 100644 streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue create mode 100644 streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js create mode 100644 streamdeck-vtubestudio/vts_logo_transparent_darker.png diff --git a/StreamdeckLib/Models/SetStateRequest.cs b/StreamdeckLib/Models/SetStateRequest.cs index 8b294cf..f141e99 100644 --- a/StreamdeckLib/Models/SetStateRequest.cs +++ b/StreamdeckLib/Models/SetStateRequest.cs @@ -1,6 +1,6 @@ namespace StreamDeckLib.Models { - record SetStateRequest : ContextMessage + public record SetStateRequest : ContextMessage { public override string Event => "setState"; public StatePayload Payload { get; set; } diff --git a/StreamdeckLib/Models/StatePayload.cs b/StreamdeckLib/Models/StatePayload.cs index f303bdf..97b38ce 100644 --- a/StreamdeckLib/Models/StatePayload.cs +++ b/StreamdeckLib/Models/StatePayload.cs @@ -1,6 +1,6 @@ namespace StreamDeckLib.Models { - public record StatePayload(uint Payload); + public record StatePayload(uint State); record TitlePayload(string? Title, int Target, int State); } \ No newline at end of file diff --git a/StreamdeckLib/StreamDeckConnection.cs b/StreamdeckLib/StreamDeckConnection.cs index db52092..3c5dab8 100644 --- a/StreamdeckLib/StreamDeckConnection.cs +++ b/StreamdeckLib/StreamDeckConnection.cs @@ -96,10 +96,10 @@ private async Task ReceiveAsync() textBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count)); if (!result.EndOfMessage) continue; - _logger.LogDebug("Got JSON data: {ToString}", textBuffer.ToString()); + //_logger.LogDebug("Got JSON data: {ToString}", textBuffer.ToString()); var message = JsonConvert.DeserializeObject(textBuffer.ToString(), new StreamDeckMessageConverter()); - _logger.LogDebug("Got message of {Event} JSON data: {ToString}", message?.Event, textBuffer.ToString()); + //_logger.LogDebug("Got message of {Event} JSON data: {ToString}", message?.Event, textBuffer.ToString()); switch (message) { @@ -190,7 +190,7 @@ public async Task SendMessage(EventMessage message) { await _send.WaitAsync(); var json = JsonConvert.SerializeObject(message, new JsonSerializerSettings() { ContractResolver = new DefaultContractResolver() { NamingStrategy = new CamelCaseNamingStrategy() }, }); - _logger.LogDebug("Sending message of {Type} with JSON data {Json}", message.Event, json); + //_logger.LogDebug("Sending message of {Type} with JSON data {Json}", message.Event, json); var buf = Encoding.UTF8.GetBytes(json); await _socket.SendAsync(new ArraySegment(buf), WebSocketMessageType.Text, true, _cancelSource.Token); diff --git a/VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs b/VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs new file mode 100644 index 0000000..81d0cdd --- /dev/null +++ b/VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Events; + +public class HotkeyTriggeredEvent +{ + [JsonProperty("hotkeyID")] + public required string Id { get; set; } + + [JsonProperty("hotkeyName")] + public required string Name { get; set; } + + [JsonProperty("hotkeyAction")] + public required string Action { get; set; } + + [JsonProperty("hotkeyFile")] + public required string File { get; set; } + + [JsonProperty("hotkeyTriggeredByAPI")] + public bool ApiTriggered { get; set; } + + [JsonProperty("modelID")] + public required string ModelId { get; set; } + + [JsonProperty("modelName")] + public required string ModelName { get; set; } + + [JsonProperty("isLive2DItem")] + public bool IsLive2DItem { get; set; } +} diff --git a/VTubeStudioAPI/Models/Expression.cs b/VTubeStudioAPI/Models/Expression.cs new file mode 100644 index 0000000..b17783c --- /dev/null +++ b/VTubeStudioAPI/Models/Expression.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace VTubeStudioAPI.Models; + +public class Expression +{ + [JsonProperty("name")] + public required string Name { get; set; } + + [JsonProperty("file")] + public required string File { get; set; } + + [JsonProperty("active")] + public bool Active { get; set; } + + [JsonProperty("deactivateWhenKeyIsLetGo")] + public bool DeactivateWhenKeyIsLetGo { get; set; } + + [JsonProperty("autoDeactivateAfterSeconds")] + public bool AutoDeactivateAfterSeconds { get; set; } + + [JsonProperty("secondsRemaining")] + public int SecondsRemaining { get; set; } +} diff --git a/VTubeStudioAPI/Requests/ApiRequest.cs b/VTubeStudioAPI/Requests/ApiRequest.cs index 3e62822..fecde5d 100644 --- a/VTubeStudioAPI/Requests/ApiRequest.cs +++ b/VTubeStudioAPI/Requests/ApiRequest.cs @@ -1,8 +1,10 @@ using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses; +using Newtonsoft.Json; namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests; public abstract class ApiRequest { + [JsonIgnore] public abstract RequestType MessageType { get; } } \ No newline at end of file diff --git a/VTubeStudioAPI/Requests/ExpressionActivationRequest.cs b/VTubeStudioAPI/Requests/ExpressionActivationRequest.cs new file mode 100644 index 0000000..18b589a --- /dev/null +++ b/VTubeStudioAPI/Requests/ExpressionActivationRequest.cs @@ -0,0 +1,16 @@ +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses; +using Newtonsoft.Json; + +namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests; + +public class ExpressionActivationRequest(string? expressionFile = null, bool activate = true, float fadeTime = 0.5f) : ApiRequest +{ + [JsonProperty("expressionFile")] + public string? ExpressionFile { get; set; } = expressionFile; + [JsonProperty("active")] + public bool? Activate { get; set; } = activate; + [JsonProperty("fadeTime")] + public float FadeTime { get; set; } = fadeTime; + + public override RequestType MessageType { get; } = RequestType.ExpressionActivationRequest; +} diff --git a/VTubeStudioAPI/Requests/ExpressionStateRequest.cs b/VTubeStudioAPI/Requests/ExpressionStateRequest.cs new file mode 100644 index 0000000..8411dbb --- /dev/null +++ b/VTubeStudioAPI/Requests/ExpressionStateRequest.cs @@ -0,0 +1,10 @@ +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses; + +namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests; + +public class ExpressionStateRequest(string? expressionFile = null) : ApiRequest +{ + public string? ExpressionFile { get; set; } = expressionFile; + + public override RequestType MessageType { get; } = RequestType.ExpressionStateRequest; +} \ No newline at end of file diff --git a/VTubeStudioAPI/Responses/ExpressionStateResponse.cs b/VTubeStudioAPI/Responses/ExpressionStateResponse.cs new file mode 100644 index 0000000..aa322d4 --- /dev/null +++ b/VTubeStudioAPI/Responses/ExpressionStateResponse.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using VTubeStudioAPI.Models; + +namespace VTubeStudioAPI.Responses; + +public class ExpressionStateResponse +{ + [JsonProperty("modelLoaded")] + public bool ModelLoaded { get; set; } + + [JsonProperty("modelName")] + public required string ModelName { get; set; } + + [JsonProperty("modelID")] + public required string ModelId { get; set; } + + [JsonProperty("expressions")] + public required List Expressions { get; set; } +} \ No newline at end of file diff --git a/VTubeStudioAPI/Responses/ResponseType.cs b/VTubeStudioAPI/Responses/ResponseType.cs index fdacec6..d9638a4 100644 --- a/VTubeStudioAPI/Responses/ResponseType.cs +++ b/VTubeStudioAPI/Responses/ResponseType.cs @@ -23,6 +23,8 @@ public enum RequestType ParameterDeletionRequest, InjectParameterDataRequest, EventSubscriptionRequest, + ExpressionStateRequest, + ExpressionActivationRequest, } public enum ResponseType @@ -52,6 +54,8 @@ public enum ResponseType ModelMovedEvent, ModelConfigChangedEvent, HotkeyTriggeredEvent, + ExpressionStateResponse, + ExpressionActivationResponse, } /// diff --git a/VTubeStudioAPI/VTubeStudioWebsocketClient.cs b/VTubeStudioAPI/VTubeStudioWebsocketClient.cs index 5a5f8c9..9b92edf 100644 --- a/VTubeStudioAPI/VTubeStudioWebsocketClient.cs +++ b/VTubeStudioAPI/VTubeStudioWebsocketClient.cs @@ -4,6 +4,7 @@ using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using VTubeStudioAPI.Responses; using WebSocketSharp; namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi; @@ -42,7 +43,7 @@ private void HandleAuthResponse(object? sender, ApiEventArgs("ModelMovedEvent")); Send(new EventSubscriptionRequest("ModelConfigChangedEvent")); - // Send(new EventSubscriptionRequest("HotkeyTriggeredEvent")); + Send(new EventSubscriptionRequest("HotkeyTriggeredEvent")); } public void Send(ApiRequest request, string? requestId = null) @@ -77,7 +78,7 @@ public void SetConnection(string host, ushort port) public void ConnectIfNeeded() { - _logger.LogDebug("Ws is null? {WsNull}, ws alive? {WsAlive}, trying to connect? {TryingToConnect}", _ws == null, _ws?.IsAlive, _tryingToConnect); + //_logger.LogDebug("Ws is null? {WsNull}, ws alive? {WsAlive}, trying to connect? {TryingToConnect}", _ws == null, _ws?.IsAlive, _tryingToConnect); if (_ws is not null && _ws.IsAlive) return; if (_tryingToConnect) return; @@ -87,6 +88,7 @@ public void ConnectIfNeeded() { Connect(); _tryingToConnect = false; + _tryingToConnect = false; } ); } @@ -131,6 +133,7 @@ private void MessageReceived(object? sender, MessageEventArgs e) var response = JsonConvert.DeserializeObject(e.Data); if (response is null) return; + _logger.LogDebug("Got message: {JsonString}", e.Data); _logger.LogDebug("Got message of type: {Type}", response.MessageType); switch (response.MessageType) @@ -192,9 +195,15 @@ private void MessageReceived(object? sender, MessageEventArgs e) case ResponseType.ModelConfigChangedEvent: OnModelConfigChangedEvent?.Invoke(this, new (response.Data!.ToObject()!)); break; - // case ResponseType.HotkeyTriggeredEvent: - // OnHotkeyTriggeredEvent?.Invoke(this, new (response.Data!.ToObject()!)); - // break; + case ResponseType.HotkeyTriggeredEvent: + OnHotkeyTriggeredEvent?.Invoke(this, new (response.Data!.ToObject()!)); + break; + case ResponseType.ExpressionStateResponse: + OnExpressionState?.Invoke(this, new (response.Data!.ToObject()!)); + break; + case ResponseType.ExpressionActivationResponse: + OnExpressionActivation?.Invoke(this, EventArgs.Empty); + break; default: throw new ArgumentOutOfRangeException(); } } @@ -234,5 +243,8 @@ public void Reconnect() public static event EventHandler>? OnCurrentModelInformation; public static event EventHandler>? OnModelMove; public static event EventHandler>? OnModelConfigChangedEvent; + public static event EventHandler>? OnHotkeyTriggeredEvent; + public static event EventHandler>? OnExpressionState; + public static event EventHandler? OnExpressionActivation; #endregion } \ No newline at end of file diff --git a/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs b/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs new file mode 100644 index 0000000..27837a6 --- /dev/null +++ b/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs @@ -0,0 +1,72 @@ +#nullable enable +using System.Linq; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using StreamDeckLib; +using StreamDeckLib.Models; + +namespace Cazzar.StreamDeck.VTubeStudio.Actions; + + +[StreamDeckAction("dev.cazzar.vtubestudio.expressiontoggle")] +public class ExpressionToggleAction : BaseAction +{ + private readonly ExpressionCache _expressionCache; + + public class ExpressionTogglePayload + { + [JsonProperty("expression")] + public string? Expression { get; set; } = "Cat Tail.exp3.json"; + + [JsonProperty("fadeTime")] + public float FadeTime { get; set; } = 0.5f; + } + + public ExpressionToggleAction(GlobalSettingsManager gsm, VTubeStudioWebsocketClient vts, IStreamDeckConnection isd, ILogger logger, ExpressionCache expressionCache) : base(gsm, vts, isd, logger) + { + _expressionCache = expressionCache; + } + + protected override void Pressed() + { + // if (!_expressionCache.ModelLoaded) + // return; + + var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false; + + Vts.Send(new ExpressionActivationRequest(Settings.Expression, activate)); + + Connection.SendMessage(new SetStateRequest + { + Payload = new ((uint) (activate ? 1 : 0)), + Context = ContextId, + }); + } + + public override void Tick() + { + base.Tick(); + var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false; + + Connection.SendMessage(new SetStateRequest + { + Payload = new ((uint) (activate ? 1 : 0)), + Context = ContextId, + }); + } + + protected override object GetClientData() => new + { + sxpressions = _expressionCache.Expressions.DistinctBy(e => e.File).Select(e => new { e.Name, e.File }), + }; + + protected override void Released() + { + } + + protected override void SettingsUpdated(ExpressionTogglePayload oldSettings, ExpressionTogglePayload newSettings) + { + } +} \ No newline at end of file diff --git a/streamdeck-vtubestudio/ExpressionCache.cs b/streamdeck-vtubestudio/ExpressionCache.cs new file mode 100644 index 0000000..fbca189 --- /dev/null +++ b/streamdeck-vtubestudio/ExpressionCache.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Events; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests; +using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses; +using Microsoft.Extensions.Logging; +using VTubeStudioAPI.Models; +using VTubeStudioAPI.Responses; + +namespace Cazzar.StreamDeck.VTubeStudio; + +public class ExpressionCache +{ + private readonly VTubeStudioWebsocketClient _vts; + private readonly ILogger _logger; + + private readonly SemaphoreSlim _semaphore = new (1, 1); + + private static readonly List> instances = new(); + + public ReadOnlyCollection Expressions { get; set; } + public string LastModelId { get; set; } + public string LastModelName { get; set; } + public bool ModelLoaded { get; set; } + + + public ExpressionCache(ModelCache modelCache, VTubeStudioWebsocketClient vts, ILogger logger) + { + instances.Add(new(this)); + _vts = vts; + _logger = logger; + VTubeStudioWebsocketClient.OnExpressionState += OnExpressionState; + VTubeStudioWebsocketClient.OnModelLoad += Refresh; + VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent += RefreshHotkey; + VTubeStudioWebsocketClient.OnCurrentModelInformation += Refresh; + VTubeStudioWebsocketClient.OnAuthenticationResponse += HandleAuthenticated; + VTubeStudioWebsocketClient.OnExpressionActivation += Refresh; + } + private void HandleAuthenticated(object sender, ApiEventArgs e) + { + if (!e.Response.Authenticated) + return; + + Refresh(sender, e); + } + + private void RefreshHotkey(object sender, ApiEventArgs e) + { + if (e.Response.Action != "ToggleExpression") return; + + Refresh(sender, e); + } + + private void Refresh(object sender, object e) + { + _vts.Send(new ExpressionStateRequest()); + } + + private async void OnExpressionState(object sender, ApiEventArgs e) + { + try + { + await _semaphore.WaitAsync(); + + Expressions = e.Response.Expressions.AsReadOnly(); + LastModelId = e.Response.ModelId; + LastModelName = e.Response.ModelName; + ModelLoaded = e.Response.ModelLoaded; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating expression cache"); + } + finally + { + _semaphore.Release(); + } + } + + ~ExpressionCache() + { + instances.Remove(new(this)); + VTubeStudioWebsocketClient.OnExpressionState -= OnExpressionState; + VTubeStudioWebsocketClient.OnModelLoad -= Refresh; + VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent -= RefreshHotkey; + VTubeStudioWebsocketClient.OnCurrentModelInformation -= Refresh; + VTubeStudioWebsocketClient.OnAuthenticationResponse -= HandleAuthenticated; + } + + public event EventHandler Updated; +} + +public class ExpressionCacheUpdatedEventArgs : EventArgs +{ + public IDictionary> Expressions { get; init; } +} \ No newline at end of file diff --git a/streamdeck-vtubestudio/Program.cs b/streamdeck-vtubestudio/Program.cs index c7e5822..6ea6b0c 100644 --- a/streamdeck-vtubestudio/Program.cs +++ b/streamdeck-vtubestudio/Program.cs @@ -20,8 +20,8 @@ public static async Task Main(string[] args) #if DEBUG // //get roaming app data folder - // var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); - // args = await File.ReadAllLinesAsync(Path.Combine(path, @"Elgato\StreamDeck\Plugins\dev.cazzar.streamdeck.vtubestudio.sdPlugin\argv.txt")); + var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); + args = await File.ReadAllLinesAsync(Path.Combine(path, @"Elgato\StreamDeck\Plugins\dev.cazzar.streamdeck.vtubestudio.sdPlugin\argv.txt")); #endif var hostBuilder = CreateHostBuilder(args); @@ -51,6 +51,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) services.AddSingleton((sp) => sp.GetService()!); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddLogging( logging => { @@ -60,7 +61,10 @@ private static IHostBuilder CreateHostBuilder(string[] args) } ); } - ); + ).UseDefaultServiceProvider((context, options) => + { + options.ValidateOnBuild = true; + }); } } } diff --git a/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue b/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue new file mode 100644 index 0000000..62d177c --- /dev/null +++ b/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js b/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js new file mode 100644 index 0000000..5f27c5f --- /dev/null +++ b/streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js @@ -0,0 +1,26 @@ +import { createApp } from 'vue' +import StreamDeck from '../../utils/streamdeck'; +import App from './App.vue' +import { createStore } from 'vuex'; + +window.connectElgatoStreamDeckSocket = function (inPort, inPluginUUID, inRegisterEvent, inInfo, inActionInfo) { + let streamDeck = new StreamDeck(inPort, inPluginUUID, inRegisterEvent, inInfo, inActionInfo); + console.log("Connected to stream deck") + + let store = createStore({ + state: { + streamDeck, + }, + mutations: { + }, + actions: { + }, + modules: { + } + }) + + createApp(App).use(store).mount('#app') +} + + + diff --git a/streamdeck-vtubestudio/manifest.json b/streamdeck-vtubestudio/manifest.json index b3b1285..b2699dd 100644 --- a/streamdeck-vtubestudio/manifest.json +++ b/streamdeck-vtubestudio/manifest.json @@ -181,6 +181,26 @@ "Tooltip": "VTubeStudio [Hold To Zoom]", "UUID": "dev.cazzar.vtubestudio.holdtozoom", "PropertyInspectorPath": "PropertyInspector/TempZoom.html" + }, + { + "Icon": "vts_logo_transparent", + "Name": "Hold To Zoom", + "States": [ + { + "Image": "vts_logo_transparent", + "TitleAlignment": "bottom", + "FontSize": "10" + }, + { + "Image": "vts_logo_transparent_darker", + "TitleAlignment": "bottom", + "FontSize": "10" + } + ], + "SupportedInMultiActions": true, + "Tooltip": "VTubeStudio [Expression Toggle]", + "UUID": "dev.cazzar.vtubestudio.expressiontoggle", + "PropertyInspectorPath": "PropertyInspector/NoSettings.html" } ], "Author": "Cazzar", diff --git a/streamdeck-vtubestudio/nlog.config b/streamdeck-vtubestudio/nlog.config index b569e0c..b0e7a33 100644 --- a/streamdeck-vtubestudio/nlog.config +++ b/streamdeck-vtubestudio/nlog.config @@ -13,16 +13,17 @@ layout="${longdate}|${level}|${logger}|${message} | ${all-event-properties} ${exception:format=tostring}" maxArchiveFiles="2" archiveOldFileOnStartup="true" archiveEvery="Day" enableArchiveFileCompression="true" /> + --> + - --> - --> \ No newline at end of file diff --git a/streamdeck-vtubestudio/streamdeck-vtubestudio.csproj b/streamdeck-vtubestudio/streamdeck-vtubestudio.csproj index 485b04d..e47754e 100644 --- a/streamdeck-vtubestudio/streamdeck-vtubestudio.csproj +++ b/streamdeck-vtubestudio/streamdeck-vtubestudio.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 true win-x64 win-x64;osx-x64 diff --git a/streamdeck-vtubestudio/vts_logo_transparent_darker.png b/streamdeck-vtubestudio/vts_logo_transparent_darker.png new file mode 100644 index 0000000000000000000000000000000000000000..19bc513dea04aa8acb79f0174d4fce8c3b68e447 GIT binary patch literal 55587 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 (0M5)n&Z9?sOmb0=mcPvEnti@WEp) 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 e*=ZZwEPGxbcC$BM5x=wtel8kW8Wvx0x%)4crm+ Date: Tue, 20 May 2025 01:21:42 +1000 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Actions/ExpressionToggleAction.cs | 30 +++++++++---------- streamdeck-vtubestudio/ExpressionCache.cs | 19 ++++++++---- streamdeck-vtubestudio/manifest.json | 2 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs b/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs index 27837a6..077ee69 100644 --- a/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs +++ b/streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs @@ -29,21 +29,21 @@ public ExpressionToggleAction(GlobalSettingsManager gsm, VTubeStudioWebsocketCli _expressionCache = expressionCache; } - protected override void Pressed() - { - // if (!_expressionCache.ModelLoaded) - // return; +protected override void Pressed() +{ + if (!_expressionCache.ModelLoaded) + return; - var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false; - - Vts.Send(new ExpressionActivationRequest(Settings.Expression, activate)); - - Connection.SendMessage(new SetStateRequest - { - Payload = new ((uint) (activate ? 1 : 0)), - Context = ContextId, - }); - } + var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false; + + Vts.Send(new ExpressionActivationRequest(Settings.Expression, activate, Settings.FadeTime)); + + Connection.SendMessage(new SetStateRequest + { + Payload = new ((uint) (activate ? 1 : 0)), + Context = ContextId, + }); +} public override void Tick() { @@ -59,7 +59,7 @@ public override void Tick() protected override object GetClientData() => new { - sxpressions = _expressionCache.Expressions.DistinctBy(e => e.File).Select(e => new { e.Name, e.File }), + expressions = _expressionCache.Expressions.DistinctBy(e => e.File).Select(e => new { e.Name, e.File }), }; protected override void Released() diff --git a/streamdeck-vtubestudio/ExpressionCache.cs b/streamdeck-vtubestudio/ExpressionCache.cs index fbca189..e825712 100644 --- a/streamdeck-vtubestudio/ExpressionCache.cs +++ b/streamdeck-vtubestudio/ExpressionCache.cs @@ -21,10 +21,10 @@ public class ExpressionCache private static readonly List> instances = new(); - public ReadOnlyCollection Expressions { get; set; } - public string LastModelId { get; set; } - public string LastModelName { get; set; } - public bool ModelLoaded { get; set; } + public ReadOnlyCollection Expressions { get; set; } = new List().AsReadOnly(); + public string LastModelId { get; set; } = string.Empty; + public string LastModelName { get; set; } = string.Empty; + public bool ModelLoaded { get; set; } = false; public ExpressionCache(ModelCache modelCache, VTubeStudioWebsocketClient vts, ILogger logger) @@ -82,7 +82,16 @@ private async void OnExpressionState(object sender, ApiEventArgs !wr.TryGetTarget(out _) || + (wr.TryGetTarget(out var cache) && cache == this)); + VTubeStudioWebsocketClient.OnExpressionState -= OnExpressionState; + VTubeStudioWebsocketClient.OnModelLoad -= Refresh; + VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent -= RefreshHotkey; + VTubeStudioWebsocketClient.OnCurrentModelInformation -= Refresh; + VTubeStudioWebsocketClient.OnAuthenticationResponse -= HandleAuthenticated; + } VTubeStudioWebsocketClient.OnExpressionState -= OnExpressionState; VTubeStudioWebsocketClient.OnModelLoad -= Refresh; VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent -= RefreshHotkey; diff --git a/streamdeck-vtubestudio/manifest.json b/streamdeck-vtubestudio/manifest.json index b2699dd..4717d0e 100644 --- a/streamdeck-vtubestudio/manifest.json +++ b/streamdeck-vtubestudio/manifest.json @@ -184,7 +184,7 @@ }, { "Icon": "vts_logo_transparent", - "Name": "Hold To Zoom", + "Name": "Expression Toggle", "States": [ { "Image": "vts_logo_transparent",