From 41bf15516fbda93de75efb67d33c4560765b824d Mon Sep 17 00:00:00 2001 From: Ivan Martell Date: Sat, 20 Jul 2024 18:43:44 +0900 Subject: [PATCH] Added custom hotkeys in .ini file, and more robustness --- LittleWarGameClient/AddOns.js | 44 ++++-- LittleWarGameClient/Form1.cs | 10 +- LittleWarGameClient/Fullscreen.cs | 1 + LittleWarGameClient/KeyboardHandler.cs | 109 ++++++++++----- LittleWarGameClient/Settings.cs | 177 ++++++++++++++++++------- LittleWarGameClient/SettingsHelper.cs | 129 ++++++++++++++++++ LittleWarGameClient/VersionHandler.cs | 35 +++-- 7 files changed, 393 insertions(+), 112 deletions(-) create mode 100644 LittleWarGameClient/SettingsHelper.cs diff --git a/LittleWarGameClient/AddOns.js b/LittleWarGameClient/AddOns.js index 2389c46..bfd90e8 100644 --- a/LittleWarGameClient/AddOns.js +++ b/LittleWarGameClient/AddOns.js @@ -44,6 +44,22 @@ } }, + addOptionsMenuHotkey: function (hotkey) { + this.addCustomHotkeyToTitle("ingameMenuButton", hotkey); + }, + + addFriendsMenuHotkey: function (hotkey) { + this.addCustomHotkeyToTitle("friendsButton", hotkey); + }, + + addChatHistoryHotkey: function (hotkey) { + this.addCustomHotkeyToTitle("ingameChatHistoryButton", hotkey); + }, + + addFullscreenBtnHotkey: function (hotkey) { + this.addCustomHotkeyToInnerText("optionsFullscreenButton", hotkey); + }, + setSmallWindowSizes: function () { this.setElement("optionsWindow", "height: 600px;"); this.setElement("queriesWindow", "height: 600px;"); @@ -76,6 +92,24 @@ if (smallIdx == -1) { element.style.cssText += styleProperty; } + }, + + addCustomHotkeyToTitle: function (elementId, hotkey) { + var element = document.getElementById(elementId); + var hotkeyIdx = element.title.indexOf('[') + if (hotkeyIdx != -1) { + element.title = element.title.substring(0, hotkeyIdx).trim(); + } + element.title = `${element.title} [${hotkey}]`; + }, + + addCustomHotkeyToInnerText: function (elementId, hotkey) { + var element = document.getElementById(elementId); + var hotkeyIdx = element.innerText.indexOf('[') + if (hotkeyIdx != -1) { + element.innerText = element.innerText.substring(0, hotkeyIdx).trim(); + } + element.innerText = `${element.innerText} [${hotkey}]`; } }; @@ -83,7 +117,6 @@ addons.init = { function(mouseLock, clientVersion) { this.addExitButton(); this.addClientVersion(clientVersion); - this.addCustomHotkeysToTitles(); this.replaceMouseLockCheckbox(mouseLock); var fullScreenButton = document.getElementById("optionsFullscreenButton"); fullScreenButton.onclick = function () { @@ -98,15 +131,6 @@ addons.init = { title.innerText = `${title.innerText} [Client v${clientVersion}]`; }, - addCustomHotkeysToTitles: function () { - var menuButton = document.getElementById("ingameMenuButton"); - menuButton.title = `${menuButton.title} [F10]`; - var friendsButton = document.getElementById("friendsButton"); - friendsButton.title = `${friendsButton.title} [F9]`; - var chatHistoryButton = document.getElementById("ingameChatHistoryButton"); - chatHistoryButton.title = `${chatHistoryButton.title} [F11]`; - }, - addExitButton: function () { var exitId = "exitButton"; if (!document.getElementById(exitId)) { diff --git a/LittleWarGameClient/Form1.cs b/LittleWarGameClient/Form1.cs index b461506..3910130 100644 --- a/LittleWarGameClient/Form1.cs +++ b/LittleWarGameClient/Form1.cs @@ -25,7 +25,7 @@ public Form1() settings = new Settings(); this.Size = settings.GetWindowSize(); fullScreen = new Fullscreen(this, settings); - kbHandler = new KeyboardHandler(webView, fullScreen); + kbHandler = new KeyboardHandler(webView, fullScreen, settings); vHandler = new VersionHandler(settings); mouseLocked = settings.GetMouseLock(); } @@ -42,13 +42,15 @@ private void webView_NavigationStarting(object sender, Microsoft.Web.WebView2.Co { webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false; + webView.CoreWebView2.Settings.IsStatusBarEnabled = false; } private void webView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e) { var addOnJS = System.IO.File.ReadAllText("AddOns.js"); webView.CoreWebView2.ExecuteScriptAsync(addOnJS); - ElementMessage.CallJSFunc(webView, $"init.function", $"{settings.GetMouseLock().ToString().ToLower()}, \"{vHandler.CurrentVersion}\""); + ElementMessage.CallJSFunc(webView, "init.function", $"{settings.GetMouseLock().ToString().ToLower()}, \"{vHandler.CurrentVersion}\""); + kbHandler.InitHotkeyNames(settings); gameHasLoaded = true; ResizeGameWindows(); } @@ -72,6 +74,7 @@ private void webView_WebMessageReceived(object sender, Microsoft.Web.WebView2.Co else mouseLocked = false; settings.SetMouseLock(mouseLocked); + settings.Save(); CaptureCursor(); break; default: @@ -105,7 +108,8 @@ private void Form1_Activated(object sender, EventArgs e) private void Form1_ResizeEnd(object sender, EventArgs e) { CaptureCursor(); - settings.SetWindowSize(this.Width, this.Height); + settings.SetWindowSize(this.Size); + settings.Save(); } private void Form1_Resize(object sender, EventArgs e) diff --git a/LittleWarGameClient/Fullscreen.cs b/LittleWarGameClient/Fullscreen.cs index 6c7cdc6..68b2cde 100644 --- a/LittleWarGameClient/Fullscreen.cs +++ b/LittleWarGameClient/Fullscreen.cs @@ -33,6 +33,7 @@ internal void Toggle() Enter(); } settings.SetFullScreen(state); + settings.Save(); } private void Enter() diff --git a/LittleWarGameClient/KeyboardHandler.cs b/LittleWarGameClient/KeyboardHandler.cs index 47e21e6..9098dfc 100644 --- a/LittleWarGameClient/KeyboardHandler.cs +++ b/LittleWarGameClient/KeyboardHandler.cs @@ -1,45 +1,95 @@ using Microsoft.Web.WebView2.WinForms; +using System; +using System.Reflection.Metadata; +using System.Reflection; namespace LittleWarGameClient { internal class KeyboardHandler { - readonly WebView2 webView; - readonly Fullscreen fullScreen; - internal KeyboardHandler(WebView2 wv, Fullscreen fs) + private readonly WebView2 webView; + private readonly Fullscreen fullScreen; + private readonly Dictionary hotKeys = new Dictionary(); + + internal KeyboardHandler(WebView2 wv, Fullscreen fs, Settings settings) { + InitHotkeys(settings); fullScreen = fs; webView = wv; webView.KeyDown += TargetWebView_KeyDown; webView.KeyUp += TargetWebView_KeyUp; } - private void TargetWebView_KeyUp(object? sender, KeyEventArgs e) + private void InitHotkeys(Settings settings) + { + Type type = typeof(Settings); + foreach (var methodInfo in type.GetMethods().Where(p => Attribute.IsDefined(p, typeof(Hotkey)))) + { + object[] attribute = methodInfo.GetCustomAttributes(typeof(Hotkey), true); + MethodInfo? funcToCall = null; + if (attribute.Length > 0) + { + Hotkey hotkey = (Hotkey)attribute[0]; + if (hotkey.FuncToCall != null) + { + Type thisType = this.GetType(); + funcToCall = thisType.GetMethod(hotkey.FuncToCall, BindingFlags.NonPublic | BindingFlags.Instance); + } + } + var key = methodInfo.Invoke(settings, null); + if (key != null) + hotKeys.Add((Keys)key, funcToCall); + } + } + + internal void InitHotkeyNames(Settings settings) { - switch (e.KeyData) + Type type = typeof(Settings); + foreach (var methodInfo in type.GetMethods().Where(p => Attribute.IsDefined(p, typeof(Hotkey)))) { - case Keys.F8: - fullScreen.Toggle(); - e.Handled = true; - break; - case Keys.F9: - if (sender != null) - ElementMessage.CallJSFunc((WebView2)sender, "toggleFriends"); - e.Handled = true; - break; - case Keys.F10: - if (sender != null) - ElementMessage.CallJSFunc((WebView2)sender, "toggleMenu"); - e.Handled = true; - break; - case Keys.F11: - if (sender != null) - ElementMessage.CallJSFunc((WebView2)sender, "toggleChat"); - e.Handled = true; - break; + object[] attribute = methodInfo.GetCustomAttributes(typeof(Hotkey), true); + string? jsFuncToCall = null; + if (attribute.Length > 0) + { + Hotkey hotkey = (Hotkey)attribute[0]; + jsFuncToCall = hotkey.JSFuncToCall; + } + var key = methodInfo.Invoke(settings, null); + if (jsFuncToCall != null && key != null) + ElementMessage.CallJSFunc(webView, jsFuncToCall, $"\"{(Keys)key}\""); } } + private void TargetWebView_KeyUp(object? sender, KeyEventArgs e) + { + if (hotKeys.ContainsKey(e.KeyData)) + { + var funcToCall = hotKeys[e.KeyData]; + if (funcToCall != null && sender != null) + funcToCall.Invoke(this, new object[] { (WebView2)sender }); + e.Handled = true; + } + } + + private void FullscreenHotkeyFunc(WebView2 sender) + { + fullScreen.Toggle(); + } + + private void OptionsMenuHotkeyFunc(WebView2 sender) + { + ElementMessage.CallJSFunc(sender, "toggleMenu"); + } + + private void ChatHistoryHotkeyFunc(WebView2 sender) + { + ElementMessage.CallJSFunc((WebView2)sender, "toggleChat"); + } + + private void FriendsHotkeyFunc(WebView2 sender) + { + ElementMessage.CallJSFunc((WebView2)sender, "toggleFriends"); + } private void TargetWebView_KeyDown(object? sender, KeyEventArgs e) { @@ -51,15 +101,8 @@ private void TargetWebView_KeyDown(object? sender, KeyEventArgs e) return; } #endif - switch (e.KeyData) - { - case Keys.F8: - case Keys.F9: - case Keys.F10: - case Keys.F11: - e.Handled = true; - break; - } + if (hotKeys.ContainsKey(e.KeyData)) + e.Handled = true; } } } diff --git a/LittleWarGameClient/Settings.cs b/LittleWarGameClient/Settings.cs index fcfe8f2..92ba46e 100644 --- a/LittleWarGameClient/Settings.cs +++ b/LittleWarGameClient/Settings.cs @@ -1,7 +1,9 @@ using IniFile; +using Octokit; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection.Metadata; using System.Text; using System.Threading.Tasks; @@ -10,12 +12,38 @@ namespace LittleWarGameClient internal class Settings { private const string fileName = "Settings.ini"; + private const int defaultWidth = 1280; + private const int defaultHeight = 720; + private const bool defaultFullscreen = false; + private const bool defaultMouseLock = false; + private const int defaultUpdateInterval = 1; + private readonly DateTime defaultUpdateLastChecked = DateTime.MinValue; + private const Keys defaultOptionsMenu = Keys.F10; + private const Keys defaultFriendsMenu = Keys.F9; + private const Keys defaultChatHistoryMenu = Keys.F11; + private const Keys defaultFullscreenHotkey = Keys.F8; private readonly Ini settings; + private readonly SettingsHelper helper; + public Settings() { if (!File.Exists(fileName)) CreateDefaultIniFile(); settings = new Ini(fileName); + helper = new SettingsHelper(settings); + Init(); + } + + private void Init() + { + SetMouseLock(GetMouseLock()); + SetFullScreen(GetFullScreen()); + SetWindowSize(GetWindowSize()); + SetLastUpdateChecked(GetLastUpdateChecked()); + SetUpdateInterval(GetUpdateInterval()); + SetOptionsMenuHotkey(GetOptionsMenuHotkey()); + SetFriendsMenuHotkey(GetFriendsMenuHotkey()); + Save(); } private void CreateDefaultIniFile() @@ -24,94 +52,147 @@ private void CreateDefaultIniFile() { new Section("Window") { - new Property("width", 1280), - new Property("height", 720), - new Property("fullscreen", false) + new Property("width", defaultWidth), + new Property("height", defaultHeight), + new Property("fullscreen", defaultFullscreen) }, new Section("Mouse") { - new Property("lock", false) + new Property("lock", defaultMouseLock) }, new Section("Update") { - new Property("lastChecked", DateTime.MinValue), - new Property("interval", 1), + new Property("lastChecked", defaultUpdateLastChecked), + new Property("interval", defaultUpdateInterval), + }, + new Section("Hotkeys") + { + new Property("optionsMenu", defaultOptionsMenu.ToString()), + new Property("friendsMenu", defaultFriendsMenu.ToString()), + new Property("chatHistoryMenu", defaultChatHistoryMenu.ToString()), + new Property("fullscreen", defaultFullscreenHotkey.ToString()) } }; settings.SaveTo(fileName); } - internal async void SetMouseLock(bool value) + internal void Save() { - await Task.Run(() => + for (int numTries = 0; numTries < 5; numTries++) { - settings["Mouse"]["lock"] = value; - settings.SaveTo(fileName); - }); + try + { + settings.SaveTo(fileName); + break; + } + catch + { + Thread.Sleep(50); + } + } } - internal bool GetMouseLock() + internal void SetMouseLock(bool value) { - return settings["Mouse"]["lock"]; + helper.SetVariable("Mouse", "lock", value); } - internal async void SetFullScreen(bool value) + public bool GetMouseLock() { - await Task.Run(() => - { - settings["Window"]["fullscreen"] = value; - settings.SaveTo(fileName); - }); + return helper.GetVariable("Mouse", "lock", defaultMouseLock); } - internal bool GetFullScreen() + internal void SetFullScreen(bool value) { - return settings["Window"]["fullscreen"]; + helper.SetVariable("Window", "fullscreen", value); } - internal async void SetWindowSize(int width, int height) + public bool GetFullScreen() { - await Task.Run(() => - { - settings["Window"]["width"] = width; - settings["Window"]["height"] = height; - settings.SaveTo(fileName); - }); + return helper.GetVariable("Window", "fullscreen", defaultFullscreen); + } + + internal void SetWindowSize(Size size) + { + helper.SetVariable("Window", "width", size.Width); + helper.SetVariable("Window", "height", size.Height); } - internal Size GetWindowSize() + public Size GetWindowSize() { - int width = settings["Window"]["width"]; - int height = settings["Window"]["height"]; + var width = helper.GetVariable("Window", "width", defaultWidth); + var height = helper.GetVariable("Window", "height", defaultHeight); return new Size(width, height); } - internal async void SetLastUpdateChecked(DateTime value) + internal void SetLastUpdateChecked(DateTime value) { - await Task.Run(() => - { - settings["Update"]["lastChecked"] = value; - settings.SaveTo(fileName); - }); + helper.SetVariable("Update", "lastChecked", value); } - internal DateTime GetLastUpdateChecked() + public DateTime GetLastUpdateChecked() { - return settings["Update"]["lastChecked"]; + return helper.GetVariable("Update", "lastChecked", defaultUpdateLastChecked); } - internal async void SetUpdateInterval(int value) + internal void SetUpdateInterval(int value) { - await Task.Run(() => - { - settings["Update"]["interval"] = value; - settings.SaveTo(fileName); - }); + helper.SetVariable("Update", "interval", value); + } + + public int GetUpdateInterval() + { + return helper.GetVariable("Update", "interval", defaultUpdateInterval); + } + + internal void SetOptionsMenuHotkey(Keys value) + { + helper.SetVariable("Hotkeys", "optionsMenu", value); + } + + [Hotkey(FuncToCall = "OptionsMenuHotkeyFunc", JSFuncToCall = "addOptionsMenuHotkey")] + public Keys GetOptionsMenuHotkey() + { + return helper.GetVariable("Hotkeys", "optionsMenu", defaultOptionsMenu); + } + + internal void SetFriendsMenuHotkey(Keys value) + { + helper.SetVariable("Hotkeys", "friendsMenu", value); + } + + [Hotkey(FuncToCall = "FriendsHotkeyFunc", JSFuncToCall = "addFriendsMenuHotkey")] + public Keys GetFriendsMenuHotkey() + { + return helper.GetVariable("Hotkeys", "friendsMenu", defaultFriendsMenu); + } + + internal void SetChatHistoryMenuHotkey(Keys value) + { + helper.SetVariable("Hotkeys", "chatHistoryMenu", value); + } + + [Hotkey(FuncToCall = "ChatHistoryHotkeyFunc", JSFuncToCall = "addChatHistoryHotkey")] + public Keys GetChatHistoryMenuHotkey() + { + return helper.GetVariable("Hotkeys", "chatHistoryMenu", defaultChatHistoryMenu); } - internal int GetUpdateInterval() + internal void SetFullscreenHotkey(Keys value) { - return settings["Update"]["interval"]; + helper.SetVariable("Hotkeys", "fullscreen", value); } + + [Hotkey(FuncToCall = "FullscreenHotkeyFunc", JSFuncToCall = "addFullscreenBtnHotkey")] + public Keys GetFullscreenHotkey() + { + return helper.GetVariable("Hotkeys", "fullscreen", defaultFullscreenHotkey); + } + } + + internal class Hotkey : Attribute + { + public string? FuncToCall { get; set; } + public string? JSFuncToCall { get; set; } } -} +} \ No newline at end of file diff --git a/LittleWarGameClient/SettingsHelper.cs b/LittleWarGameClient/SettingsHelper.cs new file mode 100644 index 0000000..0768af1 --- /dev/null +++ b/LittleWarGameClient/SettingsHelper.cs @@ -0,0 +1,129 @@ +using IniFile; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LittleWarGameClient +{ + internal class SettingsHelper + { + readonly Ini settings; + public SettingsHelper(Ini s) + { + settings = s; + } + + internal Keys GetVariable(string sectionName, string propertyName, Keys defaultValue) + { + try + { + return settings[sectionName][propertyName].AsEnum(); + } + catch + { + return defaultValue; + } + } + + internal bool GetVariable(string sectionName, string propertyName, bool defaultValue) + { + try + { + return settings[sectionName][propertyName]; + } + catch + { + return defaultValue; + } + } + + internal int GetVariable(string sectionName, string propertyName, int defaultValue) + { + try + { + return settings[sectionName][propertyName]; + } + catch + { + return defaultValue; + } + } + + internal DateTime GetVariable(string sectionName, string propertyName, DateTime defaultValue) + { + try + { + return settings[sectionName][propertyName]; + } + catch + { + return defaultValue; + } + } + + private Section EnsureSection(string sectionName) + { + Section section = settings[sectionName]; + if (section == null) + { + section = new Section(sectionName); + settings.Add(section); + } + return section; + } + + internal void SetVariable(string sectionName, string propertyName, bool value) + { + Section section = EnsureSection(sectionName); + try + { + section[propertyName] = value; + } + catch + { + section.Add(new Property(propertyName, value)); + } + } + + internal void SetVariable(string sectionName, string propertyName, DateTime value) + { + Section section = EnsureSection(sectionName); + try + { + section[propertyName] = value; + } + catch + { + section.Add(new Property(propertyName, value)); + } + } + + internal void SetVariable(string sectionName, string propertyName, int value) + { + Section section = EnsureSection(sectionName); + try + { + section[propertyName] = value; + } + catch + { + section.Add(new Property(propertyName, value)); + } + } + + internal void SetVariable(string sectionName, string propertyName, Keys value) + { + Section section = EnsureSection(sectionName); + try + { + section[propertyName] = value.ToString(); + } + catch + { + section.Add(new Property(propertyName, value.ToString())); + } + } + } +} diff --git a/LittleWarGameClient/VersionHandler.cs b/LittleWarGameClient/VersionHandler.cs index 9d75c20..c2f3daf 100644 --- a/LittleWarGameClient/VersionHandler.cs +++ b/LittleWarGameClient/VersionHandler.cs @@ -54,6 +54,7 @@ internal virtual void CheckForUpdate(object? sender, EventArgs e) System.Windows.Forms.Application.Exit(); } settings.SetLastUpdateChecked(DateTime.Now.Date); + settings.Save(); } private bool RequiresUpdate() @@ -84,31 +85,29 @@ private async Task GetLatestGitHubVersion() { var client = new GitHubClient(new ProductHeaderValue("LWGClient")); IReadOnlyList releases = await client.Repository.Release.GetAll("ivanpmartell", "LittleWarGameClient"); - return new Version(releases.First().TagName.Substring(1)); + return new Version(releases[0].TagName.Substring(1)); } private async Task TimeoutAfter(Task task, TimeSpan timeout) { // We need to be able to cancel the "timeout" task, so create a token source - using (var cts = new CancellationTokenSource()) - { - // Create the timeout task (don't await it) - var timeoutTask = Task.Delay(timeout, cts.Token); + using var cts = new CancellationTokenSource(); + // Create the timeout task (don't await it) + var timeoutTask = Task.Delay(timeout, cts.Token); - // Run the task and timeout in parallel, return the Task that completes first - var completedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); + // Run the task and timeout in parallel, return the Task that completes first + var completedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); - if (completedTask == task) - { - // Cancel the "timeout" task so we don't leak a Timer - cts.Cancel(); - // await the task to bubble up any errors etc - return await task.ConfigureAwait(false); - } - else - { - throw new TimeoutException($"Task timed out after {timeout}"); - } + if (completedTask == task) + { + // Cancel the "timeout" task so we don't leak a Timer + cts.Cancel(); + // await the task to bubble up any errors etc + return await task.ConfigureAwait(false); + } + else + { + throw new TimeoutException($"Task timed out after {timeout}"); } } }