I'm new to coding and have decided to start with C#. I've decided to write a simple console program that will detect key press and if only Enter is pressed, it will show number. The problem is that you can just hold down key and it will continue to show numbers. What should I add to my code so program will detect only SINGLE presses and ignore if user is HOLDING the key?
(I didn't find anything about this issue for CONSOLE C#, only for Forms one. Neither on this forum, nor in Web at all)
Thanks in advance
static void Main(string[] args)
{
Console.WriteLine("Press Enter to play!");
int num = 0;
void WaitForKey(ConsoleKey key)
{
while (Console.ReadKey(true).Key != key)
{ }
}
for (int i = 0; i < 10; i++)
{
WaitForKey(ConsoleKey.Enter);
Console.Write("{0} ", num);
num++;
}
}
If your task is to count amount of changes of typed/holded keys you can do it with just remembering of last key
var lastChar = char.MaxValue;
var index = 0;
do
{
var x = Console.ReadKey();
if (lastChar != x.KeyChar)
{
lastChar = x.KeyChar;
Console.WriteLine(++index);
}
} while (index < 10);
If you need to define single key you can use StopWatch class to check time lapsed from previous key had come (in the example 300 is only for testing, I would suggest to research it deeply if that is fit your goal)
var sw = Stopwatch.StartNew();
do
{
var x = Console.ReadKey();
if (sw.ElapsedMilliseconds > 300)
{
Console.WriteLine(++index);
}
sw.Restart();
} while (index < 10);
or combine both of the ways
Yes, you cant use ReadKey here. I would recommend to use ReadConsoleInput WinApi function. You can write wrapper class for this purpose, for example:
internal class KeyboardInput
{
private readonly short _exitKey;
private readonly uint[] _keyStates = new uint[short.MaxValue];
public KeyboardInput(ConsoleKey exitKey)
{
_exitKey = (short) exitKey;
// subscribe with empty delegates to prevent null reference check before call
OnKeyDown += delegate { };
OnKeyUp += delegate { };
}
public event Action<char, short> OnKeyDown;
public event Action<char, short> OnKeyUp;
public void Run()
{
var exitKeyPressed = false;
var nRead = 0;
var records = new INPUT_RECORD[10];
var handle = GetStdHandle(STD_INPUT_HANDLE);
while (!exitKeyPressed)
{
ReadConsoleInputW(handle, records, records.Length, ref nRead);
for (var i = 0; i < nRead; i++)
{
// process only Key events
if (records[i].EventType != KEY_EVENT) continue;
// process key state
ProcessKey(records[i].KeyEvent.wVirtualKeyCode, records[i].KeyEvent.bKeyDown,
records[i].KeyEvent.UnicodeChar);
// check for exit key press
if ((exitKeyPressed = records[i].KeyEvent.wVirtualKeyCode == _exitKey) == true) break;
}
}
}
private void ProcessKey(short virtualKeyCode, uint keyState, char key)
{
if (_keyStates[virtualKeyCode] != keyState)
if (keyState == 1) OnKeyDown(key, virtualKeyCode);
else OnKeyUp(key, virtualKeyCode);
_keyStates[virtualKeyCode] = keyState;
}
#region Native methods
private const short KEY_EVENT = 0x0001;
private const int STD_INPUT_HANDLE = -10;
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool ReadConsoleInputW(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, int nLength,
ref int lpNumberOfEventsRead);
[StructLayout(LayoutKind.Explicit)]
private struct INPUT_RECORD
{
[FieldOffset(0)] public readonly short EventType;
//union {
[FieldOffset(4)] public KEY_EVENT_RECORD KeyEvent;
//[FieldOffset(4)]
//public MOUSE_EVENT_RECORD MouseEvent;
//[FieldOffset(4)]
//public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
//[FieldOffset(4)]
//public MENU_EVENT_RECORD MenuEvent;
//[FieldOffset(4)]
//public FOCUS_EVENT_RECORD FocusEvent;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEY_EVENT_RECORD
{
public readonly uint bKeyDown;
public readonly short wRepeatCount;
public readonly short wVirtualKeyCode;
public readonly short wVirtualScanCode;
public readonly char UnicodeChar;
public readonly int dwControlKeyState;
}
#endregion
}
Example of usage:
internal class Program
{
private static void Main(string[] args)
{
var kbInput = new KeyboardInput(ConsoleKey.Escape);
kbInput.OnKeyDown += OnKeyDown;
kbInput.OnKeyUp += OnKeyUp;
kbInput.Run();
}
private static void OnKeyDown(char key, short code)
{
Console.WriteLine($"Key pressed: {key} (virtual code: 0x{code:X})");
}
private static void OnKeyUp(char key, short code)
{
Console.WriteLine($"Key released: {key} (virtual code: 0x{code:X})");
}
}
Did you try to add a method keyRelease like that in your loop for after num++ :
WaitForKeyRelease(ConsoleKey.Enter);
And in your method : while (Console.ReadKey(true).Key == key)
You wait until the key pressed is not enter so it's like a key release.
You can check if "enter" is inputted by doing something like this:
`
string input = Console.ReadLine();
if (input == "") {
//do stuff
}
`
Related
SDK link: https://www.logitechg.com/en-us/innovation/developer-lab.html
It can create a profile in the LGS, but if I debug in GkeySDKCallback() it can't enter it when I pressed my mouse button.
I sent an email to Logitech Dev Support regarding this, but have not received anything back at this time.
Here is my code [2 files]
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Logitceh
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
bool usingCallback = false;
private void Form1_Load(object sender, EventArgs e)
{
this.TransparencyKey = Color.Red;
this.BackColor = Color.Red;
Start();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
OnDestroy();
}
void Start()
{
usingCallback = true;
if (usingCallback)
{
LogitechGSDK.logiGkeyCB cbInstance = new
LogitechGSDK.logiGkeyCB(this.GkeySDKCallback);
LogitechGSDK.LogiGkeyInitWithoutContext(cbInstance);
}
else
{
LogitechGSDK.LogiGkeyInitWithoutCallback();
}
}
void OnUpdate()
{
if (!usingCallback)
{
for (int index = 6; index <= LogitechGSDK.LOGITECH_MAX_MOUSE_BUTTONS; index++)
{
if (LogitechGSDK.LogiGkeyIsMouseButtonPressed(index) == 1)
{
// Code to handle what happens on gkey pressed on mouse
}
}
for (int index = 1; index <= LogitechGSDK.LOGITECH_MAX_GKEYS; index++)
{
for (int mKeyIndex = 1; mKeyIndex <= LogitechGSDK.LOGITECH_MAX_M_STATES;
mKeyIndex++)
{
if (LogitechGSDK.LogiGkeyIsKeyboardGkeyPressed(index, mKeyIndex) == 1)
{
// Code to handle what happens on gkey pressed on keyboard/headset
}
}
}
}
}
void GkeySDKCallback(LogitechGSDK.GkeyCode gKeyCode, String gKeyOrButtonString, IntPtr context)
{
if (gKeyCode.keyDown == 1)
{
if (gKeyCode.mouse == 1)
{
// Code to handle what happens on gkey released on mouse
}
else
{
// Code to handle what happens on gkey released on keyboard/headset
}
}
else
{
if (gKeyCode.mouse == 1)
{
// Code to handle what happens on gkey pressed on mouse
}
else
{
// Code to handle what happens on gkey pressed on keyboard
}
}
}
void OnDestroy()
{
//Free G-Keys SDKs before quitting the game
LogitechGSDK.LogiGkeyShutdown();
}
}
}
LogitechGSDK.cs
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Specialized;
using System;
public class LogitechGSDK
{
//G-KEY SDK
public const int LOGITECH_MAX_MOUSE_BUTTONS = 20;
public const int LOGITECH_MAX_GKEYS = 29;
public const int LOGITECH_MAX_M_STATES = 3;
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct GkeyCode
{
public ushort complete;
// index of the G key or mouse button, for example, 6 for G6 or Button 6
public int keyIdx
{
get
{
return complete & 255;
}
}
// key up or down, 1 is down, 0 is up
public int keyDown
{
get
{
return (complete >> 8) & 1;
}
}
// mState (1, 2 or 3 for M1, M2 and M3)
public int mState
{
get
{
return (complete >> 9) & 3;
}
}
// indicate if the Event comes from a mouse, 1 is yes, 0 is no.
public int mouse
{
get
{
return (complete >> 11) & 15;
}
}
// reserved1
public int reserved1
{
get
{
return (complete >> 15) & 1;
}
}
// reserved2
public int reserved2
{
get
{
return (complete >> 16) & 131071;
}
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void logiGkeyCB(GkeyCode gkeyCode,
[MarshalAs(UnmanagedType.LPWStr)]String gkeyOrButtonString, IntPtr context); // ??
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern int LogiGkeyInitWithoutCallback();
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern int LogiGkeyInitWithoutContext(logiGkeyCB gkeyCB);
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern int LogiGkeyIsMouseButtonPressed(int buttonNumber);
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr LogiGkeyGetMouseButtonString(int buttonNumber);
public static String LogiGkeyGetMouseButtonStr(int buttonNumber)
{
String str =
Marshal.PtrToStringUni(LogiGkeyGetMouseButtonString(buttonNumber));
return str;
}
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern int LogiGkeyIsKeyboardGkeyPressed(int gkeyNumber, int
modeNumber);
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper")]
private static extern IntPtr LogiGkeyGetKeyboardGkeyString(int gkeyNumber, int
modeNumber);
public static String LogiGkeyGetKeyboardGkeyStr(int gkeyNumber, int modeNumber)
{
String str =
Marshal.PtrToStringUni(LogiGkeyGetKeyboardGkeyString(gkeyNumber, modeNumber));
return str;
}
[DllImport(#"C:/DLL/LogitechGkeyEnginesWrapper", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
public static extern void LogiGkeyShutdown();
}
I am trying to create global mouse hotkeys in my program using SetWindowsHookEx(). So far I have tried to create them like in one post I have seen here but, I dont really know how to finsh this.
The problem is currently that I dont exactly know what _globalMouseHookCallback is.
This is what I have written so far:
class GlobalHotkey
{
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private IntPtr _hGlobalMouseHook;
MainWindow _m;
private const int WH_KEYBOARD_LL = 13;
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
private static IntPtr hook = IntPtr.Zero;
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public void SetUpHook()
{
_m.rtbLog.AppendText("Setting up global Hotkey \n");
_globalMouseHookCallback = LowLevelMouseProc;
_hGlobalMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _globalMouseHookCallback, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
if (_hGlobalMouseHook == IntPtr.Zero)
{
_m.rtbLog.AppendText("Unable to set up global mouse hook\n");
}
}
public void ClearHook()
{
_m.rtbLog.AppendText("Deleting global mouse hook\n");
if (_hGlobalMouseHook != IntPtr.Zero)
{
if (!UnhookWindowsHookEx(_hGlobalMouseHook))
{
_m.rtbLog.AppendText("Unable to delete global mouse hook\n");
}
_hGlobalMouseHook = IntPtr.Zero;
}
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var wmMouse = wParam;
if (wmMouse == (IntPtr)WM_LBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_LBUTTONUP)
{
_m.rtbLog.AppendText("Left Mouse up");
}
if (wmMouse == (IntPtr)WM_RBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_RBUTTONUP)
{
_m.rtbLog.AppendText("Right Mouse up");
}
}
return CallNextHookEx(_hGlobalMouseHook, nCode, wParam, lParam);
}
}
This global hotkey stuff is pretty hard is there like a tutorial that explains it easily for newbs like me :P ?
EDIT
So I tried adapting Koby Ducks example to my code.
This is my Hotkey class:
class GlobalHotkey
{
MainWindow _m;
private static readonly object sSyncObj = new object();
private static readonly HashSet<Key> sDownKeys = new HashSet<Key>();
private static readonly Dictionary<Key, Action> sPressActions = new Dictionary<Key, Action>();
private static readonly Dictionary<Key, Action> sReleaseActions = new Dictionary<Key, Action>();
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public static void ProcessKeyDown(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (!sDownKeys.Contains(key))
{
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void ProcessKeyUp(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (sDownKeys.Remove(key))
{
if (sReleaseActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void AttachPressAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(Key key)
{
lock (sSyncObj)
{
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(Key key)
{
lock (sSyncObj)
{
return sReleaseActions.Remove(key);
}
}
}
And I created my action
public void MyTestAction()
{
rtbLog.AppendText("The B key was pressed");
}
myAction = new Action(MyTestAction);
But as soon as I added my Eventhandlers to the PreviewKeyUp- and Down Event it gave me an error saying that the Parameters of ProcessKeyUp- and Down, are not the same as PreviewKeyUp- and Down.
PreviewKeyDown += GlobalHotkey.ProcessKeyDown;
PreviewKeyUp += GlobalHotkey.ProcessKeyUp;
Edit: For input regardless of the currently focused window(aka "global"), see this answer on the Win32 keyboard API.
For input while your app is focused(aka "local"), you could use preview events.
public static class HotKeySystem
{
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (!sDownKeys.Contains(key)) {
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void ProcessKeyUp(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (sDownKeys.Remove(key)) {
if (sReleaseActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void AttachPressAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(KeyCode key)
{
lock (sSyncObj) {
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(KeyCode key)
{
lock (sSyncObj) {
return sReleaseActions.Remove(key);
}
}
private static readonly object sSyncObj = new object();
// The keys that are currently down.
private static readonly HashSet<KeyCode> sDownKeys = new HashSet<KeyCode>();
// Actions triggered when a key was up, but is now down.
private static readonly Dictionary<KeyCode, Action> sPressActions = new Dictionary<KeyCode, Action>();
// Actions triggered when a key was down, but is now up.
private static readonly Dictionary<KeyCode, Action> sReleaseActions = new Dictionary<KeyCode, Action>();
}
// When possible, subclass your windows from this to automatically add hotkey support.
public class HotKeyWindow : Window
{
protected override void OnPreviewKeyDown(KeyEventArgs args)
{
HotKeySystem.ProcessKeyDown(this, args);
base.OnPreviewKeyDown(args);
}
protected override void OnPreviewKeyUp(KeyEventArgs args)
{
HotKeySystem.ProcessKeyUp(this, args);
base.OnPreviewKeyUp(args);
}
}
// When not possible, attach event handlers like this:
window.PreviewKeyDown += HotKeySystem.ProcessKeyDown;
window.PreviewKeyUp += HotKeySystem.ProcessKeyUp;
// Use it like this:
HotKeySystem.AttachPressAction(KeyCode.F1, () => {
// F1 hotkey functionality.
});
Regardless of if you're using this method or the Win32 API, consider the implications. If you have 'A' bound, then you won't be able to input 'a' or 'A' into text input controls. One way to work around this is:
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
// Detect keyboard input controls you may have issues with.
// If one has keyboard focus, skip hotkey processing.
if (Keyboard.FocusedElement is TextBox) {
return;
}
// ...
}
I want to write text to the currently selected application but its writing junk and causing weird things to happen.
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Runtime.InteropServices;
namespace i_allbwn
{
class Program
{
static void Main(string[] args)
{
Thread.Sleep(500);
ActionWithChance.brif_allbwn();
Console.ReadKey();
}
}
class ActionWithChance
{
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
public const int KEYEVENTF_EXTENDEDKEY = 0x0001; //Key down flag
public const int KEYEVENTF_KEYUP = 0x0002; //Key up flag
public static void brif_allbwn()
{
argraffu(new String[] {
"line1",
"line2",
"line3",
}
);
}
public static void allbwn(Byte[] Name)
{
for (int i = 0; i < Name.Length; i++)
{
Console.WriteLine("Writing " + (Char)Name[i]);
keybd_event((Byte)Name[i], 0, KEYEVENTF_EXTENDEDKEY, 0);
Thread.Sleep(10);
keybd_event((Byte)Name[i], 0, KEYEVENTF_KEYUP, 0);
}
}
public static void argraffu(String[] text)
{
foreach (String s in text)
{
allbwn(ToByteArray(s));
keybd_event((Byte)'\r', 0, KEYEVENTF_EXTENDEDKEY, 0);
Thread.Sleep(10);
keybd_event((Byte)'\r', 0, KEYEVENTF_KEYUP, 0);
}
}
public static Byte[] ToByteArray(String StringToConvert)
{
Char[] CharArray = StringToConvert.ToCharArray();
Byte[] ByteArray = new Byte[CharArray.Length];
for (int i = 0; i < CharArray.Length; i++)
{
ByteArray[i] = Convert.ToByte(CharArray[i]);
}
return ByteArray;
}
}
}
My functions for doing it are like this:
public const Int32 WM_CHAR = 0x0102;
public void SendKeys(string message)
{
foreach (char c in message)
{
int charValue = c;
IntPtr val = new IntPtr((Int32)c);
SendMessage(WindowHandle, WM_CHAR, val, new IntPtr(0));
}
}
Basically what I'm doing is getting the handle of the application, e.g:
Process proc = Process.GetProcessesByName("Notepad")[0];
Then getting the handle with proc.MainModule.Handle()
The Autoit library makes it really easy to interact with external windows.
Install the nuget package called AutoItX.Dotnet
Then it's just a matter of:
using AutoIt;
class Program
{
static void Main(string[] args)
{
AutoItX.Run("notepad.exe", null);
AutoItX.WinWait("Untitled - Notepad");
AutoItX.ControlSend("Untitled - Notepad", "", "[CLASSNN:Edit1]", "testing");
//ControlSend is the ideal way to send text, but you can also pretend text was typed into the keyboard:
AutoItX.Send("howdy pilgrim");
}
}
I found a opensource code that was a much simpler version of NAudio for my C# application. Which for me is better because all I am looking to do is play a brief sound out of the speakers to test that they are plugged in and working, and I also want to listen to the microphone. The project it self looked ok, but i felt that it could be refactored a bit into smaller chucks. Another thing i found is that the WaveOutBuffer.cs and WaveInBuffer.cs files were remarkably similar. So i started to make a abstract class WaveBuffer. This would have all the similar functions and variables in it that would be passed. One that i am having troubles with is as follows. Another file WaveNative.cs has delegate in it. I am very new to delegates and Events so i know it has the potential to be very important. but i dont like the way that it is setup to be used. So i'll post what Is in each file and how it is used, then show what i have been trying to get to work. Maybe between all of our minds we can consolidate and refactor this to make it more efficient :)
so First.
WaveNative.cs
//callbacks
public delegate void WaveDelegate(IntPtr hdrvr, int uMsg, int dwUser, ref WaveHdr wavhdr, int dwParam2);
WaveInBuffer.cs / WaveOutBuffer.cs
internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
if (uMsg == WaveNative.MM_WIM_DATA)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveInBuffer buf = (WaveInBuffer)h.Target;
buf.OnCompleted();
}
catch
{
}
}
}
//WaveOutBuffer.cs version
internal static void WaveOutProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
if (uMsg == WaveNative.MM_WOM_DONE)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveOutBuffer buf = (WaveOutBuffer)h.Target;
buf.OnCompleted();
}
catch
{
}
}
}
WaveInRecorder.cs / WaveOutRecorder
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc);
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveOutBuffer.WaveOutProc);
In the end they are used for a PInvoke call as follows
[DllImport(mmdll)]
public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
[DllImport(mmdll)]
public static extern int waveInOpen(out IntPtr phwi, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
I've consolidated most the other things in the abstract version of WaveBuffer such as the HeaderData, the size of the header, the IntPtr data, a GCHandle HeaderHandle, and HeaderDataHandle. A WaitFor command a OnCompleteCommand, a bool Busy, and a AutoResetEvent. Not sure what it is used for but it's used for, but it is used the same in each file so i just moved it over. Thank you for your patience and reading through this thread.
EDIT
sorry for the confusion, i got so wrapped up in finding all this stuff I forgot to ask what i was meaning to ask. Basically teh question is How can I combine these 2 functions that do nearly EXACTLY the same thing? How does this Delegate work from WaveNative such that i can make a new instance in WaveInBuffer/WaveOutBuffer and it means the same thing. I just assumed that I had to always either call the one from the other or just call the base class one. As for m_BufferedProc I'll post the entire code since it is hard for me to understand. Mind you this code I'm posting is not my own. Here it is
public class WaveInRecorder : IDisposable
{
private IntPtr m_WaveIn;
private WaveInBuffer m_Buffers; // linked list
private WaveInBuffer m_CurrentBuffer;
private Thread m_Thread;
private BufferDoneEventHandler m_DoneProc;
private bool m_Finished;
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc);
public static int DeviceCount
{
get { return WaveNative.waveInGetNumDevs(); }
}
public WaveInRecorder(int device, WaveFormat format, int bufferSize, int bufferCount, BufferDoneEventHandler doneProc)
{
m_DoneProc = doneProc;
WaveInHelper.Try(WaveNative.waveInOpen(out m_WaveIn, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION));
AllocateBuffers(bufferSize, bufferCount);
for (int i = 0; i < bufferCount; i++)
{
SelectNextBuffer();
m_CurrentBuffer.Record();
}
WaveInHelper.Try(WaveNative.waveInStart(m_WaveIn));
m_Thread = new Thread(new ThreadStart(ThreadProc));
m_Thread.Start();
}
~WaveInRecorder()
{
Dispose();
}
public void Dispose()
{
if (m_Thread != null)
try
{
m_Finished = true;
if (m_WaveIn != IntPtr.Zero)
WaveNative.waveInReset(m_WaveIn);
WaitForAllBuffers();
m_Thread.Join();
m_DoneProc = null;
FreeBuffers();
if (m_WaveIn != IntPtr.Zero)
WaveNative.waveInClose(m_WaveIn);
}
finally
{
m_Thread = null;
m_WaveIn = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
private void ThreadProc()
{
while (!m_Finished)
{
Advance();
if (m_DoneProc != null && !m_Finished)
m_DoneProc(m_CurrentBuffer.Data, m_CurrentBuffer.Size);
m_CurrentBuffer.Record();
}
}
private void AllocateBuffers(int bufferSize, int bufferCount)
{
FreeBuffers();
if (bufferCount > 0)
{
m_Buffers = new WaveInBuffer(m_WaveIn, bufferSize);
WaveInBuffer Prev = m_Buffers;
try
{
for (int i = 1; i < bufferCount; i++)
{
WaveInBuffer Buf = new WaveInBuffer(m_WaveIn, bufferSize);
Prev.NextBuffer = Buf;
Prev = Buf;
}
}
finally
{
Prev.NextBuffer = m_Buffers;
}
}
}
private void FreeBuffers()
{
m_CurrentBuffer = null;
if (m_Buffers != null)
{
WaveInBuffer First = m_Buffers;
m_Buffers = null;
WaveInBuffer Current = First;
do
{
WaveInBuffer Next = Current.NextBuffer;
Current.Dispose();
Current = Next;
} while(Current != First);
}
}
private void Advance()
{
SelectNextBuffer();
m_CurrentBuffer.WaitFor();
}
private void SelectNextBuffer()
{
m_CurrentBuffer = m_CurrentBuffer == null ? m_Buffers : m_CurrentBuffer.NextBuffer;
}
private void WaitForAllBuffers()
{
WaveInBuffer Buf = m_Buffers;
while (Buf.NextBuffer != m_Buffers)
{
Buf.WaitFor();
Buf = Buf.NextBuffer;
}
}
Mind you that that code is not mine but rather it is Ianier Munoz's. I have a similar version I'm working on, you can browse the code that i extracted from it at http://code.google.com/p/adli/source/browse/#svn%2Ftrunk%2FAspects%2FCustom%2FAudio
how I use it (not fully implemented is at here)
http://code.google.com/p/adli/source/browse/trunk/Aspects/Panels/Main%20Panels/AudioBrightnessPanel.cs
look at the action listener for my microphone test.
again sorry for the confusion i will try not to post a question without a question again.
well you've got mutually exclusive if blocks which means its pretty easy to merge the 2 together... (i removed the empty catch block because its evil)
internal static void WaveProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
GCHandle h = (GCHandle)wavhdr.dwUser;
if (uMsg == WaveNative.MM_WIM_DATA)
{
WaveInBuffer buf = (WaveInBuffer)h.Target;
buf.OnCompleted();
}
if (uMsg == WaveNative.MM_WOM_DONE)
{
WaveOutBuffer buf = (WaveOutBuffer)h.Target;
buf.OnCompleted();
}
}
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveProc);
I am creating a program that monitors key presses for controlling iTunes globally. It also has a few WinForms (for displaying track information and editing options).
The low-level keyboard hook works great for awhile. If I just start up the program, keyboard hook is set and iTunes opens. Then I open Notepad and can type tons of stuff really fast and every stroke is captured, with at most 30ms being spent in the hook function (and for the most part <10ms). The hook function simply adds the events onto a queue which is processed by another thread. It is running on its own high-priority thread using it's own Application.Run().
However if I start doing things within iTunes (such as a couple of play/pause clicks which generate events in my program) or within the program (like opening the options window) then the hook function stops being called! This can happen even if the keyboard has never been used (e.g. startup, click play and pause a few times in iTunes, then press a key).
The cause of the hook not being called is not due to too much time being spent in the hook function.
When I call UnhookWindowsHookEx it always returns true, regardless if the hook function was still being called or not.
So, what could be the cause?
One idea (although I have no proof or solutions) is that the managed thread is no longer the correct native thread. I use numerous (managed) threads in my program and I have read that a single native thread can run many managed threads and that a managed thread can change which native thread is running it. Is it possible that the hook is still producing messages but sending them to the wrong thread? If this is the case, how can I work around it?
Edit: The hook and callbacks
A slightly stripped done version of my KeyMonitor. It is stripped down for clarity. I have removed some utilities (like most of the values of the Key enum and many functions of the Keys class like ToString() and FromString()) along with some error handling.
Most of the important stuff is in the KeyMonitor class. KeyMonitor.Start() starts a thread for the messages, KeyMonitor.HookThread() is that thread and creates the hook along with an Application.Run() for the message loop, KeyMonitor.KeyboardHookProc() is the callback function, and KeyMonitor.HookEventDispatchThread() is what dispatches events recorded by the callback.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace KeyTest
{
enum Key : int
{
Shift = 0x10, Ctrl, Alt,
Left_Win = 0x5B, Right_Win,
Left_Shift = 0xA0, Right_Shift, Left_Ctrl, Right_Ctrl, Left_Alt, Right_Alt,
}
class Keys
{
[DllImport("user32.dll")]
private static extern int GetKeyboardState(byte[] pbKeyState);
public const int Count = 256; // vkCode are from 1 to 254, but GetKeyboardState uses 0-255
private readonly bool[] keys = new bool[Count];
public Keys() { }
private void DoModifier(Key x, Key l, Key r) { keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers()
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt);
}
private void DoModifier(Key x, Key l, Key r, Key k) { if (k == l || k == r) keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers(Key k)
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift, k);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl, k);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt, k);
}
public bool this[int i] { get { return this.keys[i]; } set { this.keys[i] = value; DoModifiers((Key)i); } }
public bool this[Key k] { get { return this.keys[(int)k]; } set { this.keys[(int)k] = value; DoModifiers(k); } }
public void LoadCurrentState()
{
byte[] keyState = new byte[Count];
if (GetKeyboardState(keyState) != 0)
for (int i = 0; i < Count; ++i)
keys[i] = (keyState[i] & 0x80) != 0;
DoModifiers();
}
}
static class KeyMonitor
{
#region Windows API
private delegate int HookProc(int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll")]
private static extern int CallNextHookEx(int idHook, int nCode, UIntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private readonly static UIntPtr WM_KEYDOWN = new UIntPtr(0x100), WM_SYSKEYDOWN = new UIntPtr(0x104);
#endregion
public static event KeyEventHandler OverridingKeyChange;
public static event KeyEventHandler KeyChange;
private struct KeyEventData { public int vk; public bool down; }
private static int hook = 0;
private static Thread dispatchThread = null, hookThread = null;
private static Keys keys = new Keys();
private static Queue<KeyEventData> queue = new Queue<KeyEventData>();
private static void Enqueue(int vk, bool down)
{
lock (queue)
{
queue.Enqueue(new KeyEventData() { vk = vk, down = down });
Monitor.Pulse(queue);
}
}
public static Keys Keys { get { return keys; } }
public static void Start()
{
if (hook == 0)
{
dispatchThread = new Thread(HookEventDispatchThread);
hookThread = new Thread(HookThread);
hookThread.Priority = ThreadPriority.Highest;
dispatchThread.Start();
hookThread.Start();
}
}
public static void Stop()
{
if (hook != 0)
{
// Minimal cleanup...
UnhookWindowsHookEx(hook);
Application.Exit();
dispatchThread.Interrupt();
}
}
private static void HookThread()
{
hook = SetWindowsHookEx(WH_KEYBOARD_LL, new HookProc(KeyboardHookProc), IntPtr.Zero, 0);
if (hook == 0) { /* Handle error */ }
keys.LoadCurrentState();
Application.Run();
}
private static int KeyboardHookProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
Enqueue(Marshal.ReadInt32(lParam), wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN);
return CallNextHookEx(hook, nCode, wParam, lParam);
}
private static void HookEventDispatchThread()
{
for (; ; )
{
KeyEventData data;
lock (queue)
{
if (queue.Count == 0)
try
{
Monitor.Wait(queue);
}
catch (ThreadInterruptedException) { return; }
data = queue.Dequeue();
}
if (data.vk == -1)
{
// Done!
keys = new Keys();
queue.Clear();
return;
}
else if (keys[data.vk] == data.down)
continue;
keys[data.vk] = data.down;
KeyEventArgs e = new KeyEventArgs((System.Windows.Forms.Keys)data.vk);
if (OverridingKeyChange != null) OverridingKeyChange(null, e);
if (!e.Handled && KeyChange != null) KeyChange(null, e);
}
}
}
}
You need to save the delegate to a variable that will survive for the duration of your application. Otherwise, delegate is garbage-collected (strange the app did not crash!).
static HookProc hookProc;
...
hookProc = new HookProc(KeyboardHookProc);
hook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, IntPtr.Zero, 0);