Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Keyboard.ModifiersChanged event #2509

Merged
merged 1 commit into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add Keyboard.ModifiersChanged event
  • Loading branch information
cwensley committed Jun 30, 2023
commit c542a06434446ffd633feb4055f13bc073d04a67
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
Loading