From 5febf8f9c1220e9f05ff377cf05fa2e47fdac164 Mon Sep 17 00:00:00 2001 From: Nyako <24845294+nyakowint@users.noreply.github.com> Date: Sun, 3 Dec 2023 06:03:52 -0500 Subject: [PATCH] Character counter, Live send mode, character counter, typing indicator option & text macros --- KeyboardOSC/ChatMode.cs | 160 ++++++-- KeyboardOSC/KeyboardOSC.sln | 25 ++ KeyboardOSC/Patches.cs | 99 ++++- KeyboardOSC/Plugin.cs | 47 ++- KeyboardOSC/PluginSettings.cs | 23 ++ KeyboardOSC/Tools.cs | 110 ++++-- README.md | 50 ++- SettingsKO.html | 24 ++ settingsKO.js | 528 +++++++++++++++++++++++++++ copy-xslibs.ps1 => update-xslibs.ps1 | 0 10 files changed, 950 insertions(+), 116 deletions(-) create mode 100644 KeyboardOSC/KeyboardOSC.sln create mode 100644 SettingsKO.html create mode 100644 settingsKO.js rename copy-xslibs.ps1 => update-xslibs.ps1 (100%) diff --git a/KeyboardOSC/ChatMode.cs b/KeyboardOSC/ChatMode.cs index 02c4442..e92fd3a 100644 --- a/KeyboardOSC/ChatMode.cs +++ b/KeyboardOSC/ChatMode.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Timers; +using BepInEx; using BepInEx.Logging; using HarmonyLib; using TMPro; @@ -15,13 +16,14 @@ namespace KeyboardOSC; public static class ChatMode { private static bool _isSilentMsg; - private static bool _allowPartialSend; + private static bool _isFirstMsg; private static string _currentText = ""; private static string _lastMsg = ""; - private static DateTime _lastTypingTime; private static readonly ManualLogSource Logger = Plugin.PluginLogger; private static TextMeshProUGUI _oscBarText; + private static TextMeshProUGUI _charCounter; private static List _currentlyDownStickyKeys = new(); + private static Timer _eventsTimer = new(1300); public static void HandleKey(KeyboardKey.VirtualKeyEventData eventData) { @@ -44,6 +46,8 @@ private static void ProcessKey(VirtualKeyCode key, KeyboardKey.VirtualKeyEventDa { var isCtrlHeld = _currentlyDownStickyKeys .Any(k => k.Key[0] is VirtualKeyCode.LCONTROL or VirtualKeyCode.RCONTROL); + var sendTyping = PluginSettings.GetSetting("TypingIndicator").Value; + var liveSendMode = PluginSettings.GetSetting("LiveSend").Value; switch (key) { // backspace/delete keys @@ -58,10 +62,14 @@ private static void ProcessKey(VirtualKeyCode key, KeyboardKey.VirtualKeyEventDa #if DEBUG Logger.LogInfo("bulk deleting chat text: " + _currentText); #endif + if (sendTyping) SendTyping(_currentText.Length != 0); + if (liveSendMode) _eventsTimer.Start(); return; } _currentText = _currentText.Remove(key is VirtualKeyCode.DELETE ? 0 : _currentText.Length - 1, 1); + if (sendTyping) SendTyping(_currentText.Length != 0); + if (liveSendMode) _eventsTimer.Start(); UpdateChatText(_currentText); return; } @@ -69,20 +77,22 @@ private static void ProcessKey(VirtualKeyCode key, KeyboardKey.VirtualKeyEventDa case VirtualKeyCode.TAB: _isSilentMsg = !_isSilentMsg; UpdateChatColor(); + Logger.LogInfo($"Silent mode: {_isSilentMsg}"); return; // clear shortcut case VirtualKeyCode.ESCAPE: - _currentText = ""; - UpdateChatText(_currentText); - Logger.LogInfo("INPUT CLEARED"); - SendTyping(false); + ClearInput(); + Logger.LogInfo("Input cleared"); return; case VirtualKeyCode.END: - SendMessage("/chatbox/input", string.Empty, true, false); + Tools.SendOsc("/chatbox/input", string.Empty, true, false); + Logger.LogInfo("Chatbox cleared"); return; case VirtualKeyCode.INSERT: _currentText = _lastMsg; UpdateChatText(_currentText); + _isFirstMsg = true; + Logger.LogInfo("Inserted last input"); return; // copy + paste case VirtualKeyCode.VK_C: @@ -94,54 +104,91 @@ private static void ProcessKey(VirtualKeyCode key, KeyboardKey.VirtualKeyEventDa _currentText += GUIUtility.systemCopyBuffer; UpdateChatText(_currentText); return; - // that silly "send as you're typing" quirk some other osc apps do - // no idea if this will break to the rate limit like my old method did, we'll see - case VirtualKeyCode.F6: - _allowPartialSend = !_allowPartialSend; - break; + // Send message (or clear for continuous) + case VirtualKeyCode.RETURN: + if (liveSendMode) + { + Logger.LogInfo($"Sending message: {_currentText.ReplaceShortcodes()} [^-^]"); + _lastMsg = _currentText; + SendMessage(true); + ClearInput(); + } + else + { + Logger.LogInfo($"Sending message: {_currentText.ReplaceShortcodes()}"); + SendMessage(); + ClearInput(); + } + + return; } + // Normal character inputs + if (sendTyping) SendTyping(_currentText.Length != 0); + if (liveSendMode) + { + _eventsTimer.Start(); + if (_currentText.IsNullOrWhiteSpace()) + _isFirstMsg = true; + } - if (key is VirtualKeyCode.RETURN) + _currentText += character; + UpdateChatText(_currentText); + } + + private static void TimerElapsed(object sender, ElapsedEventArgs e) + { + if (_isSilentMsg) return; + Logger.LogInfo("Timer elapsed, sending message"); + if (_currentText.IsNullOrWhiteSpace()) { - if (_currentText.Length <= 0) return; - Logger.LogInfo("CHAT SENT: " + _currentText); - _lastMsg = _currentText; - SendMessage("/chatbox/input", _currentText, true, !_isSilentMsg); - UpdateChatText(""); - _currentText = ""; - _isSilentMsg = false; - Plugin.ReleaseStickyKeys.Invoke(Plugin.Instance.inputHandler, null); + Tools.SendOsc("/chatbox/input", string.Empty, true, false); SendTyping(false); - UpdateChatColor(); - return; } + var sendTyping = PluginSettings.GetSetting("TypingIndicator").Value; + SendMessage(true); + if (sendTyping) SendTyping(true); + } - SendTyping(true); - _currentText += character; - UpdateChatText(_currentText); + private static void SendMessage(bool liveSend = false) + { + if (liveSend) + { + _eventsTimer.Stop(); + Tools.SendOsc("/chatbox/input", _currentText.ReplaceShortcodes(), true, !_isSilentMsg || _isFirstMsg); + SendTyping(false); + _isFirstMsg = false; + } + else + { + Tools.SendOsc("/chatbox/input", _currentText.ReplaceShortcodes(), true, !_isSilentMsg); + SendTyping(false); + _lastMsg = _currentText; + ClearInput(); + } } - private static void SendMessage(string address, string msg, bool now, bool sound) + private static void ClearInput() { - Tools.SendOsc(address, msg, now, sound); + UpdateChatText(string.Empty); + _currentText = string.Empty; + _isSilentMsg = false; + UpdateChatColor(); + Plugin.ReleaseStickyKeys.Invoke(Plugin.Instance.inputHandler, null); } private static void SendTyping(bool typing) { - if (typing && (DateTime.Now - _lastTypingTime).TotalSeconds <= 2 || _isSilentMsg) return; - _lastTypingTime = DateTime.Now; + if (typing && _isSilentMsg) return; Tools.SendOsc("/chatbox/typing", typing); - if (_allowPartialSend) - { - SendMessage("/chatbox/input", _currentText, true, false); - } } - public static void Setup(TextMeshProUGUI obText) + public static void Setup(TextMeshProUGUI barText, TextMeshProUGUI charCounter) { - _oscBarText = obText; + _oscBarText = barText; + _charCounter = charCounter; + _eventsTimer.Elapsed += TimerElapsed; var stickyKeysField = AccessTools.Field(typeof(KeyboardInputHandler), "CurrentlyDownStickyKeys"); _currentlyDownStickyKeys = (List) stickyKeysField.GetValue(Plugin.Instance.inputHandler); } @@ -150,15 +197,48 @@ private static void UpdateChatColor() { _oscBarText.color = _isSilentMsg ? UIThemeHandler.Instance.T_WarningTone : UIThemeHandler.Instance.T_ConstrastingTone; + _charCounter.color = _currentText.Length switch + { + >= 120 => UIThemeHandler.Instance.T_ErrTone, + >= 85 => UIThemeHandler.Instance.T_WarningTone, + _ => UIThemeHandler.Instance.T_ConstrastingTone + }; } private static void UpdateChatText(string text) { - if (text.Length > 250) + if (text.Length > 144) { - text = text.Substring(0, 250); + text = text.Substring(0, 144); } + XSTools.SetTMPUIText(_charCounter, $"{text.Length}/144"); + UpdateChatColor(); + XSTools.SetTMPUIText(_oscBarText, text); } + + private static string ReplaceShortcodes(this string input) + { + var shortcodes = new Dictionary + { + {"//shrug", "¯\\_(ツ)_/¯"}, + {"//happy", "(¬‿¬)"}, + {"//table", "┬─┬"}, + {"//music", "🎵"}, + {"//cookie", "🍪"}, + {"//star", "⭐"}, + {"//hrt", "💗"}, + {"//2hrt", "💕"}, + {"//skull", "💀"}, + {"//skull2", "☠"}, + }; + + foreach (var shortcode in shortcodes) + { + input = input.Replace(shortcode.Key, shortcode.Value); + } + + return input; + } } \ No newline at end of file diff --git a/KeyboardOSC/KeyboardOSC.sln b/KeyboardOSC/KeyboardOSC.sln new file mode 100644 index 0000000..e60bb23 --- /dev/null +++ b/KeyboardOSC/KeyboardOSC.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyboardOSC", "KeyboardOSC.csproj", "{72AD72A8-83B0-4A8B-B359-38C07643272E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {72AD72A8-83B0-4A8B-B359-38C07643272E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72AD72A8-83B0-4A8B-B359-38C07643272E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72AD72A8-83B0-4A8B-B359-38C07643272E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72AD72A8-83B0-4A8B-B359-38C07643272E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A5466BE1-0020-4D4B-9735-7FA8E10143B1} + EndGlobalSection +EndGlobal diff --git a/KeyboardOSC/Patches.cs b/KeyboardOSC/Patches.cs index b195071..4411352 100644 --- a/KeyboardOSC/Patches.cs +++ b/KeyboardOSC/Patches.cs @@ -11,20 +11,21 @@ namespace KeyboardOSC; public static class Patches { private static Harmony Harmony; + private static bool HasSettingsBeenOpenedOnce = false; public static void PatchAll() { Harmony = new Harmony("nwnt.keyboardosc"); - // patch key presses + + #region Patch Key Presses + var sendKeyMethod = AccessTools.Method(typeof(KeyboardInputHandler), "SendKey"); var sendKeyPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(KeyboardPatch))); Harmony.Patch(sendKeyMethod, sendKeyPatch); - // scale bar with keyboard? - var scaleMethod = - AccessTools.Method(typeof(WindowMovementManager), nameof(WindowMovementManager.DoScaleWindowFixed)); - var scalePatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(ScalePatch))); - Harmony.Patch(scaleMethod, null, scalePatch); + #endregion + + #region Stop inputs being sent to overlays // stop inputs being sent to overlays var keyPress = AccessTools.Method(typeof(KeyboardSimulator), nameof(KeyboardSimulator.KeyPress), @@ -34,20 +35,48 @@ public static void PatchAll() var keyUp = AccessTools.Method(typeof(KeyboardSimulator), nameof(KeyboardSimulator.KeyUp), new[] {typeof(VirtualKeyCode)}); var blockInputPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(BlockInput))); + Harmony.Patch(keyPress, prefix: blockInputPatch); Harmony.Patch(keyDown, prefix: blockInputPatch); Harmony.Patch(keyUp, prefix: blockInputPatch); + #endregion + + // Scale bar with keyboard + var scaleMethod = + AccessTools.Method(typeof(WindowMovementManager), nameof(WindowMovementManager.DoScaleWindowFixed)); + var scalePatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(ScalePatch))); + Harmony.Patch(scaleMethod, null, scalePatch); + // Disable analytics by default (Xiexe loves seeing my plugin errors im sure XD) // can be turned back on after launching if you want to send him stuff for some reason var initAnalytics = AccessTools.Method(typeof(AnalyticsManager), "Initialize"); var analyticsPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(AnalyticsPatch))); Harmony.Patch(initAnalytics, postfix: analyticsPatch); + + + #region Settings UI Related Patches + + var toggleAppSettings = + AccessTools.Method(typeof(Overlay_Manager), nameof(Overlay_Manager.ToggleEditMode)); + var settingsOverlayPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(SettingsOverlayPatch))); + + var setSettings = AccessTools.Method(typeof(XSettingsManager), nameof(XSettingsManager.SetSetting)); + var setSettingsPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(SetSettingPatch))); + + var reqSettings = AccessTools.Method(typeof(ServerBridge), "OnRequestCurrentSettings"); + var reqSettingsPatch = new HarmonyMethod(typeof(Patches).GetMethod(nameof(RequestSettingsPatch))); + + Harmony.Patch(toggleAppSettings, postfix: settingsOverlayPatch); + Harmony.Patch(setSettings, setSettingsPatch); + Harmony.Patch(reqSettings, reqSettingsPatch); + + #endregion } public static bool KeyboardPatch(KeyboardKey.VirtualKeyEventData keyEventData) { - if (Plugin.IsChatModeActive) ChatMode.HandleKey(keyEventData); + if (Plugin.ChatModeActive) ChatMode.HandleKey(keyEventData); return true; } @@ -55,18 +84,68 @@ public static void AnalyticsPatch() { XSettingsManager.Instance.Settings.SendAnalytics = false; } - + public static void ScalePatch(float dist, Unity_Overlay activeOverlay, float StartingWidth) { - if (!Plugin.IsChatModeActive || activeOverlay.overlayKey != "xso.overlay.keyboard") return; + if (!Plugin.ChatModeActive || activeOverlay.overlayKey != "xso.overlay.keyboard") return; var chatBar = Plugin.Instance.oscBarWindowObj.GetComponent(); chatBar.opacity = activeOverlay.opacity; Plugin.Instance.RepositionBar(chatBar, activeOverlay); } + public static void SettingsOverlayPatch() + { + if (!Plugin.ModifiedUiSuccess || HasSettingsBeenOpenedOnce) return; + var loadString = + $"http://localhost:{ExternalMessageHandler.Instance.Config.WebSocketPort + 1}/ui/SettingsKO.html"; + Overlay_Manager.Instance.GlobalSettingsMenuOverlay.OverlayWebView._webView.WebView.LoadUrl(loadString); + HasSettingsBeenOpenedOnce = true; + } + + public static void RequestSettingsPatch(string sender, string data) + { + // create new UiSettings instance + var settings = new UiSettings + { + KBCheckForUpdates = PluginSettings.GetSetting("CheckForUpdates").Value, + KBLiveSend = PluginSettings.GetSetting("LiveSend").Value, + KBTypingIndicator = PluginSettings.GetSetting("TypingIndicator").Value + }; + var data2 = JsonUtility.ToJson(settings, true); + ServerBridge.Instance.SendMessage("UpdateSettings", data2, null, sender); + } + + public static bool SetSettingPatch(string name, string value, string value1, bool sendAnalytics = false) + { + switch (name) + { + case "GoToOgSettings": + var loadString = + $"http://localhost:{ExternalMessageHandler.Instance.Config.WebSocketPort + 1}/ui/Settings.html"; + Overlay_Manager.Instance.GlobalSettingsMenuOverlay.OverlayWebView._webView.WebView.LoadUrl(loadString); + Overlay_Manager.Instance.ToggleApplicationSettings(); + break; + case "KBCheckForUpdates": + PluginSettings.SetSetting("CheckForUpdates", value); + break; + case "KBLiveSend": + PluginSettings.SetSetting("LiveSend", value); + break; + case "KBTypingIndicator": + PluginSettings.SetSetting("TypingIndicator", value); + break; + case "KBOpenRepo": + Application.OpenURL("https://github.com/nyakowint/xsoverlay-keyboard-osc"); + Tools.SendBread("KeyboardOSC Github link opened in browser!"); + break; + } + + return true; + } + public static bool BlockInput(VirtualKeyCode keyCode) { - if (!Plugin.IsChatModeActive) return true; + if (!Plugin.ChatModeActive) return true; // small caveat with the way i'm doing this: // modifier keys still get passed to windows so that i don't have to reimplement xso's logic for them diff --git a/KeyboardOSC/Plugin.cs b/KeyboardOSC/Plugin.cs index 6ec2447..9175d3c 100644 --- a/KeyboardOSC/Plugin.cs +++ b/KeyboardOSC/Plugin.cs @@ -2,7 +2,6 @@ using System.Reflection; using System.Threading.Tasks; using BepInEx; -using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using KeyboardOSC; @@ -19,16 +18,18 @@ namespace KeyboardOSC [BepInPlugin("nwnt.keyboardosc", "KeyboardOSC", AssemblyVersion)] public class Plugin : BaseUnityPlugin { - public const string AssemblyVersion = "1.1.1.0"; + public const string AssemblyVersion = "1.2.0.0"; public static Plugin Instance; public static ManualLogSource PluginLogger; - private static bool _isDebugConfig; - public static bool IsChatModeActive; + + public static bool isDebugConfig; + public static bool ChatModeActive; + public static bool ModifiedUiSuccess; + public Overlay_Manager overlayManager; public KeyboardInputHandler inputHandler; public GameObject toggleBarObj; - public GameObject oscBarWindowObj; public GameObject oscBarCanvas; @@ -37,13 +38,17 @@ public class Plugin : BaseUnityPlugin private void Awake() { #if DEBUG - _isDebugConfig = true; + isDebugConfig = true; #endif PluginLogger = Logger; if (Instance != null) Destroy(this); PluginSettings.ConfigFile = Config; PluginSettings.Init(); - if (!Environment.CommandLine.Contains("-batchmode") || _isDebugConfig) return; + + // Download modified settings code + ModifiedUiSuccess = Tools.DownloadModifiedUi(); + + if (!Environment.CommandLine.Contains("-batchmode") || isDebugConfig) return; Logger.LogWarning("XSOverlay runs in batchmode normally (headless, without a window)."); Logger.LogWarning("To see extended logs launch XSOverlay directly."); } @@ -56,7 +61,7 @@ private void Start() ReleaseStickyKeys = AccessTools.Method(typeof(KeyboardInputHandler), "ReleaseStickyKeys"); Patches.PatchAll(); - + ServerBridge.Instance.CommandMap["Keyboard"] = delegate { InitializeKeyboard(); @@ -75,7 +80,7 @@ public void InitializeKeyboard() SetupBar(); ServerBridge.Instance.CommandMap["Keyboard"] = delegate { Overlay_Manager.Instance.EnableKeyboard(); }; - Config.TryGetEntry(PluginSettings.sectionId, "CheckForUpdates", out var checkUpdates); + var checkUpdates = PluginSettings.GetSetting("CheckForUpdates"); if (checkUpdates.Value) Task.Run(Tools.CheckVersion); } @@ -176,22 +181,30 @@ private void SetupBar() camTrans.position = canvasTrans.position; camTrans.rotation = canvasTrans.rotation; // - var oscBarTextObj = Instantiate(keyboard.transform - .Find("Keyboard Canvas | Manager/Keyboard Background/KeyboardSettings/Options/Audio Toggle/Text (TMP)") - .gameObject, kbBackground.transform); + var oscBarTextObj = Instantiate(keyboard.transform.Find("Keyboard Canvas | Manager/Keyboard Background/KeyboardSettings/Options/Audio Toggle/Text (TMP)").gameObject, kbBackground.transform); + var barCharCounter = Instantiate(oscBarTextObj, kbBackground.transform); Destroy(oscBarCanvas.transform.Find("Keyboard Background/KeyboardSettings").gameObject); oscBarTextObj.Rename("KeyboardOSC Bar Text"); + barCharCounter.Rename("Character Counter"); var oscbarText = oscBarTextObj.GetComponent(); + var charCounterText = barCharCounter.GetComponent(); XSTools.SetTMPUIText(oscbarText, "type something silly!"); + XSTools.SetTMPUIText(charCounterText, "0/144"); oscbarText.fontSize = 250f; oscbarText.fontSizeMax = 250f; oscbarText.horizontalAlignment = HorizontalAlignmentOptions.Center; oscbarText.verticalAlignment = VerticalAlignmentOptions.Middle; + + charCounterText.fontSize = 100f; + charCounterText.fontSizeMax = 100f; + charCounterText.fontStyle = FontStyles.Bold; + charCounterText.horizontalAlignment = HorizontalAlignmentOptions.Right; + charCounterText.verticalAlignment = VerticalAlignmentOptions.Top; - ChatMode.Setup(oscbarText); + ChatMode.Setup(oscbarText, charCounterText); oscBarRoot.SetActive(true); keyboardWindowObj.SetActive(true); WindowMovementManager.MoveToEdgeOfWindowAndInheritRotation(oscBarWindow, keyboardWindow, @@ -210,11 +223,11 @@ public void ToggleChatMode() { ReleaseStickyKeys.Invoke(inputHandler, null); - IsChatModeActive = !IsChatModeActive; - oscBarWindowObj.SetActive(IsChatModeActive); + ChatModeActive = !ChatModeActive; + oscBarWindowObj.SetActive(ChatModeActive); var barOverlay = oscBarWindowObj.GetComponent(); - if (IsChatModeActive) + if (ChatModeActive) { RepositionBar(barOverlay, overlayManager.Keyboard_Overlay); } @@ -227,7 +240,7 @@ private void SetToggleColor() var chatButton = toggleBarObj.GetComponent