diff --git a/src/SoulSplitter/Hotkeys/GlobalHotKey.cs b/src/SoulSplitter/Hotkeys/GlobalHotKey.cs index 567c327..073eb00 100644 --- a/src/SoulSplitter/Hotkeys/GlobalHotKey.cs +++ b/src/SoulSplitter/Hotkeys/GlobalHotKey.cs @@ -22,99 +22,100 @@ using System.Windows.Input; using SoulSplitter.Native; -namespace SoulSplitter.Hotkeys; - -public static class GlobalHotKey +namespace SoulSplitter.Hotkeys { - private static readonly List<(int id, ModifierKeys modifier, Key key, Action action)> Hotkeys = []; - private static readonly ManualResetEvent WindowReadyEvent = new(false); - public static volatile HotkeyForm? HotkeyForm; - public static volatile IntPtr Handle; - private static int _currentId; - - static GlobalHotKey() + public static class GlobalHotKey { - var messageLoopThread = new Thread(delegate () - { - Application.Run(new HotkeyForm(WindowReadyEvent)); - }); - messageLoopThread.Name = "MessageLoopThread"; - messageLoopThread.IsBackground = true; - messageLoopThread.Start(); - } + private static readonly List<(int id, Hotkey hotkey, Action action)> Hotkeys = new List<(int, Hotkey, Action)>(); + private static readonly ManualResetEvent WindowReadyEvent = new ManualResetEvent(false); + public static volatile HotkeyForm HotkeyForm = null!; + public static volatile IntPtr Handle; + private static int _currentId; - public static void OnHotkeyPressed(ModifierKeys modifier, Key key) - { - Hotkeys.ForEach(x => + static GlobalHotKey() { - if (modifier == x.modifier && key == x.key) + var messageLoopThread = new Thread(delegate () { - x.action(); - } - }); - } - - public static int RegisterHotKey(ModifierKeys modifier, Key key, Action action) - { - WindowReadyEvent.WaitOne(); //wait for hotkey window to have initialized + Application.Run(new HotkeyForm(WindowReadyEvent)); + }); + messageLoopThread.Name = "MessageLoopThread"; + messageLoopThread.IsBackground = true; + messageLoopThread.Start(); + } - var virtualKeyCode = (Keys)KeyInterop.VirtualKeyFromKey(key); - int id = Interlocked.Increment(ref _currentId); + public static void OnHotkeyPressed(Hotkey hotkey) + { + Hotkeys.ForEach(x => + { + if (hotkey.Modifiers == x.hotkey.Modifiers && hotkey.Key == x.hotkey.Key) + { + x.action(); + } + }); + } - Delegate register = (Action)(() => + public static int RegisterHotKey(Hotkey hotkey, Action action) { - User32.RegisterHotkey( - Handle, - id, - (uint)modifier | 0x4000, //no repeat - (uint)virtualKeyCode); - }); + WindowReadyEvent.WaitOne(); //wait for hotkey window to have initialized - HotkeyForm?.Invoke(register); - Hotkeys.Add((id, modifier, key, action)); - return id; - } + var virtualKeyCode = (Keys)KeyInterop.VirtualKeyFromKey(hotkey.Key); + int id = Interlocked.Increment(ref _currentId); - public static void UnregisterHotKey(int id) - { - if (Hotkeys.All(i => i.id != id)) - { - return; + Delegate register = (Action)(() => + { + User32.RegisterHotkey( + Handle, + id, + (uint)hotkey.Modifiers | 0x4000, //no repeat + (uint)virtualKeyCode); + }); + + HotkeyForm.Invoke(register); + Hotkeys.Add((id, hotkey, action)); + return id; } - Delegate unregister = (Action)(() => + public static void UnregisterHotKey(int id) { - User32.UnregisterHotkey(Handle, id); - }); + if (Hotkeys.All(i => i.id != id)) + { + return; + } - HotkeyForm?.Invoke(unregister); - Hotkeys.Remove(Hotkeys.Find(i => i.id == id)); - } -} + Delegate unregister = (Action)(() => + { + User32.UnregisterHotkey(Handle, id); + }); -public class HotkeyForm : Form -{ - public HotkeyForm(ManualResetEvent windowReadyEvent) - { - GlobalHotKey.Handle = Handle; - GlobalHotKey.HotkeyForm = this; - windowReadyEvent.Set(); + HotkeyForm.Invoke(unregister); + Hotkeys.Remove(Hotkeys.Find(i => i.id == id)); + } } - protected override void WndProc(ref Message windowMessage) + public class HotkeyForm : Form { - base.WndProc(ref windowMessage); + public HotkeyForm(ManualResetEvent windowReadyEvent) + { + GlobalHotKey.Handle = Handle; + GlobalHotKey.HotkeyForm = this; + windowReadyEvent.Set(); + } - if (windowMessage.Msg == 0x0312) //0x0312 is the hotkey message + protected override void WndProc(ref Message windowMessage) { - var key = KeyInterop.KeyFromVirtualKey((int)windowMessage.LParam >> 16 & 0xFFFF); - var modifier = (ModifierKeys)((int)windowMessage.LParam & 0xFFFF); - GlobalHotKey.OnHotkeyPressed(modifier, key); + base.WndProc(ref windowMessage); + + if (windowMessage.Msg == 0x0312) //0x0312 is the hotkey message + { + var key = KeyInterop.KeyFromVirtualKey((int)windowMessage.LParam >> 16 & 0xFFFF); + var modifier = (ModifierKeys)((int)windowMessage.LParam & 0xFFFF); + GlobalHotKey.OnHotkeyPressed(new Hotkey(){Modifiers = modifier, Key = key}); + } } - } - protected override void SetVisibleCore(bool value) - { - base.SetVisibleCore(false); + protected override void SetVisibleCore(bool value) + { + base.SetVisibleCore(false); + } } } diff --git a/src/SoulSplitter/Hotkeys/Hotkey.cs b/src/SoulSplitter/Hotkeys/Hotkey.cs new file mode 100644 index 0000000..39ac277 --- /dev/null +++ b/src/SoulSplitter/Hotkeys/Hotkey.cs @@ -0,0 +1,31 @@ +// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter). +// Copyright (c) 2022 Frank van der Stam. +// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace SoulSplitter.Hotkeys +{ + public class Hotkey + { + public ModifierKeys Modifiers { get; set; } + public Key Key { get; set; } + } +} diff --git a/src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs b/src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs new file mode 100644 index 0000000..e684259 --- /dev/null +++ b/src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs @@ -0,0 +1,59 @@ +// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter). +// Copyright (c) 2022 Frank van der Stam. +// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using SoulSplitter.Hotkeys; +using SoulSplitter.UI.EldenRing; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows.Input; + +namespace SoulSplitter.UI.Converters +{ + public class HotkeyToStringConverter : IValueConverter + { + private static List _modifiers = Enum.GetValues(typeof(ModifierKeys)).Cast().Where(i => i != ModifierKeys.None).ToList(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Hotkey hotkey) + { + var sb = new StringBuilder(); + foreach (var modifier in _modifiers) + { + if (hotkey.Modifiers.HasFlag(modifier)) + { + sb.Append(modifier); + sb.Append(" + "); + } + } + sb.Append(hotkey.Key); + return sb.ToString(); + } + + throw new ArgumentException($"{value} is not a {nameof(Hotkey)}", nameof(value)); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return ""; + } + } +} diff --git a/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml b/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml index a1e16d5..0a9d012 100644 --- a/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml +++ b/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml @@ -45,6 +45,7 @@ + @@ -94,462 +95,558 @@ - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/SoulSplitter/UI/EldenRing/EldenRingViewModel.cs b/src/SoulSplitter/UI/EldenRing/EldenRingViewModel.cs index d5b5de1..22b177c 100644 --- a/src/SoulSplitter/UI/EldenRing/EldenRingViewModel.cs +++ b/src/SoulSplitter/UI/EldenRing/EldenRingViewModel.cs @@ -18,8 +18,10 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Windows.Input; using System.Xml.Serialization; using SoulMemory.EldenRing; +using SoulSplitter.Hotkeys; using SoulSplitter.Splits.EldenRing; using SoulSplitter.UI.Generic; @@ -29,6 +31,9 @@ public class EldenRingViewModel : ICustomNotifyPropertyChanged { public EldenRingViewModel() { + Hotkeys.Add(new FpsPatchHotkey(){ Action = FpsPatchHotkeyAction.SetFpsValue, Hotkey = new Hotkey(){ Key = Key.A, Modifiers = ModifierKeys.Control }, Fps = 33}); + Hotkeys.Add(new FpsPatchHotkey(){ Action = FpsPatchHotkeyAction.SetFpsValue, Hotkey = new Hotkey(){ Key = Key.B, Modifiers = ModifierKeys.Shift }, Fps = 24}); + Hotkeys.Add(new FpsPatchHotkey(){ Action = FpsPatchHotkeyAction.RemoveFpsPatch, Hotkey = new Hotkey(){ Key = Key.C, Modifiers = ModifierKeys.Alt }}); } public bool StartAutomatically @@ -438,14 +443,29 @@ public void RestoreHierarchy() #endregion + #region Fps hotkeys - public ObservableCollection Splits { get; set; }= []; - - //source lists - public static ObservableCollection Bosses { get; set; } = new(Enum.GetValues(typeof(Boss)).Cast().Select(i => new BossViewModel(i))); - public static ObservableCollection Graces { get; set; } = new(Enum.GetValues(typeof(Grace)).Cast().Select(i => new GraceViewModel(i))); - public static ObservableCollection ItemPickups { get; set; } = new(Enum.GetValues(typeof(ItemPickup)).Cast().Select(i => new ItemPickupViewModel(i))); - public static ObservableCollection KnownFlags { get; set; } = new(Enum.GetValues(typeof(KnownFlag)).Cast().Select(i => new KnownFlagViewModel(i))); + [XmlIgnore] + public int NewFpsValue + { + get => _newFpsValue; + set => this.SetField(ref _newFpsValue, value); + } + private int _newFpsValue = 30; + + public ObservableCollection Hotkeys { get; set; } = new ObservableCollection(); + + + + + #endregion + public ObservableCollection Splits { get; set; }= new ObservableCollection(); + + //source lists + public static ObservableCollection Bosses { get; set; } = new ObservableCollection(Enum.GetValues(typeof(Boss)).Cast().Select(i => new BossViewModel(i))); + public static ObservableCollection Graces { get; set; } = new ObservableCollection(Enum.GetValues(typeof(Grace)).Cast().Select(i => new GraceViewModel(i))); + public static ObservableCollection ItemPickups { get; set; } = new ObservableCollection(Enum.GetValues(typeof(ItemPickup)).Cast().Select(i => new ItemPickupViewModel(i))); + public static ObservableCollection KnownFlags { get; set; } = new ObservableCollection(Enum.GetValues(typeof(KnownFlag)).Cast().Select(i => new KnownFlagViewModel(i))); #region ICustomNotifyPropertyChanged diff --git a/src/SoulSplitter/UI/EldenRing/FpsPatchHotkey.cs b/src/SoulSplitter/UI/EldenRing/FpsPatchHotkey.cs new file mode 100644 index 0000000..62d1e15 --- /dev/null +++ b/src/SoulSplitter/UI/EldenRing/FpsPatchHotkey.cs @@ -0,0 +1,39 @@ +// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter). +// Copyright (c) 2022 Frank van der Stam. +// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using SoulSplitter.Hotkeys; + +namespace SoulSplitter.UI.EldenRing +{ + public enum FpsPatchHotkeyAction + { + SetFpsValue, + RemoveFpsPatch, + } + + public class FpsPatchHotkey + { + public int Fps { get; set; } + public Hotkey Hotkey { get; set; } = null!; + public FpsPatchHotkeyAction Action { get; set; } + } +} diff --git a/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml b/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml new file mode 100644 index 0000000..fb10904 --- /dev/null +++ b/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml.cs b/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml.cs new file mode 100644 index 0000000..065d4ad --- /dev/null +++ b/src/SoulSplitter/UI/Generic/HotkeyPicker.xaml.cs @@ -0,0 +1,130 @@ +// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter). +// Copyright (c) 2022 Frank van der Stam. +// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using System.Globalization; +using System.Windows.Forms; +using System.Windows.Input; +using SoulSplitter.UI.EldenRing; +using KeyEventArgs = System.Windows.Input.KeyEventArgs; +using TextBox = System.Windows.Controls.TextBox; +using UserControl = System.Windows.Controls.UserControl; + +namespace SoulSplitter.UI.Generic +{ + /// + /// Interaction logic for HotkeyPicker.xaml + /// + public partial class HotkeyPicker : UserControl + { + public HotkeyPicker() + { + InitializeComponent(); + } + + public class HotkeyCompletedParameter + { + public object Sender { get; set; } = null!; + public ModifierKeys ModifierKeys; + public Key Key; + } + + public RelayCommand HotkeyCompletedCommand { get; set; } = null!; + + private bool _isActive = false; + private ModifierKeys _modifierKeys = ModifierKeys.None; + private Key _key = Key.None; + private void OnPreviewKeyDown(object sender, KeyEventArgs e) + { + if (_isActive) + { + if (e.IsDown && sender is TextBox textBox) + { + //Get modifier key and remember state + if (_modifierKeysLookup.TryGetValue(e.Key, out ModifierKeys modifierKeys)) + { + if (!_modifierKeys.HasFlag(modifierKeys)) + { + if (!string.IsNullOrWhiteSpace(textBox.Text)) + { + textBox.Text += " + "; + } + + textBox.Text += modifierKeys; + _modifierKeys |= modifierKeys; + } + } + else //get regular key, end listening for keypresses, invoke command + { + if (!string.IsNullOrWhiteSpace(textBox.Text)) + { + textBox.Text += " + "; + } + + textBox.Text += e.Key; + _key = e.Key; + + HotkeyCompletedCommand?.Execute(new HotkeyCompletedParameter + { + Sender = this, + Key = _key, + ModifierKeys = _modifierKeys + }); + _isActive = false; + } + + var converter = new ModifierKeysConverter(); + e.Handled = true; + } + } + } + + private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (!_isActive && sender is TextBox textBox) + { + _isActive = true; + _key = Key.None; + _modifierKeys = ModifierKeys.None; + textBox.Text = ""; + } + } + + private static Dictionary _modifierKeysLookup = new Dictionary() + { + { Key.LeftAlt, ModifierKeys.Alt }, + { Key.RightAlt, ModifierKeys.Alt }, + { Key.System, ModifierKeys.Alt }, + { Key.LeftCtrl, ModifierKeys.Control }, + { Key.RightCtrl, ModifierKeys.Control }, + { Key.LeftShift, ModifierKeys.Shift }, + { Key.RightShift, ModifierKeys.Shift }, + { Key.LWin, ModifierKeys.Windows }, + { Key.RWin, ModifierKeys.Windows }, + }; + + private void OnPreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + if (_isActive && sender is TextBox textBox) + { + _isActive = false; + _key = Key.None; + _modifierKeys = ModifierKeys.None; + textBox.Text = ""; + } + } + } +} diff --git a/src/cli/Program.cs b/src/cli/Program.cs index 3eccfbe..d86778a 100644 --- a/src/cli/Program.cs +++ b/src/cli/Program.cs @@ -42,9 +42,19 @@ internal class Program static void Main(string[] args) { - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.A, () =>{ Debug.WriteLine("A"); }); - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.S, () =>{ Debug.WriteLine("S"); }); - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.D, () =>{ Debug.WriteLine("D"); }); + TestUi(); + + GameLoop((e) => + { + var saveSlot = e.GetCurrentSaveSlot(); + var pos = e.GetPosition(); + var igtElapsed = TimeSpan.FromMilliseconds(e.GetInGameTimeMilliseconds()); + Console.WriteLine($"IGT: {igtElapsed} slot: {saveSlot} pos: {pos}"); + }); + + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.A, () =>{ Debug.WriteLine("A"); }); + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.S, () =>{ Debug.WriteLine("S"); }); + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.D, () =>{ Debug.WriteLine("D"); }); //TestUi();