using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; using ClassicAssist.Annotations; using ClassicAssist.Data.Hotkeys.Commands; using ClassicAssist.Data.Macros.Commands; using ClassicAssist.Misc; using ClassicAssist.UI.Misc; using static ClassicAssist.Misc.SDLKeys; namespace ClassicAssist.Data.Hotkeys { public class KeyState { [DllImport("SDL2.dll", EntryPoint = "SDL_GetKeyboardState", CallingConvention = CallingConvention.Cdecl)] private static unsafe extern byte* GetKeyboardState(out int numKeys); public static unsafe bool GetKeyState(SDL_Scancode scanCode) { var result = GetKeyboardState(out int numKeys); int code = (int) scanCode; return code < numKeys && result[(int)code] == 1; } } public class HotkeyManager : INotifyPropertyChanged { private static HotkeyManager _instance; private static readonly object _instanceLock = new object(); private readonly object _lock = new object(); private readonly Key[] _modifierKeys = { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift, Key.RightShift, Key.LeftAlt, Key.RightAlt }; private readonly List _modifiers = new List(); private bool _enabled = true; private ObservableCollectionEx _items = new ObservableCollectionEx(); private HotkeyManager() { } public Action ClearAllHotkeys { get; set; } public bool Enabled { get => _enabled; set => SetProperty( ref _enabled, value ); } public ObservableCollectionEx Items { get => _items; set => SetProperty( ref _items, value ); } public event PropertyChangedEventHandler PropertyChanged; public void AddCategory( HotkeyCommand item, IComparer comparer = null ) { if ( Items.Contains( item ) ) { Items.Remove( item ); } if ( comparer == null ) { comparer = Comparer.Default; } int i = 0; while ( i < Items.Count && comparer.Compare( Items[i], item ) < 0 ) { i++; } Items.Insert( i, item ); } public void ClearPreviousHotkey( ShortcutKeys keys ) { foreach ( HotkeyCommand hotkeyEntry in Items ) { if ( hotkeyEntry.Children == null ) { continue; } foreach ( HotkeyEntry hotkeyEntryChild in hotkeyEntry.Children ) { if ( Equals( hotkeyEntryChild.Hotkey, keys ) ) { hotkeyEntryChild.Hotkey = ShortcutKeys.Default; } } } } public static HotkeyManager GetInstance() { // ReSharper disable once InvertIf if ( _instance == null ) { lock ( _instanceLock ) { if ( _instance == null ) { _instance = new HotkeyManager(); } } } return _instance; } [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null ) { PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) ); } // ReSharper disable once RedundantAssignment public void SetProperty( ref T obj, T value, [CallerMemberName] string propertyName = "" ) { obj = value; OnPropertyChanged( propertyName ); } public bool OnHotkeyPressed( Key keys ) { lock ( _lock ) { bool filter = false; if ( _modifierKeys.Contains( keys ) ) { _modifiers.Clear(); _modifiers.Add( keys ); return false; } Key modifier = Key.None; if ( _modifiers.Count > 0 ) { modifier = _modifiers[0]; } bool down = KeyState.GetKeyState(KeysToSDLScancode(modifier)); if ( !down ) { modifier = Key.None; } // Sanity check if ( keys == Key.None ) { return false; } foreach ( HotkeyCommand hke in Items ) { if ( hke.Children == null ) { continue; } try { IEnumerable hotkeyEntries = hke.Children.Where( t => t.Hotkey.Modifier == modifier && t.Hotkey.Key == keys && t.Hotkey.Mouse == MouseOptions.None ); foreach ( HotkeyEntry hks in hotkeyEntries ) { if ( hks.Disableable && !Enabled ) { continue; } filter = !hks.PassToUO; AliasCommands.SetDefaultAliases(); Task.Run( () => hks.Action.Invoke( hks ) ); break; } } catch ( InvalidOperationException ) { // When spamming keys } } return filter; } } private SDL_Scancode KeysToSDLScancode(Key modifier ) { //TODO Enter keys / find better way switch ( modifier ) { case Key.LeftAlt: return SDL_Scancode.SDL_SCANCODE_LALT; case Key.Right: return SDL_Scancode.SDL_SCANCODE_RIGHT; case Key.LeftCtrl: return SDL_Scancode.SDL_SCANCODE_LCTRL; case Key.RightCtrl: return SDL_Scancode.SDL_SCANCODE_RCTRL; case Key.LeftShift: return SDL_Scancode.SDL_SCANCODE_LSHIFT; case Key.RightShift: return SDL_Scancode.SDL_SCANCODE_RSHIFT; } return 0; } public void OnMouseAction( MouseOptions mouse ) { // Sanity check if ( mouse == MouseOptions.None ) { return; } lock ( _lock ) { foreach ( HotkeyCommand hke in Items ) { if ( hke.Children == null ) { continue; } try { Key modifier = Key.None; //Key modifier = _modifierKeys.FirstOrDefault( key => // Engine.Dispatcher.Invoke( () => Keyboard.IsKeyDown( key ) ) ); IEnumerable hotkeyEntries = hke.Children.Where( t => t.Hotkey.Modifier == modifier && t.Hotkey.Key == Key.None && t.Hotkey.Mouse == mouse ); foreach ( HotkeyEntry hks in hotkeyEntries ) { if ( hks.Disableable && !Enabled ) { continue; } AliasCommands.SetDefaultAliases(); Task.Run( () => hks.Action.Invoke( hks ) ); break; } } catch ( InvalidOperationException ) { // When spamming wheel } } } } public void ClearItems() { foreach ( HotkeyEntry entry in Items ) { ClearHotkeys( entry ); } Items.Clear(); } private void ClearHotkeys( HotkeyEntry entry ) { entry.Hotkey = ShortcutKeys.Default; if ( !entry.IsCategory ) { return; } foreach ( HotkeyEntry hotkeyEntry in entry.Children ) { ClearHotkeys( hotkeyEntry ); } } } }