1

I'm making a program with a global mouse and keyboard hook. The program will record the user's input when they press a record button and will stop recording once they press a stop button. I'm getting the timespan of each event by using a stopwatch, as that seems to be the most accurate method.

Here's a small snippet of the events a user's recording:

01:45:20 - Time: 00:00:03.7028259 - Mousemove
01:45:20 - Time: 00:00:03.7190386 - Mouseleftdown
01:45:20 - Time: 00:00:03.7363274 - Mousemove
01:45:20 - Time: 00:00:03.7431332 - Mousemove
01:45:20 - Time: 00:00:03.7519057 - Mousemove
01:45:20 - Time: 00:00:03.7594302 - Mouseleftup

As you can see, some events events are nearly 8 milliseconds apart (this seems to be the smallest amount of time I can get between each event).

The user can press the play button, and their recording will begin to play. At first, I looked into the Timer component as it seemed perfect for this situation. However, the component is fairly inaccurate, so I thought I'd ask here. I considered the idea of making a new thread with a stopwatch and a while loop that will compare the events TimeSpans and the stopwatch's Elapsed time, but that would suck up the CPU a lot. What would you guys do to replay the events accurately? Am I wasting my time looking for something so accurate on a desktop?

John
  • 326
  • 4
  • 21
  • 2
    Low level keyboard procs already include a timestamp in them. For example, [`KBDLLHOOKSTRUCT`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967(v=vs.85).aspx), which is one of the params in the callback for the hook. Perhaps this is a better option than trying to measure the timestamp yourself. – vcsjones Nov 08 '16 at 19:13
  • @vcsjones, Thanks for that. Got any clues on how to play the events accurately? Should I just stick to `Timer`? EDIT: Also, with the `KBDLLHOOKSTRUCT`, up to about four events sometimes end up having the same `time` value, which doesn't seem right, right? – John Nov 08 '16 at 19:19
  • You might need to look at the answer below: http://stackoverflow.com/questions/3729169/how-can-i-get-the-windows-system-time-with-millisecond-resolution – user6788933 Nov 08 '16 at 19:22
  • A [journal playback hook](https://msdn.microsoft.com/library/windows/desktop/ms644982) is a special kind of hook designed to play back events. – Jeroen Mostert Nov 08 '16 at 19:23
  • Why a loop? Can you not query the stopwatch when an event arrives? – usr Nov 08 '16 at 19:40
  • @usr, I query the stopwatch for each event when I'm recording the events. However, I'm not sure how to do this when playing back the events. What @Chris Dunaway mentioned about using the `Thread.Sleep` seems much better than a loop, but it also looks like it isn't made for accuracy. – John Nov 08 '16 at 19:46
  • 1
    I see, it's about the playback. Raise the timer resolution to 1ms (which is max). Then use any timing method you like (timers, sleep, events, ...) to obtain 1ms accuracy. Is 1000Hz not enough?! – usr Nov 08 '16 at 19:47

2 Answers2

1

Raise the timer resolution to 1ms (which is max). Then use any timing method you like (timers, sleep, events, ...) to obtain 1ms accuracy. 1000Hz should be enough for anything aimed at human perception.

usr
  • 168,620
  • 35
  • 240
  • 369
0

Take a look at this library: http://www.codeproject.com/Articles/28064/Global-Mouse-and-Keyboard-Library

They just use Environment.TickCount and just keep track of it that way. Each time a mouse/keyboard event occurs, they save it into a list, with the tick count. Then, to play back the events, they just loop through the list in a background thread and sleep for the length of each event.

void mouseHook_MouseMove(object sender, MouseEventArgs e)
{

    events.Add(
        new MacroEvent(
            MacroEventType.MouseMove,
            e,
            Environment.TickCount - lastTimeRecorded
        ));

    lastTimeRecorded = Environment.TickCount;

}

void mouseHook_MouseDown(object sender, MouseEventArgs e)
{

    events.Add(
        new MacroEvent(
            MacroEventType.MouseDown,
            e,
            Environment.TickCount - lastTimeRecorded
        ));

    lastTimeRecorded = Environment.TickCount;

}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    foreach (MacroEvent macroEvent in events)
    {
        Thread.Sleep(macroEvent.TimeSinceLastEvent);

        switch (macroEvent.MacroEventType)
        {
            case MacroEventType.MouseMove:
                {
                    MouseEventArgs mouseArgs = (MouseEventArgs)macroEvent.EventArgs;
                    MouseSimulator.X = mouseArgs.X;
                    MouseSimulator.Y = mouseArgs.Y;
                }
                break;
            case MacroEventType.MouseDown:
                {
                    MouseEventArgs mouseArgs = (MouseEventArgs)macroEvent.EventArgs;
                    MouseSimulator.MouseDown(mouseArgs.Button);
                }
                break;
            default:
                break;
        }
    }
}
Chris Dunaway
  • 10,974
  • 4
  • 36
  • 48
  • Is this known to be accurate? According to http://stackoverflow.com/questions/1303667/how-accurate-is-thread-sleeptimespan, `Thread.Sleep` isn't the best for accuracy – John Nov 08 '16 at 19:38
  • I can't really say how accurate this is. I have used this library and the macro recorder and it seems to be accurate enough for just standard actions. If you need much tighter timings, then the comment regarding the Journal Playback hook is probably the better option, though. – Chris Dunaway Nov 08 '16 at 19:45