Skip to content

Commit

Permalink
Merge pull request #2509 from cwensley/curtis/keyboard-modifierschanged
Browse files Browse the repository at this point in the history
Add Keyboard.ModifiersChanged event
  • Loading branch information
cwensley committed Jun 30, 2023
2 parents 6a3e075 + c542a06 commit 9931162
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 84 deletions.
30 changes: 29 additions & 1 deletion src/Eto.Gtk/Forms/KeyboardHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@
{
public class KeyboardHandler : Keyboard.IHandler
{
EventHandler<EventArgs> _modifiersChanged;

public event EventHandler<EventArgs> ModifiersChanged
{
add
{
if (_modifiersChanged == null)
{
Gdk.Keymap.Default.StateChanged += Keymap_StateChanged;
}
_modifiersChanged += value;
}
remove
{
_modifiersChanged -= value;
if (_modifiersChanged == null)
{
Gdk.Keymap.Default.StateChanged -= Keymap_StateChanged;
}
}
}

private void Keymap_StateChanged(object sender, EventArgs e)
{
_modifiersChanged?.Invoke(null, EventArgs.Empty);
}

public bool IsKeyLocked(Keys key)
{
#if GTK3
Expand All @@ -23,6 +50,7 @@ public Keys Modifiers
{
get
{

var ev = Gtk.Application.CurrentEvent;
if (ev != null)
{
Expand All @@ -32,7 +60,7 @@ public Keys Modifiers
return state.ToEtoKey();
}
}
return Keys.None;
return ((Gdk.ModifierType)Gdk.Keymap.Default.ModifierState).ToEtoKey();
}
}

Expand Down
38 changes: 34 additions & 4 deletions src/Eto.Mac/Forms/KeyboardHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,46 @@
{
public class KeyboardHandler : Keyboard.IHandler
{
public bool IsKeyLocked(Keys key)
EventHandler<EventArgs> _modifiersChanged;
NSObject _monitor;
public event EventHandler<EventArgs> ModifiersChanged
{
add
{
if (_modifiersChanged == null)
{
_monitor = NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.FlagsChanged, HandleFlagsChanged);
}
_modifiersChanged += value;
}
remove
{
_modifiersChanged -= value;
if (_modifiersChanged == null && _monitor != null)
{
NSEvent.RemoveMonitor(_monitor);
_monitor = null;
}
}
}

private NSEvent HandleFlagsChanged(NSEvent theEvent)
{
return NSEvent.CurrentModifierFlags == key.ModifierMask();
_modifiersChanged?.Invoke(null, EventArgs.Empty);
return theEvent;
}

public Keys Modifiers
public bool IsKeyLocked(Keys key)
{
get { return NSEvent.CurrentModifierFlags.ToEto(); }
var modifier = key.ModifierMask();
return (ModifierFlags & modifier) == modifier;
}

public Keys Modifiers => ModifierFlags.ToEto();

NSEventModifierMask ModifierFlags => NSEvent.CurrentModifierFlags;
// NSEventModifierMask ModifierFlags => NSApplication.SharedApplication.CurrentEvent?.ModifierFlags ?? NSEvent.CurrentModifierFlags;

public IEnumerable<Keys> SupportedLockKeys
{
get
Expand Down
3 changes: 3 additions & 0 deletions src/Eto.WinForms/Eto.WinForms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ You do not need to use any of the classes of this assembly (unless customizing t
<Compile Include="..\Eto.Wpf\Forms\DataObjectHandler.cs">
<Link>Forms\DataObjectHandler.cs</Link>
</Compile>
<Compile Include="..\Eto.Wpf\Forms\KeyboardHandler.cs">
<Link>Forms\KeyboardHandler.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
Expand Down
26 changes: 0 additions & 26 deletions src/Eto.WinForms/Forms/KeyboardHandler.cs

This file was deleted.

45 changes: 41 additions & 4 deletions src/Eto.WinForms/Win32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,25 @@ public enum WM
PRINT = 0x0317,
SHOWWINDOW = 0x00000018
}

public enum VK : long
{
SHIFT = 0x10,
CONTROL = 0x11,
MENU = 0x12,
CAPSLOCK = 0x14,
ESCAPE = 0x1B,
NUMLOCK = 0x90,
SCROLL = 0x91,
LSHIFT = 0xA0,
RSHIFT = 0xA1,
LCONTROL = 0xA2,
RCONTROL = 0xA3,
LMENU = 0xA4,
RMENU = 0xA5,
LWIN = 0x5B,
RWIN = 0x5C
}

public enum HT
{
Expand Down Expand Up @@ -392,23 +411,41 @@ public static string GetWindowText(IntPtr hwnd)
}

// for tray indicator

public enum WH
{
KEYBOARD = 2,
KEYBOARD_LL = 13,
MOUSE_LL = 14
}


public static IntPtr SetHook(WH hookId, HookProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx((IntPtr)hookId, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int hookId, int code, int param, IntPtr dataPointer);
public static extern IntPtr CallNextHookEx(IntPtr hookId, int code, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string moduleName);

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int hookId, HookProc function, IntPtr instance, int threadId);
public static extern IntPtr SetWindowsHookEx(IntPtr hookId, HookProc function, IntPtr instance, int threadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int UnhookWindowsHookEx(int hookId);
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hookId);

[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();

public delegate int HookProc(int code, int wParam, IntPtr structPointer);
public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

[StructLayout(LayoutKind.Sequential)]
public struct MouseLowLevelHook
Expand Down
134 changes: 124 additions & 10 deletions src/Eto.Wpf/Forms/KeyboardHandler.cs
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,26 +1,140 @@
namespace Eto.Wpf.Forms
#if WINFORMS

namespace Eto.WinForms.Forms
#else

namespace Eto.Wpf.Forms
#endif
{
public class KeyboardHandler : Keyboard.IHandler
{
public bool IsKeyLocked(Keys key)
EventHandler<EventArgs> _modifiersChanged;
Keys _modifiers;
List<Keys> _oldLockedKeys = new List<Keys>();

Win32.HookProc _hookProc;
IntPtr _hookId;
Keys? _downKeys;
Keys? _upKeys;

IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
return swi.Keyboard.IsKeyToggled(key.ToWpfKey());
// only trigger event when the application is active
if (nCode == 0 && Application.Instance.IsActive)
{
if (wParam == (IntPtr)Win32.WM.KEYDOWN || wParam == (IntPtr)Win32.WM.KEYUP
|| wParam == (IntPtr)Win32.WM.SYSKEYDOWN || wParam == (IntPtr)Win32.WM.SYSKEYUP)
{
var kb = Marshal.PtrToStructure<Win32.KeyboardLowLevelHook>(lParam);
// Console.WriteLine($"Callback: {wParam:x}, {lParam}, {kb.VirtualKeyCode:x}, {kb.ScanCode:x}");

// this event happens before the state is updated, so we check which key was pressed.
var keys = Keys.None;
switch (kb.VirtualKeyCode)
{
case (int)Win32.VK.RMENU:
case (int)Win32.VK.LMENU:
case (int)Win32.VK.MENU:
keys = Keys.Alt;
break;
case (int)Win32.VK.RCONTROL:
case (int)Win32.VK.LCONTROL:
case (int)Win32.VK.CONTROL:
keys = Keys.Control;
break;
case (int)Win32.VK.RSHIFT:
case (int)Win32.VK.LSHIFT:
case (int)Win32.VK.SHIFT:
keys = Keys.Shift;
break;
case (int)Win32.VK.RWIN:
case (int)Win32.VK.LWIN:
keys = Keys.Application;
break;
}
if (wParam == (IntPtr)Win32.WM.KEYDOWN || wParam == (IntPtr)Win32.WM.SYSKEYDOWN)
_downKeys = keys;
else if (wParam == (IntPtr)Win32.WM.KEYUP || wParam == (IntPtr)Win32.WM.SYSKEYUP)
_upKeys = keys;
TriggerChanged();
_downKeys = null;
_upKeys = null;
}
}
return Win32.CallNextHookEx(_hookId, nCode, wParam, lParam);
}

public IEnumerable<Keys> SupportedLockKeys
public event EventHandler<EventArgs> ModifiersChanged
{
get
add
{
if (_modifiersChanged == null)
{
_hookProc = new Win32.HookProc(HookCallback);
_hookId = Win32.SetHook(Win32.WH.KEYBOARD_LL, _hookProc);
_modifiers = Modifiers;
_oldLockedKeys.Clear();
_oldLockedKeys.AddRange(SupportedLockKeys.Where(IsKeyLocked));
}
_modifiersChanged += value;
}
remove
{
yield return Keys.CapsLock;
yield return Keys.NumberLock;
yield return Keys.ScrollLock;
yield return Keys.Insert;
_modifiersChanged -= value;
if (_modifiersChanged == null && _hookId != IntPtr.Zero)
{
Win32.UnhookWindowsHookEx(_hookId);
_hookProc = null;
_hookId = IntPtr.Zero;
_modifiers = Keys.None;
_oldLockedKeys.Clear();
}
}
}

private void TriggerChanged()
{
var newModifiers = Modifiers;
var newLockedKeys = SupportedLockKeys.Where(IsKeyLocked).ToList();

if (_modifiers != newModifiers || !_oldLockedKeys.SequenceEqual(newLockedKeys))
{
_modifiers = newModifiers;
_oldLockedKeys = newLockedKeys;
_modifiersChanged?.Invoke(null, EventArgs.Empty);
}
}

#if WINFORMS
public bool IsKeyLocked(Keys key) => swf.Control.IsKeyLocked(key.ToSWF());
#else
public bool IsKeyLocked(Keys key) => swi.Keyboard.IsKeyToggled(key.ToWpfKey());
#endif

static readonly Keys[] _supportedLockKeys = new[] {
Keys.CapsLock,
Keys.NumberLock,
Keys.ScrollLock,
Keys.Insert
};

public IEnumerable<Keys> SupportedLockKeys => _supportedLockKeys;

public Keys Modifiers
{
get { return swi.Keyboard.Modifiers.ToEto(); }
get
{
#if WINFORMS
var modifiers = swf.Control.ModifierKeys.ToEto();
#else
var modifiers = swi.Keyboard.Modifiers.ToEto();
#endif
if (_downKeys != null)
modifiers |= _downKeys.Value;
if (_upKeys != null)
modifiers &= ~_upKeys.Value;
return modifiers;
}
}
}
}
Loading

0 comments on commit 9931162

Please sign in to comment.